04. 分布式 ID 生成

分布式系统中 ID 生成方案对比:雪花算法、号段模式、Leaf、Ulid、UUID 选型与实践

分布式系统中,传统数据库自增 ID 无法满足分库分表后的全局唯一需求。本文对比分析主流分布式 ID 生成方案。

1. 方案对比总览

方案趋势唯一性性能长度可读性依赖
UUID v4无概率唯一极高36 字符差无
Snowflake时间递增绝对唯一极高64bit中时钟
号段模式趋势递增绝对唯一高64bit好数据库
Leaf趋势递增绝对唯一极高64bit中数据库/ZK
Ulid时间递增概率唯一极高26 字符较好无

2. 雪花算法 (Snowflake)

Twitter 开源,64bit 长整型,结构如下:

0 | 0000000000 0000000000 0000000000 0000000000 0 | 00000 | 00000 | 000000000000
1位  41位时间戳(毫秒,约69年)                        10位机器ID  12位序列号
符号                                                    5位DC+5位Worker

2.1 Java 实现

public class SnowflakeIdWorker {
    private final long workerId;
    private final long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    
    private final long twepoch = 1288834974657L;  // 起始时间戳
    private final long workerIdBits = 5L;
    private final long datacenterIdBits = 5L;
    private final long maxWorkerId = ~(-1L << workerIdBits);        // 31
    private final long maxDatacenterId = ~(-1L << datacenterIdBits); // 31
    private final long sequenceBits = 12L;
    private final long workerIdShift = sequenceBits;                // 12
    private final long datacenterIdShift = sequenceBits + workerIdBits; // 17
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 22
    private final long sequenceMask = ~(-1L << sequenceBits);        // 4095
    
    public synchronized long nextId() {
        long timestamp = timeGen();
        
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards");
        }
        
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        
        lastTimestamp = timestamp;
        return ((timestamp - twepoch) << timestampLeftShift)
            | (datacenterId << datacenterIdShift)
            | (workerId << workerIdShift)
            | sequence;
    }
    
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
    
    private long timeGen() {
        return System.currentTimeMillis();
    }
}

2.2 时钟回拨问题

策略实现适用
等待Thread.sleep() 直到追上回拨短
异常抛出异常,由上层处理严格场景
备用位扩展时间戳位数长期方案
序列号扩展借用 workerId 位临时方案

推荐使用 NTP 配合 -x 选项(逐渐调整,而非跳变)。

3. 美团的 Leaf

Leaf 提供两种模式:号段模式(Leaf-segment)和 Snowflake 模式(Leaf-snowflake)。

3.1 Leaf-segment(号段模式)

