Files
idgenerator/Delphi/source/Core/uSnowWorkerM1.pas
2022-11-07 14:24:31 +08:00

405 lines
10 KiB
ObjectPascal
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.