分布式 ID:雪花、号段、UUID 取舍¶
30 秒版(开场)¶
分布式 ID 要在 全局唯一、趋势递增、高性能、可运维 之间取舍:订单用 雪花/号段(索引友好),日志追踪用 UUID v7。生产关键词:时钟回拨、WorkerId 分配、号段双 Buffer。
3 分钟版(精讲深度)¶
- 是什么:多节点无协调或轻协调下生成全局唯一标识,用于主键、订单号、TraceId。
- 为什么:DB 自增无法水平扩展;UUID v4 随机导致 B+ 树页分裂;业务需要可读、有序、分库分表路由。
- 怎么做:高 QPS 订单 → 号段(Leaf/Baidu)或雪花;跨系统关联 → UUID v7;简单场景 → DB
AUTO_INCREMENT+ 分库偏移。
10 分钟版(原理 + 图示)¶
方案对比
| 方案 | QPS | 有序性 | 长度 | 依赖 | 典型场景 |
|---|---|---|---|---|---|
| 雪花 Snowflake | 400万+/节点 | 趋势递增 | 64bit | 时钟、WorkerId | 订单、流水 |
| 号段 Segment | 百万+ | 趋势递增;跨节点不保证按生成时间严格有序 | 64bit | DB/Redis | 超高 QPS 写 |
| UUID v4 | 极高 | 随机 | 128bit | 无 | 不推荐做主键 |
| UUID v7 | 高 | 时间有序 | 128bit | 无 | 日志、外部 ID |
| Redis INCR | 10万+ | 递增 | 可变 | Redis | 中小规模 |
雪花结构(64 bit)
0 | timestamp(41) | datacenter(5) | worker(5) | sequence(12)
- 41 bit 时间 ≈ 69 年;12 bit 序列 = 4096/ms/节点。
- 时钟回拨:拒绝发号或借用未来序列位;NTP 同步监控告警。
flowchart LR
App[Go 服务] --> SF[Snowflake 本地生成]
App --> Leaf[Leaf 号段服务]
Leaf --> DB[(号段表)]
SF -->|WorkerId| ZK[ZK / 配置中心]
App --> UUID[uuid.NewV7]
号段双 Buffer(美团 Leaf 思路)
- 从 DB 批量取
[max_id, max_id+step),内存发号。 - 当前号段用到 90% 时异步加载下一号段,避免 DB 抖动。
- step 默认 1000~10000,DB 访问 QPS = 业务 QPS / step。
容量估算
- 雪花:单节点 409.6 万 ID/秒(4096×1000),订单 1 万 TPS 绰绰有余。
- 号段 step=5000,10 万 TPS 写 → DB 20 QPS 取号,可接受。
生产场景¶
- 订单号:雪花,便于按时间范围查询与分库(
order_id % 1024)。 - 支付流水:号段可获得紧凑、趋势递增的 ID;若审计要求严格业务顺序,应另存业务时间/序列,不能依赖多节点发号顺序。
- OpenAPI 外部 ID:UUID v7,不暴露业务量。
排查与工具¶
| 现象 | 排查 |
|---|---|
| 主键冲突 | WorkerId 重复分配 |
| ID 跳变/重复 | 时钟回拨、序列溢出 |
| 号段服务慢 | DB 锁竞争、step 过小 |
| B+ 树插入慢 | 是否用了 UUID v4 |
架构取舍¶
| 方案 | 适用 | 不适用 |
|---|---|---|
| 雪花 | 分布式、高 QPS、需时间序 | 强依赖单调、无法接受回拨处理 |
| 号段 | 超高写、可容忍 DB 依赖 | 完全无 DB 环境 |
| UUID v7 | 跨系统、无中心 | 需要纯数字短 ID |
| DB 自增 | 单库、低 QPS | 分库分表 |
深挖问答¶
- 雪花 WorkerId 怎么分配? → ZK 临时节点、K8s StatefulSet 序号、或配置中心手动段。
- 时钟回拨怎么处理? → 等待追平、备用 WorkerId、或切换号段模式。
- 号段模式 DB 挂了? → 双 Buffer 有缓存可撑;长期需多副本或 Redis 号段。
- 为什么不用 UUID v4 做主键? → 随机插入导致 InnoDB 页分裂,写放大。
- Go 里用什么库? →
github.com/bwmarrin/snowflake;UUID 用google/uuidv7。
反模式与事故¶
- 多实例硬编码
workerId=1,偶发冲突。 - 忽略 NTP 回拨,生成重复 ID。
- 号段 step=100,10 万 TPS 把 DB 打挂。
- 把订单号暴露给前端当连续数字,被爬取。
代码示例¶
import "github.com/bwmarrin/snowflake"
func NewSnowflake(nodeID int64) (*snowflake.Node, error) {
return snowflake.NewNode(nodeID) // nodeID 0~1023
}
func GenOrderID(node *snowflake.Node) int64 {
return node.Generate().Int64()
}
// UUID v7 — 日志/外部关联
import "github.com/google/uuid"
func GenTraceID() string {
id, _ := uuid.NewV7()
return id.String()
}