唯一ID生成
1 雪花算法
生成后是一个 64bit 的 long 型的数值,组成部分引入了时间戳,基本保持了自增。结构如下图所示:
固定值: 1bit,最高位是符号位,0 表示正,1 表示负,固定为 0,如果是 1 就是负数了。
时间戳: 41bit,存储毫秒级时间戳(41 位的长度可以使用 69 年)。
标识位(存储机器码):10bit,上面中的 机器id(5bit)和 服务id(5bit)统一叫作“标识位”,两个标识位组合起来最多可以支持部署 1024 个节点。
序列号:12bit,用于表示在同一毫秒内生成的多个ID的序号。如果在同一毫秒内生成的ID超过了4096个(2的12次方),则需要等到下一毫秒再生成ID。
默认的雪花算法是 64 bit,具体的长度可以自行配置。如果希望运行更久,增加时间戳的位数;如果需要支持更多节点部署,增加标识位长度;如果并发很高,增加序列号位数。
总结:雪花算法并不是一成不变的,可以根据系统内具体场景进行定。在单机上,生成的ID是递增的,但在多台机器上,只能大致保持递增趋势,并不能严格保证递增。这是因为多台机器之间的时钟不一定完全同步
缺点:依赖服务器时间,服务器时间回拨时可能会生成重复 id。
时间回拨:服务器上的时间突然倒退到之前的时间
生成 ID 重复问题,假设场景:
一个订单微服务,通过雪花算法生成 ID,共部署三个节点,标识位一致。此时有 200 并发,均匀散布三个节点,三个节点同一毫秒同一序列号下生成 ID,那么就会产生重复 ID。
通过上述假设场景,可以知道雪花算法生成 ID 冲突存在一定的前提条件:
- 服务通过集群的方式部署,其中部分机器标识位一致;
- 业务存在一定的并发量,没有并发量无法触发重复问题;
- 生成 ID 的时机:同一毫秒下的序列号一致。
解决方案:如果能保证标识位不重复,那么雪花 ID 也不会重复
- 预分配
应用上线前,统计当前服务的节点数,人工去申请标识位。这种方案,没有代码开发量,在服务节点固定或者项目少可以使用,但是解决不了服务节点动态扩容性问题。
- 动态分配
通过将标识位存放在 Redis、Zookeeper、MySQL 等中间件,在服务启动的时候去请求标识位,请求后标识位更新为下一个可用的。
- 统一分配ID
启动一个专门分配ID的服务,它来统一分配各个业务或服务需要的ID。
leaf分为雪花模式、号段模式,
A: ↑ sonwflake方式完全无依赖,容灾等级更高,ID有意义可读,不会暴露数量信息;长度固定Long 64位
B: ↓ 号段模式能够自定义ID初始值,方便业务迁移,通常情况下号段利用率更高;id递增,最大Long Max
2、号段模式
号段模式:leaf每台服务器会缓存步长号段,如果设置步长是1000,那么每台机器都会缓存1000个ID,如下图:
步长也就是服务器每次回去加载的号段长度,并缓存到leaf服务中。平台申请key时设置步长【日调用量/100】,设置ID初始值。leaf对号段模式进行了优化,采用了了双buffer机制;
步长设置不合理可能带来的问题:
在内存中会存在两个号段,如果步长为1000,相当于缓存了2000个ID。当瞬时请求量很大时,两个号段会被瞬间消费完毕,而如果此时号段更新线程没有及时从DB中获取到新的号段,将导致获取id失败(获取到的id不是正确的id值,而是-1)。
leaf可动态调整步长,leaf根据号段消费速度动态调整步长大小。
原理:每次从DB中获取新的号段时,都与前一次号段维持时间做对比,如果小于N,则增加号段长度,如果大于N则减小号段长度,从而做到号段至少能够维持N时间的长度。
在Leaf中,设置的号段可维持的时间长度为15分钟。
假设两次更新号段的时间间隔为duration。
- 如果 0 < duration < 15mins,step=step*2
- 如果 15mins <= duration < 30mins, step维持不变
- 如果 30mins <= duration, step=step/2
为了防止step的值过小或过大,对step的最大值和最小值做了限制。
step的最小值不能小于申请时的初始固定值;step的最大值不能大于100,0000
leaf目前提供的两种ID发号模式都只保证了id号不重复,不保证ID号码单调递增