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 自动分配 + 时钟回拨处理)。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「distributed-systems」更多文章

  1. 分布式高可用架构模式:多活、容灾、降级与 K8s 编排高可用
  2. 分布式链路追踪实战:OpenTelemetry、Jaeger 与 W3C Trace Context
  3. 分布式缓存深度策略:Redis Cluster、一致性哈希与多级缓存架构