上线半年后的灵异撞号
时钟回拨治理完毕,老王以为雪花稳了。直到某天深夜告警:订单 ID 重复,但全集群时钟正常、没做校时。排查半天,真相啼笑皆非:上周新加的节点是直接克隆旧虚机镜像部署的,workerID 配置文件一起被克隆了过来——两台机器都揣着 3 号身份证,同一毫秒发出同一个 ID。时间戳加序列的组合在两台机器上完全同构,三维唯一瞬间塌成两维。
教训一句话:机器号是雪花的身份证,发放制度没建好,算法再精密也会翻车。这篇盘点四种发证方式。
方案一:数据库分配表
最直接:建一张 worker 分配表,节点启动时插入一条记录拿自增主键当机器号,退出时注销。改造点是租约续期:节点定时心跳更新租约时间,崩溃的节点租约过期后,机器号可被回收复用,不然 1024 个号迟早发尽。
优点是实现零新依赖,一个 DB 就够;缺点是启动强依赖 DB 可用性(DB 挂了新节点起不来),心跳和回收逻辑要自己写扎实——第 4 篇的心跳经验直接复用。
方案二:ZooKeeper 顺序节点
Leaf-snowflake 的标准做法:节点启动后在 ZK 上创建临时顺序节点,ZK 自动分配全局递增序号当 workerID。临时节点的妙处与生俱来:会话断开(进程崩了、网络断了)节点自动删除,机器号自动回收;顺序性由 ZK 保证,绝不重号。
代价是引入一整套 ZK 运维:三节点起步、脑裂调参、版本升级——为发个机器号养一套协调服务,值不值看公司基建。已有 ZK(很多公司用 ZK 做 Kafka 或 Dubbo 注册中心)的团队,这是顺手牵羊的选择。
方案三:Redis 自增分配
INCR 拿号加 TTL 续约,思路与 DB 方案同构,轻在依赖更少、操作更快。风险点与 Redis 发号器同源(第 9 篇细讲):持久化策略决定了重启后号码会不会回跳——RDB 快照回档可能把同一个号发给两台机器。用 AOF 每秒刷盘加发号后二次确认,风险可控。
方案四:环境自推导,零协调
云原生时代的新思路:让平台保证唯一,应用只管取。K8s 里把发号器部署成 StatefulSet,Pod 名天然有序(pod-0、pod-1、pod-2),序号就是 workerID,重建也保持序号稳定——零额外依赖,伸缩自然。退一步还有 IP 后缀推导、环境变量注入(部署流水线里按序生成)等简化路线。
自推导家族的共同风险是哈希碰撞:IP 或主机名哈希到同一个号(IP 换绑、容器重建),碰撞概率小但非零,启动时加一次「号占用探测」能把风险再压一层。
四方案对比与选型
| 方案 | 新增依赖 | 可靠性来源 | 适用场景 |
|---|---|---|---|
| DB 分配表 | 无(复用现有 DB) | 主键唯一加租约回收 | 中小规模,快速落地 |
| ZooKeeper | ZK 集群 | 临时顺序节点天然唯一 | 已有 ZK 基建 |
| Redis | Redis | INCR 加 TTL,注意持久化 | 已有 Redis 且重视持久化 |
| 环境自推导 | 无 | 平台命名保证 | K8s 云原生部署 |
老王的选择:K8s 里的服务用 StatefulSet 序号,虚机上的老服务用 DB 分配表加租约,两套并行,规则都写进部署文档。唯一红线:机器号绝不进镜像、不进代码仓库——那是克隆事故的祸根。
小结
workerID 一句话:手工配置是事故温床,分配要么交给协调组件,要么交给平台命名;租约回收防号尽,启动探测防碰撞。至此雪花的两大前提(时钟可信、机器号唯一)都有了工程答案。下一篇换个轻量路线——Redis 发号器,以及它背后的两本账。
评论 (0)