tags, create time
| tags | create time | |||||
|---|---|---|---|---|---|---|
|
2026-05-05 |
分布式 ID 生成
概述
在微服务架构中,数据库被拆分成多个独立实例,不再共享自增主键。如何生成分布式环境下全局唯一的 ID,是一个经典问题。
graph LR
A["❌ 数据库 AUTO_INCREMENT"] -->|"分库后冲突"| Problem["不可用"]
B["✅ 分布式 ID"] --> Unique["全局唯一"]
B --> Ordered["趋势有序"]
B --> HighThroughput["高吞吐"]
style Problem fill:#ffebee
style Unique fill:#e8f5e9
style Ordered fill:#e8f5e9
style HighThroughput fill:#e8f5e9
[!question] 为什么要自己生成?不用数据库自增?
- 分库后:每个库的自增 ID 从 1 开始,必然冲突
- 性能瓶颈:DB 序列或号段模式在高并发下成为瓶颈
- 耦合度高:ID 生成依赖特定数据库类型
主流方案对比
| 方案 | 唯一性保证 | 有序性 | 性能 (QPS) | 复杂度 | 适用场景 |
|---|---|---|---|---|---|
| UUID | MD5/SHA 哈希保证 | ❌ 完全无序 | ⭐⭐⭐⭐⭐ | 极低 | 临时标识、缓存 Key |
| 雪花算法 (Snowflake) | WorkerId + 时间戳 | ✅ 趋势有序 | ⭐⭐⭐⭐⭐ | 中 | 通用方案,首选 |
| 号段模式 | DB 批量发放 | ✅ 严格有序 | ⭐⭐⭐⭐ | 中 | 已有 DB 架构改造 |
| Leaf (美团) | Snowflake + 号段 | ✅ 趋势/严格有序 | ⭐⭐⭐⭐⭐ | 中高 | 大规模生产环境 |
| NanoID | Base62 + CSPRNG | ❌ 无序 | ⭐⭐⭐⭐⭐ | 低 | API Token、短链 |
雪花算法 (Snowflake)
Twitter 开源的经典算法,将 ID 分为三部分:
64 bit: 1 bit(符号位) + 41 bit(时间戳) + 10 bit(机器ID) + 12 bit(序列号)
│ │ │ │
│ │ │ └─ 每毫秒最多 4096 个 ID
│ │ └──────────────── 1024 台机器
│ └────────────────────────────── ~69 年跨度
└────────────────────────────────────────── 固定为 0
核心公式
// 伪代码
func generateID() int64 {
timestamp = currentMillis() // 当前毫秒时间戳(相对时间戳)
workerId = 1 // 机器编号 (0-1023)
sequence = incrementSequence() // 递增序号 (0-4095)
return (timestamp << 22) | // 时间戳占高位
(workerId << 12) | // 机器 ID 放中间
sequence // 序列号放在低位
}
Go 实现要点
type Snowflake struct {
mu sync.Mutex
lastTime int64
sequence int64
workerId int64
workerBits uint8 = 10
stepBits uint8 = 12
}
func (sf *Snowflake) NextID() int64 {
sf.mu.Lock()
defer sf.mu.Unlock()
now := time.Now().UnixMilli()
if now < sf.lastTime {
panic("clock moved backwards") // 时钟回拨保护
}
if now == sf.lastTime {
sf.sequence++
} else {
sf.sequence = 0
}
sf.lastTime = now
return (now << 22) | (sf.workerId << 12) | sf.sequence
}
注意事项
| 问题 | 解决方案 |
|---|---|
| 时钟回拨 | 等待到回拨前的时间点再继续;或分配备用 WorkerId |
| WorkerId 分配 | 启动时从注册中心获取,或使用 K8s Pod Name 映射 |
| 单机 QPS 上限 | 单 WorkerId × 单机房约 409.6 万/秒,通常够用 |
| 跨代际兼容性 | 新字段插入会改变位移量,设计时需预留比特位 |
UUID v7 (时间有序 UUID)
如果你不想维护 Snowflake 的 WorkerId,可以考虑 UUID v7——它是 RFC 9562 定义的新版本,保持了时序性。
UUID v7: 48-bit timestamp + 12-bit rand_a + 4-bit version + 12-bit rand_b + 2-bit variant + 62-bit rand_c
| 维度 | UUID v4 | UUID v7 |
|---|---|---|
| 生成方式 | 纯随机 | 时间戳 + 随机数 |
| 有序性 | ❌ 完全随机 | ✅ 趋势有序 |
| 索引效率 | ❌ 随机插入导致页分裂 | ✅ 顺序插入友好 |
| 存储大小 | 16 bytes | 16 bytes |
| 实现复杂度 | 极低 | 低(标准库支持) |
[!tip] 如果你的数据库是 MySQL 5.7+,直接改用
BINARY(16)存储 UUID v7,比 BIGINT AUTO_INCREMENT 更优。
号段模式
基于数据库分批领取 ID 区间:
sequenceDiagram
App->>DB: SELECT max_id FROM id_generator WHERE biz='order'
DB-->>App: max_id = 10000, step = 2000
Note over App: 内存中维护 [10000, 12000) 号段
loop 每次请求
App->>App: ID++ (本地原子操作)
end
App->>DB: UPDATE id_generator SET max_id = 12000 WHERE max_id = 10000
DB-->>App: ACK
Note over App: 下次取号段: [12000, 14000)
优点:兼容现有 MySQL 架构,无需引入外部组件。 缺点:存在 ID 浪费(宕机未用完的号段),极端情况下可能产生空洞。
关联笔记
- 03-数据一致性/数据库拆分/README — 分库分表场景下的 ID 生成
- 03-数据一致性/分布式事务/README — Outbox 消息 ID 也需要全局唯一