如何设计一个订单号生成服务?应该考虑那些问题?


如何设计一个订单号生成服务?应该考虑那些问题?
description: 在高并发的电商系统中,生成全局唯一的订单编号是关键。本文探讨了几种常见的订单编号生成方法,包括UUID、数据库自增、雪花算法和基于Redis的分布式组件,并详细分析了它们的优缺点。让你在面试过程中更进一步。
这边先来看一个场景:

在实际开发过程中,业务中最常见的一个服务就是生成业务单号,比如电商订单编号、入库单号、服务单号等等。那么如果让你创建一个订单号,你会考虑那些问题?有什么好的设计思路嘛?

需求分析

考虑一下如果让你设计一个服务单号,需要满足那些基本需求呢?、

  • 全局唯一性: 订单号需要在在整个系统独一无二,避免重复
  • 安全性: 订单号不能暴漏太多信息,比如流水信息、用户信息,那么自增这种方案就不能考虑了
  • 禁用随机码: 随机码虽然可以满足前两个条件,但是随机码没有更多信息,比如相对顺序、日期信息,同时伴有一定概率会重复
  • 满足并发需求: 像在特定场合下(如秒杀)需要做到并发场景下的订单号的生成
  • 控制位数: 订单号的位数尽量在 10 位 ~ 18 位之间。太短的情况下,如果交易量过大,很难做到防止重复,太长可读性差、意义也不大。
  • 有业务含义: 订单号尽可能包含业务信息,比如订单时间、业务类型等

再此基础上如何设计一个优秀的订单号服务呢?这个时候最好梳理一下功能点,需要满足那些要求:

设计目标:

  • 满足高并发需求
  • 可拓展性高
  • 满足峰值压力
  • 支持分布式架构
  • 检索效率,如果订单号存储在数据库中,检索效率需要考虑

设计方案

接下来会阐述一些常见的方案,再谈论一下这种解决方案的优缺点。

方案一:数据库自增ID

所谓数据库自增,意思是在数据库中给某个列设置为自增列,并且给该列设置一个初始值,代码层面无需任何特殊处理,以 Mysql 的用户表 ID 列为例,可以通过如下方式在创建表的时候生产。

CREATE TABLE `tb_user` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,`name` varchar(20) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

优点:

  • 实现简单
  • 保证唯一性

缺点:

  • 数据库存在性能瓶颈,特别是高并发情况下
  • 分布式数据库,在多个实例情况下难以保证全局唯一、
  • 安全性低,自增ID容易暴露信息
  • 无业务含义

适用场景:

如果是单体服务下,并且是初始业务情况下可以使用,但需要对数据库进行分库分表会出现重复ID。不建议直接使用这种方式,上限较低。

方案二:UUID

UUID 是Universally Unique Indentifier的缩写,翻译为通用唯一识别码,顾名思义 UUID 是一个用于记录唯一标识一条的数据,其按照开放软件基金会(OSF)指定的标准进行计算,用到了以太网卡地址(MAC)、纳秒级时间、芯片 ID 码和许多可能的数字。

总的来说,UUID 码由以下三部分组成:

  • 当前日期和时间
  • 时钟序列
  • 全局唯一的 IEEE 机器识别码(如果有网卡从网卡获得,没有网卡则通过其他方式获得)

UUID 的标准形式包含 32 个 16 进制数字,以连字号分为五段,示例:00000191-adc6-4314-8799-5c3d737aa7de

java为例,通过以下方式即可生成:

String uuid = UUID.randomUUID().toString();

优点:

  • 全局唯一性
  • 简单易用
  • 安全性: UUID的随机性使得它不容易被猜测或预测,增加了数据的安全性
  • 无需集中管理:由于UUID是本地生成的,不需要依赖于中心化的ID生成服务,减少了单点故障的风险。
  • 可扩展性:UUID的生成过程是分布式的,可以在多个节点上并行生成,适合大规模分布式系统。

缺点:

  • 长度较长
  • 可读性差: 无业务含义,难以理解
  • 索引效率低: UUID的随机性会导致插入时的索引分裂和碎片化,从而降低写入性能,查询效率也低
  • 排序问题: UUID不具备自然的时间顺序,因此不适合用于需要按时间顺序进行排序的场景

适用场景:

如果是在分布式系统中,对订单号有着特别高的要求,并且不需要长期持久化存储以及不需要频繁查询那么就可以推荐使用。

分布式文件存储系统,该系统需要处理大量的文件上传、下载和管理操作。这些文件可能来自不同的用户和设备,并且需要在多个服务器之间进行分布存储和访问。为了确保每个文件都有一个唯一的标识符,并且能够在全球范围内保持唯一性,UUID是一个非常适合的选择。

