关于雪环算法生成 ID 冲突问题,文中给了一种方案:分配标示位;通过分配雪花算法的组成标识位,来达到默认 1024 节点下 ID 生成唯一。
雪花算法不是万能的,并不能适用于所有场景。如果 ID 要求全局唯一并且服务节点超出 1024 节点,可以选择修改算法本身的组成,即扩展标识位,或者选择开源方案:LEAF、UID。
有些时候我们希望能使用一种简单些的 ID,并且希望 ID 能够按照时间有序生成。
什么是雪花算法
Twitter 雪花算法生成后是一个 64bit 的 long 型的数值,组成部分引入了时间戳,基本保持了自增
SnowFlake 算法的优点:
高性能高可用:生成时不依赖于数据库,完全在内存中生成
高吞吐:每秒钟能生成数百万的自增 ID
ID 自增:存入数据库中,索引效率高
SnowFlake 算法的缺点:
雪花算法组成
snowflake 结构如下图所示:
不使用:1bit,最高位是符号位,0 表示正,1 表示负,固定为 0
时间戳:41bit,毫秒级的时间戳(41 位的长度可以使用 69 年)
标识位:5bit 数据中心 ID,5bit 工作机器 ID,两个标识位组合起来最多可以支持部署 1024 个节点
序列号:12bit 递增序列号,表示节点毫秒内生成重复,通过序列号表示唯一,12bit 每毫秒可产生 4096 个 ID
默认的雪花算法是 64 bit,具体的长度可以自行配置。如果希望运行更久,增加时间戳的位数;如果需要支持更多节点部署,增加标识位长度;如果并发很高,增加序列号位数
总结:雪花算法并不是一成不变的,可以根据系统内具体场景进行定制
雪花算法适用场景
所以,日常业务使用中,雪花算法更多是被应用在数据库的主键 ID 和业务关联主键
雪花算法生成 ID 重复问题
假设:一个订单微服务,通过雪花算法生成 ID,共部署三个节点,标识位一致
通过上述假设场景,可以知道雪花算法生成 ID 冲突存在一定的前提条件
服务通过集群的方式部署,其中部分机器标识位一致
业务存在一定的并发量,没有并发量无法触发重复问题
生成 ID 的时机:同一毫秒下的序列号一致
标识位如何定义
通过上面的案例,知道了 ID 重复的必要条件。如果要避免服务内产生重复的 ID,那么就需要从标识位上动文章
Mybatis-Plus v3.4.2 雪花算法实现类 Sequence,提供了两种构造方法:无参构造,自动生成 dataCenterId 和 workerId;有参构造,创建 Sequence 时明确指定标识位
一起看下 Sequence 的创建默认无参构造,如何生成 dataCenterId 和 workerId
public static long getDataCenterId(long maxDatacenterId {
long id = 1L;
final byte[] mac = NetUtil.getLocalHardwareAddress(;
if (null != mac {
id = ((0x000000FF & (long mac[mac.length - 2]
| (0x0000FF00 & (((long mac[mac.length - 1] << 8 >> 6;
id = id % (maxDatacenterId + 1;
}
return id;
}
入参 是一个固定值,代表数据中心 ID 最大值,默认值 31
获取 dataCenterId 时存在两种情况,一种是网络接口为空,默认取 1L;另一种不为空,通过 Mac 地址获取 dataCenterId
接下来再看看 workerId
public static long getWorkerId(long datacenterId, long maxWorkerId {
final StringBuilder mpid = new StringBuilder(;
mpid.append(datacenterId;
try {
mpid.append(RuntimeUtil.getPid(;
} catch (UtilException igonre {
//ignore
}
return (mpid.toString(.hashCode( & 0xffff % (maxWorkerId + 1;
}
入参 maxWorkderId 也是一个固定值,代表工作机器 ID 最大值,默认值 31;datacenterId 取自上述的 getDatacenterId 方法
通过 MAC + PID 的 hashcode 获取16个低位,进行运算,最终得到 workerId
分配标识位
标识位如何定义才能不重复?有两种方案:预分配和动态分配
预分配
这种方案,没有代码开发量,在服务节点固定或者项目少可以使用,但是解决不了服务节点动态扩容性问题
动态分配
通过存放标识位,延伸出一个问题:雪花算法的 ID 是 服务内唯一还是全局唯一
两者的区别仅是 不同的服务间是否公用 Redis。如果没有全局唯一的需求,最好使 ID 服务内唯一,因为这样可以避免单点问题
动态分配实现方案
第一个服务节点在获取时,Redis 可能是没有 snowflake_work_id_key 这个 Hash 的,应该先判断 Hash 是否存在,不存在初始化 Hash,dataCenterId、workerId 初始化为 0
如果 Hash 已存在,判断 dataCenterId、workerId 是否等于最大值 31,满足条件初始化 dataCenterId、workerId 设置为 0 返回
dataCenterId 和 workerId 的排列组合一共是 1024,在进行分配时,先分配 workerId
判断 workerId 是否 != 31,条件成立对 workerId 自增,并返回;如果 workerId = 31,自增 dataCenterId 并将 workerId 设置为 0
dataCenterId、workerId 是一直向下推进的,总体形成一个环状。通过 Lua 脚本的原子性,保证 1024 节点下的雪花算法生成不重复。如果标识位等于 1024,则从头开始继续循环推进。