Quantcast
Channel: 小蓝博客
Viewing all articles
Browse latest Browse all 3145

Java 全局唯一订单号设计

$
0
0

Java 全局唯一订单号设计

在电商系统或支付系统中,生成全局唯一的订单号是非常关键的需求。订单号不仅需要在不同系统、不同服务器实例之间保持唯一,还需要具备高效生成、易于排序、方便追踪等特点。本文将从实际需求出发,介绍几种常用的订单号设计方案,并详细展示如何在 Java 中实现全局唯一订单号生成。

1. 订单号设计要求

设计全局唯一订单号时,需要考虑以下几个关键点:

  1. 唯一性:订单号必须全局唯一,避免冲突。
  2. 有序性:在某些场景下,需要订单号具备一定的有序性,便于排序和查询。
  3. 可读性:订单号中可包含特定的业务信息,如时间戳、数据中心标识等,便于追踪订单来源和时间。
  4. 高效生成:订单号生成算法需要具备高性能,在高并发情况下仍能快速生成唯一的订单号。
  5. 可扩展性:系统在分布式环境中扩展时,订单号生成方案应具备良好的扩展性。

2. 常见的订单号生成方案

2.1 基于 UUID 生成订单号

UUID(Universally Unique Identifier) 是一种生成全局唯一标识符的算法,它能保证在分布式系统中生成的 ID 唯一且不重复。

实现方式

import java.util.UUID;

public class OrderIdGenerator {
    public static String generateUUID() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    public static void main(String[] args) {
        System.out.println("Order ID: " + generateUUID());
    }
}

优点

  • 唯一性强,无需额外依赖。
  • 适合分布式系统。

缺点

  • UUID 长度较长(32位),占用较多存储空间。
  • 无序性,不利于按时间排序。

2.2 基于时间戳的订单号

使用时间戳生成订单号,可以保证订单号的有序性,同时具有一定的可读性。

实现方式

import java.text.SimpleDateFormat;
import java.util.Date;

public class OrderIdGenerator {
    private static long counter = 0;

    public static synchronized String generateTimestampOrderId() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        String timestamp = sdf.format(new Date());
        counter = (counter + 1) % 1000;  // 保证同一毫秒内生成的订单号不同
        return timestamp + String.format("%03d", counter);
    }

    public static void main(String[] args) {
        System.out.println("Order ID: " + generateTimestampOrderId());
    }
}

优点

  • 有序性好,订单号按时间递增。
  • 结构简单,易于实现。

缺点

  • 同一毫秒内需要保证订单号不重复,需通过附加的计数器解决。

2.3 分布式系统中的 Snowflake 算法

Snowflake 是 Twitter 开源的分布式唯一 ID 生成算法。它通过时间戳、数据中心 ID、机器 ID 和序列号组成唯一的订单号,适合高并发和分布式环境。

Snowflake ID 格式

  • 1 位符号位(总是0)
  • 41 位时间戳(毫秒级)
  • 10 位数据中心和机器标识(5 位数据中心 ID + 5 位机器 ID)
  • 12 位序列号(在同一毫秒内生成的不同 ID)

实现方式

public class SnowflakeIdGenerator {
    private final long twepoch = 1288834974657L;
    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(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate id");
        }
        if (lastTimestamp == timestamp) {
            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 tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
        System.out.println("Order ID: " + idGenerator.nextId());
    }
}

优点

  • 高性能:可以在高并发环境下每秒生成大量唯一 ID。
  • 有序性:基于时间戳,生成的 ID 按时间递增。
  • 可扩展:通过数据中心 ID 和机器 ID,适合大规模分布式系统。

缺点

  • 实现复杂度较高。
  • 依赖系统时钟,如果服务器时钟回拨可能会出现重复 ID。

3. 订单号生成方案比较

方案唯一性有序性可读性性能适用场景
UUID很强一般全局唯一标识
时间戳较强较好较高适合小规模应用
Snowflake很强分布式、高并发环境

4. 总结

在 Java 中生成全局唯一订单号有多种方案可以选择,根据不同的应用场景,选择合适的方案至关重要。对于小规模应用,基于时间戳的方案简单易用,性能较好;对于分布式系统,Snowflake 是一种高效且易扩展的解决方案。


Viewing all articles
Browse latest Browse all 3145

Trending Articles