方案三: 雪花算法

Snowflake(中文简称:雪花算法) 是 Twitter 内部的一个 ID 生算法,可以通过一些简单的规则保证在大规模分布式情况下生成唯一的 ID 号码。Snowflake把 64-bit分别划分成多段,分开来标识机器ID、时间等。其核心思想是:使用 41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号,最后还有一个符号位,永远是0。SnowFlake 的结构图如下所示:

SnowFlake

可以很清晰的看出,Snowflake 由 4个部分组成:

  • 第一部分:bit 值,为未使用的符号位
  • 第二部分:由 41 位的时间戳(毫秒)构成,它的取值是当前时间相对于某一时间的偏移
  • 第三部分:表示工作机器 id,由服务节点 id 和数据中心 id 组合而成
  • 第四部分:表示每个工作机器每毫秒生成的序列号 ID,同一毫秒内最多可生成生产 4095 个 ID。

由于在 Java 中 64bit 的整数是 long 类型,因此在 Java 中 SnowFlake 算法生成的 id 就是 long 来存储的。

SnowFlake 算法可以保证:

  • 1.所有生成的 id 按时间趋势递增
  • 2.整个分布式系统内不会产生重复id(因为有服务节点 id 和数据中心 id 来做区分)

需要注意的是:

  • 在分布式环境中,5 个 bit 位的 datacenter 和 worker 表示最多能部署 31 个数据中心,每个数据中心最多可部署 31 台节点。
  • 41 位的二进制长度最多能表示2^41 -1毫秒即 69 年,所以雪花算法最多能正常使用 69 年,为了能最大限度的使用该算法,在使用的时候,应该为其指定一个开始时间,不然会发生重复!

优点

  1. 高并发下的唯一性:由于算法设计考虑到了时间戳、机器标识等因素,因此即使是在大规模分布式系统中也能保证生成的ID是唯一的。
  2. 趋势递增:生成的ID通常是按照时间顺序递增的,这有利于数据库索引性能优化。
  3. 信息量丰富:ID中包含了时间戳、工作节点ID等信息,使得每个ID都携带了额外的信息价值。
  4. 不依赖数据库:相比于传统基于数据库自增ID的方式,雪花算法不需要访问数据库即可生成ID,减少了数据库的压力。
  5. 高效:计算效率高,适合快速生成大量ID的需求。

缺点

  1. 时钟回拨问题:如果服务器时钟出现回拨,则可能产生重复ID的问题。不过,可以通过等待时钟追赶上来或者拒绝服务来解决这个问题。
  2. 有限的空间:虽然64位足够大,但理论上还是存在上限,对于某些极端情况可能不够用。
  3. 可读性差:生成的ID对人类来说不易于阅读和理解。
  4. 安全性较低:因为包含时间戳,所以可能泄露一些关于数据创建时间的信息,这在某些安全敏感的应用场景下可能是不利的。

适用场景

  • 分布式系统:特别适用于需要跨多个服务器或数据中心工作的应用程序,如电商网站、社交平台等。
  • 高并发应用:当系统需要处理大量的并发请求并要求快速响应时,比如在线游戏、实时消息传递服务等。
  • 需要唯一标识符的服务:任何需要生成全局唯一标识符的服务都可以采用此方法,例如订单管理系统、用户账号注册等。
  • 大数据分析:由于ID带有时间信息,有助于进行时间序列数据分析。

方案四:借助Redis,分布式组件

要想在分布式环境下生成一个唯一的订单编号,我们可以通过分布式组件的方式,来帮忙我们生成全局唯一的订单号,例如我们可以采用 redis 分布式缓存组件中的incr命令,来帮我们生成一个全局自增长的序列号。

实现某个Key实现自增的代码如下:

// 基于某个key实现自增长
String res = jedis.get(key);
if (StringUtils.isBlank(res)) {// 设置初始值,INIT_ID 是初始值jedisClient.set(key, INIT_ID);// 设置过期时间,seconds 是多少秒过期jedisClient.expire(key, seconds);
}
//存在就生成+1的订单号
long orderId = jedis.incr(key);

这种方式生成的自增长序列号,非常的快,可以很好的满足大流量环境下的编号要求唯一的特性!

案例分析:

在互联网几个大厂的订单号分析一下:

  • 京东商城订单号格式:157444499

  • 苏宁易购订单号格式:2000839647

  • 凡客诚品订单号格式:213052230059

  • 小米订单号格式:1111218032345170

