Java SpringBoot如何生成唯一的订单号

news2025/1/11 13:26:49

1. 引言

在现代电子商务和金融系统中,生成唯一的订单号是确保数据一致性和系统可靠性的关键环节。特别是在分布式系统架构下,如何生成一个全局唯一的订单号变得尤为重要。本文将详细探讨在Spring Boot应用中生成唯一订单号的多种方法,包括UUID、雪花算法(Snowflake)、数据库自增ID等,并结合实际代码示例,阐述如何确保订单号的唯一性、可扩展性以及业务相关性。

2. 订单号生成的基本要求

在设计订单号生成机制时,需要考虑以下几个关键因素:

1.唯一性:确保每个订单号在系统内是唯一的,避免重复。

2.可扩展性:系统能够随着业务增长而扩展,订单号生成机制不应成为瓶颈。

3.性能:订单号生成过程应高效,不影响系统整体性能。

4.业务相关性:订单号可以包含一定的业务信息,如时间戳、区域信息等,便于业务分析和追踪。

3. 常见生成方法

3.1 UUID(通用唯一识别码)

3.1.1 实现原理

UUID(Universally Unique Identifier)是一种标准化的标识符,旨在确保在分布式系统中的全局唯一性。UUID的标准长度为128位,通常表示为32个十六进制数字,分为5组,用连字符连接,例如:123e4567-e89b-12d3-a456-426614174000

UUID的生成主要依赖于以下几种算法:

  1. 1.基于时间的UUID(Version 1):结合时间戳和MAC地址生成。

  2. 2.基于随机数的UUID(Version 4):使用随机数生成。

  3. 3.基于命名空间的UUID(Version 3 和 5):基于命名空间和名称的哈希值生成。

在大多数编程语言中,UUID的生成默认采用基于随机数的方式,以确保高唯一性。

3.1.2 优缺点及适用场景

优点

  1. 全局唯一性:UUID的设计保证了在全球范围内的唯一性,适用于分布式系统。

  2. 无需中央协调:无需依赖数据库或其他中央服务生成ID,减少了系统复杂性。

  3. 生成速度快:基于随机数的UUID生成速度非常快,适用于高并发场景。

缺点

  1. 长度较长:UUID长度为128位,通常表示为36个字符(包括连字符),这在存储和传输时占用较多空间。例如,在数据库中存储UUID会比存储整数类型占用更多的空间。

  2. 无序性:UUID通常是随机生成的,缺乏时间上的顺序性。这在数据库索引中可能导致性能问题,因为插入操作可能需要在B树索引中频繁分裂节点。

  3. 可读性差:UUID对人类不友好,难以记忆和识别,不利于在用户界面或日志中直接使用。

  4. 缺乏业务相关性:UUID不包含任何业务信息,如时间戳或区域信息,难以用于业务分析和追踪。

适用场景

  • 分布式系统:在多节点、多数据中心的环境中,UUID是生成唯一标识符的理想选择。
  • 无需排序的场景:如果不需要按时间或其他顺序对ID进行排序,UUID是一个不错的选择。
  • 高并发环境:UUID的生成速度非常快,适用于需要快速生成唯一标识符的高并发场景。

3.1.3 代码示例

import java.util.UUID;

public class OrderService {
    public String generateOrderId() {
        return UUID.randomUUID().toString();
    }
}

3.2 数据库自增ID

3.2.1 实现原理

数据库自增ID是一种常见的生成唯一标识符的方法,通过在数据库表中设置自增字段(如AUTO_INCREMENT),每次插入新记录时,数据库会自动为该字段生成一个唯一的、递增的整数。

3.2.2 优缺点及适用场景

优点

  1. 简单易用:实现简单,只需在数据库表中设置自增字段,无需额外的代码或配置。

  2. 有序性:自增ID是有序的,有利于数据库索引的性能,特别是在使用B树索引时。

  3. 节省存储空间:整数类型的ID通常比UUID占用更少的存储空间。

  4. 可读性较好:整数ID对人类相对友好,易于识别和记忆。

