This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
2026-05-18 00:17:59 +08:00

158 lines
5.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [microservice, id-generation, snowflake, uuid, distributed-id]
create time: 2026-05-05 12:00
---
# 分布式 ID 生成
## 概述
在微服务架构中,数据库被拆分成多个独立实例,不再共享自增主键。如何生成分布式环境下全局唯一的 ID,是一个经典问题。
```mermaid
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
```
### 核心公式
```go
// 伪代码
func generateID() int64 {
timestamp = currentMillis() // 当前毫秒时间戳(相对时间戳)
workerId = 1 // 机器编号 (0-1023)
sequence = incrementSequence() // 递增序号 (0-4095)
return (timestamp << 22) | // 时间戳占高位
(workerId << 12) | // 机器 ID 放中间
sequence // 序列号放在低位
}
```
### Go 实现要点
```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 区间:
```mermaid
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-数据一致性/01-数据库拆分]] — 分库分表场景下的 ID 生成
- [[03-数据一致性/02-分布式事务]] — Outbox 消息 ID 也需要全局唯一