--- 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 也需要全局唯一