缺点

  1. 单点瓶颈:在分布式数据库环境中,自增ID难以保证全局唯一性,通常需要依赖单个数据库实例来生成ID,这会成为系统的单点瓶颈,影响性能和可扩展性。

  2. 难以水平扩展:如果系统需要水平扩展到多个数据库实例,自增ID的生成会成为问题,因为每个实例都会生成自己的ID序列,导致ID冲突。

  3. 依赖数据库:生成ID依赖于数据库,这在数据库故障或高负载时可能成为问题。

  4. 缺乏业务相关性:自增ID不包含任何业务信息,如时间戳或区域信息,难以用于业务分析和追踪。

适用场景

  • 单体应用:在单体应用中,数据库自增ID是一个简单且有效的方法。
  • 无需分布式唯一性的场景:如果系统不需要在多个数据库实例或多个数据中心中保持全局唯一性,自增ID是一个不错的选择。
  • 对ID有序性有要求的场景:如果需要按时间或其他顺序对ID进行排序,自增ID的有序性是一个优势。

3.2.3 代码示例

@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 其他字段和方法
}

3.3 雪花算法(Snowflake)

‌雪花算法(Snowflake Algorithm)‌是一种由[Twitter]开发的分布式系统中生成全局唯一ID的算法。该算法生成的ID是一个64位的二进制数,通常用于分布式系统中,如分布式数据库、分布式锁等场景,以确保ID的唯一性和有序性。‌

3.3.1 实现原理

雪花算法的核心思想是将一个64位的二进制数分成四部分:符号位、时间戳、数据中心ID、机器ID和序列号。具体来说:

  • 符号位‌:占用1位,始终为0,用于标识ID是正数。
  • 时间戳‌:占用41位,精确到毫秒级,可以使用约69年。
  • 数据中心ID‌:占用5位,用于标识不同的数据中心,最多可以有32个数据中心。
  • 机器ID‌:占用5位,用于标识不同的机器,最多可以有32个机器。
  • 序列号‌:占用12位,用于表示同一毫秒内生成的不同ID,最多可以生成4096个序列号。

雪花算法的实现过程如下:

  1. 获取当前时间戳,精确到毫秒级别。
  2. 根据给定的数据中心ID和机器ID,生成一个10位的二进制数。
  3. 将时间戳左移22位,将数据中心ID左移17位,将机器ID左移12位,然后使用位或操作符将它们组合成一个64位的二进制数。
  4. 如果在同一毫秒内生成了多个ID,使用序列号来区分它们,序列号从0开始递增,最多可以生成4096个序列号。

3.3.2 优缺点及适用场景

优点‌:

  • 全局唯一性‌:在分布式系统中,雪花算法可以确保生成的ID全局唯一。
  • 有序性‌:生成的ID按照时间戳有序递增,便于数据管理和查询。
  • 高并发‌:每毫秒可以生成4096个ID,适合高并发场景。

缺点‌:

  • 依赖服务器时间‌:如果服务器时间回拨,可能会导致生成重复的ID。可以通过记录最后一个生成ID的时间戳来解决这个问题。
  • 序列号浪费‌:在分库分表时,如果序列号一直从0开始,可能会导致数据倾斜和不均匀分布。

适用场景‌:

  • 分布式系统‌:如分布式数据库、分布式锁等,需要全局唯一且有序的ID。
  • 高并发场景‌:如订单号生成、用户ID生成等。

3.3.3 代码示例

public class SnowflakeIdGenerator {
    // 起始时间戳(2023-01-01 00:00:00),用于计算时间差
    private final long twepoch = 1672531200000L;

    // 机器ID所占的位数
    private final long workerIdBits = 5L;
    // 数据中心ID所占的位数
    private final long datacenterIdBits = 5L;

    // 支持的最大机器ID,结果是31(2^5 - 1)
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    // 支持的最大数据中心ID,结果是31(2^5 - 1)
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    // 序列在ID中占的位数
    private final long sequenceBits = 12L;

    // 机器ID向左移12位
    private final long workerIdShift = sequenceBits;
    // 数据中心ID向左移17位(12 + 5)
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    // 时间戳向左移22位(12 + 5 + 5)
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    // 生成序列的掩码,这里为4095(0b111111111111 = 2^12 - 1)
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    // 工作机器ID(0~31)
    private long workerId;
    // 数据中心ID(0~31)
    private long datacenterId;
    // 毫秒内序列(0~4095)
    private long sequence = 0L;
    // 上次生成ID的时间戳
    private long lastTimestamp = -1L;

