雪花算法是一种产生编号的算法;因其产号率高,且可以分布式部署,受到大家的青睐;
该算法的详细介绍和优点网上很多,自行搜索一下;本篇文章重点介绍该算法的局限性:
- 雪花算法的使用年限为69年;这个就需要考虑下是否够你的系统使用;还有就是注意起始原点时间的设定,特别重要;我的初步想法就是在原有算法基础上加上一个第几轮的前缀或后缀,因我的这个系统感觉用不了那么多年,没有继续研究和实践;有兴趣的伙伴可以自行研究一下;
- 雪花算法虽然可以分布式部署,但该算法严重依赖系统时钟;所以系统时钟不可以回拨;对于云服务器等基本没有问题;但是对于本地部署的服务器可能会存在时间回拨的情况;对于这个情况,网上也有解决方案,就是把每毫秒的当前已经产生了的最大计数记录一下,当时钟回拨后, 读取该最大计数值,然后再生成;这个方法貌似可以,但是实际操作难度很大(需要存储的数据量庞大进而导致产码率下降); 我的方法是:时钟回拨后直接用GUID返回,哈哈,简单吧!关于排序嘛记录中增加一个时间戳的字段就可以解决了;这么做的主要原因还是需要追求产码效率~~
以下是代码,主要代码是网上的;我只增加了时钟回拨后的产生GUID的部分;代码仅供参考
unit uSnowflakeEx;
interface
uses
DateUtils, SysUtils, IniFiles;
type
// 雪花算法
{
雪花算法简单描述:
+ 最高位是符号位,始终为0,不可用。
+ 41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
+ 10位的机器标识,10位的长度最多支持部署1024个节点。
+ 12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。
}
TSnowflakeAlgorithm = class
private
FLock: TObject; // 临界区
FStartTime : TDateTime; // 开始计数的原点时间
FRecordTime : TDateTime; // 上次关闭的时间
FNowTime: Int64; // 现在时间[单位: ms]
FLastTime: Int64; // 上个时间[单位: ms]
FMachineID: Integer; // 机器标识 0 .. 1023 [十进制 1023 = 二进制 1111111111 ]
FCount: Integer; // 计数序列号 0 .. 4095 [十进制 4095 = 二进制 111111111111 ]
function IniFilePath:string;
function Init:Boolean; //从配置文件中读取时间戳;用于防止时钟回拨
function UnInit:Boolean; //向配置文件中写入时间戳;用于防止时钟回拨
public
constructor Create(AMachineID:Integer); //0~1023
destructor Destroy; override;
function GetSnowflakeID: string; // 获取雪花ID
end;
implementation
{ TSnowflakeAlgorithm }
constructor TSnowflakeAlgorithm.Create(AMachineID:Integer);
begin
FLock := TObject.Create; // 初始化临界区对象
FMachineID := AMachineID; // 机器 ID
// FStartTime := EncodeDateTime(1970, 1, 1, 8, 0, 0, 0);
FStartTime := EncodeDateTime(2020, 1, 1, 8, 0, 0, 0);
//从配置文件中读取最后使用时间
Init;
FLastTime := MilliSecondsBetween(FRecordTime, FStartTime);
FCount := 0;
end;
destructor TSnowflakeAlgorithm.Destroy;
begin
UnInit;
FreeAndNil(FLock); // 删除临界区对象
inherited;
end;
function TSnowflakeAlgorithm.GetSnowflakeID: string;
var
_int64 : Int64;
function GetGUID: string;
var
LTep: TGUID;
sGUID: string;
begin
CreateGUID(LTep);
sGUID := GUIDToString(LTep);
sGUID := StringReplace(sGUID, '-', '', [rfReplaceAll]);
sGUID := Copy(sGUID, 2, Length(sGUID) - 2);
Result := LowerCase(sGUID);
end;
begin
TMonitor.Enter(FLock);
try
FNowTime := MilliSecondsBetween(Now, FStartTime); // 当前毫秒时间戳
if FLastTime > FNowTime then
begin
//时间倒退了; 则直接产生GUID
Result := GetGUID + Format('_%d', [FMachineID]) ;
end
else
begin
//当前时间大于或等于LastTime
FRecordTime := Now;
UnInit; //这个地方需要根据系统的特点适时调用;如果你几秒才产生一个号,这里就可以这样写;如果你一毫秒就要产生几千个号,这个地方就需要考虑下重写;毕竟写磁盘频率太高,影响效率;
if FLastTime = FNowTime then // 如果上一毫秒的时间 = 现在的时间
begin
if FCount > 4095 then // 如果同一毫秒内生成的 ID 个数 > 4096
begin
// Sleep(1); // 等待下一毫秒再进行生成
while FLastTime >= FNowTime do
begin
FNowTime := MilliSecondsBetween(Now, FStartTime);
end;
FLastTime := FNowTime; // 重新给上一毫秒时间 赋值
end;
end
else
begin
FCount := 0; // 初始化计数
FLastTime := FNowTime; // 重新给上一毫秒时间 赋值
end;
_int64 := (FNowTime shl 22) or (FMachineID shl 12) or FCount;
Result := IntToStr(_int64);
Inc(FCount); // 对计数变量进行自增
end;
finally
TMonitor.Exit(FLock);
end;
end;
function TSnowflakeAlgorithm.IniFilePath: string;
begin
Result := ExtractFilePath(ParamStr(0)) + 'Snowflake.ini';
end;
function TSnowflakeAlgorithm.Init: Boolean;
var
_IniFile : TIniFile;
begin
Result := False;
_IniFile := TIniFile.Create(IniFilePath);
try
try
FRecordTime := _IniFile.ReadDateTime('param', 'recordtime', Now());
Result := True;
except
on E: Exception do
end;
finally
FreeAndNil(_IniFile);
end;
end;
function TSnowflakeAlgorithm.UnInit: Boolean;
var
_IniFile : TIniFile;
begin
Result := False;
_IniFile := TIniFile.Create(IniFilePath);
try
try
_IniFile.WriteDateTime('param', 'recordtime', FRecordTime);
Result := True;
except
on E: Exception do
end;
finally
FreeAndNil(_IniFile);
end;
end;
end.
Pascal使用方法
//声明对象
var
GSnowflakeAlgorithm : TSnowflakeAlgorithm;
//创建对象
//其中的入参取值范围0~1023; 不同的分布站点需要配置不同的编号
GSnowflakeAlgorithm := TSnowflakeAlgorithm.Create(999);
//调用
if Assigned(GSnowflakeAlgorithm) then
begin
_sID := GSnowflakeAlgorithm.GetSnowflakeID;
end;
//释放
if Assigned(GSnowflakeAlgorithm) then FreeAndNil(GSnowflakeAlgorithm);
Pascal