vault backup: 2026-05-17 22:00:24

This commit is contained in:
hhs
2026-05-17 22:00:24 +08:00
parent df6489e370
commit 64e34e6871
37 changed files with 8087 additions and 0 deletions
+157
View File
@@ -0,0 +1,157 @@
---
tags: [microservice, id-generation, snowflake, uuid, distributed-id]
create time: 2026-05-05
---
# 分布式 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 也需要全局唯一