雪花算法调研落地

393 阅读5分钟

what(目标)

  1. 全局唯一:每一个用户都需要有一个ID,不同用户ID不同
  2. 递增:满足唯一性的同时ID最好具有时间递增性
  3. 数据安全:ID最好不要暴露平台用户体量
  4. 量级:ID池要足够大,支持平台至少百万级别的用户量级
  5. 性能:ID的生成要支持高并发

how(实现)

根据目标要求,采用雪花算法能满足

41bit时间戳,毫秒级别 可以用69年

10bit机器ID 可以有1024台机器

12bit序列号 一毫秒可以分发4096个序列号

机器ID生成

在雪花算法里,最复杂的是机器ID的生成,最本质的含义是真正运行代码的机器的ID标识,但是随着容器化等服务运行方式多样,机器ID的用法也有很多,最基本的保持全局范围内,同一时间机器号不重复即可,涉及 机器ID的分配和回收

方案具体优点缺点
zookeeper每个服务都需要去zookeeper注册,获取自己的ID严谨增加架构复杂性,引入其他组件,依赖zookeeper
redis使用incr命令获取递增ID,设置过期时间,过期之后从0开始简单依赖redis,大体量可能有重复风险
数据库记录服务和ID的记录,没有就insert简单依赖数据库
生成随机数暴力随机,重复就取下一个随机值简单,本地实现过于粗暴,可能会经常有冲突,用户不友好
pod名截取po d名转换为整形作为机器ID本地需要转换逻辑,依赖pod名不重复,形式是storage-operator-677cf8bc7b-4tf28截取后面两部分,需要8字节
hostname在pod中就是pod IP 本地,相对比pod名省空间,两个字节需要考虑集群隔离,不同集群可能IP相同
etcd注册运维层面根据pod生命周期向etcd注册回收ID代码层面简单,直接获取环境变量,占用字节少,不用考虑集群,机房等问题增加运维成本,将问题转移到运维层面解决1.需要考虑获取不到环境变量的保底策略,告警,2.pod意外退出没有及时回收问题3.pod反复重启问题

hostname

按照hostname考虑,IP取后面16位的主机地址,占用2个字节,不同集群IP可能重复,前面3个位的集群标识,workid 的组成就是 集群ID(3bit) + pod IP (16bit)=19bi t ,剩下的64-1-19=44bit 分配给时间和序列号,不同组合如下

组合是否可行
时间是41位,单位是毫秒,可以用69.730570年,序列号可以有8.000000个
时间是41位,单位是10毫秒,可以用697.305700年,序列号可以有8.000000个
时间是41位,单位是100毫秒,可以用6973.057000年,序列号可以有8.000000个
时间是41位,单位是秒,可以用69730.570001年,序列号可以有8.000000个
时间是40位,单位是毫秒,可以用34.865285年,序列号可以有16.000000个
时间是40位,单位是10毫秒,可以用348.652850年,序列号可以有16.000000个
时间是40位,单位是100毫秒,可以用3486.528500年,序列号可以有16.000000个
时间是40位,单位是秒,可以用34865.285001年,序列号可以有16.000000个
时间是39位,单位是毫秒,可以用17.432643年,序列号可以有32.000000个
时间是39位,单位是10毫秒,可以用174.326425年,序列号可以有32.000000个
时间是39位,单位是100毫秒,可以用1743.264250年,序列号可以有32.000000个
时间是39位,单位是秒,可以用17432.642500年,序列号可以有32.000000个
时间是38位,单位是毫秒,可以用8.716321年,序列号可以有64.000000个
时间是38位,单位是10毫秒,可以用87.163213年,序列号可以有64.000000个
时间是38位,单位是100毫秒,可以用871.632125年,序列号可以有64.000000个
时间是38位,单位是秒,可以用8716.321250年,序列号可以有64.000000个
时间是37位,单位是毫秒,可以用4.358161年,序列号可以有128.000000个
时间是37位,单位是10毫秒,可以用43.581606年,序列号可以有128.000000个
时间是37位,单位是100毫秒,可以用435.816063年,序列号可以有128.000000个
时间是37位,单位是秒,可以用4358.160625年,序列号可以有128.000000个
时间是36位,单位是毫秒,可以用2.179080年,序列号可以有256.000000个
时间是36位,单位是10毫秒,可以用21.790803年,序列号可以有256.000000个
时间是36位,单位是100毫秒,可以用217.908031年,序列号可以有256.000000个
时间是36位,单位是秒,可以用2179.080313年,序列号可以有256.000000个
时间是35位,单位是毫秒,可以用1.089540年,序列号可以有512.000000个
时间是35位,单位是10毫秒,可以用10.895402年,序列号可以有512.000000个
时间是35位,单位是100毫秒,可以用108.954016年,序列号可以有512.000000个
时间是35位,单位是秒,可以用1089.540156年,序列号可以有512.000000个

注册中心

跟运维同事沟通,决定运维层面使用e t c d作为注册中心,在pod起来的时候去注册,拿到ID之后装载到环境变量,服务里面获取这个环境变量,用于机器ID的生成,在pod注销的时候在去注册中心回收,需要注意一些问题

1.需要考虑获取不到环境变量的保底策略,告警,

2.pod意外退出没有及时回收问题

3.pod反复重启问题

时间回拨

由于业务需要,机器需要同步时间服务器,

解决:

  1. 时间回拨,时间短的话可以等待,长的话可以直接注册机器ID换新的机器ID

流程图

流程图.jpg

具体代码 github.com/shanhuang94…

参考文章

利用pod本地生成workID

palexu.github.io/2019/10/26/…

docker ip 生成workid

developer.aliyun.com/article/870…

hostname 生成 workid 

chenyongjun.vip/articles/15…

go实现雪花的简易demo

cloud.tencent.com/developer/a…

变种的雪花算法

time.geekbang.org/column/arti…

时间回拨问题

www.jianshu.com/p/98c202f64…