概述

分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。

有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。

而TWitter的snowflake解决了这种需求,最初TWitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一ID生成服务。

结构

snowflake的结构如下(每部分用-分开):

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年),然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点) ,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)一共加起来刚好64位,为一个Long型。(转换成字符串后长度最多19)。

Snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。经测试snowflake每秒能够产生409.6万个ID。

在 Ubuntu 18.04 下运行的截图:

源码

{ *
* Twitter_Snowflake https://github.com/twitter-archive/snowflake
* SnowFlake的结构如下(每部分用-分开):
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
* 加起来刚好64位,为一个Long型。
* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生409.6万ID左右。
*
* 本算法参考官方 Twitter Snowflake 修改而来,同时借鉴了网上Java语言的版本。
* 作者:全能中间件 64445322 https://www.centmap.cn/server
* 使用方法:var OrderId := IdGenerator.NextId(),IdGenerator 不用创建也不用释放,而且该方法是线程安全的。
* } // 参考美团点评分布式ID生成系统
// https://tech.meituan.com/2017/04/21/mt-leaf.html
// https://github.com/Meituan-Dianping/Leaf/blob/master/leaf-core/src/main/java/com/sankuai/inf/leaf/snowflake/SnowflakeIDGenImpl.java unit Snowflake; interface uses
System.SysUtils, System.SyncObjs; type
TSnowflakeIdWorker = class(TObject)
private const
// 最大可用69年
MaxYears = ;
// 机器id所占的位数
WorkerIdBits = ;
// 数据标识id所占的位数
DatacenterIdBits = ;
// 序列在id中占的位数
SequenceBits = ;
// 机器ID向左移12位
WorkerIdShift = SequenceBits;
// 数据标识id向左移17位(12+5)
DatacenterIdShift = SequenceBits + WorkerIdBits;
// 时间截向左移22位(5+5+12)
TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits;
{$WARNINGS OFF}
// 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
SequenceMask = - xor (- shl SequenceBits);
// 支持的最大机器id
MaxWorkerId = - xor (- shl WorkerIdBits);
// 支持的最大数据标识id,结果是 31
MaxDatacenterId = - xor (- shl DatacenterIdBits);
{$WARNINGS ON}
private type
TWorkerID = .. MaxWorkerId;
TDatacenterId = .. MaxDatacenterId;
strict 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; inline;
function CurrentTimeStamp: Int64; inline;
function ElapsedMilliseconds: Int64; inline;
private
class var FLock: TSpinLock;
class var FInstance: TSnowflakeIdWorker;
class function GetInstance: TSnowflakeIdWorker; static;
class constructor Create;
class destructor Destroy;
protected
function GetEpoch: TDateTime;
procedure SetEpoch(const Value: TDateTime);
public
constructor Create; overload;
/// <summary>
/// 获得下一个ID (该方法是线程安全的)
/// </summary>
function NextId: Int64;inline;
/// <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; class property Instance: TSnowflakeIdWorker read GetInstance;
end; function IdGenerator: TSnowflakeIdWorker; 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'; implementation uses
System.Math, System.TimeSpan
{$IF defined(MSWINDOWS)}
, Winapi.Windows
{$ELSEIF defined(MACOS)}
, Macapi.Mach
{$ELSEIF defined(POSIX)}
, Posix.Time
{$ENDIF}
, System.DateUtils; function IdGenerator: TSnowflakeIdWorker;
begin
Result := TSnowflakeIdWorker.GetInstance;
end; { TSnowflakeIdWorker } constructor TSnowflakeIdWorker.Create;
{$IF defined(MSWINDOWS)}
var
Frequency: Int64;
{$ENDIF}
begin
inherited;
{$IF defined(MSWINDOWS)}
FIsHighResolution := QueryPerformanceFrequency(Frequency);
{$ELSEIF defined(POSIX)}
FIsHighResolution := True;
{$ENDIF}
FSequence := ;
FWorkerID := ;
FDatacenterId := ;
FLastTimeStamp := -;
FEpoch := DateTimeToUnix(EncodeDate(, , ), True) * MSecsPerSec;
FUnixTimestamp := DateTimeToUnix(Now, True) * MSecsPerSec;
FStartTimeStamp := CurrentTimeStamp;
end; class destructor TSnowflakeIdWorker.Destroy;
begin
FreeAndNil(FInstance);
end; class constructor TSnowflakeIdWorker.Create;
begin
FInstance := nil;
FLock := TSpinLock.Create(False);
end; class function TSnowflakeIdWorker.GetInstance: TSnowflakeIdWorker;
begin
FLock.Enter;
try
if FInstance = nil then
FInstance := TSnowflakeIdWorker.Create;
Result := FInstance;
finally
FLock.Exit;
end;
end; function TSnowflakeIdWorker.CurrentTimeStamp: Int64;
{$IF defined(POSIX) and not defined(MACOS)}
var
res: timespec;
{$ENDIF}
begin
{$IF defined(MSWINDOWS)}
if FIsHighResolution then
QueryPerformanceCounter(Result)
else
Result := GetTickCount * Int64(TTimeSpan.TicksPerMillisecond);
{$ELSEIF defined(MACOS)}
Result := Int64(AbsoluteToNanoseconds(mach_absolute_time) div );
{$ELSEIF defined(POSIX)}
clock_gettime(CLOCK_MONOTONIC, @res);
Result := (Int64() * res.tv_sec + res.tv_nsec) div ;
{$ENDIF}
end; function TSnowflakeIdWorker.ElapsedMilliseconds: Int64;
begin
Result := (CurrentTimeStamp - FStartTimeStamp) div TTimeSpan.TicksPerMillisecond;
end; function TSnowflakeIdWorker.GetEpoch: TDateTime;
begin
Result := UnixToDateTime(FEpoch div MSecsPerSec, True);
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 <= then
begin
// 时间偏差大小小于5ms,则等待两倍时间
System.SysUtils.Sleep(Offset shr ); Timestamp := CurrentMilliseconds();
// 还是小于,抛异常并上报
if Timestamp < FLastTimeStamp then
raise Exception.CreateFmt(ERROR_CLOCK_MOVED_BACKWARDS, [FLastTimeStamp - Timestamp]);
end;
end; // 如果是同一时间生成的,则进行毫秒内序列
if (FLastTimeStamp = Timestamp) then
begin
FSequence := (FSequence + ) and SequenceMask;
// 毫秒内序列溢出
if (FSequence = ) then
// 阻塞到下一个毫秒,获得新的时间戳
Timestamp := WaitUntilNextTime(FLastTimeStamp);
end
// 时间戳改变,毫秒内序列重置
else
FSequence := ; // 上次生成ID的时间截
FLastTimeStamp := Timestamp; // 移位并通过或运算拼到一起组成64位的ID
Result := ((Timestamp - FEpoch) shl TimestampLeftShift)
or (DatacenterId shl DatacenterIdShift)
or (WorkerID shl WorkerIdShift)
or FSequence;
finally
FLock.Exit;
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, True) * MSecsPerSec;
end; function TSnowflakeIdWorker.CurrentMilliseconds: Int64;
begin
Result := FUnixTimestamp + ElapsedMilliseconds;
end; end.

