【java - 最常用的3种ID生成器】
·
Java 全局 ID 生成器实战:Snowflake、订单号、TraceId 三种方案对比与选型
一、为什么需要多种 ID 生成器?
在分布式系统中,「全局唯一、有序 ID」是常见需求,但不同业务对 ID 的要求并不相同:
| 需求维度 | 数据库主键 | 业务订单号 | 链路 TraceId |
|---|---|---|---|
| 存储效率 | 高 | 中 | 低 |
| 可读性 | 低 | 高 | 低 |
| 趋势递增 | 必须 | 期望 | 不要求 |
| 对外展示 | 否 | 是 | 否 |
| 并发性能 | 高 | 高 | 极高 |
结论:没有一种 ID 适合所有场景。 生产环境中,往往是 Snowflake 主键(雪花算法) + 订单号展示 + TraceId 追踪 三者配合使用。
二、三种生成器概览
com.zx.idgenerator/
├── SnowflakeIdGenerator.java # 64 bit 数字 ID,数据库主键
├── OrderNoGenerator.java # 字符串订单号,业务展示
└── TraceIdGenerator.java # hex 链路 ID,日志追踪
快速对比
| 生成器 | 输出类型 | 示例 | 并发策略 | 分布式 |
|---|---|---|---|---|
| Snowflake | long(64 bit) |
1847562890123456789 |
synchronized |
✅ workerId + datacenterId |
| OrderNo | String(~23 字符) |
ORD202407031119000001 |
CAS + 分段预取 | ⚠️ 需扩展号段 |
| TraceId | String(16/24 hex) |
a1b2c3d4e5f6789012345678 |
CAS + ThreadLocalRandom | ⚠️ 依赖 PID + 随机 |
并发策略对比
Snowflake → synchronized 全局锁(多字段联动,逻辑简单可靠)
OrderNo → AtomicLong 无锁取号 + 分段补货时 synchronized
TraceId → AtomicLong + ThreadLocalRandom(完全无全局锁)
三、完整源码
3.1 SnowflakeIdGenerator — 分布式数字主键
适用场景: 用户 ID、订单表主键、任何需要 long 型全局唯一 ID 的场景。
核心设计:
- 64 bit 布局:1 符号位 + 41 时间戳 + 5 数据中心 + 5 机器 + 12 序列
- 单例 +
synchronized保证同毫秒内序列不冲突 - 支持按
workerId:datacenterId维度注册多实例
package com.zx.idgenerator;
/**
* Twitter Snowflake 风格发号器:全局唯一、趋势递增。
* <p>
* 64 位布局:1 符号位 + 41 位时间戳 + 5 位数据中心 + 5 位机器 + 12 位序列。
* 单例 + synchronized 保证多线程下同一毫秒内的序列号不冲突。
*/
public final class SnowflakeIdGenerator {
/** 自定义纪元,减小 ID 数值 */
private static final long EPOCH = 1_704_067_200_000L;
private static final long WORKER_ID_BITS = 5L;
private static final long DATACENTER_ID_BITS = 5L;
private static final long SEQUENCE_BITS = 12L;
private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);
private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;
private final long workerId;
private final long datacenterId;
private long sequence;
private long lastTimestamp = -1L;
private SnowflakeIdGenerator(long workerId, long datacenterId) {
if (workerId < 0 || workerId > MAX_WORKER_ID) {
throw new IllegalArgumentException("workerId must be between 0 and " + MAX_WORKER_ID);
}
if (datacenterId < 0 || datacenterId > MAX_DATACENTER_ID) {
throw new IllegalArgumentException("datacenterId must be between 0 and " + MAX_DATACENTER_ID);
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/** 静态内部类持有单例(JVM 类加载保证线程安全) */
private static final class Holder {
private static final SnowflakeIdGenerator INSTANCE = new SnowflakeIdGenerator(1L, 1L);
}
public static SnowflakeIdGenerator getInstance() {
return Holder.INSTANCE;
}
public static SnowflakeIdGenerator getInstance(long workerId, long datacenterId) {
return InstanceRegistry.INSTANCE.getOrCreate(workerId, datacenterId);
}
/**
* 生成下一个 Snowflake ID。
* synchronized 锁住发号临界区,避免同一毫秒内序列竞争。
*/
public synchronized long nextId() {
long timestamp = currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new IllegalStateException(
"Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " ms");
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & SEQUENCE_MASK;
if (sequence == 0) {
timestamp = waitNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - EPOCH) << TIMESTAMP_SHIFT)
| (datacenterId << DATACENTER_ID_SHIFT)
| (workerId << WORKER_ID_SHIFT)
| sequence;
}
private static long waitNextMillis(long lastTimestamp) {
long timestamp = currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = currentTimeMillis();
}
return timestamp;
}
private static long currentTimeMillis() {
return System.currentTimeMillis();
}
/** 支持按 worker/datacenter 维度复用单例实例 */
private static final class InstanceRegistry {
private static final InstanceRegistry INSTANCE = new InstanceRegistry();
private final java.util.concurrent.ConcurrentHashMap<String, SnowflakeIdGenerator> cache =
new java.util.concurrent.ConcurrentHashMap<>();
SnowflakeIdGenerator getOrCreate(long workerId, long datacenterId) {
String key = workerId + ":" + datacenterId;
return cache.computeIfAbsent(key, k -> new SnowflakeIdGenerator(workerId, datacenterId));
}
}
}
调用示例:
// 默认实例(workerId=1, datacenterId=1)
long id = SnowflakeIdGenerator.getInstance().nextId();
// 多机部署:每台机器不同 workerId
long id = SnowflakeIdGenerator.getInstance(2, 1).nextId();
3.2 OrderNoGenerator — 可读业务订单号
适用场景: 对外展示的订单号、支付流水号、退款单号等。
核心设计:
- 格式:
{prefix}{yyyyMMddHHmmss}{6位序列},如ORD202407031119000001 - 单例 + 分段预取:AtomicLong 无锁取号,每 1000 个号才加锁补段
- 按 prefix 维度注册(ORD / PAY / REF 各自独立序列)
package com.zx.idgenerator;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicLong;
/**
* 业务订单号生成器:前缀 + 时间戳 + 序列,可读且趋势有序。
* <p>
* 单例 + 分段预取(segment):AtomicLong 无锁取号,段耗尽时 synchronized 补段,降低锁竞争。
*/
public final class OrderNoGenerator {
private static final String DEFAULT_PREFIX = "ORD";
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
private static final int SEGMENT_SIZE = 1000;
private final String prefix;
private final AtomicLong segmentCursor = new AtomicLong(0);
private volatile long segmentEnd = 0;
private volatile String timeBucket = "";
private final Object segmentLock = new Object();
private OrderNoGenerator(String prefix) {
this.prefix = prefix;
}
private static final class Holder {
private static final OrderNoGenerator INSTANCE = new OrderNoGenerator(DEFAULT_PREFIX);
}
public static OrderNoGenerator getInstance() {
return Holder.INSTANCE;
}
public static OrderNoGenerator getInstance(String prefix) {
if (DEFAULT_PREFIX.equals(prefix)) {
return getInstance();
}
return PrefixRegistry.INSTANCE.getOrCreate(prefix);
}
/**
* 生成订单号,格式:{prefix}{yyyyMMddHHmmss}{6位序列}。
*/
public String nextOrderNo() {
long seq = segmentCursor.incrementAndGet();
if (seq > segmentEnd) {
synchronized (segmentLock) {
if (segmentCursor.get() > segmentEnd) {
refillSegment();
}
seq = segmentCursor.incrementAndGet();
}
}
return prefix + timeBucket + String.format("%06d", seq);
}
private void refillSegment() {
String newBucket = LocalDateTime.now().format(TIME_FORMAT);
if (!newBucket.equals(timeBucket)) {
timeBucket = newBucket;
segmentCursor.set(0);
segmentEnd = SEGMENT_SIZE;
} else {
segmentEnd += SEGMENT_SIZE;
}
}
private static final class PrefixRegistry {
private static final PrefixRegistry INSTANCE = new PrefixRegistry();
private final java.util.concurrent.ConcurrentHashMap<String, OrderNoGenerator> cache =
new java.util.concurrent.ConcurrentHashMap<>();
OrderNoGenerator getOrCreate(String prefix) {
return cache.computeIfAbsent(prefix, OrderNoGenerator::new);
}
}
}
调用示例:
String orderNo = OrderNoGenerator.getInstance().nextOrderNo();
// → ORD202407031119000001
String payNo = OrderNoGenerator.getInstance("PAY").nextOrderNo();
// → PAY202407031119000001
3.3 TraceIdGenerator — 链路追踪 ID
适用场景: 日志 TraceId、请求链路追踪、MDC、网关 Header 透传。
核心设计:
- 时间戳 + 进程 PID + 自增序列 + 随机数,多维组合保证唯一
- 纯 AtomicLong + ThreadLocalRandom,无全局锁,并发性能最高
- 提供长版(24 hex)和短版(16 hex)两种格式
package com.zx.idgenerator;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;
/**
* 分布式链路 TraceId 生成器:时间戳 + 进程标识 + 计数 + 随机数,保证高并发下唯一。
* <p>
* 单例 + AtomicLong 计数 + ThreadLocalRandom 随机后缀,避免全局锁。
*/
public final class TraceIdGenerator {
private final AtomicLong sequence = new AtomicLong(0);
private final long processTag;
private TraceIdGenerator() {
this.processTag = (ProcessHandle.current().pid() & 0xFFFFL) << 32;
}
private static final class Holder {
private static final TraceIdGenerator INSTANCE = new TraceIdGenerator();
}
public static TraceIdGenerator getInstance() {
return Holder.INSTANCE;
}
/**
* 长 TraceId:24 位 hex(96 bit)。
* 组成:16 hex combined(时间戳+序列+PID) + 8 hex 随机数
*/
public String nextTraceId() {
long timestamp = System.currentTimeMillis();
long seq = sequence.incrementAndGet() & 0xFFF;
int random = ThreadLocalRandom.current().nextInt();
long combined = (timestamp << 20)
| (seq << 8)
| ((processTag >>> 32) & 0xFF);
return String.format("%016x%08x", combined, random);
}
/**
* 短 TraceId:16 位 hex(64 bit),适合日志 header 长度受限场景。
*/
public String nextShortTraceId() {
long timestamp = System.currentTimeMillis() & 0xFFFFFFFFL;
long seq = sequence.incrementAndGet() & 0xFFFF;
int random = ThreadLocalRandom.current().nextInt(0x10000);
return String.format("%08x%04x%04x", timestamp, seq, random);
}
}
调用示例:
String traceId = TraceIdGenerator.getInstance().nextTraceId();
// → a1b2c3d4e5f6789012345678abcdef12(24 hex)
String shortId = TraceIdGenerator.getInstance().nextShortTraceId();
// → 65a3f2b100429c7e(16 hex)
四、优缺点详细对比
4.1 SnowflakeIdGenerator
| 优点 | 缺点 |
|---|---|
✅ 紧凑:8 字节 long,索引友好 |
❌ 纯数字,人类不可读 |
| ✅ 严格趋势递增,B+ 树插入性能好 | ❌ 依赖 workerId 分配,多机需配置 |
| ✅ 支持分布式(32 机房 × 32 机器) | ❌ 时钟回拨需处理(当前实现直接抛异常) |
| ✅ 理论 QPS ~409 万/秒 | ❌ synchronized 极高并发下有锁竞争 |
| ✅ 业界成熟方案,文档丰富 | ❌ 暴露给前端需转字符串 |
4.2 OrderNoGenerator
| 优点 | 缺点 |
|---|---|
✅ 可读性强:ORD202407031119000001 |
❌ 字符串 ~23 字符,存储比 long 大 |
| ✅ 前缀区分业务:ORD / PAY / REF | ❌ 字符串索引性能略低于数字 |
| ✅ 分段预取,锁竞争低(约 1/1000) | ❌ 当前实现偏单机,多 JVM 需扩展 |
| ✅ 符合国内订单号展示习惯 | ❌ 生成有字符串拼接开销(可忽略) |
| ✅ 客服、对账、人工排查友好 | ❌ 6 位序列同秒上限 999999 |
4.3 TraceIdGenerator
| 优点 | 缺点 |
|---|---|
| ✅ 并发性能最高:无全局锁 | ❌ 不保证趋势递增,不适合做主键 |
| ✅ 实现最轻,延迟最低 | ❌ hex 字符串,可读性差 |
| ✅ 不要求严格有序 | ❌ 分布式靠 PID+随机,不如 Snowflake 严谨 |
| ✅ 长/短两种格式可选 | ❌ PID 截断、随机截断,理论有极低碰撞概率 |
| ✅ 适合每个请求生成一次 | ❌ 非行业标准格式(OpenTelemetry 等有规范) |
五、业务场景选型指南
5.1 决策流程图
5.2 按场景推荐
| 业务场景 | 推荐方案 | 理由 |
|---|---|---|
| 用户表主键 | Snowflake | long 型、索引友好、分布式 |
| 订单表主键 | Snowflake | 同上 |
| 对外订单号 | OrderNo | 可读、含时间、有前缀 |
| 支付/退款流水号 | OrderNo(不同 prefix) | PAY / REF 前缀区分 |
| HTTP 请求 TraceId | TraceId | 无锁、低延迟、不要求有序 |
| 日志 MDC traceId | TraceId(short 版) | header 长度受限 |
| 消息队列 messageId | Snowflake 或 TraceId | 看是否需要有序 |
| 数据库分库分表 shardKey | Snowflake | 趋势递增利于范围查询 |
| 优惠券码(给人看) | OrderNo 变体 | 可读 + 前缀 |
| 会话 sessionId | TraceId | 唯一即可,不需有序 |
5.3 典型电商下单流程(三者配合)
// 1. 生成数据库主键
long orderId = SnowflakeIdGenerator.getInstance().nextId();
// 2. 生成对外订单号
String orderNo = OrderNoGenerator.getInstance().nextOrderNo();
// 3. 生成链路 TraceId(通常在网关/Filter 层生成)
String traceId = TraceIdGenerator.getInstance().nextTraceId();
// 入库
order.setId(orderId); // 1847562890123456789
order.setOrderNo(orderNo); // ORD202407031119000001
// 日志
MDC.put("traceId", traceId); // a1b2c3d4e5f6789012345678
数据库存储:
+----+---------------------+---------------------------+
| id | order_no | trace_id (可选) |
+----+---------------------+---------------------------+
| 1847562890123456789 | ORD202407031119000001 | a1b2c3... |
+----+---------------------+---------------------------+
↑ 主键/关联 ↑ 展示/对账 ↑ 日志追踪
六、并发与性能对比
| 指标 | Snowflake | OrderNo | TraceId |
|---|---|---|---|
| 快路径 | synchronized | CAS | CAS |
| 慢路径 | 毫秒序列用尽时空转 | 每 1000 号加锁补段 | 无 |
| 单实例理论 QPS | ~409 万/秒 | 受字符串拼接限制,通常够用 | 极高 |
| 多线程竞争 | 全局锁 | 低(分段) | 几乎无 |
| 内存占用 | 8 字节/ID | ~46 字节/ID | ~48 字节/ID |
七、共同设计模式
三种生成器都采用:
- 单例模式(静态内部类 Holder) — 保证全局唯一发号状态
- 线程安全 — synchronized / AtomicLong / volatile 组合
- Registry 扩展 — Snowflake 按 workerId,OrderNo 按 prefix
单例 ≠ 线程安全
单例:保证只有一个实例
线程安全:靠 synchronized / CAS / volatile 保证
八、总结
| 如果你需要… | 选择 |
|---|---|
| 数据库主键、分布式数字 ID | SnowflakeIdGenerator |
| 对外订单号、业务流水号 | OrderNoGenerator |
| 日志 TraceId、链路追踪 | TraceIdGenerator |
没有绝对最优,只有场景最优。 生产环境推荐三者分工协作:Snowflake 存库、OrderNo 展示、TraceId 追踪。
更多推荐



所有评论(0)