【Java开发】如何设计一个全局唯一的订单号?

news/2024/12/25 13:17:33/文章来源:https://www.cnblogs.com/o-O-oO/p/18630155
一、背景介绍二、方案实践2.1 方案一:UUID2.2 方案二:数据库自增2.3 方案三:雪花算法2.4 方案四:分布式组件
总结

一、背景介绍

在实际的软件系统开发过程中,由于业务的需要,我们经常需要生成业务单号,例如订单编号、入库单号、投诉服务单号等等,针对这个问题也做了一些研究,有一些收获想分享给大家!

本文主要以讨论电商的订单编号规则为案例,其他类型的服务编号设计思路其实也是相似的。

不废话,直接干货!

订单命名的几种规则总结:

不重复:这点我相信大家都懂,必须全局唯一

安全性:订单号需要做到不容易被人为的猜测或者推测出来,例如订单号就是流水号的话,那么别人就很容易从订单号推测出公司的整体运营情况。

禁用随机码:很多人分析生成订单号的时候,第一个念头肯定是不重复唯一性,那么第二个念头可能就是安全性,想要同时满足前两者,很容易想到使用随机码,随机码从一定程度来说,更安全、不重复性更高,但是可读性差,有概率会发生重复。

防止并发:针对系统的并发业务场景(如秒杀),需要做到并发场景下,订单编号生成快速、不重复等要求

控制位数:订单号的位数尽量在 10 位 ~ 18 位之间。太短的情况下,如果交易量过大,很难做到防止重复,太长可读性差、意义也不大。

二、方案实践

上面提到了订单编号生成的规则,那要实现这样的规则,该如何实现会比较好呢?

下面总结几种常见的处理方式,我们一一分析!

2.1 方案一:UUID

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

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

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

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

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

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

这种方案,虽然实现简单、方便;但是数据库查询效率非常差,而且内容长,在实际的项目场景开发中,一般用于于记录用户的手机设备ID等硬件信息!

因此不推荐采用 uuid 来生成订单编号!

2.2 方案二:数据库自增

所谓数据库自增,意思是在数据库中给某个列设置为自增列,并且给该列设置一个初始值,代码层面无需任何特殊处理,以 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;

这种通过数据库自增方式实现唯一值,在单体服务下是没有问题,但是在大流量分布式服务环境下,并发性能很低。

以后数量大的时候,需要对 mysql 进行分库分表,此时订单号会重复,因此不推荐采用!

2.3 方案三:雪花算法

Snowflake(中文简称:雪花算法) 是 Twitter 内部的一个 ID 生算法,可以通过一些简单的规则保证在大规模分布式情况下生成唯一的 ID 号码。其内部结构如下:

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

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

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

SnowFlake 算法可以保证:

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

需要注意的是:

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

在高并发的环境下,Snowflake 算法可以生成全局唯一的订单编号,但是他的长度达到21位,因此不推荐采用,但是可以用它来生成主键 ID,是完全没有问题的!

2.4 方案四:分布式组件

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

实现逻辑如下:

// 基于某个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

银泰网订单号格式:10030522161715

小米订单号格式:1111218032345170

我们先来分析一下凡客诚品和银泰网的订单号生成规则。

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

基本猜测一下,凡客的订单规则是:业务编码+年的后2位+月+日+订单数

泰网的订单号规则:年的第三位数+业务编码+年的后1位+月+日+订单数;而京东商城苏宁易购的订单号看不出规则。

最后我们来分析一下小米订单号1111218032345170,可以将其分解成四个部分1——111218—03234—5170。

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

总结起来,小米的订单规则是:业务编码+年的后 2 位+月+日+秒+订单数,固定长度为16,这种订单号规则可以保证 100 年不会重复!

同样的,借鉴小米的订单号规则,我们也可以生成同样的订单号,实现过程如下:

//获取当前时间
Date currentTime  = new Date();
//格式化当前时间为【年的后2位+月+日】
String originDateStr = new SimpleDateFormat("yyMMdd").format(currentTime );
//计算当前时间走过的秒
Date startTime =  new SimpleDateFormat("yyyyMMdd").parse(new SimpleDateFormat("yyyyMMdd").format(originDate));
long differSecond = (currentTime.getTime() - startTime.getTime()) / 1000;
//获取【年的后2位+月+日+秒】,秒的长度不足补充0
String yyMMddSecond = originDateStr +  StringUtils.leftPad(String.valueOf(differSecond), 5, '0');//获取【业务编码】 + 【年的后2位+月+日+秒】,作为自增key;
String prefixOrder = sourceType + "" + yyMMddSecond;
//通过key,采用redis自增函数,实现单秒自增;不同的key,从0开始自增,同时设置60秒过期
Long incrId = redisUtils.saveINCR(prefixComplaint, 60);
//生成订单编号
String orderNo = prefixOrder + StringUtils.leftPad(String.valueOf(incrId), 4, '0');

