分布式ID生成方案详解与实战
一、分布式ID概述
在分布式系统中,全局唯一ID是核心需求之一,用于标识业务实体。
1.1 ID生成要求
| 特性 |
说明 |
| 唯一性 |
全局唯一,永不重复 |
| 递增性 |
按时间递增,便于排序 |
| 高性能 |
高并发下低延迟 |
| 可扩展性 |
支持水平扩展 |
| 安全性 |
不可预测,防止枚举攻击 |
1.2 常见ID生成方案对比
| 方案 |
优点 |
缺点 |
适用场景 |
| 数据库自增 |
简单可靠 |
单点瓶颈 |
中小型系统 |
| UUID |
无中心依赖 |
无序、过长 |
对ID有序性要求低 |
| Snowflake |
高性能、有序 |
依赖时钟 |
分布式高并发 |
| Redis自增 |
高性能 |
需要部署Redis |
缓存场景 |
| 数据库分段 |
高可用 |
实现复杂 |
大型分布式系统 |
二、Snowflake算法
2.1 Snowflake结构
┌─────────────────────────────────────────────────────────────────────┐
│ 64位Snowflake ID │
├─────────────────────────────────────────────────────────────────────┤
│ 1位 │ 41位时间戳 │ 10位机器ID │ 12位序列号 │
│ 符号 │ (毫秒级) │ (5位数据中心+5位工作节点) │ │
│ │ 可表示约69年 │ 支持1024个节点 │ 每毫秒4096个ID │
└─────────────────────────────────────────────────────────────────────┘
2.2 Java实现
public class SnowflakeIdGenerator {
private final long twepoch = 1609459200000L; // 2021-01-01 00:00:00
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
private long workerId;
private long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(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 = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards");
}
if (timestamp == lastTimestamp) {
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 timeGen() {
return System.currentTimeMillis();
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
}
2.3 时钟回拨处理
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
long offset = lastTimestamp - timestamp;
if (offset <= 5) {
try {
wait(offset << 1);
timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException("Clock moved backwards too much");
}
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence;
}
三、数据库自增ID
3.1 单库自增
CREATE TABLE `sequence` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`biz_type` VARCHAR(64) NOT NULL COMMENT '业务类型',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_biz_type` (`biz_type`)
) ENGINE=InnoDB AUTO_INCREMENT=100000 DEFAULT CHARSET=utf8mb4;
3.2 分段ID生成
public class SegmentIdGenerator {
private volatile long currentId;
private volatile long maxId;
private final Object lock = new Object();
private final JdbcTemplate jdbcTemplate;
public long nextId(String bizType) {
if (currentId >= maxId) {
synchronized (lock) {
if (currentId >= maxId) {
fetchSegment(bizType);
}
}
}
return ++currentId;
}
private void fetchSegment(String bizType) {
String sql = "UPDATE sequence SET id = LAST_INSERT_ID(id + ?) WHERE biz_type = ?";
jdbcTemplate.update(sql, 1000, bizType);
String querySql = "SELECT LAST_INSERT_ID()";
long newId = jdbcTemplate.queryForObject(querySql, Long.class);
this.currentId = newId;
this.maxId = newId + 1000 - 1;
}
}
3.3 多库多表方案
┌─────────────────────────────────────────────────────────────────┐
│ ID生成器集群 │
├─────────────────────────────────────────────────────────────────┤
│ DB1: sequence_0 DB2: sequence_1 DB3: sequence_2 │
│ step=3, start=0 step=3, start=1 step=3, start=2 │
│ IDs: 0,3,6,9... IDs: 1,4,7,10... IDs: 2,5,8,11... │
└─────────────────────────────────────────────────────────────────┘
四、Redis自增ID
4.1 基本实现
public class RedisIdGenerator {
private final StringRedisTemplate redisTemplate;
private final String keyPrefix = "sequence:";
private final long step = 100;
public long nextId(String bizType) {
String key = keyPrefix + bizType;
Long current = redisTemplate.opsForValue().increment(key);
if (current <= step) {
redisTemplate.opsForValue().set(key, String.valueOf(current + step * 10));
}
return current;
}
}
4.2 Lua脚本保证原子性
local key = KEYS[1]
local step = tonumber(ARGV[1])
local current = redis.call('INCR', key)
if current <= step then
redis.call('SET', key, current + step * 10)
end
return current
五、UUID方案
5.1 UUID生成
// 标准UUID
String uuid = UUID.randomUUID().toString();
// 去除横线
String compactUuid = uuid.replace("-", "");
// 简化UUID(前8位)
String shortUuid = compactUuid.substring(0, 8);
5.2 基于时间戳的UUID
public class TimeBasedUUIDGenerator {
public String generate() {
long timestamp = System.currentTimeMillis();
String timestampHex = Long.toHexString(timestamp);
String randomPart = UUID.randomUUID().toString().substring(16);
return timestampHex + randomPart;
}
}
六、美团Leaf方案
6.1 Leaf架构
┌─────────────────────────────────────────────────────────────┐
│ Leaf ID Generator │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Segment │ │ Snowflake │ │ DB Backup │ │
│ │ Mode │ │ Mode │ │ │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └──────────────────┴──────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ REST API │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────┘
6.2 Segment模式配置
leaf:
segment:
enable: true
datasource:
url: jdbc:mysql://localhost:3306/leaf?useUnicode=true&characterEncoding=utf-8
username: root
password: password
6.3 Snowflake模式配置
leaf:
snowflake:
enable: true
zk:
address: localhost:2181
path: /leaf/snowflake
七、实战建议
7.1 方案选择策略
| 场景 |
推荐方案 |
原因 |
| 中小型系统 |
数据库自增 |
简单可靠,无需额外组件 |
| 高并发系统 |
Snowflake |
高性能,毫秒级生成数万ID |
| 跨数据中心 |
Leaf/Snowflake |
支持多节点部署 |
| 对ID有序性要求高 |
Segment |
ID连续递增 |
7.2 性能对比
| 方案 |
QPS |
延迟(ms) |
优点 |
缺点 |
| Snowflake |
100万+ |
<1 |
极高性能 |
依赖时钟 |
| Redis |
50万+ |
1-5 |
高性能 |
需要Redis |
| Segment |
10万+ |
5-10 |
ID连续 |
依赖DB |
| UUID |
10万+ |
<1 |
无依赖 |
无序、长 |
7.3 部署建议
- Worker ID分配:通过配置中心或ZK自动分配
- 时钟同步:使用NTP服务同步时间
- 容灾备份:部署多节点,支持故障转移
- 监控告警:监控ID生成速率、异常情况
八、总结
分布式ID生成方案需要根据业务场景选择:
- Snowflake适合高并发、对ID有序性有要求的场景
- Segment适合需要连续ID的场景
- UUID适合对ID无特殊要求的场景
- Redis适合缓存场景或已有Redis集群的系统
通过合理选择和配置,可以构建可靠、高性能的分布式ID生成系统。