凡客诚品和银泰网订单号都含有 0522,这是因为这 2 张订单都是2013年5月22号下的订单。

基本猜测一下,凡客的订单规则是:业务编码+年的后2位+月+日+订单数;泰网的订单号规则:年的第三位数+业务编码+年的后1位+月+日+订单数;而京东商城和苏宁易购的订单号看不出规则。

再来分析一下小米订单编号:

1211218032345170(16位)//拆解成为四个部分
1——211218—03234—5170
  • 第一部分,1 表示购买,2 表示退货。
  • 第二部分,表示 2021 年 12 月 18 日下的单,前面两位省掉了。
  • 第三部分,时间戳对应00:53:54,换算成秒是03234秒。
  • 最后一部分,表示在同一秒内下的第 5170 单,也就是说,小米认为,在一秒内不会超过一万个订单。

总结

通过上面的示例演示,下面针对这几种情况做一个分析与总结。尽可能的选择一种合理的方式。

实现方案优势劣势
数据库自增代码层面无需任何特殊处理;利用MySQL特点实现数据递增并发性能差;MySQL负担重
UUID实现简单、方便;重复性低可读性低;过于冗长;数据库查询效率低
雪花算法基于内存、速度快;性能高;不会产生额外的网络开销;数据依次成递增依赖于服务器时间,如变动服务器时间则存在重复的情况
Redis基于内存、速度库;使用简单;可分布数据、扩展性强需要独立搭建一套服务、增加了维护成本;跨应用调用、存在网络开销

总体上来说,优先选择使用Redis进行分布式的处理方式,如果在没有Redis的情况下,那就优先选用雪花算法。

如果想要设计一个订单号,需要保留业务类型、时间信息等。

比如通过2位数字表示业务类型,如交易订单、支付单、结算单等都是不同的业务类型,可以有不同的编号。
中间的18-20位用一个唯一的ID来表示,可以用雪花算法,也可以用Leaf,总之就是他需要保证唯一性。
最后4位,基于基因法,将分表后的结果获取到,把他也编码到订单号中。

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

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

相关文章

Springboot 集成 Flowable 6.8.0