此订单编号可以保证大流量环境下全局唯一、生成速度非常的快、支持高并发环境,同时还支持按时间排序!

总结

通过上面的示例演示,我们可用做一个详细的总结!

综上所述,在大流量的环境下,我们可以通过 redis 的incr函数实现序列号自增的特性,同时搭配订单的设计规则,从而保证高并发的环境下,订单唯一性!

原创 潘志的研发笔记

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

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

相关文章

极致 ElasticSearch 调优,让你的ES 狂飙100倍!

本文原文链接 文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备 免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 …

开源项目管理系统对比:10款最佳工具详评,助你决策

在当今数字化时代,项目管理的效率和效果直接关系到企业的竞争力和发展前景。开源项目管理系统因其灵活性、可定制性和成本效益,成为众多企业和团队的首选。从初创公司到大型企业,从软件开发团队到市场营销部门,合适的项目管理工具能够帮助团队更好地规划、执行和监控项目,…

java~重写hashcode时为什么要乘以31

在Java中,重写hashCode()方法时常常会使用31作为乘数,这是因为31具有一些独特的数学性质,使其成为一个优秀的选择。以下是几个原因: 1. 奇质数的特性 31是一个奇数和质数,这意味着它能有效地减少哈希冲突的概率。使用质数作为乘数可以帮助分散哈希值,从而提高哈希表的性能…

数据误删?别怕!COS防误删攻略请查收

在云存储领域,数据的安全性始终是悬在头顶的达摩克利斯之剑。长期以来,腾讯云对象存储服务(COS)一直致力于数据安全的探索和实践,以保障数据的安全性和完整性。对象存储COS准备了一份防误删攻略给大家: 背景与意义在云存储领域,数据的安全性始终是悬在头顶的达摩克利斯之…

【日记】拼好了衣架!(524 字)

正文今早不小心把闹钟给关了,继 7:50 闹钟响了之后,下一次睁眼就是 8:28 了。吓得一哆嗦,穿衣服换鞋的速度起飞。到一楼,正好 8:30。还好行长没来(小声。昨天晚上,朝哥给我说他伤好得差不多了,可以开始考虑上课了。我一直在等他说出这句话。暑假班他受伤,完事儿之后我受…

【原创】解决ncnn yolov11 乱框,输出维度出错,无框问题

Abstract 解决:ncnn yolov11 乱框,输出维度出错,无框问题 Solution: ncnn yolov11 has random boxes, incorrect output dimensions, and no boxes 0x00: model export 首先是模型转换问题 最开始,我是用python的ultralytics导出为onnx格式文件(.onnx),这个文件在python的…

polarctf-crypto中等难度wp整理(截止至2024.12)

二进制一个音频文件,听了,不是摩斯电码看了wp,原来是拨号 考察:DTMF拨号音识别 使用dtmf2num这个工具解析这个音频二进制1和0 但是不是,它是01字符变形成摩斯密码,把0替换成.,把1替换成-,把*替换成空格。 得到一组摩斯电码,然后去解即可还要小写md5加密,服了这个老六…

杭州数据恢复之某公司经理的三星移动硬盘摔坏了盘片划伤二次开盘

这是一块老款三星Samsung使用mini USB接口的500G移动硬盘,采用了一体式电路板,型号是HM502JX。硬盘是用户不小心摔坏了,接电脑不识别而且有异响,先送修到百脑汇电脑城某家数据恢复中心进行开盘修复,但被告知盘片有划伤无法恢复数据。很巧用户公司里的一名员工曾经在我们这…

第十七次作业

1、安装最新版phpstudy集成工具并创建一个网站,编写php代码输出网站信息(phpinfo)2、安装vscode,并安装php开发插件、汉化插件、xdebug等插件 中⽂语⾔包安装php调试插件配置Open PHP/HTML/JS In Browser插件3、配置phpstudy集成工具xdebug扩展,并使用vscode对php代码进行…

dataezse接入zabbix监控

常用查询 目录常用查询zabbix 常用库表说明主机资源监控主机资源监控(纯值)oracle状态监控CPU top10DISK TOP 10Memory TOP 10SPACE USERD TOP 10问题告警级别分布问题列表null问题主机组正常主机总数主机问题排行 zabbix 常用库表说明 https://www.cnblogs.com/yaoyaojcy/p/…

MySQL 千万 级数据量根据(索引)优化 查询 速度

MySQL 千万 级数据量根据(索引)优化 查询 速度| Id | Title | DateAdded | SourceUrl | PostType | Body | BlogId | Description | DateUpdated | IsMarkdown | EntryName | CreatedTime | IsActive | AutoDesc | AccessPermission | | -------------| -------------| -----…

kafka中文教程

kafka中文教程| Id | Title | DateAdded | SourceUrl | PostType | Body | BlogId | Description | DateUpdated | IsMarkdown | EntryName | CreatedTime | IsActive | AutoDesc | AccessPermission | | -------------| -------------| -------------| -------------| -------…