在分布式系统中,如何高效生成全局唯一ID是一个关键挑战。本文将深入剖析雪花算法、UUID及多种主流ID生成方案,帮助开发者根据业务场景选择最佳方案。
在分布式系统中,传统数据库自增ID存在明显瓶颈:
理想分布式ID应满足:
Twitter提出的64位ID结构:
0 | 0000000000 0000000000 0000000000 0000000000 0 | 00000 | 00000 | 000000000000
└─ 符号位(1位) └─ 时间戳(41位) └─ 数据中心(5位) └─ 机器ID(5位) └─ 序列号(12位)
public class SnowflakeIdGenerator {
private final long datacenterId;
private final long workerId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨异常");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & 0xFFF; // 12位序列号
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - 1288834974657L) << 22)
| (datacenterId << 17)
| (workerId << 12)
| sequence;
}
}
时钟回拨处理:
// 解决方案1:等待时钟同步
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
// 解决方案2:备用ID生成器(如启用UUID降级)
版本 | 名称 | 生成方式 | 特点 |
---|---|---|---|
v1 | 时间+MAC地址 | 时间戳+时钟序列+MAC地址 | 暴露机器信息,基本弃用 |
v4 | 随机UUID | 122位随机数 | 最常用,完全随机 |
v5 | 命名空间SHA-1 | 基于命名空间和名称的SHA-1哈希 | 可重复生成相同UUID |
// 生成v4 UUID
String uuid = UUID.randomUUID().toString();
// 示例:f47ac10b-58cc-4372-a567-0e02b2c3d479
// 生成v5 UUID(基于命名空间)
UUID namespace = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
String name = "example";
UUID v5Uuid = UUID.nameUUIDFromBytes(
("namespace" + name).getBytes(StandardCharsets.UTF_8)
);
优势:
致命缺陷:
美团Leaf-segment方案:
// 数据库表设计
CREATE TABLE id_segment (
biz_tag VARCHAR(128) PRIMARY KEY,
max_id BIGINT NOT NULL,
step INT NOT NULL,
update_time TIMESTAMP
);
工作流程:
优势:
// 使用INCR命令生成ID
Long id = redisTemplate.opsForValue().increment("order_id");
// 集群环境下分片生成
// 示例:机器ID为2,生成规则
// ID = (current_timestamp << 24) | (shard_id << 16) | sequence
优势:
缺陷:
基于雪花算法改进:
// Spring Boot集成
@Resource
private UidGenerator uidGenerator;
public long genId() {
return uidGenerator.getUID(); // 返回64位整数
}
方案 | 长度 | 有序性 | QPS | 依赖 | 适用场景 |
---|---|---|---|---|---|
雪花算法 | 64位 | 时间序 | 26万+ | 时钟 | 高并发订单系统 |
UUID v4 | 128位 | 无序 | 10万+ | 无 | 临时令牌、文件命名 |
数据库自增 | 64位 | 严格序 | 5千 | 单点数据库 | 小型应用 |
号段模式 | 64位 | 局部序 | 5万+ | 数据库 | 中大型业务系统 |
Redis生成 | 64位 | 可配置 | 10万+ | Redis集群 | 缓存密集型应用 |
UidGenerator | 64位 | 时间序 | 30万+ | 时钟 | 金融级高可靠系统 |
性能测试环境:4核8G云服务器,JDK11,Redis 6.x,MySQL 8.0
graph TD
A[是否需要严格有序?]
-->|是| B[选择数据库自增或号段模式]
A -->|否| C[QPS是否超过10万?]
C -->|是| D[选择雪花算法或UidGenerator]
C -->|否| E[是否需要零依赖?]
E -->|是| F[选择UUID]
E -->|否| G[选择Redis生成]
典型场景推荐:
// 解决时钟回拨的增强版
public class SafeSnowflake {
private long backupSequence = 0;
public synchronized long nextId() {
try {
return standardNextId();
} catch (ClockBackwardException e) {
// 启用备用序列
backupSequence++;
return ((System.currentTimeMillis() - EPOCH) << 22)
| (datacenterId << 17)
| (workerId << 12)
| (backupSequence & 0xFFF);
}
}
}
// 前端处理大ID的精度问题
function parseBigId(id) {
return String(id); // 转为字符串避免精度丢失
}
// Java实体类注解
public class UserDTO {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long id; // 确保JSON序列化为字符串
}
订单ID生成策略:
第1位:ID类型标记(1=订单)
2-8位:业务编码(如2023=年度)
9-18位:雪花算法ID(截取后10位)
19位:校验位(Luhn算法)
根据ID2020联盟预测,到2025年全球分布式ID生成请求将达到5万亿/天,选择合适的ID方案将成为系统架构的关键决策点。
通过对各方案的深入分析,我们得出核心结论:
终极建议:
IdType.ASSIGN_ID
(雪花算法实现)正确选择ID生成方案,将为分布式系统奠定坚实基石!