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 决策流程图

需要生成 ID

是否需要给人看?

是否需要业务前缀?

是否需要趋势递增?

OrderNoGenerator

是否用于日志追踪?

是否存数据库主键?

TraceIdGenerator

考虑 Snowflake + 展示层格式化

SnowflakeIdGenerator

是否多机分布式?

Snowflake 或 TraceId 均可

5.2 按场景推荐

业务场景 推荐方案 理由
用户表主键 Snowflake long 型、索引友好、分布式
订单表主键 Snowflake 同上
对外订单号 OrderNo 可读、含时间、有前缀
支付/退款流水号 OrderNo(不同 prefix) PAY / REF 前缀区分
HTTP 请求 TraceId TraceId 无锁、低延迟、不要求有序
日志 MDC traceId TraceId(short 版) header 长度受限
消息队列 messageId SnowflakeTraceId 看是否需要有序
数据库分库分表 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

七、共同设计模式

三种生成器都采用:

  1. 单例模式(静态内部类 Holder) — 保证全局唯一发号状态
  2. 线程安全 — synchronized / AtomicLong / volatile 组合
  3. Registry 扩展 — Snowflake 按 workerId,OrderNo 按 prefix
单例 ≠ 线程安全
单例:保证只有一个实例
线程安全:靠 synchronized / CAS / volatile 保证

八、总结

如果你需要… 选择
数据库主键、分布式数字 ID SnowflakeIdGenerator
对外订单号、业务流水号 OrderNoGenerator
日志 TraceId、链路追踪 TraceIdGenerator

没有绝对最优,只有场景最优。 生产环境推荐三者分工协作:Snowflake 存库、OrderNo 展示、TraceId 追踪。


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