分布式ID生成方案对比
约 1670 字大约 6 分钟
distributedid-generation
2025-05-22
概述
在分布式系统中,全局唯一ID的生成是一个基础但关键的问题。不同的ID生成方案在唯一性、有序性、性能、可用性等方面各有权衡。本文将详细分析主流的分布式ID生成方案,帮助你根据实际业务场景做出最佳选择。
ID生成的核心需求
- 全局唯一:整个分布式系统中不能出现重复ID
- 趋势递增:利于数据库索引和排序
- 高性能:单机每秒可生成数万到数百万ID
- 高可用:ID生成服务不能成为单点瓶颈
- 信息安全:不暴露业务量等敏感信息
UUID
UUID(Universally Unique Identifier)是最简单的分布式ID方案,基于时间戳、MAC地址、随机数等生成128位标识符。
import java.util.UUID;
public class UUIDGenerator {
// UUID v4 - 基于随机数
public static String generateV4() {
return UUID.randomUUID().toString();
// 示例: 550e8400-e29b-41d4-a716-446655440000
}
// UUID v1 - 基于时间戳和MAC地址(需要第三方库)
// UUID v7 - 基于时间戳的有序UUID(RFC 9562, 2024)
// 推荐使用v7,兼具唯一性和有序性
}优点: 本地生成,无网络开销,无中心依赖 缺点: 128位太长(存储浪费),无序(B+树索引性能差),不可读
数据库自增ID
利用数据库的自增特性生成全局唯一的递增ID。
-- 单库方案
CREATE TABLE id_generator (
id BIGINT NOT NULL AUTO_INCREMENT,
stub CHAR(1) NOT NULL DEFAULT '',
PRIMARY KEY (id),
UNIQUE KEY stub (stub)
) ENGINE=InnoDB;
-- 获取新ID
REPLACE INTO id_generator (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
-- 多库方案:设置不同的起始值和步长
-- 数据库1: auto_increment_offset=1, auto_increment_increment=2 → 1,3,5,7...
-- 数据库2: auto_increment_offset=2, auto_increment_increment=2 → 2,4,6,8...优点: 简单可靠,递增有序 缺点: 数据库是瓶颈,扩容困难(改步长需要停服)
Redis INCR
利用Redis的原子递增命令生成ID。
public class RedisIdGenerator {
private final StringRedisTemplate redis;
private static final String KEY_PREFIX = "id:";
public long generateId(String bizType) {
String key = KEY_PREFIX + bizType;
// INCR 是原子操作,保证唯一性
return redis.opsForValue().increment(key);
}
// 带日期前缀的ID,便于分库分表
public String generateIdWithDate(String bizType) {
String date = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
String key = KEY_PREFIX + bizType + ":" + date;
long seq = redis.opsForValue().increment(key);
// 设置过期时间避免Key无限增长
redis.expire(key, Duration.ofDays(2));
return date + String.format("%08d", seq);
// 示例: 2025052200000001
}
}优点: 高性能(单机10万+ QPS),递增有序 缺点: 需要维护Redis高可用,持久化策略可能导致ID重复
Snowflake算法(Twitter)
Snowflake是Twitter开源的分布式ID生成算法,生成64位的long型ID。
+--------+-------------------------------------------+-----------+-----------+
| 1 bit | 41 bits | 10 bits | 12 bits |
| 符号位 | 时间戳(ms) | 机器ID | 序列号 |
+--------+-------------------------------------------+-----------+-----------+
| 0 | 00000000 00000000 00000000 00000000 00000 | 0000000000| 000000000000|public class SnowflakeIdWorker {
// 起始时间戳 (2025-01-01 00:00:00)
private final long epoch = 1735689600000L;
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long sequenceBits = 12L;
private final long maxWorkerId = ~(-1L << workerIdBits); // 31
private final long maxDatacenterId = ~(-1L << datacenterIdBits); // 31
private final long sequenceMask = ~(-1L << sequenceBits); // 4095
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
private final long timestampShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long workerId;
private final long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException("Worker ID out of range");
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException("Datacenter ID out of range");
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
// 时钟回拨检测
if (timestamp < lastTimestamp) {
throw new RuntimeException(
"Clock moved backwards. Refusing to generate id for "
+ (lastTimestamp - timestamp) + "ms");
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒序列号用尽,等待下一毫秒
timestamp = waitNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - epoch) << timestampShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence;
}
private long waitNextMillis(long lastTs) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTs) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}时钟回拨问题
Snowflake最大的隐患是时钟回拨(Clock Skew),常见场景包括NTP同步、闰秒调整、虚拟机迁移等。
Leaf(美团)
美团开源的分布式ID生成系统,提供两种模式:
Leaf-Segment(号段模式)
-- Leaf-Segment 数据库表
CREATE TABLE leaf_alloc (
biz_tag VARCHAR(128) NOT NULL,
max_id BIGINT NOT NULL DEFAULT 1,
step INT NOT NULL,
description VARCHAR(256),
update_time TIMESTAMP NOT NULL,
PRIMARY KEY (biz_tag)
);
-- 获取号段
UPDATE leaf_alloc SET max_id = max_id + step WHERE biz_tag = 'order';
SELECT biz_tag, max_id, step FROM leaf_alloc WHERE biz_tag = 'order';Leaf-Snowflake(雪花模式)
使用ZooKeeper自动分配workerID,解决了手动配置的麻烦。
Tinyid(滴滴)
滴滴出行开源的分布式ID系统,基于号段模式,支持多DB和HTTP/SDK两种接入方式:
// Tinyid SDK 使用方式
Long id = TinyId.nextId("order");
List<Long> ids = TinyId.nextId("order", 100); // 批量获取方案对比
| 方案 | 唯一性 | 有序性 | 性能 | 可用性 | 长度 | 依赖 |
|---|---|---|---|---|---|---|
| UUID | 极高 | 无序(v7除外) | 极高 | 极高 | 128bit | 无 |
| DB自增 | 高 | 严格递增 | 低 | 低 | 64bit | 数据库 |
| Redis INCR | 高 | 严格递增 | 高 | 中 | 64bit | Redis |
| Snowflake | 高 | 趋势递增 | 极高 | 高 | 64bit | 时钟 |
| Leaf-Segment | 高 | 趋势递增 | 极高 | 高 | 64bit | 数据库 |
| Leaf-Snowflake | 高 | 趋势递增 | 极高 | 高 | 64bit | ZooKeeper |
选型建议
总结: 没有银弹方案,选择取决于业务场景。对于大多数互联网应用,Snowflake变体(如Leaf-Snowflake)是较好的平衡选择——64位long型、趋势递增、高性能、低延迟。如果追求简单且无外部依赖,UUID v7是一个值得关注的新选择。
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于