雪花ID

雪花ID

unit core.Snowflake;

interface

uses
  SysUtils, SyncObjs;

const
    // 最大可用69年
    MaxYears = 69;
    // 机器id所占的位数
    WorkerIdBits = 5;
    // 数据标识id所占的位数
    DatacenterIdBits = 5;
    // 序列在id中占的位数
    SequenceBits = 12;
    // 机器ID向左移12位
    WorkerIdShift = SequenceBits;
    // 数据标识id向左移17位(12+5)
    DatacenterIdShift = SequenceBits + WorkerIdBits;
    // 时间截向左移22位(5+5+12)
    TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits;

    TicksPerMillisecond = 10000;
{$WARNINGS OFF}
    // 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
    SequenceMask = -1 xor (-1 shl SequenceBits);
    // 支持的最大机器id
    MaxWorkerId = -1 xor (-1 shl WorkerIdBits);
    // 支持的最大数据标识id,结果是 31
    MaxDatacenterId = -1 xor (-1 shl DatacenterIdBits);
{$WARNINGS ON}

type
    TWorkerID = 0 .. MaxWorkerId;
    TDatacenterId = 0 .. MaxDatacenterId;

type
  TSnowflakeIdWorker = class(TObject)
  private
    FWorkerID: TWorkerID;
    FDatacenterId: TDatacenterId;
    FEpoch: Int64;
    FSequence: Int64;
    FLastTimeStamp: Int64;
    FStartTimeStamp: Int64;
    FUnixTimestamp: Int64;
    FIsHighResolution: Boolean;
    /// <summary>
    /// 阻塞到下一个毫秒,直到获得新的时间戳
    /// </summary>
    /// <param name="ATimestamp ">上次生成ID的时间截</param>
    /// <returns>当前时间戳 </returns>
    function WaitUntilNextTime(ATimestamp: Int64): Int64;
    /// <summary>
    /// 返回以毫秒为单位的当前时间
    /// </summary>
    /// <remarks>
    /// 时间的表达格式为当前计算机时间和1970年1月1号0时0分0秒所差的毫秒数
    /// </remarks>
    function CurrentMilliseconds: Int64;
    function CurrentTimeStamp: Int64;
    function ElapsedMilliseconds: Int64;
  private

    class function GetInstance: TSnowflakeIdWorker;
    constructor Create;
    destructor Destroy;
  protected
    function GetEpoch: TDateTime;
    procedure SetEpoch(const Value: TDateTime);
  public
    /// <summary>
    /// 获得下一个ID (该方法是线程安全的)
    /// </summary>
    function NextId: Int64;
    /// <summary>
    /// 工作机器ID(0~31)
    /// </summary>
    property WorkerID: TWorkerID read FWorkerID write FWorkerID;
    /// <summary>
    /// 数据中心ID(0~31)
    /// </summary>
    property DatacenterId: TDatacenterId read FDatacenterId write FDatacenterId;
    /// <summary>
    /// 开始时间
    /// </summary>
    property Epoch: TDateTime read GetEpoch write SetEpoch;
  end;

var
    FLock: TCriticalSection;
    FInstance: TSnowflakeIdWorker = nil;
    _ServerDateTime: TDateTime;      //服务器时间
    _ServerTimeStamp: Integer = 0;   //服务器时间跟本地时间间隔,毫秒

const
  ERROR_CLOCK_MOVED_BACKWARDS = 'Clock moved backwards. Refusing to generate id for %d milliseconds';
  ERROR_EPOCH_INVALID         = 'Epoch can not be greater than current';


//todo: 电脑时间比正常时间慢n年,出现负数问题
function IdGenerator(): TSnowflakeIdWorker;

implementation

uses
  Math,
//  TimeSpan,
  Windows,
  DateUtils;

function IdGenerator: TSnowflakeIdWorker;
begin
  Result := TSnowflakeIdWorker.GetInstance;
end;

{ TSnowflakeIdWorker }

constructor TSnowflakeIdWorker.Create;
var
  Frequency: Int64;
begin
  inherited;
  FIsHighResolution := QueryPerformanceFrequency(Frequency);

  FSequence      := 0;
  FWorkerID      := 1;
  FDatacenterId  := 1;
  FLastTimeStamp := -1;
  FEpoch         := DateTimeToUnix(EncodeDate(2019, 12, 12)) * MSecsPerSec;
  FUnixTimestamp := DateTimeToUnix(Now) * MSecsPerSec;
  FStartTimeStamp:= CurrentTimeStamp;

  FInstance := nil;
end;

destructor TSnowflakeIdWorker.Destroy;
begin
  FreeAndNil(FInstance);
end;

