unit uSnowWorkerM1; interface uses uISnowWorker, uIdGeneratorOptions, System.SyncObjs, uTOverCostActionArg, System.DateUtils, System.SysUtils; /// /// 雪花漂移算法 /// type TSnowWorkerM1 = class(TInterfacedObject, ISnowWorker) private // private static long _StartTimeTick = 0; // private static long _BaseTimeTick = 0; protected SyncLock: TCriticalSection; protected // FBaseTime: TDateTime; FBaseTime: Int64; FWorkerId: Word; FWorkerIdBitLength: Byte; FSeqBitLength: Byte; FMaxSeqNumber: Integer; FMinSeqNumber: Word; FTopOverCostCount: Integer; // FTimestampShift: Byte; FCurrentSeqNumber: Word; FLastTimeTick: Int64; FTurnBackTimeTick: Int64; FTurnBackIndex: Byte; FIsOverCost: Boolean; FOverCostCountInOneTerm: Integer; // FGenCountInOneTerm: Integer; FTermIndex: Integer; protected /// /// 基础时间 /// // property BaseTime: TDateTime read FBaseTime write FBaseTime; property BaseTime: Int64 read FBaseTime; /// /// 机器码 /// property WorkerId: Word read FWorkerId default 0; /// /// 机器码位长 /// property WorkerIdBitLength: Byte read FWorkerIdBitLength default 0; /// /// 自增序列数位长 /// property SeqBitLength: Byte read FSeqBitLength default 0; /// /// 最大序列数(含) /// property MaxSeqNumber: Integer read FMaxSeqNumber default 0; /// /// 最小序列数(含) /// property MinSeqNumber: Word read FMinSeqNumber default 0; /// /// 最大漂移次数(含) /// property TopOverCostCount: Integer read FTopOverCostCount write FTopOverCostCount default 0; // property TimestampShift: Byte read FTimestampShift write FTimestampShift default 0; // property CurrentSeqNumber: Word read FCurrentSeqNumber write FCurrentSeqNumber; property LastTimeTick: Int64 read FLastTimeTick write FLastTimeTick default 0; // -1L property TurnBackTimeTick: Int64 read FTurnBackTimeTick write FTurnBackTimeTick default 0; // -1L; property TurnBackIndex: Byte read FTurnBackIndex write FTurnBackIndex default 0; property IsOverCost: Boolean read FIsOverCost write FIsOverCost default False; property OverCostCountInOneTerm: Integer read FOverCostCountInOneTerm write FOverCostCountInOneTerm default 0; {$IFDEF DEBUG} property GenCountInOneTerm: Integer read FGenCountInOneTerm write FGenCountInOneTerm default 0; property TermIndex: Integer read FTermIndex write FTermIndex default 0; {$ENDIF} protected {$IFDEF DEBUG} procedure DoGenIdAction(arg: TOverCostActionArg); procedure BeginOverCostAction(UseTimeTick: Int64); procedure EndOverCostAction(UseTimeTick: Int64); procedure BeginTurnBackAction(UseTimeTick: Int64); procedure EndTurnBackAction(UseTimeTick: Int64); {$ENDIF} // function GetSecondTimeStamp(): Int64; function GetMillisecondTimeStamp(): Int64; // function CalcId(UseTimeTick: Int64): Int64; virtual; function CalcTurnBackId(UseTimeTick: Int64): Int64; virtual; function NextOverCostId(): Int64; function GetCurrentTimeTick(): Int64; virtual; function GetNextTimeTick(): Int64; public // Action GenAction { get; set; } function NextId(): Int64; function NextNormalId(): Int64; constructor Create(options: TIdGeneratorOptions); overload; destructor Destroy(); override; end; implementation { TSnowWorkerM1 } function TSnowWorkerM1.GetSecondTimeStamp(): Int64; var ST: TDateTime; begin ST := EncodeDateTime(1970, 1, 1, 0, 0, 0, 0); Result := MilliSecondsBetween(Now(), ST) - 28800; // 8*60*60; end; function TSnowWorkerM1.GetMillisecondTimeStamp(): Int64; var ST: TDateTime; begin ST := EncodeDateTime(1970, 1, 1, 0, 0, 0, 0); Result := MilliSecondsBetween(Now(), ST) - 28800000; // 8*60*60*1000; end; constructor TSnowWorkerM1.Create(options: TIdGeneratorOptions); begin SyncLock := TCriticalSection.Create; // 1.BaseTime if (options.BaseTime <> 0) then FBaseTime := options.BaseTime; // 2.WorkerIdBitLength if (options.WorkerIdBitLength <> 0) then begin FWorkerIdBitLength := 6; end else begin FWorkerIdBitLength := options.WorkerIdBitLength; end; // 3.WorkerId FWorkerId := options.WorkerId; // 4.SeqBitLength if (options.SeqBitLength = 0) then begin FSeqBitLength := 6; end else begin FSeqBitLength := options.SeqBitLength; end; // 5.MaxSeqNumber if (options.MaxSeqNumber <= 0) then begin FMaxSeqNumber := (1 shl SeqBitLength) - 1; end else begin FMaxSeqNumber := options.MaxSeqNumber; end; // 6.MinSeqNumber FMinSeqNumber := options.MinSeqNumber; // 7.Others FTopOverCostCount := options.TopOverCostCount; // if (TopOverCostCount = 0) then // begin // FTopOverCostCount := 2000; // end; FTimestampShift := Byte(WorkerIdBitLength + SeqBitLength); FCurrentSeqNumber := options.MinSeqNumber; // FBaseTimeTick = BaseTime.Ticks; // FStartTimeTick = (long)(DateTime.UtcNow.Subtract(BaseTime).TotalMilliseconds) - Environment.TickCount; end; destructor TSnowWorkerM1.Destroy(); begin SyncLock.Free; inherited; end; {$IFDEF DEBUG} procedure TSnowWorkerM1.DoGenIdAction(arg: TOverCostActionArg); begin // //return; // Task.Run(() => // { // GenAction(arg); // }); end; procedure TSnowWorkerM1.BeginOverCostAction(UseTimeTick: Int64); begin end; procedure TSnowWorkerM1.EndOverCostAction(UseTimeTick: Int64); begin end; procedure TSnowWorkerM1.BeginTurnBackAction(UseTimeTick: Int64); begin end; procedure TSnowWorkerM1.EndTurnBackAction(UseTimeTick: Int64); begin end; {$ENDIF} function TSnowWorkerM1.NextOverCostId(): Int64; var CurrentTimeTick: Int64; begin CurrentTimeTick := GetCurrentTimeTick(); if (CurrentTimeTick > FLastTimeTick) then begin {$IFDEF DEBUG} EndOverCostAction(CurrentTimeTick); FGenCountInOneTerm := 0; {$ENDIF} FLastTimeTick := CurrentTimeTick; FCurrentSeqNumber := FMinSeqNumber; FIsOverCost := False; FOverCostCountInOneTerm := 0; Result := CalcId(FLastTimeTick); Exit; end; if (FOverCostCountInOneTerm >= FTopOverCostCount) then begin {$IFDEF DEBUG} EndOverCostAction(CurrentTimeTick); FGenCountInOneTerm := 0; {$ENDIF} // TODO: 在漂移终止,等待时间对齐时,如果发生时间回拨较长,则此处可能等待较长时间。 // 可优化为:在漂移终止时增加时间回拨应对逻辑。(该情况发生概率低,暂不处理) FLastTimeTick := GetNextTimeTick(); FCurrentSeqNumber := FMinSeqNumber; FIsOverCost := False; FOverCostCountInOneTerm := 0; Result := CalcId(FLastTimeTick); Exit; end; if (FCurrentSeqNumber > FMaxSeqNumber) then begin {$IFDEF DEBUG} Inc(FGenCountInOneTerm); {$ENDIF} Inc(FLastTimeTick); FCurrentSeqNumber := FMinSeqNumber; FIsOverCost := True; Inc(FOverCostCountInOneTerm); Result := CalcId(FLastTimeTick); Exit; end; {$IFDEF DEBUG} Inc(FGenCountInOneTerm); {$ENDIF} Result := CalcId(FLastTimeTick); end; function TSnowWorkerM1.NextNormalId: Int64; var CurrentTimeTick: Int64; begin CurrentTimeTick := GetCurrentTimeTick(); if (CurrentTimeTick < FLastTimeTick) then begin if (FTurnBackTimeTick < 1) then begin FTurnBackTimeTick := FLastTimeTick - 1; Inc(FTurnBackIndex); // 每毫秒序列数的前5位是预留位,0用于手工新值,1-4是时间回拨次序 // 支持4次回拨次序(避免回拨重叠导致ID重复),可无限次回拨(次序循环使用)。 if (FTurnBackIndex > 4) then begin FTurnBackIndex := 1; end; {$IFDEF DEBUG} BeginTurnBackAction(FTurnBackTimeTick); {$ENDIF} end; // Sleep(1); Result := CalcTurnBackId(FTurnBackTimeTick); Exit; end; // 时间追平时,_TurnBackTimeTick清零 if (FTurnBackTimeTick > 0) then begin {$IFDEF DEBUG} EndTurnBackAction(FTurnBackTimeTick); {$ENDIF} FTurnBackTimeTick := 0; end; if (CurrentTimeTick > FLastTimeTick) then begin FLastTimeTick := CurrentTimeTick; FCurrentSeqNumber := FMinSeqNumber; Result := CalcId(FLastTimeTick); Exit; end; if (FCurrentSeqNumber > FMaxSeqNumber) then begin {$IFDEF DEBUG} BeginOverCostAction(CurrentTimeTick); Inc(FTermIndex); FGenCountInOneTerm := 1; {$ENDIF} FOverCostCountInOneTerm := 1; Inc(FLastTimeTick); FCurrentSeqNumber := FMinSeqNumber; FIsOverCost := True; Result := CalcId(FLastTimeTick); Exit; end; Result := CalcId(FLastTimeTick); end; function TSnowWorkerM1.CalcId(UseTimeTick: Int64): Int64; begin Result := ((UseTimeTick shl FTimestampShift) + (Int64(FWorkerId) shl FSeqBitLength) + Cardinal(FCurrentSeqNumber)); Inc(FCurrentSeqNumber); end; function TSnowWorkerM1.CalcTurnBackId(UseTimeTick: Int64): Int64; begin Result := ((UseTimeTick shl FTimestampShift) + (Int64(FWorkerId) shl FSeqBitLength) + FTurnBackIndex); Dec(FTurnBackTimeTick); end; function TSnowWorkerM1.GetCurrentTimeTick(): Int64; var Millis: Int64; begin // Millis := DateTimeToUnix(Now(), False); Millis := GetMillisecondTimeStamp(); Result := Millis - FBaseTime; end; function TSnowWorkerM1.GetNextTimeTick(): Int64; var TempTimeTicker: Int64; begin TempTimeTicker := GetCurrentTimeTick(); while (TempTimeTicker <= FLastTimeTick) do begin // Sleep(1); TSpinWait.SpinUntil( function(): Boolean begin Result := False; end, 1); TempTimeTicker := GetCurrentTimeTick(); end; Result := TempTimeTicker; end; function TSnowWorkerM1.NextId(): Int64; begin SyncLock.Enter; try if FIsOverCost then Result := NextOverCostId() else Result := NextNormalId(); finally SyncLock.Leave; end; end; end.