    /**
     * 构造函数
     *
     * @param workerId     工作机器ID(0~31)
     * @param datacenterId 数据中心ID(0~31)
     */
    public SnowflakeIdGenerator(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("workerId 超出范围 [0, %d]", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenterId 超出范围 [0, %d]", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    /**
     * 生成下一个唯一的ID
     *
     * @return 唯一ID
     */
    public synchronized long nextId() {
        long currentTimestamp = currentTimeMillis();

        // 如果当前时间小于上一次生成ID的时间戳,说明系统时钟回拨,此时抛出异常
        if (currentTimestamp < lastTimestamp) {
            throw new RuntimeException(
                String.format("系统时钟回拨,当前时间 %d 小于上一次生成ID的时间 %d", currentTimestamp, lastTimestamp));
        }

        // 如果当前时间与上一次生成ID的时间戳相同,则在毫秒内生成序列号
        if (currentTimestamp == lastTimestamp) {
            // 序列号自增
            sequence = (sequence + 1) & sequenceMask;
            // 如果序列号溢出(即超过4095),则等待下一毫秒
            if (sequence == 0) {
                currentTimestamp = waitNextMillis(lastTimestamp);
            }
        } else {
            // 如果当前时间与上一次生成ID的时间戳不同,则序列号重置为0
            sequence = 0L;
        }

        // 更新上一次生成ID的时间戳
        lastTimestamp = currentTimestamp;

        // 组合各部分生成最终的ID
        return ((currentTimestamp - twepoch) << timestampLeftShift) |
               (datacenterId << datacenterIdShift) |
               (workerId << workerIdShift) |
               sequence;
    }

    /**
     * 等待下一毫秒
     *
     * @param lastTimestamp 上一次生成ID的时间戳
     * @return 当前时间戳
     */
    private long waitNextMillis(long lastTimestamp) {
        long timestamp = currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = currentTimeMillis();
        }
        return timestamp;
    }

    /**
     * 获取当前时间戳(毫秒)
     *
     * @return 当前时间戳
     */
    private long currentTimeMillis() {
        return System.currentTimeMillis();
    }
}

4. 雪花算法结合Spring Boot的实现

在Spring Boot应用中,可以将订单号生成逻辑封装在一个服务类中,并通过依赖注入的方式进行调用。

代码示例

@Service
public class OrderService {
    private final SnowflakeIdGenerator idGenerator;

    @Autowired
    public OrderService() {
        this.idGenerator = new SnowflakeIdGenerator(1, 1);
    }

    public Order createOrder(OrderRequest request) {
        String orderId = String.valueOf(idGenerator.nextId());
        Order order = new Order();
        order.setOrderId(orderId);
        order.setAmount(request.getAmount());
        // 设置其他字段
        return orderRepository.save(order);
    }
}

为了增强订单号的可扩展性和业务相关性,可以对生成的ID进行编码,嵌入额外的信息,如时间戳、区域信息等。

public class CustomOrderIdGenerator {
    private final SnowflakeIdGenerator idGenerator;

    public CustomOrderIdGenerator() {
        this.idGenerator = new SnowflakeIdGenerator(1, 1);
    }

