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
all-in-kingsoft/hhs/MS/03-数据一致性/03-ID生成.md
T
2026-05-18 00:17:59 +08:00

5.4 KiB
Raw Blame History

tags, create time
tags create time
microservice
id-generation
snowflake
uuid
distributed-id
2026-05-05 12:00

分布式 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 浪费(宕机未用完的号段),极端情况下可能产生空洞。

关联笔记