分布式系统中生成唯一ID是常见需求,尤其在微服务、分库分表等场景下,需要满足全局唯一、有序递增、高可用、低延迟等特性。以下是七种主流方案及其核心逻辑:
一、数据库自增ID
原理 :利用数据库自增主键(如MySQL的AUTO_INCREMENT
)生成连续ID。
优化方案:
- 单数据库多实例 :不同实例设置不同自增步长(
auto_increment_increment
和auto_increment_offset
)。 - 分片号段模式 :每次从数据库批量获取ID段(如1~1000),应用内存中分配。
优点 :简单易用、ID连续递增。
缺点 :依赖数据库性能,扩展性差,存在单点故障风险。
适用场景:小型系统、单数据库架构。
二、UUID
原理 :基于时间、MAC地址、随机数生成128位唯一字符串(如550e8400-e29b-41d4-a716-446655440000
)。
优点 :本地生成、无网络开销、无重复风险。
缺点:
- 无序性导致数据库索引效率低(B+树分裂频繁)。
- 长度过长(36字符),存储和传输效率低。
适用场景:临时标识、低并发且无需排序的场景。
三、Redis生成ID
原理 :利用Redis单线程特性,通过INCR
或INCRBY
命令生成递增ID。
优化方案:
- 集群分片:每个Redis实例分配独立步长(类似数据库多实例)。
- Lua脚本原子操作 :批量获取ID段,减少网络开销。
优点 :性能高于数据库(10万+ QPS),可扩展性强。
缺点 :需维护Redis集群,持久化策略影响数据可靠性。
适用场景:高并发但对ID连续性要求不高的场景。
四、Snowflake算法(雪花算法)
原理:由Twitter提出的64位ID结构,包含时间戳、机器ID、序列号:
0 | 41位时间戳 | 10位机器ID | 12位序列号
- 时间戳:毫秒级时间差(自定义起始时间,如2020-01-01)。
- 机器ID:通过ZooKeeper、配置中心或IP哈希分配。
- 序列号 :同一毫秒内的并发序列(支持4096/ms)。
优点 :本地生成、性能高(单机万级QPS)、ID有序。
缺点 :依赖时钟同步(时钟回拨导致重复ID)。
解决方案: - 关闭NTP同步(不推荐)。
- 记录上次生成时间戳,检测到回拨时等待或报警。
适用场景:分布式系统、分库分表场景。
五、Leaf算法
原理 :美团开源的分布式ID生成服务,整合Snowflake 和号段模式 。
两种模式:
- Leaf-Snowflake:优化时钟回拨问题,通过ZooKeeper协调Worker ID。
- Leaf-Segment :基于数据库号段预分配,双Buffer异步更新。
优点 :高可用、支持监控和管理后台。
缺点 :需独立部署服务,增加系统复杂度。
适用场景:中大型企业级应用。
六、TinyID(百度)
原理 :基于数据库号段模式,支持HTTP和RPC两种接入方式。
核心优化:
- 多号段缓存:提前加载多个号段,避免分配延迟。
- 动态步长调整 :根据历史QPS自动调整号段大小。
优点 :高吞吐、低延迟。
缺点 :依赖数据库和中心化服务。
适用场景:电商、金融等高并发业务。
七、MongoDB ObjectId
原理:12字节(24字符)的十六进制字符串,结构如下:
4字节时间戳 | 5字节机器ID | 3字节进程ID | 4字节计数器
优点 :无需中心化服务,内置分布式支持。
缺点 :无序性、长度较长。
适用场景:MongoDB数据库集成场景。
方案对比与选型建议
方案 | 有序性 | 性能 | 复杂度 | 适用场景 |
---|---|---|---|---|
数据库自增ID | ✔️ | 低 | 低 | 单数据库、小规模系统 |
UUID | ❌ | 高 | 低 | 临时标识、低并发场景 |
Redis | ✔️ | 高 | 中 | 高并发、非强连续场景 |
Snowflake | ✔️ | 极高 | 中 | 分布式系统、分库分表 |
Leaf | ✔️ | 高 | 高 | 企业级应用、需高可用 |
TinyID | ✔️ | 高 | 高 | 高并发、动态调整需求 |
MongoDB ObjectId | ❌ | 高 | 低 | MongoDB集成场景 |
选型原则:
- 业务需求:是否需要有序性(影响数据库索引效率)?
- 并发量级:是否需支持每秒数万级ID生成?
- 系统架构:是否已存在ZooKeeper、Redis等中间件?
- 容灾能力:能否接受单点故障或时钟回拨风险?
实战示例:Snowflake的Java实现
public class SnowflakeIdGenerator {
private final long twepoch = 1625097600000L; // 2021-07-01 00:00:00
private final long workerIdBits = 5L;
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long timestampShift = sequenceBits + workerIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
private long workerId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long workerId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException("Worker ID超出范围");
}
this.workerId = workerId;
}
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨,拒绝生成ID");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampShift)
| (workerId << workerIdShift)
| sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
总结
分布式ID设计需权衡性能、有序性、可用性和复杂度。Snowflake及其衍生方案(如Leaf)是通用性最强的选择,而号段模式适合数据库友好的场景。最终选型应基于业务实际需求,结合团队技术栈和运维能力,确保系统的稳定性和扩展性。