class function TSnowflakeIdWorker.GetInstance: TSnowflakeIdWorker;
begin
  FLock.Enter;
  try
    if FInstance = nil then
      FInstance := TSnowflakeIdWorker.Create;
    Result := FInstance;
  finally
    FLock.Leave;
  end;
end;

function TSnowflakeIdWorker.CurrentTimeStamp: Int64;
begin
  if FIsHighResolution then
    QueryPerformanceCounter(Result)
  else
    Result := GetTickCount * Int64(TicksPerMillisecond);
end;

function TSnowflakeIdWorker.ElapsedMilliseconds: Int64;
begin
  Result := (CurrentTimeStamp - FStartTimeStamp) div TicksPerMillisecond;
end;

function TSnowflakeIdWorker.GetEpoch: TDateTime;
begin
  Result := UnixToDateTime(FEpoch div MSecsPerSec);
end;

function TSnowflakeIdWorker.NextId: Int64;
var
  Offset: Integer;
  Timestamp: Int64;
begin
  FLock.Enter;
  try
    Timestamp := CurrentMilliseconds();

    // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
    if (Timestamp < FLastTimeStamp) then
    begin
      Offset := FLastTimeStamp - Timestamp;
      if Offset <= 5 then
      begin
        // 时间偏差大小小于5ms,则等待两倍时间
        Sleep(Offset shr 1);

        Timestamp := CurrentMilliseconds();
        // 还是小于,抛异常并上报
        if Timestamp < FLastTimeStamp then
          raise Exception.CreateFmt(ERROR_CLOCK_MOVED_BACKWARDS, [FLastTimeStamp - Timestamp]);
      end;
    end;

    // 如果是同一时间生成的,则进行毫秒内序列
    if (FLastTimeStamp = Timestamp) then
    begin
      FSequence := (FSequence + 1) and SequenceMask;
      // 毫秒内序列溢出
      if (FSequence = 0) then
        // 阻塞到下一个毫秒,获得新的时间戳
        Timestamp := WaitUntilNextTime(FLastTimeStamp);
    end
    // 时间戳改变,毫秒内序列重置
    else
      FSequence := 0;

    // 上次生成ID的时间截
    FLastTimeStamp := Timestamp;

    // 移位并通过或运算拼到一起组成64位的ID
    Result := ((Timestamp - FEpoch) shl TimestampLeftShift)
      or (DatacenterId shl DatacenterIdShift)
      or (WorkerID shl WorkerIdShift)
      or FSequence;
  finally
    FLock.Leave;
  end;
end;

function TSnowflakeIdWorker.WaitUntilNextTime(ATimestamp: Int64): Int64;
var
  Timestamp: Int64;
begin
  Timestamp := CurrentMilliseconds();
  while Timestamp <= ATimestamp do
    Timestamp := CurrentMilliseconds();

  Result := Timestamp;
end;

procedure TSnowflakeIdWorker.SetEpoch(const Value: TDateTime);
begin
  if Value > Now then
    raise Exception.Create(ERROR_EPOCH_INVALID);

  if YearsBetween(Now, Value) <= MaxYears then
    FEpoch := DateTimeToUnix(Value) * MSecsPerSec;
end;

function TSnowflakeIdWorker.CurrentMilliseconds: Int64;
begin
  Result := FUnixTimestamp + ElapsedMilliseconds;
end;

initialization
  FLock := TCriticalSection.Create();
finalization
  FLock.Free;
  if Assigned(FInstance) then
    FInstance.Free;


end.
procedure TSnowService.SnowId(const ARequest: THttpRequest; const AResponse: THttpResponse);
var
  LRequestData, LResponseData: TData;
  LSnowFlake: TSnowflakeIdWorker;
begin
  LRequestData := DataPool.Lock;
  LResponseData := DataPool.Lock;
  try
    try
      TRequestFunc.UnMarshal(ARequest, LRequestData);
      LSnowFlake := IdGenerator;
      LSnowFlake.WorkerID := LRequestData.I[TConst.WorkerId];
      LSnowFlake.DatacenterId := LRequestData.I[TConst.DataCenterId];
      LResponseData.I64['snowflake'] := LSnowFlake.NextId;
      LResponseData.B[TConst.Success] := True;
      TResponseFunc.Send(AResponse, LResponseData);
    except
      on E: Exception do
      begin
        LResponseData.B[TConst.Success] := False;
        LResponseData.S[TConst.Msg] := E.Message;
        TResponseFunc.Send(AResponse, LResponseData);
        WriteLog('TSnowService.SnowId()' + E.Message);
      end;
    end;
  finally
    DataPool.Unlock(LRequestData);
  end;
end;

 

posted @ 2026-01-31 09:56  delphi中间件  阅读(7)  评论(0)    收藏  举报