995 字
约 3 分钟
9
S17-J05 · 分布式 ID:雪花、号段与时钟回拨
适用读者:被"ID 重复"或"ID 太长"困扰的人。
前置:S11-A01(自增主键)、S17-J04。
实测环境:Python 参考实现(结构验证),2026-11-05。
覆盖旧文:—(E 驱动;旧文未覆盖分布式 ID)
覆盖扩展:E-S17-009(雪花结构)、E-S17-010(时钟回拨)。
上一篇:S17-J04 · 分布式事务:2PC / TCC / Saga / 本地消息表
下一篇:S17-A01 · 服务治理。
1. 一句话结论
雪花 ID = 时间戳(41 位)+ 机器号(10 位)+ 序列号(12 位)。它靠"时间递增"保证趋势有序,所以时钟回拨会让它重复或倒序**。**
| 41 bit 毫秒时间戳 | 10 bit workerId | 12 bit 序列号 |
2. 结构(E-S17-009)
| 段 | 位数 | 容量 |
|---|---|---|
| 时间戳(相对纪元) | 41 | 约 69 年 |
| 机器/数据中心 | 10 | 1024 个节点 |
| 序列号 | 12 | 每毫秒 4096 个 |
理论吞吐:4096 × 1000 = 409.6 万/秒/节点。
实测(Python 参考实现,仅验证结构与序性):
生成 1000000 个雪花 ID:1632.5 ms,约 613 千/秒
单调递增: True 无重复: True
示例 ID: 393884553522974720 位长: 59
注意:Python 吞吐不代表生产实现(Java 可达数百万/秒);这里只证明单调性与唯一性在"同毫秒内序列号用尽 → 等下一毫秒"的实现下成立。
3. 为什么不用 UUID / 数据库自增
| 方案 | 问题 |
|---|---|
| UUID | 无序(B+ 树插入随机,S11-B05 的页分裂) |
| 数据库自增 | 单点、分库后冲突、暴露业务量 |
| 雪花 | 趋势递增、无中心、可排序 |
"趋势递增为什么重要":MySQL 聚簇索引按主键有序存储——有序 ID 让插入集中在尾部,避免随机 IO。
4. 时钟回拨(E-S17-010)
| 原因 | 后果 |
|---|---|
| NTP 校时回退 | 可能生成重复 ID |
| 人工改时间 | 同上 |
| 闰秒/虚拟化时钟漂移 | 顺序错乱 |
四种处理:
| 方案 | 说明 |
|---|---|
| 检测并等待 | 回拨小于阈值就等(简单,短暂不可用) |
| 用备用 workerId | 换节点(需协调) |
| 追加回拨计数位 | 牺牲位宽换可用 |
| 用号段模式 | 规避时钟问题(数据库/Redis 发号) |
判据:"节点时钟受控吗?" 不受控 → 用号段(美团 Leaf 类方案),别硬上雪花。
5. 号段模式:另一种主流
DB/Redis 存 max_id → 应用一次取 1000 个号段 → 内存递增发号
| 优点 | 代价 |
|---|---|
| 不依赖时钟 | 需中心存储(但可多实例) |
| ID 短、有序 | 重启会跳号 |
6. 五个误区
| 误区 | 真相 |
|---|---|
| "雪花 ID 一定不重复" | 时钟回拨会重复 |
| "UUID 更适合分布式" | 无序导致写放大 |
| "位宽越大越好" | 64 位是常见上限(bigint) |
| "号段模式不会跳号" | 重启会浪费一段 |
| "用时间戳当 ID 就行" | 并发下会重复 |
7. 自检
- 雪花的三段位宽与容量?
- 理论吞吐怎么算?
- 时钟回拨为什么会导致重复?
- 四种处理方案?
- 什么时候该用号段而不是雪花?
8. 下一篇
S17-A01 · 服务治理:限流、熔断、降级
ID 讲完,进架构师层:一次故障为什么会扩散成雪崩。
本篇属于系列《S17 · 分布式与消息队列》。扩展点 E-S17-009/010。实测为 Python 参考实现(结构与序性验证,吞吐不代表生产)。
S17-J05 · 分布式 ID:雪花、号段与时钟回拨
https://www.clxhxhhr.top/posts/5428/ 评论
0 条
还没有评论,先写一条吧。