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.