1. 创建 Spring Boot 项目 通过 Spring Initializr(https://start.spring.io/ )创建一个基础的 Spring Boot 项目,添加以下依赖: Spring WebSpring Data JPAMySQL DriverLombok(可选,用于简化代码&#x…

《TCP/IP网络编程》学习笔记 | Chapter 22:重叠 I/O 模型

《TCP/IP网络编程》学习笔记 | Chapter 22:重叠 I/O 模型 《TCP/IP网络编程》学习笔记 | Chapter 22:重叠 I/O 模型理解重叠 I/O 模型重叠 I/O本章讨论的重叠 I/O 的重点不在于 I/O 创建重叠 I/O 套接字执行重叠 I/O 的 WSASend 函数进行重叠 I/O 的 WSA…

搭建Redis哨兵集群

停掉现有的redis集群 因为这篇文章我是在 搭建完redis主从集群之后写的,如果要是没有搭建过这些,可以直接略过。要是从我上一篇 搭建redis主从集群过来的,可以执行下。 docker compose down 查找下redis相关进程 ps -ef | grep redis 可以看…

MySQL中,聚集索引和非聚集索引到底有什么区别?

文章目录 1. 数据存储方式2. 索引结构3. 查询效率4. 索引数量5. 适用场景6. 示例说明7. 总结 在MySQL中,聚集索引和非聚集索引(也称二级索引)的区别主要体现在数据存储方式、索引结构和查询效率等方面。以下是详细对比: 1. 数据存…

看 MySQL InnoDB 和 BoltDB 的事务实现

BoltDB 事务实现 BoltDB 支持多读单写方式的并发级别 事务操作会锁表 它的 MVCC 为 2 个版本,当前版本和正在写的版本 多读:可以并发读当前版本 单写(串行写):写时拷贝当前 B 树,构建新 B 树&#xff…

08_JavaScript数据操作方法_数组

目录 一、创建一个数组 1.1 数组如何创建 字面量创建 构造函数创建 1.2 数组的长度 数组名.length 1.3 数组的索引 1.4 数组如何循环遍历 for 循环遍历 for in for of 二、数组的常用方法 (重点 面试) push 方法 unshift 方法 pop shif…

2025.3.25总结

工作:这两天工作都没啥产出,主要是工作状态不太好,周日晚上两点睡,周一晚上一点睡。熬夜伤身,但就是控制不住自己,睡前总要刷刷手机。本来想睡前看会书的,但这行为及其不稳定,抖音也…

《Python实战进阶》第33集:PyTorch 入门-动态计算图的优势

第33集:PyTorch 入门-动态计算图的优势 摘要 PyTorch 是一个灵活且强大的深度学习框架,其核心特性是动态计算图机制。本集将带您探索 PyTorch 的张量操作、自动求导系统以及动态计算图的特点与优势,并通过实战案例演示如何使用 PyTorch 实现…

初识哈希表

一、题意 给定一个整数数组 nums 和一个目标值 target,要求你在数组中找出和为目标值的那两个整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 示例: 给定 nums [2, 7, …

23种设计模式-创建型模式-单例

文章目录 简介问题1. 确保一个类只有一个实例2. 为该实例提供全局访问点 解决方案示例重构前:重构后: 拓展volatile 在单例模式中的双重作用 总结 简介 单例是一种创建型设计模式,它可以确保一个类只有一个实例,同时为该实例提供…

python裁剪nc文件数据

问题描述: 若干个nc文件储存全球的1850-2014年月尺度的mrro数据(或其他数据),从1850-1到2014-12一共1980个月,要提取出最后35年1980.1~2014.12年也就是420个月的数据。 代码实现 def aaa(input_file,output_file,bianliang,start_index,en…

深入解析 Spring Framework 5.1.8.RELEASE 的源码目录结构

深入解析 Spring Framework 5.1.8.RELEASE 的源码目录结构 1. 引言 Spring Framework 是 Java 领域最流行的企业级开发框架之一,广泛用于 Web 开发、微服务架构、数据访问等场景。本文将深入解析 Spring Framework 5.1.8.RELEASE 的源码目录结构,帮助开…

数据清洗:基于python抽取jsonl文件数据字段

基于python抽取目录下所有“jsonl”格式文件。遍历文件内某个字段进行抽取并合并。 import os import json import time from tqdm import tqdm # 需要先安装:pip install tqdmdef process_files():# 设置目录路径dir_path r"D:\daku\关键词识别\1623-00000…

Windows 下使用 Docker 部署 Go 应用与 Nginx 详细教程

一、环境准备 1. 安装必要软件 Docker Desktop for Windows 下载地址:Docker Desktop: The #1 Containerization Tool for Developers | Docker 安装时勾选"使用 WSL 2 引擎"(推荐) WSL 2(Windows Subsystem for Li…

C# .net ai Agent AI视觉应用 写代码 改作业 识别屏幕 标注等

C# net deepseek RAG AI开发 全流程 介绍_c# 向量处理 deepseek-CSDN博客 视觉多模态大模型 通义千问2.5-VL-72B AI大模型能看懂图 看懂了后能干啥呢 如看懂图 让Agent 写代码 ,改作业,识别屏幕 标注等等。。。 据说是目前最好的免费图片识别框架 通…

Docker多阶段构建:告别臃肿镜像的终极方案

Docker多阶段构建:告别臃肿镜像的终极方案 你是否遇到过这样的问题:一个简单的应用,Docker镜像却高达1GB?编译工具、临时文件、开发依赖全被打包进去,导致镜像臃肿且不安全。 多阶段构建(Multi-stage Build) 就是为解决这一问题而生——它像搬家时“只带必需品”,让生…

大模型应用开发之大模型工作流程

一:大模型的问答工作流程 1.1: 分词和向量化 如上图所示,我们如果让大模型去回答问题,首先我们会输入一些文字给到大模型,大模型本质上是个数学模型,它是理解不了人类的整句话的,所以它会把我们的对应的句…

SpringMVC 请求处理

SpringMVC 请求处理深度解析:从原理到企业级应用实践 一、架构演进与核心组件协同 1.1 从传统Servlet到前端控制器模式 SpringMVC采用前端控制器架构模式,通过DispatcherServlet统一处理请求,相比传统Servlet的分散处理方式,实…

12届蓝桥杯—货物摆放

货物摆放 题目描述 小蓝有一个超大的仓库,可以摆放很多货物。 现在,小蓝有 nn 箱货物要摆放在仓库,每箱货物都是规则的正方体。小蓝规定了长、宽、高三个互相垂直的方向,每箱货物的边都必须严格平行于长、宽、高。 小蓝希望所…

Reactor/Epoll为什么可以高性能?

在 Reactor 模式中使用 epoll_wait 实现低 CPU 占用率的核心原理是 ​事件驱动的阻塞等待机制,而非忙等待。以下通过分步骤解析其工作原理和性能优势: void network_thread() {int epoll_fd epoll_create1(0);epoll_event events[MAX_EVENTS];// 添加U…