分布式 ID 服务如何保证高可用?
简化版
ID 服务的高可用首先要保证“故障时不重复”,其次才是持续发号。号段模式用数据库原子分配互斥区间并在实例本地缓存;Snowflake 模式保证 WorkerId 租约唯一并处理时钟回拨。 多实例、双缓冲、故障域隔离和客户端降级能提高可用性,但不能在失去唯一性依据时随意继续生成。
详细版
号段服务把数据库操作从每个 ID 降为每个号段一次,实例提前加载下一段,数据库短暂故障时仍能消费本地剩余号码。数据库自身使用主备/共识和 fencing,更新 max_id 通过事务行锁或 CAS 保证区间不重叠。
Snowflake 不依赖中心逐号分配,但 WorkerId 必须在有效租约内全局唯一,重启和网络分区时不能提前复用;旧持有者必须在本地租约截止前留出时钟误差裕量并停发,分配端也只能在旧租约截止及安全裕量过去后再分配该 ID。时间小幅回拨时可等待,大幅回拨则停发、切逻辑时钟或告警。监控剩余号段、分配延迟、重复检测、时钟偏差和 worker 租约,演练数据库中断、实例重启和跨机房分区。
完整版教学
一、高可用的优先级是唯一性高于发号率
ID 重复会造成主键冲突、幂等串单甚至数据覆盖,通常比短时无法创建新对象更严重。因此无法证明号段互斥或 WorkerId 唯一时,应停止发号而不是 fail-open。可用性设计必须建立在安全不变量上。
记忆钩子:ID 服务可以暂时不发,不能自信地发出可能重复的号码。
二、号段模式如何隔离中心故障
DB: max_id 10000 --atomic add 1000--> 11000
Instance A owns [10001,11000]
实例只在申请新区间时访问数据库,随后在内存原子递增。若每秒 5000 个 ID、号段 100000 个,单段可支撑 20 秒数据库故障。双 buffer 在当前段用到 10%~20% 剩余时异步申请下一段,避免切段抖动。
三、中心数据库也要有安全切换
| 风险 | 保护 |
|---|---|
| 并发申请重叠 | 行锁/CAS 原子加步长 |
| 主备双写 | 共识或 fencing |
| 回滚 max_id | 禁止恢复到旧值直接发号 |
| 跨库多主 | 预分区号段/单一权威 |
备份恢复后的 max_id 必须大于所有已发号段上界,可通过高水位审计或跳跃预留保证,不能只恢复最近备份。
四、Snowflake 的高可用边界
Snowflake 实例本地发号,中心只分配 WorkerId,吞吐和故障隔离更好。但 WorkerId 租约失联后,旧实例可能仍运行;若新实例过早获得同一 ID,时间戳和序列相同时就会重复。租约记录要有 epoch,旧实例必须在本地截止时间前按最大时钟误差预留安全裕量并停发;分配端要等旧租约截止再加安全裕量后才能复用该 WorkerId。若无法约束时钟误差或执行外部 fencing,就不能只靠普通租约安全复用。
时钟回拨 5ms 可等待,回拨 5 分钟不能阻塞线程池;应停发、切备用 worker/逻辑时间或人工处理,并记录最后时间戳持久化。
五、多机房与降级
可在 ID 高位加入 regionId,或给每个地域预分互斥号段,使网络分区时独立发号。代价是位数占用、容量不均和跨地域迁移复杂。若主方案异常,切换另一算法必须保证命名空间不碰撞,例如保留算法/地域位,不能临时改用数据库自增。
客户端可批量预取或本地号段缓存,但过大缓存会增加重启浪费。浪费通常可接受,重复不可接受。
六、容量、监控与演练
64 位 Snowflake 每毫秒 4096 序列,单 worker 理论约 409.6 万 ID/s,但实际受锁、CPU 和持久化策略限制。号段则按峰值与数据库最长恢复时间计算,例如 2 万 ID/s、目标容忍 60 秒,至少需 120 万剩余号码再加余量。
监控发号 QPS/P99、剩余段、预取失败、DB CAS 冲突、WorkerId 租约、时钟回拨、序列耗尽和重复采样。演练中心 DB 断开、租约服务分区、进程快照克隆与时钟跳变。
七、常见误区与追问
- 误区:部署多个 ID 实例就高可用了。 多实例若号段或 WorkerId 冲突会更危险。
- 误区:数据库恢复后 max_id 可从备份继续。 可能与故障前已发区间重叠。
- 误区:Snowflake 完全不依赖中心协调。 WorkerId 分配和时钟仍需治理。
- 误区:故障时切 UUID 一定安全。 字段格式、索引和命名空间都可能不兼容。
- 追问:号段用完且 DB 不可用怎么办? 停止发号或使用事先隔离的备用命名空间,不能猜新段。
- 追问:实例重启未用完号段怎么办? 可安全丢弃造成空洞,不应让别的实例复用。
- 追问:如何发现重复? 数据库唯一约束是最后防线,另做跨实例采样与告警。
八、加强记忆
ID 高可用围绕“不重号、可续发、能停得安全”设计。号段靠中心原子划区间与本地双缓存,Snowflake 靠唯一 WorkerId 租约和单调时间,多地域靠互斥命名空间。容量算剩余可发多久,故障演练则证明中心断开和时钟异常时不会重复。
降级目标要明确:发号服务不可用时,创建类请求通常应失败并可重试,而不是退化到可能冲突的随机数或本机计数器。
恢复完成后还要核验控制面水位、活跃租约和数据库最大已用 ID,确认不存在旧实例携带过期状态重新上线。