分布式ID生成器是分布式系统中的关键基础设施,用于在分布式环境下生成全局唯一的标识符。以下是各种实现方案的深度解析和最佳实践。
一、核心需求与设计考量
1. 核心需求矩阵
需求 |
重要性 |
实现难点 |
全局唯一 |
必须保证 |
时钟回拨/节点冲突 |
高性能 |
高并发场景关键 |
锁竞争/网络开销 |
有序性 |
分页查询友好 |
时间戳精度问题 |
高可用 |
服务不可中断 |
故障转移/数据恢复 |
易用性 |
接入成本低 |
协议兼容性 |
2. 典型业务场景
- 电商订单号生成
- 金融交易流水号
- 物联网设备标识
- 分布式日志追踪
- 数据库分片键
二、主流实现方案对比
1. 方案全景图
mermaid
graph TD
A[分布式ID] --> B[中心化]
A --> C[去中心化]
B --> D[数据库序列]
B --> E[Redis原子操作]
B --> F[Zookeeper节点]
C --> G[UUID]
C --> H[Snowflake]
C --> I[Leaf/美团]
2. 详细方案对比
方案 |
示例ID |
优点 |
缺点 |
QPS |
UUID |
|
无中心节点 |
无序存储效率低 |
100,000+ |
数据库自增 |
|
简单可靠 |
单点瓶颈 |
1,000~5,000 |
Redis INCR |
|
性能较好 |
持久化问题 |
50,000~100,000 |
Snowflake |
|
有序紧凑 |
时钟敏感 |
100,000+ |
Leaf-Segment |
|
缓冲优化 |
需DB配合 |
50,000+ |
Tinyid |
|
批量获取 |
强依赖ZK |
20,000+ |
三、Snowflake 深度实现
1. 标准位分配
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
|____________________________| |________| |_____| |________________________|
时间戳(41bit) 数据中心(5bit) 机器ID(5bit) 序列号(12bit)
2. Java优化实现
public class SnowflakeIdGenerator {
private final long twepoch = 1288834974657L; // 起始时间戳(2010-11-04)
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long sequenceBits = 12L;
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private volatile long lastTimestamp = -1L;
private volatile long sequence = 0L;
public synchronized long nextId() {
long timestamp = timeGen();
// 时钟回拨处理
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & ((1 << sequenceBits) - 1);
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << (workerIdBits + sequenceBits))
| (datacenterId << (workerIdBits + sequenceBits))
| (workerId << sequenceBits)
| sequence;
}
protected long tilNextMillis(long lastTimestamp) {
// 阻塞到下一毫秒
long timestamp;
do {
timestamp = timeGen();
} while (timestamp <= lastTimestamp);
return timestamp;
}
}
3. 时钟回拨解决方案
方案 |
实现方式 |
适用场景 |
异常抛出 |
直接拒绝请求 |
严格要求时序 |
等待时钟 |
自旋直到时钟追回 |
短暂回拨(<100ms) |
备用ID池 |
提前生成备用ID |
容忍短暂无序 |
扩展位记录 |
增加回拨计数位 |
需要改造ID结构 |
四、Leaf-Segment 方案详解
1. 数据库设计
CREATE TABLE `leaf_alloc` (
`biz_tag` varchar(128) NOT NULL,
`max_id` bigint NOT NULL DEFAULT '1',
`step` int NOT NULL,
`update_time` timestamp NOT NULL,
PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB;
2. 双Buffer优化流程
sequenceDiagram
participant Client
participant Service
participant DB
Client->>Service: 获取ID(biz_tag=order)
Service->>DB: 查询当前max_id和step
DB-->>Service: max_id=1000, step=1000
Service->>Service: 分配本地缓存[1001-2000]
Service->>Client: 返回1001
Service->>DB: 异步更新max_id=2000
Note right of Service: Buffer1耗尽前<br>提前加载Buffer2
3. 异常处理机制
- DB故障:使用本地缓存直到耗尽
- ID耗尽:动态调整step大小
- 双Buffer同时失效:降级到同步获取
五、高性能服务架构
1. 服务化部署架构
+-----------------+
| Load Balancer |
+--------+--------+
|
+----------------+----------------+
| | |
+------+------+ +------+------+ +------+------+
| ID Service | | ID Service | | ID Service |
+------+------+ +------+------+ +------+------+
| | |
+------+------+ +------+------+ +------+------+
| Redis | | DB | | Zookeeper |
+-------------+ +------------+ +-------------+
2. 性能优化技巧
技术 |
效果 |
实现示例 |
本地缓存 |
减少网络IO |
存储Segment |
批量获取 |
降低DB压力 |
|
异步持久化 |
提高吞吐 |
先响应后写WAL日志 |
分层设计 |
故障隔离 |
内存->Redis->DB 三级获取 |
3. 容灾方案对比
方案 |
恢复时间 |
数据丢失风险 |
主从同步 |
秒级 |
少量异步数据 |
多活部署 |
几乎为零 |
无 |
定期快照 |
分钟级 |
取决于备份频率 |
六、生产实践案例
案例1:电商订单ID
需求:
- 每日亿级订单
- 需要时间有序
- 包含业务类型信息
实现:
// 格式: 业务类型(2位) + 时间(yyMMddHHmm) + 序列(6位) + 机器(3位)
public String generateOrderId(String bizType) {
String timePart = new SimpleDateFormat("yyMMddHHmm").format(new Date());
long seq = redis.incr("order:id:" + timePart);
return String.format("%s%s%06d%03d",
bizType, timePart, seq % 1000000, machineId);
}
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@Service
public class BufferedOrderIdGenerator {
private final Queue<String> idPool = new ConcurrentLinkedQueue<>();
private final RedisOrderIdGenerator redisGenerator;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private static final int BATCH_SIZE = 100;
private static final int REFILL_THRESHOLD = 20;
public BufferedOrderIdGenerator(RedisOrderIdGenerator redisGenerator) {
this.redisGenerator = redisGenerator;
this.scheduler.scheduleAtFixedRate(this::refillPool, 0, 1, TimeUnit.SECONDS);
}
public String getOrderId() {
String id = idPool.poll();
if (id == null) {
// 缓冲池为空时同步获取
return redisGenerator.generateOrderId();
}
return id;
}
private void refillPool() {
if (idPool.size() < REFILL_THRESHOLD) {
// 批量预生成ID
String date = LocalDate.now().format(DATE_FORMAT);
Long startSeq = redisTemplate.opsForValue()
.increment(ORDER_ID_PREFIX + date, BATCH_SIZE);
for (long i = startSeq - BATCH_SIZE + 1; i <= startSeq; i++) {
idPool.add(String.format(ID_FORMAT, date, i));
}
redisTemplate.expire(ORDER_ID_PREFIX + date, 48, TimeUnit.HOURS);
}
}
}
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
@Service
public class RedisOrderIdGenerator {
private final StringRedisTemplate redisTemplate;
private static final String ORDER_ID_PREFIX = "order:id:";
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.BASIC_ISO_DATE;
// 订单ID格式:年月日(8位) + 序列号(8位)
private static final String ID_FORMAT = "%s%08d";
public RedisOrderIdGenerator(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 生成订单ID
*/
public String generateOrderId() {
String date = LocalDate.now().format(DATE_FORMAT);
String key = ORDER_ID_PREFIX + date;
// 使用Redis原子操作INCR
Long sequence = redisTemplate.opsForValue().increment(key);
// 设置48小时过期(避免跨日期问题)
redisTemplate.expire(key, 48, TimeUnit.HOURS);
return String.format(ID_FORMAT, date, sequence);
}
}
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;
@Service
public class PersistentOrderIdService {
private final RedisOrderIdGenerator redisGenerator;
private final JdbcTemplate jdbcTemplate;
// WAL(Write-Ahead Log)表
private static final String WAL_TABLE = "order_id_wal";
public PersistentOrderIdService(RedisOrderIdGenerator redisGenerator,
JdbcTemplate jdbcTemplate) {
this.redisGenerator = redisGenerator;
this.jdbcTemplate = jdbcTemplate;
}
/**
* 带持久化保障的ID生成
*/
@Transactional
public String generatePersistentOrderId() {
// 1. 先写入预写日志
String pendingId = redisGenerator.generateOrderId();
jdbcTemplate.update(
"INSERT INTO " + WAL_TABLE + " (order_id, create_time, status) VALUES (?, NOW(), 'PENDING')",
pendingId);
// 2. 确认写入Redis
// 如果Redis操作失败会抛出异常触发事务回滚
// 3. 更新日志状态
jdbcTemplate.update(
"UPDATE " + WAL_TABLE + " SET status = 'CONFIRMED' WHERE order_id = ?",
pendingId);
return pendingId;
}
/**
* 恢复未确认的ID
*/
@Scheduled(fixedRate = 60000) // 每分钟执行一次
public void recoverPendingIds() {
jdbcTemplate.query(
"SELECT order_id FROM " + WAL_TABLE + " WHERE status = 'PENDING' AND create_time > DATE_SUB(NOW(), INTERVAL 1 HOUR)",
(rs, rowNum) -> rs.getString("order_id"))
.forEach(pendingId -> {
if (!redisTemplate.hasKey(buildRedisKey(pendingId))) {
redisTemplate.opsForValue().set(buildRedisKey(pendingId),
extractSequence(pendingId));
}
});
}
private String buildRedisKey(String orderId) {
return ORDER_ID_PREFIX + orderId.substring(0, 8); // 提取日期部分
}
private long extractSequence(String orderId) {
return Long.parseLong(orderId.substring(8));
}
}
案例2:分布式追踪ID
需求:
- 全局唯一
- 高吞吐
- 可解析
实现(借鉴Twitter的Zipkin):
// 128-bit ID = 应用节点(32bit) + 时间(64bit) + 随机数(32bit)
public static String newTraceId() {
return String.format("%08x%016x%08x",
nodeId,
System.currentTimeMillis(),
ThreadLocalRandom.current().nextInt());
}
七、监控与治理
1. 关键监控指标
指标 |
采集方式 |
告警阈值 |
ID生成延迟 |
Micrometer Timer |
P99 > 10ms |
段缓存命中率 |
缓存统计 |
<90% |
时钟偏移量 |
NTP监控 |
>50ms |
DB连接池使用率 |
Druid监控 |
>80% |
2. 运维指令集
bash
# 动态调整Leaf步长
curl -X POST "http://id-service/segment/step?bizTag=order&step=5000"
# 强制刷新缓存
redis-cli DEL leaf:order:cache
# 节点下线
./admin.sh disableNode --nodeId=3
八、选型决策树
mermaid
graph TD
A[是否需要有序ID?] -->|是| B[考虑Snowflake/Leaf]
A -->|否| C[考虑UUID]
B --> D[QPS>10万?]
D -->|是| E[Leaf-Segment+缓存]
D -->|否| F[原生Snowflake]
C --> G[需要可读性?]
G -->|是| H[时间戳+序列组合]
G -->|否| I[标准UUIDv4]
通过深入理解这些实现方案和架构设计,可以构建出满足不同业务场景需求的分布式ID服务。建议根据实际业务规模、性能要求和运维能力进行技术选型。