    public String generateOrderId() {
        long id = idGenerator.nextId();
        // 假设需要嵌入时间戳和区域信息
        String timestamp = String.valueOf(System.currentTimeMillis());
        String region = "CN";
        return region + timestamp + id;
    }
}

5. 总结

在Spring Boot应用中生成唯一的订单号,需要综合考虑唯一性、可扩展性、性能以及业务相关性。本文介绍了UUID、数据库自增ID和雪花算法等多种方法,并结合实际代码示例,展示了如何实现一个高效、可靠的订单号生成机制。根据具体的业务需求和系统架构,选择合适的方案,可以确保订单号在分布式系统中的唯一性和系统的整体性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2274928.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2025年中科院分区大类划分公布!新增8155本

2025年中科院分区表变更情况 扩大收录范围 2025年的期刊分区表在原有的自然科学&#xff08;SCIE&#xff09;、社会科学&#xff08;SSCI&#xff09;和人文科学&#xff08;AHCI&#xff09;的基础上&#xff0c;增加了ESCI期刊的收录&#xff0c;并根据这些期刊的数据进行…

机器人避障不再“智障”:HEIGHT——拥挤复杂环境下机器人导航的新架构

导读&#xff1a; 由于环境中静态障碍物和动态障碍物的约束&#xff0c;机器人在密集且交互复杂的人群中导航&#xff0c;往往面临碰撞与延迟等安全与效率问题。举个简单的例子&#xff0c;商城和车站中的送餐机器人往往在人流量较大时就会停在原地无法运作&#xff0c;因为它不…

Spring Boot教程之五十二:CrudRepository 和 JpaRepository 之间的区别

Spring Boot – CrudRepository 和 JpaRepository 之间的区别 Spring Boot建立在 Spring 之上&#xff0c;包含 Spring 的所有功能。由于其快速的生产就绪环境&#xff0c;使开发人员能够直接专注于逻辑&#xff0c;而不必费力配置和设置&#xff0c;因此如今它正成为开发人员…

加速物联网HMI革命,基于TouchGFX的高效GUI显示方案

TouchGFX 是一款针对 STM32 微控制器优化的先进免费图形软件框架。 TouchGFX 利用 STM32 图形功能和架构&#xff0c;通过创建令人惊叹的类似智能手机的图形用户界面&#xff0c;加速了物联网 HMI 革命。 TouchGFX 框架包括 TouchGFX Designer (TouchGFXDesigner)&#xff08;…

Java-数据结构-栈与队列(StackQueue)

一、栈(Stack) ① 栈的概念 栈是一种特殊的线性表&#xff0c;它只允许固定一端进行"插入元素"和"删除元素"的操作&#xff0c;这固定的一端被称作"栈顶"&#xff0c;对应的另一端就被称做"栈底"。 &#x1f4da; 栈中的元素遵循后…

案例研究:UML用例图中的结账系统

在软件工程和系统分析中&#xff0c;统一建模语言&#xff08;UML&#xff09;用例图是一种强有力的工具&#xff0c;用于描述系统与其用户之间的交互。本文将通过一个具体的案例研究&#xff0c;详细解释UML用例图的关键概念&#xff0c;并说明其在设计结账系统中的应用。 用…

【动态规划篇】欣赏概率论与镜像法融合下,别出心裁探索解答括号序列问题

本篇鸡汤&#xff1a;没有人能替你承受痛苦&#xff0c;也没有人能拿走你的坚强. 欢迎拜访&#xff1a;羑悻的小杀马特.-CSDN博客 本篇主题&#xff1a;带你解答洛谷的括号序列问题&#xff08;绝对巧解&#xff09; 制作日期&#xff1a;2025.01.10 隶属专栏&#xff1a;C/C题…

点击底部的 tabBar 属于 wx.switchTab 跳转方式,目标页面的 onLoad 不会触发(除非是第一次加载)

文章目录 1. tabBar 的跳转方式2. tabBar 跳转的特点3. 你的配置分析4. 生命周期触发情况5. 总结 很多人不明白什么是第一次加载&#xff0c;两种情况讨论&#xff0c;第一种情况假设我是开发者&#xff0c;第一次加载就是指点击微信开发者工具上边的编译按钮&#xff0c;每点击…

CUDA、CUDNN以及tensorRT的版本对应关系

各版本的对应除了可以看文件的命名上还可以查看TensorRT的Release日志&#xff1a; Release Notes :: NVIDIA Deep Learning TensorRT Documentation 这个是官方测试TensorRT的Release日志&#xff0c;里面指明了当前测试的TensorRT版本是在哪个CUDNN等库下的测试结果&#x…

设计模式(观察者模式)

设计模式&#xff08;观察者模式&#xff09; 第三章 设计模式之观察者模式 观察者模式介绍 观察者模式&#xff08;Observer Design Pattern&#xff09; 也被称为发布订阅模式 。模式定义&#xff1a;在对象之间定义一个一对多的依赖&#xff0c;当一个对象状态改变的时候…

Helm部署activemq

1.helm create activemq 创建helm文件目录 2.修改values.yaml 修改image和port 3. helm template activemq 渲染并输出 4. helm install activemq activemq/ -n chemical-park // 安装 5.启动成功

Untiy中如何嵌入前端页面,从而播放推流视频?

最近工作中频繁用到unity,虽然楼主之前也搞过一些UNTY游戏开发项目&#xff0c;但对于视频这块还是不太了解&#xff0c;所以我们采用的方案是在Unity里寻找一个插件来播放推流视频。经过我的一番寻找&#xff0c;发现了这款Vuplex 3D WebView&#xff0c;它可以完美的打通Unit…

rabbitmq的三个交换机及简单使用

提前说一下&#xff0c;创建队列&#xff0c;交换机&#xff0c;绑定交换机和队列都是在生产者。消费者只负责监听就行了&#xff0c;不用配其他的。 完成这个场景需要两个服务哦。 1直连交换机-生产者的代码。 在配置类中创建队列&#xff0c;交换机&#xff0c;绑定交换机…

Excel 技巧07 - 如何计算到两个日期之间的工作日数?(★)如何排除节假日计算两个日期之间的工作日数?

本文讲了如何在Excel中计算两个日期之间的工作日数&#xff0c;以及如何排除节假日计算两个日期之间的工作日数。 1&#xff0c;如何计算到两个日期之间的工作日数&#xff1f; 其实就是利用 NETWORKDAYS.INTL 函数 - weekend: 1 - 星期六&#xff0c;星期日 2&#xff0c;如…

初学stm32 --- DAC模数转换器工作原理

目录 什么是DAC&#xff1f; DAC的特性参数 STM32各系列DAC的主要特性 DAC框图简介&#xff08;F1/F4/F7&#xff09; 参考电压/模拟部分电压 触发源 关闭触发时(TEN0)的转换时序图 DMA请求 DAC输出电压 什么是DAC&#xff1f; DAC&#xff0c;全称&#xff1a;Digital…

从预训练的BERT中提取Embedding

文章目录 背景前置准备思路利用Transformer 库实现 背景 假设要执行一项情感分析任务&#xff0c;样本数据如下 可以看到几个句子及其对应的标签&#xff0c;其中1表示正面情绪&#xff0c;0表示负面情绪。我们可以利用给定的数据集训练一个分类器&#xff0c;对句子所表达的…

从CentOS到龙蜥:企业级Linux迁移实践记录(系统安装)

引言&#xff1a; 随着CentOS项目宣布停止维护CentOS 8并转向CentOS Stream&#xff0c;许多企业和组织面临着寻找可靠替代方案的挑战。在这个背景下&#xff0c;龙蜥操作系统&#xff08;OpenAnolis&#xff09;作为一个稳定、高性能且完全兼容的企业级Linux发行版&#xff0…

车联网安全--TLS握手过程详解

目录 1. TLS协议概述 2. 为什么要握手 2.1 Hello 2.2 协商 2.3 同意 3.总共握了几次手&#xff1f; 1. TLS协议概述 车内各ECU间基于CAN的安全通讯--SecOC&#xff0c;想必现目前多数通信工程师们都已经搞的差不多了&#xff08;不要再问FvM了&#xff09;&#xff1b;…

iOS实际开发中使用Alamofire实现多文件上传(以个人相册为例)

引言 在移动应用中&#xff0c;图片上传是一个常见的功能&#xff0c;尤其是在个人中心或社交平台场景中&#xff0c;用户经常需要上传图片到服务器&#xff0c;用以展示个人风采或记录美好瞬间。然而&#xff0c;实现多图片上传的过程中&#xff0c;如何设计高效的上传逻辑并…

基于phpstudy快速搭建本地php环境(Windows)

好好生活&#xff0c;别睡太晚&#xff0c;别爱太满&#xff0c;别想太多。 2025.1.07 声明 仅作为个人学习使用&#xff0c;仅供参考 对于CTF-Web手而言&#xff0c;本地PHP环境必不可少&#xff0c;但对于新手来说从下载PHP安装包到配置PHP环境是个非常繁琐的事情&#xff0…