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


如何设计一个订单号生成服务?应该考虑那些问题?
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.rhkb.cn/news/39850.html

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

相关文章

Java学习总结-Stream流

啥是Stream流? 用于操作集合或数组的数据。他就像把数据化为成一条河流,我们可以对这条流操作,例如过滤。 获取Stream流 Stream流的常用方法: Stream流的终结方法: 收集Stream流

《TypeScript 面试八股:高频考点与核心知识点详解》

“你好啊!能把那天没唱的歌再唱给我听吗? ” 前言 因为主包还是主要学习js,ts浅浅的学习了一下,在简历中我也只会写了解,所以我写一些比较基础的八股,如果是想要更深入的八股的话还是建议找别人的。 Ts基…

热门面试题第14天|Leetcode 513找树左下角的值 112 113 路径总和 105 106 从中序与后序遍历序列构造二叉树 (及其扩展形式)以一敌二

找树左下角的值 本题递归偏难,反而迭代简单属于模板题, 两种方法掌握一下 题目链接/文章讲解/视频讲解:https://programmercarl.com/0513.%E6%89%BE%E6%A0%91%E5%B7%A6%E4%B8%8B%E8%A7%92%E7%9A%84%E5%80%BC.html 我们来分析一下题目&#…

Qt窗口控件之浮动窗口QDockWidget

浮动窗口QDockWidget QDockWidget 用于表示 Qt 中的浮动窗口,浮动窗口与工具栏类似,可以停靠在主窗口的上下左右位置,也可以单独拖出来作浮动窗口。 1. QDockWidget方法 方法说明setWidget(QWiget*)用于使浮动窗口能够被添加控件。setAllo…

Web前端之JavaScript的DOM操作冷门API

MENU 前言1、Element.checkVisibility()2、TreeWalker3、Node.compareDocumentPosition()4、scrollIntoViewIfNeeded()5、insertAdjacentElement()6、Range.surroundContents()7、Node.isEqualNode()8、document.createExpression()小结 前言 作为前端开发者,我们每…

【Linux-驱动开发-系统调用流程】

Linux-驱动开发-系统调用流程 ■ Linux-系统调用流程■ Linux-file_operations 结构体 ■ Linux-系统调用流程 ■ Linux-file_operations 结构体 在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集…

ToolsSet之:ASCII字符表和国际标准代码表

ToolsSet是微软商店中的一款包含数十种实用工具数百种细分功能的工具集合应用,应用基本功能介绍可以查看以下文章: Windows应用ToolsSet介绍https://blog.csdn.net/BinField/article/details/145898264 ToolsSet中Other菜单下的ASCII Table是一个ASCII…

C语言判断闰年相关问题

一、简单闰年问题引入 写一个判断年份是否为闰年的程序? 运行结果: 二、闰年问题进阶 使用switch语句根据用户输入的年份和月份,判断该月份有多少天? 第一种写法(判断年份写在switch的case的里面): 运行结果: 第二种解法(先判断闰年): 运行结果: 三、补充 switch中的ca…

基于Java的班级事务管理系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 随着世界经济信息化、全球化的到来和电子商务的飞速发展,推动了很多行业的改革。若想达到安全,快捷的目的,就需要拥有信息化的组织和管理模式,建立一套合理、畅通、高效的线上管理系统。当前的班级事务管理存在管理效率低下…

javaweb后端登录功能cookie session

登录功能 只需要这几个,用原来的返回太多用不上的信息,新写一个类只返回登录的结果 Ctrli 实现service的方法 和mapper相关的起名不用和业务一样 登录校验 登录校验思路 会话技术 cookie 创建cookie对象,响应给浏览器 服务端设置的…

《可爱风格 2048 游戏项目:HTML 实现全解析》

一、引言 在如今的数字化时代,小游戏以其简单易上手、趣味性强的特点深受大家喜爱。2048 游戏作为一款经典的数字合并游戏,拥有庞大的玩家群体。本文将详细介绍一个用单文件 HTML 实现的可爱风格 2048 游戏项目,它不仅具备传统 2048 游戏的基…

UART转APB模块ModelSim仿真

一、简介 之前介绍过一个UART转AHB模块,这个代码的框架有个好处,就是FPGA内总线接口比较容易修改成其他总线接口。下图是UART转AHB模块中子模块uart_ahb_mst的框图,主要有三个状态机: (1) UART_RX_FSM将接收…

ReAct: Synergizing Reasoning and Acting in Language Models

https://zhuanlan.zhihu.com/p/624003116https://zhuanlan.zhihu.com/p/624003116https://github.com/apssouza22/ai-agent-react-llm/tree/main

尝试在软考62天前开始成为软件设计师-信息系统安全

安全属性 保密性:最小授权原则(能干活的最小权限)、防暴露(隐藏)、信息加密、物理保密完整性(防篡改):安全协议、校验码、密码校验、数字签名、公证 可用性:综合保障( IP过滤、业务流控制、路由选择控制、审计跟踪)不可抵赖性:数字签名 对称加密 DES :替换移位 3重DESAESR…

IPv4向IPv6过渡

主要有三种过渡技术 隧道技术:用于解决IPv6节点之间通过IPv4网络进行通信的问题协议翻译技术:使纯ipv6节点与纯Ipv4节点之间进行通信双协议栈技术:使ipv4与ipv6可以共存于同一台设备和同一个网络中 隧道技术 把ipv6分组封装到Ipv4分组中&a…

算法题(107):function

审题: 本题需要我们根据题目写出递归函数,并返回递归结果 时间复杂度:本题的数据范围虽然很大,但是由于条件2的限制,数据量可以看成是20,于是我们就可以使用递归函数了 思路: 方法一&#xff1a…

【江协科技STM32】BKP备寄存器RTC实时时钟(学习笔记)

BKP备寄存器 BKP简介 BKP(Backup Registers)备份寄存器BKP可用于存储用户应用程序数据。当VDD(2.0~3.6V)电源被切断,他们仍然由VBAT(1.8~3.6V)维持供电。当系统在待机模式下被唤醒&#xff0…

leetcode 之(移除元素)

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作: 更改…

Spring MVC 请求与响应

目录 一、Spring MVC 请求 1.1 请求映射核心注解:RequestMapping 1.1.1 作用范围 1.1.2 属性详解 1.2 请求参数绑定机制 1.2.1 绑定规则 1.2.2 特殊场景处理 二、Spring MVC 响应 2.1 视图返回机制 2.1.1 String类型返回 2.1.2 ModelAndView对象 2.2 JS…

spring-security原理与应用系列:核心过滤器

目录 运行机制 WebSecurity SecurityFilterChain SecurityFilterChains FilterChainProxy VirtualFilterChain 内部结构 类图 FilterChainProxy FilterChain VirtualFilterChain 小结 紧接上一篇文章,这一篇我们来看看FilterChainProxy类的运行机制及内…