雪花Id生成算法,是鼎鼎有名的分布式Id生成算法。它的优点在于,在分布式系统中快速生成有时间顺序的唯一编号!Snowflake实测每秒可生成900万个唯一Id。
Nuget包:NewLife.Core
源码地址:
https://github.com/NewLifeX/X/blob/master/NewLife.Core/Data/Snowflake.cs
核心原理
使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增。
格式:1bit保留 + 41bit时间戳 + 10bit机器 + 12bit序列号
第一位不使用,主要是为了避免部分场景变成负数;
41位时间戳,也就是2的41次方,毫秒为单位,足够保存69年。这里一般存储1970年以来的毫秒数,建议各个系统根据需要自定义这个开始日期;
10位机器码,理论上可以表示1024台机器,也可以拆分几位表示机房几位表示机器。这里默认采用本机IPv4地址最后两段以及进程Id一起作为机器码,确保机房内部不同机器,以及相同机器上的不同进程,拥有不同的机器码;
12位序列号,表示范围0~4095,一直递增,即使毫秒数加一,这里也不会归零,避免被恶意用户轻易猜测得到前后订单号;
生成Id
NewId用于生成新的唯一Id
/// <summary>获取下一个Id</summary>
/// <returns></returns>
public virtual Int64 NewId();/// <summary>获取指定时间的Id,带上节点和序列号。可用于根据业务时间构造插入Id</summary>
/// <param name="time">时间</param>
/// <returns></returns>
public virtual Int64 NewId(DateTime time);
无参版默认使用当前时间生成唯一Id,也可以给指定时间生成唯一Id。
以下是采用雪花Id作为订单号。
解析Id
大型数据表,例如订单表、日志表等,可以使用Int64作为主键,然后使用雪花Id。因为雪花Id内带有时间戳信息,因此我们可以根据主键Id来直接搜索指定时间区间的数据。
/// <summary>时间转为Id,不带节点和序列号。可用于构建时间片段查询</summary>
/// <param name="time">时间</param>
/// <returns></returns>
public virtual Int64 GetId(DateTime time);
GetId用于计算指定时间的基准Id,只有最高的时间部分,机器码和序列化为零。我们在计算指定时间区间(start, end)内的数据时,可以有:
Select * from Order where Id>=Get(start) and Id<GetId(end);
拿到一个雪花Id,也可以从中解析得到时间等信息
/// <summary>尝试分析</summary>
/// <param name="id"></param>
/// <param name="time">时间</param>
/// <param name="workerId">节点</param>
/// <param name="sequence">序列号</param>
/// <returns></returns>
public virtual Boolean TryParse(Int64 id, out DateTime time, out Int32 workerId, out Int32 sequence);
解析订单号 152075078181383514 ,时间基准设为 2020年1月1日
总结
在分布式系统中,雪花Id具有非常重要的意义。星尘大量使用雪花Id,用于存储跟踪数据和采样数据,以前必须先插入跟踪数据得到自增Id然后才能插入采样数据(需要关联)的问题迎难而解,两者都可以同时走批量插入。
对于日志型数据表,强烈推荐使用雪花Id,因为它带有时间戳信息,等同于省去了CreateTime字段的索引。