一秒可生成500万ID的分布式自增ID算法—雪花算法 (Snowflake,Delphi 版)的更多相关文章

  1. 分布式唯一ID生成算法-雪花算法

    在我们的工作中,数据库某些表的字段会用到唯一的,趋势递增的订单编号,我们将介绍两种方法,一种是传统的采用随机数生成的方式,另外一种是采用当前比较流行的“分布式唯一ID生成算法-雪花算法”来实现. 一. ...

  2. Twitter分布式自增ID算法snowflake原理解析

    以JAVA为例 Twitter分布式自增ID算法snowflake,生成的是Long类型的id,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特(0和1). 那么一个 ...

  3. Twitter分布式自增ID算法snowflake原理解析(Long类型)

    Twitter分布式自增ID算法snowflake,生成的是Long类型的id,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特(0和1). 那么一个Long类型的6 ...

  4. 详解Twitter开源分布式自增ID算法snowflake(附演算验证过程)

    详解Twitter开源分布式自增ID算法snowflake,附演算验证过程 2017年01月22日 14:44:40 url: http://blog.csdn.net/li396864285/art ...

  5. 分布式自增ID算法-Snowflake详解

    1.Snowflake简介 互联网快速发展的今天,分布式应用系统已经见怪不怪,在分布式系统中,我们需要各种各样的ID,既然是ID那么必然是要保证全局唯一,除此之外,不同当业务还需要不同的特性,比如像并 ...

  6. Spring - jdbcTemplate - 调试代码: PreparedStatementCreator 生成的语句, update 之后没有 自增id, 已解决

    1. 概述 解决 jdbcTemplate 下, update 结果不带 自增id 的问题 2. 场景 看书 Spring in Action 5th 3.1.4 listing 3.10 saveT ...

  7. Twitter的分布式自增ID算法snowflake (Java版)

    概述 分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的. 有些时候我们希望能使用一种 ...

  8. Twitter的分布式自增ID算法snowflake(雪花算法) - C#版

    概述 分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的.有些时候我们希望能使用一种简 ...

  9. 分布式自增ID算法snowflake (Java版)

    概述 分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的. 有些时候我们希望能使用一种 ...

