what(目标)
- 全局唯一:每一个用户都需要有一个ID,不同用户ID不同
- 递增:满足唯一性的同时ID最好具有时间递增性
- 数据安全:ID最好不要暴露平台用户体量
- 量级:ID池要足够大,支持平台至少百万级别的用户量级
- 性能: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反复重启问题
时间回拨
由于业务需要,机器需要同步时间服务器,
解决:
- 时间回拨,时间短的话可以等待,长的话可以直接注册机器ID换新的机器ID
流程图
参考文章
利用pod本地生成workID
docker ip 生成workid
developer.aliyun.com/article/870…
hostname 生成 workid
go实现雪花的简易demo
cloud.tencent.com/developer/a…
变种的雪花算法
time.geekbang.org/column/arti…
时间回拨问题