← 返回分布式系统
🔢 分布式存储 · 面试常考

分布式 ID 生成

分库分表后,数据库自增 ID 不再全局唯一。需要一个全局唯一、趋势递增、高吞吐、低延迟的 ID 方案——这就是分布式 ID 生成要解决的问题。

📋 一、好的分布式 ID 要满足什么

全局唯一
跨机器、跨时间不冲突。
趋势递增
利于 B+ 树索引、避免页分裂。
高性能
本地生成、不依赖网络。
高可用
生成器本身不能成为单点。
可排序
ID 隐含时间,便于排查。

🧰 二、常见方案对比

方案唯一性递增依赖缺点
UUID极高❌ 无序无长、无序、索引碎片
数据库自增(步长)依赖 DB✅DB性能瓶颈、扩容难
Redis INCR依赖 Redis✅Redis需持久化防丢失
号段(Leaf-segment)中心分配✅DB/缓存需做好号段缓存
雪花算法机器+时间✅ 趋势时钟时钟回拨问题

❄️ 三、雪花算法(Snowflake)— 面试重点

Twitter 提出的 64 位 Long 型 ID,把一个 ID 拆成"时间戳 + 机器号 + 序列号"三段,本地即可生成,无网络依赖。

1位 41位 时间戳(毫秒) 10位 机器ID5数据中心+5工作节点 12位 序列号 符号≈69年1024台每ms 4096个
生成逻辑:ID = (当前毫秒 - 纪元) << 22 | 机器ID << 12 | 同一毫秒内自增序列号。同一毫秒内序列号用满则"等下一毫秒"。
✅
优点:纯本地计算、QPS 极高(单机能到百万级/秒)、ID 含时间可排序、不依赖中心服务。

⏰ 四、时钟回拨问题(面试必问)

雪花算法依赖"时间单调递增"。如果机器时钟因为 NTP 校准等原因往回拨,可能生成和"过去"重复的 ID。

⚠️
常见应对:① 检测到回拨 < 阈值(如 5ms),短暂等待/自旋到时间追平;② 回拨较大时,借用"备用机器位"或扩展序列号区间补偿;③ 用 ZooKeeper/etcd 记录上次时间,回拨则拒绝并报警;④ 美团 Leaf、百度 UidGenerator 等改进版用"缓存时钟/历史时间"规避。

📦 五、号段模式(Leaf-segment)

另一种思路:由一个中心服务一次性"批发"一段 ID 区间(号段)给应用,应用在内存里自增发号,发完再取下一段。

DB 存当前最大号 max_id
每次取号段 [max_id+1, max_id+step],原子更新 max_id。
应用本地自增发号
号段内纯内存自增,性能高;号段用尽前异步预取下一段,避免卡顿。
优点
对时钟不敏感、DB 压力小(几百次/天级更新)。缺点:号段耗尽瞬间若 DB 慢会短暂阻塞。

🎯 六、面试高频追问

为什么不用 UUID?
UUID 128 位太长、完全无序,作为 InnoDB 主键会导致 B+ 树频繁页分裂、索引碎片严重、查询变慢。趋势递增的 ID 对索引更友好。
雪花 ID 如何保证不重复?
时间戳保证"不同毫秒大概率不同";同一毫秒内靠机器 ID 隔离 + 序列号自增,三者组合全局唯一。关键是机器 ID 不冲突(靠配置中心/启动注册分配)。
分片键能和 ID 结合吗?
可以。把分片号编入 ID 高位(如机器 ID 段映射到分片),取 ID 即可定位分片,省去路由表(见数据分片)。

延伸阅读:数据分片 · 一致性哈希 · CAP 理论