CREATE TABLE leaf_alloc (
    biz_tag VARCHAR(128) PRIMARY KEY,
    max_id BIGINT NOT NULL DEFAULT 1,
    step INT NOT NULL,
    description VARCHAR(256),
    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 初始数据
INSERT INTO leaf_alloc (biz_tag, max_id, step, description) 
VALUES ('order', 0, 1000, '订单号');
// 双 buffer 优化,号段用完前异步加载下一个号段
public class SegmentBuffer {
    private Segment current;   // 当前使用
    private Segment next;      // 预加载
    private volatile boolean nextReady; // 是否就绪
    
    public long getNextId() {
        long value = current.getAndIncrement();
        if (value < current.getMax()) {
            return value;
        }
        // 当前号段用完,切换到 next
        lock.lock();
        try {
            if (!nextReady) {
                waitForNextReady();  // 等待异步加载完成
            }
            current = next;
            nextReady = false;
            return current.getAndIncrement();
        } finally {
            lock.unlock();
        }
    }
}

优点:趋势递增,利于 B+Tree 索引;数据库压力大为降低(步长 1000 时减少 1000 倍)。

3.2 Leaf-snowflake

基于 ZooKeeper 分配 workerId,解决 Snowflake 的 workerId 分配问题。

注册到 ZooKeeper: /leaf-snowflake/{serviceName}/{ip:port}-{sequence}
workerId = sequence (顺序分配)

4. 其他方案

4.1 Ulid(Universally Unique Lexicographically Sortable Identifier)

01ARZ3NDEKTSV4RRFFQ69G5FAV
├── 48bit 时间戳(毫秒)
└── 80bit 随机数

特点:字典序可排序、大小写不敏感、URL 安全、无特殊字符。

import ulid

u = ulid.new()
print(u)           # 01ARZ3NDEKTSV4RRFFQ69G5FAV
print(u.timestamp) #  Unix 时间戳
print(u.randomness) # 随机部分

4.2 数据库多主模式

-- 设置不同自增步长和起始值
-- 节点1: auto_increment_increment=3, auto_increment_offset=1
-- 节点2: auto_increment_increment=3, auto_increment_offset=2
-- 节点3: auto_increment_increment=3, auto_increment_offset=3

缺点:扩展性差,新增节点需重配。

5. 选型建议

场景推荐方案
高并发、趋势递增Snowflake / Leaf
无依赖、简单场景Ulid
不追求趋势、简单UUID v4
已有 MySQL、低并发号段模式
需要排队号/可读性号段模式

线上建议:使用 Leaf 或改造版 Snowflake(集成 workerId 自动分配 + 时钟回拨处理)。


6. 分布式 ID 的隐藏陷阱与排查

6.1 时钟回拨的线上应急

Snowflake 依赖系统时钟,当 NTP 同步导致时钟回拨时,可能产生重复 ID。线上排查步骤:

  1. 监控告警:部署 ntpd 或 chronyd 时钟偏移监控,超过 5ms 即告警
  2. 快速止血:临时切换为 Leaf-segment 号段模式,规避时钟依赖
  3. 根因修复:检查 NTP 配置,启用 -x 选项(slewing 模式,逐步调整而非跳变)
  4. 数据补偿:扫描回拨时间段内生成的 ID,通过业务时间戳字段去重

6.2 ID 冲突的排查思路

当出现疑似 ID 冲突时:

  1. 提取冲突 ID 的二进制表示,检查时间戳部分是否异常(如全 0 或未来时间)
  2. 分析 workerId 分布,确认是否存在重复分配(ZK ephemeral 节点未及时清理)
  3. 检查序列号是否溢出(同一毫秒内超过 4096 个请求)

6.3 高并发下的性能瓶颈

单机 Snowflake 的 QPS 上限约为 400万/秒(4096 序列号 × 1000 毫秒)。当业务峰值超过此上限时:

  • 水平扩展:增加机器节点(workerId),每个节点独立生成
  • 批量预取:客户端一次申请 1000 个 ID,减少 RPC 调用
  • 本地缓存:将号段缓存在本地内存,仅耗尽时请求服务端

6.4 不同语言的实现参考

语言库/项目特点
JavaTwitter Snowflake / 美团 Leaf工业级,文档完善
Gobwmarrin/snowflake轻量,支持自定义 epoch
Pythonpysnowflake简洁,适合脚本场景
Rustsnowflake-rs零分配,高性能
Node.jsnode-snowflake异步友好

选型原则:Java 生态优先选 Leaf,Go 服务可选 bwmarrin/snowflake,Python 脚本可用 ulid-py。无论选哪种方案,务必在压测环境验证时钟回拨处理逻辑,并建立 ID 唯一性监控。


延伸阅读

分布式 ID 与数据库主键策略的配合

数据库自增 IDUUID雪花算法建议
MySQL✅ 简单❌ 页分裂✅ 需自定义bigint / 雪花
PostgreSQL✅ 简单⚠️ 性能差✅ 需自定义bigserial / 雪花
MongoDB✅ ObjectId—✅ 可选ObjectId 内置
TiDB❌ 不连续✅ 支持✅ 推荐避免自增热点

MySQL 自增 ID 分库分表问题:
分表后多表的自增 ID 会冲突。解决方案:

  • 设置不同起始值和步长:表 A 从 1 开始步长 10,表 B 从 2 开始步长 10
  • 或完全放弃自增,改用雪花算法

TiDB 的自增 ID 陷阱:
TiDB 的自增 ID 不保证连续(分布式事务预留范围可能未用完),且在高并发写入时可能成为热点。TiDB 推荐使用 AUTO_RANDOM 替代 AUTO_INCREMENT。

ID 可读性与安全性权衡

自增 ID: 100001 → 100002 → 100003  (可预测,暴露业务规模)
雪花 ID: 1426756382912348160  (无规律,但冗长)
UUID:    550e8400-e29b-41d4-a716-446655440000  (更长,URL 不友好)
ULID:    01ARZ3NDEKTSV4RRFFQ69G5FAV  (26字符,按时间排序)

对外暴露的 ID 建议额外做混淆(如 Hashids 编码),避免被遍历攻击。内部系统使用原生雪花 ID 即可。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「distributed-systems」更多文章

  1. 边缘计算架构与就近接入
  2. 分布式系统成本优化与容量治理
  3. 单体到分布式:遗留系统迁移与绞杀者模式