mirror of
https://gitee.com/yitter/idgenerator.git
synced 2026-05-30 22:48:08 +08:00
405 lines
10 KiB
ObjectPascal
405 lines
10 KiB
ObjectPascal
unit uSnowWorkerM1;
|
||
|
||
interface
|
||
|
||
uses
|
||
uISnowWorker, uIdGeneratorOptions, System.SyncObjs, uTOverCostActionArg, System.DateUtils, System.SysUtils;
|
||
|
||
/// <summary>
|
||
/// 雪花漂移算法
|
||
/// </summary>
|
||
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
|
||
/// <summary>
|
||
/// 基础时间
|
||
/// </summary>
|
||
// property BaseTime: TDateTime read FBaseTime write FBaseTime;
|
||
property BaseTime: Int64 read FBaseTime;
|
||
/// <summary>
|
||
/// 机器码
|
||
/// </summary>
|
||
property WorkerId: Word read FWorkerId default 0;
|
||
/// <summary>
|
||
/// 机器码位长
|
||
/// </summary>
|
||
property WorkerIdBitLength: Byte read FWorkerIdBitLength default 0;
|
||
/// <summary>
|
||
/// 自增序列数位长
|
||
/// </summary>
|
||
property SeqBitLength: Byte read FSeqBitLength default 0;
|
||
/// <summary>
|
||
/// 最大序列数(含)
|
||
/// </summary>
|
||
property MaxSeqNumber: Integer read FMaxSeqNumber default 0;
|
||
/// <summary>
|
||
/// 最小序列数(含)
|
||
/// </summary>
|
||
property MinSeqNumber: Word read FMinSeqNumber default 0;
|
||
/// <summary>
|
||
/// 最大漂移次数(含)
|
||
/// </summary>
|
||
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<OverCostActionArg> 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.
|