惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Hackread – Cybersecurity News, Data Breaches, AI and More
G
Google Developers Blog
The GitHub Blog
The GitHub Blog
Jina AI
Jina AI
A
About on SuperTechFans
MongoDB | Blog
MongoDB | Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园_首页
Blog — PlanetScale
Blog — PlanetScale
酷 壳 – CoolShell
酷 壳 – CoolShell
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
B
Blog RSS Feed
大猫的无限游戏
大猫的无限游戏
美团技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
L
LangChain Blog
WordPress大学
WordPress大学
S
SegmentFault 最新的问题
腾讯CDC
Hugging Face - Blog
Hugging Face - Blog
阮一峰的网络日志
阮一峰的网络日志
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

博客园 - delphi中间件

postgresql建库建表脚本 ubuntu配置postgresql远程访问 ubuntu安装postgresql数据库 delphi 面向模型编程 array of TVarRec core.recordModel.pas 使用泛型序列结构体 DDD建模指导 rabbitMQ VS mqtt redis流的应用场景 redis消费者组 redis流的操作命令 领域服务与领域事件 业务规则和模型 限界上下文与统一语言 领域驱动 mqtt即时通讯 ActiveRecord ORM RAD(速成应用开发) unigui插件框架 工厂流水线式自动生产UNIGUI WEB软件 delphi cs\web一种统一的界面风格 - delphi中间件 - 博客园 动态生成unidbgrid 单据工厂 用json元数据填充模板 mormot2 ORM rest vs jsonrpc SSE技术详解:使用 HTTP 做服务端数据推送应用的技术 http持久连接 json-rpc 2.0
雪花ID
delphi中间件 · 2026-01-31 · via 博客园 - delphi中间件

雪花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;