Yihui’s Blog

设计分布式 ID 发号器

日期:2026-07-11
标签:#面试 #八股 #后端 #系统设计 #场景题

一句话答案

常用方案有数据库号段、Redis 自增和 Snowflake;高吞吐场景通常用号段或 Snowflake,并针对时钟回拨、节点编号冲突和趋势递增做治理。

方案对比

方案优点缺点适用场景
UUID无中心、简单128 位、无序、索引局部性差不要求数字和有序
数据库自增严格递增、简单单点与吞吐瓶颈小规模系统
号段模式高吞吐、趋势递增、抗短时 DB 故障可能跳号,需维护号段通用业务 ID
Redis INCR原子、性能较高依赖 Redis 持久化和高可用中等规模计数
Snowflake本地生成、高吞吐、64 位时钟回拨、workerId 管理大规模分布式系统

面试口语版

如果允许趋势递增和少量跳号,我优先考虑号段模式:数据库原子分配一段 ID,服务实例在内存中发号;当前号段消耗到阈值时异步加载下一段,数据库短时不可用也能继续服务。若追求完全去中心化可用 Snowflake,把时间戳、机房/机器号和序列号组成 64 位 ID,但必须通过注册中心分配 workerId,并处理时钟回拨。

关键细节

  • 双 Buffer 号段避免切换时阻塞。
  • Snowflake 时钟小幅回拨可等待,大幅回拨应拒绝启动、切换备用位或报警隔离。
  • 对外不要暴露连续订单量时,可额外生成业务展示号,内部主键仍保持索引友好。

面试官追问

  1. Snowflake 同毫秒序列耗尽怎么办?
  2. workerId 如何防重复?
  3. 号段服务宕机会不会产生重复 ID?
  4. 为什么数据库主键偏好趋势递增?

面试官追问参考答案

1. Snowflake 同毫秒序列耗尽怎么办?

序列位能表示的数量用完后,生成器应等待到下一毫秒,不能让序列回绕产生重复。若持续达到上限,说明单节点吞吐超过设计容量,应增加序列位、增加 worker 节点或采用批量号段。

2. workerId 如何防重复?

由 ZooKeeper/etcd 等协调系统通过租约分配 workerId,并绑定实例身份;启动时必须成功领取且定期续租,失去租约就停止发号。静态配置也可以,但需要严格的资产管理和重复检测,容器动态扩缩容下风险更高。

3. 号段服务宕机会不会产生重复 ID?

只要数据库以事务原子增加最大值,每段范围不会重复;实例宕机只会浪费尚未使用的号码,造成跳号。重启后不能从本地旧游标继续发号,必须重新申请新号段;双 Buffer 也需把段边界和状态管理正确。

4. 为什么数据库主键偏好趋势递增?

B+Tree 聚簇索引中,趋势递增插入大多发生在叶子末尾,页分裂和随机 I/O 较少,缓存局部性更好。完全随机 ID 会在各页插入,引发频繁页分裂、碎片和缓存抖动;但绝对连续可能暴露业务量,因此可分离内部主键与外部展示号。

维护与整理 · Yihui在 GitHub 上编辑

继续阅读

浏览全部文章