📋 一、好的分布式 ID 要满足什么
全局唯一
跨机器、跨时间不冲突。
趋势递增
利于 B+ 树索引、避免页分裂。
高性能
本地生成、不依赖网络。
高可用
生成器本身不能成为单点。
可排序
ID 隐含时间,便于排查。
🧰 二、常见方案对比
| 方案 | 唯一性 | 递增 | 依赖 | 缺点 |
|---|---|---|---|---|
| UUID | 极高 | ❌ 无序 | 无 | 长、无序、索引碎片 |
| 数据库自增(步长) | 依赖 DB | ✅ | DB | 性能瓶颈、扩容难 |
| Redis INCR | 依赖 Redis | ✅ | Redis | 需持久化防丢失 |
| 号段(Leaf-segment) | 中心分配 | ✅ | DB/缓存 | 需做好号段缓存 |
| 雪花算法 | 机器+时间 | ✅ 趋势 | 时钟 | 时钟回拨问题 |
❄️ 三、雪花算法(Snowflake)— 面试重点
Twitter 提出的 64 位 Long 型 ID,把一个 ID 拆成"时间戳 + 机器号 + 序列号"三段,本地即可生成,无网络依赖。
生成逻辑:
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 即可定位分片,省去路由表(见数据分片)。