随机推荐

  1. Load Testing Socket.IO Web Applications and Infrastructure

    转自:https://medium.com/better-programming/load-testing-socket-io-web-applications-and-infrastructure- ...

  2. three.js 加载3DS 404 文件找不到

    web.config修改如下: code: <?xml version="1.0" encoding="utf-8"?> <!-- 有关如何配 ...

  3. Git 管理篇(详细)

    新建repository 本地目录下,在命令行里新建一个代码仓库(repository) 里面只有一个README.md 命令如下: touch README.md git init 初始化repos ...

  4. 「插头dp」

    Tasklist: 标识设计 神奇游乐园 Manhattan Wiring ParkII 游览计划 CITY: 只用一条回路经过所有可通过的块 括号匹配,注意结束位置不一定是(n,m) 地板: 分已经 ...

  5. Java算法考试笔记

    1.快速构建排列树: void BackTrace(int t){//排列树 if(t<N){//如果没有到叶子结点 for(int i=t;i<N;i++){ swap(x,t,i); ...

  6. OpenFOAM——设置非均匀边界方法总结

    在使用OpenFOAM求解的时候我们经常需要设置非均匀的边界,比如我们在计算层流的时候,很多时候需要入口为充分发展的入口边界,下面我们就以入口处为充分发展的层流速度分布为总结OpenFOAM当中设定不 ...

  7. [2019BUAA软件工程]个人期末总结感想

    写在前面   经过一学期对于软件工程的学习,笔者完成了一次结对编程以及三个周期的敏捷开发流程.在本博客中笔者对于一学期的学习进行了总结,并对于自己最初的疑惑做出了回答.   笔者在学期开始前应课程要求 ...

  8. vue+elementui项目打包后样式变化问题

    博主刚刚解决了index.html空白问题,刚打开项目页面又发现了样式出现了大问题,样式与开发版本有很大不同,有些样式没有生效.利用搜索引擎,找到了问题所在以及解决办法:main.js中的引入顺序决定 ...

  9. 哈希表查找(散列表查找) c++实现HashMap

    算法思想: 哈希表 什么是哈希表 在前面讨论的各种结构(线性表.树等)中,记录在结构中的相对位置是随机的,和记录的关键字之间不存在确定的关系,因此,在结构中查找记录时需进行一系列和关键字的比较.这一类 ...

  10. PHP系列 | 代码复用trait的构造函数使用

    在ThinkPHP5.1 框架中自己封装了一个 trait 类,每次都在控制器中使用,但是在使用框架自身的验证器类(框架控制器方法)报错 在控制器中使用自定义验证器 $param = $this-&g ...