分布式 ID 有哪些生成方案?雪花算法原理与缺陷是什么?
结论先行:分布式 ID 的核心诉求是全局唯一,多数场景还要求趋势递增与高性能。常见方案有 UUID、数据库自增、号段模式、Redis 自增和雪花算法。雪花算法是其中最经典的自研方案:64 位 long 拆成时间戳 + 机器号 + 序列号,不依赖外部存储、趋势递增,但存在时钟回拨导致 ID 重复的风险。
一、方案对比
| 方案 | 唯一性 | 有序性 | 优点 | 缺点 |
|---|---|---|---|---|
| UUID | 全局唯一 | 无序 | 本地生成、零依赖 | 长、无序,不适合做索引 |
| 数据库自增 | 单库唯一 | 递增 | 实现简单 | 单点瓶颈、有规律可被遍历 |
| 号段模式 | 唯一 | 递增 | 批量取号性能好 | 需维护发号服务高可用 |
| Redis INCR | 唯一 | 递增 | 性能高 | 依赖 Redis 可用性 |
| 雪花算法 | 唯一(受时钟约束) | 趋势递增 | 无中心、高性能 | 时钟回拨会重复 |
二、雪花算法原理
- 组成:1 位符号位 + 41 位毫秒时间戳 + 10 位机器号 + 12 位序列号,共 64 位;
- 单机单毫秒可生成 4096 个 ID,实际可用约 69 年;趋势递增,适合作为数据库主键;
- 时钟回拨缺陷:系统时钟回拨后,同一毫秒的序列号可能与历史 ID 冲突;若回拨跨毫秒,更可能直接重复;
- 为什么不选 UUID 当主键:无序导致 B+ 树随机插入、页分裂频繁,长字符串还放大了索引体积;
- 机器号拆分配置:10 位可拆成 5 位机房 + 5 位机器,便于按机房隔离与排障。
三、回拨与改进方向
// 雪花 ID 位运算示意:时间戳左移 22 位 | 机器号左移 12 位 | 序列号
long id = ((timestamp - epoch) << 22) | (workerId << 12) | sequence;
// 机器号通常由注册中心或配置中心下发,必须全局唯一
- 简单兜底:记录上次生成的时间戳,检测到回拨时短暂等待时钟追平再发号;
- 生产实践:业界成熟方案有美团 Leaf 的号段与雪花双模式、百度的 UidGenerator,一般做法是自研发号服务 + 时钟监控 + 回拨熔断;
- 号段模式补充:一次批量取号放内存,宕机最多丢一个号段,用双缓冲预取可避免取号阻塞;
- 组件化:ID 生成建议做成独立组件或服务,统一管理位段分配与时钟监控,避免各团队各自实现出现机器号冲突;
- 面试提示:能补充"41 位时间戳可用约 69 年、初始纪元可配置"这类实现细节,比只背位结构得分更高。
常见追问 / 记忆点
- 追问:雪花 ID 为什么适合做数据库主键?答:趋势递增减少页分裂,比 UUID 更利于 InnoDB 聚簇索引。
- 追问:机器号怎么分配?答:可用 ZooKeeper/注册中心自动分配或配置文件静态指定,需保证全局唯一。
- 追问:时钟回拨怎么彻底解决?答:严格场景用中心化发号(号段/数据库),或检测回拨后拒绝发号并告警。
- 记忆点:41 位时间 + 10 位机器 + 12 位序列,趋势递增不递增细节;回拨是必考点。