血的教训 ,一次订单号重复的事故我差点被开除

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 曾经有个项目,我们线上出了一次事故,这个事故的表象大体是这样的:系统出现了两个一模一样的订单号,订单的内容却不是一样的,而且事情发生的不止一次,被老板发现之后,当月绩效被扣光!

一、介绍

曾经有个项目,我们线上出了一次事故,这个事故的表象大体是这样的:

系统出现了两个一模一样的订单号,订单的内容却不是一样的,而且事情发生的不止一次,被老板发现之后,当月绩效被扣光!

事后经过排查,产生这个问题,总结主要有两个原因:

  • 1、数据库订单表里面,对订单编号没有设置唯一键约束
  • 2、生成订单编号的时候,采用了随机数,导致有部分单号发生了重复

针对这个问题也做了一些研究,有一些收获想分享给大家!

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

不废话,直接干货!

订单命名的几种规则总结

  • 不重复:这点我相信大家都懂,必须全局唯一
  • 安全性:订单号需要做到不容易被人为的猜测或者推测出来,例如订单号就是流水号的话,那么别人就很容易从订单号推测出公司的整体运营情况。
  • 禁用随机码:很多人分析生成订单号的时候,第一个念头肯定是不重复唯一性,那么第二个念头可能就是安全性,想要同时满足前两者,很容易想到使用随机码,随机码从一定程度来说,更安全、不重复性更高,但是可读性差,有概率会发生重复。
  • 防止并发:针对系统的并发业务场景(如秒杀),需要做到并发场景下,订单编号生成快速、不重复等要求
  • 控制位数:订单号的位数尽量在 10 位 ~ 18 位之间。太短的情况下,如果交易量过大,很难做到防止重复,太长可读性差、意义也不大。

二、方案实践

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

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

2.1、方案一: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();

这种方案,虽然实现简单、方便;但是数据库查询效率非常差,而且内容长,在实际的项目场景开发中,一般用于于记录用户的手机设备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 号码。其内部结构如下:

91.jpg

可以很清晰的看出,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 年,为了能最大限度的使用该算法,在使用的时候,应该为其指定一个开始时间,不然会发生重复!

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

2.4、方案三:分布式组件

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

实现逻辑如下:

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

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

剩下的主要工作就是我们如何去设计一个订单号规则!

在设计规则之前,我们先来看看互联网几个大厂的订单号格式。

京东商城订单号格式: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');

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

三、总结

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

92.jpg

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

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
7月前
|
测试技术 程序员 项目管理
甲方怒喷半小时:一次项目上线失败的深刻教训
小米分享了一次项目上线失败的经历,起因是运营提出一个看似简单的白名单功能。问题包括:没有需求原型导致理解偏差,新成员对项目不熟悉,测试流程不全面,以及人员变动大。解决方案涉及需求确认会、原型图设计、交接制度、团队培训和全流程测试等。这次失败提供了关于需求分析、项目管理及团队协作的教训。
62 2
|
SQL 存储 缓存
拧螺丝|主管老大是魔鬼吧?第一道就干掉了对方,我什么时候才能有新同事?
拧螺丝|主管老大是魔鬼吧?第一道就干掉了对方,我什么时候才能有新同事?
72 0
|
7月前
|
缓存 Java 关系型数据库
踩了定时线程池的坑,导致公司损失几千万,血的教训
踩了定时线程池的坑,导致公司损失几千万,血的教训
|
安全 Java 程序员
2020 年的第一天,程序员鸭血粉丝又碰上生产事故
hello~各位读者新年好,我是鸭血粉丝(大家可以称呼我为「阿粉」),一位喜欢吃鸭血粉丝的程序员!2019 年,阿粉写了很多 bug,这不前一段时间 OOM 差点就把服务器搞挂。跨年的时刻,阿粉默默立下一个 flag,2020 年再见 bug。
2020 年的第一天,程序员鸭血粉丝又碰上生产事故
|
弹性计算 Linux 数据库
发生了这种情况,数据还有救吗?
这样的惊喜还是少一些吧!
655 0
发生了这种情况,数据还有救吗?
|
人工智能
记录一次自己在华东理工的保研(推免)复试经历
这是我第一次写博客,虽然想在博客上写一写技术性的东西,但是今天刚经历了华东理工大学的推免生复试,于是乎就想写自己的经验和经历,无私分享给后续的学子,希望能给大家带来帮助。
5660 0
|
Java 程序员 应用服务中间件
阿里员工吐槽:工作压力真的大,年假都没心情用,后悔来阿里
阿里巴巴是当下国内最顶尖的互联网公司之一,经过19年的发展,如今的阿里已经从单纯的电子商务公司过渡到了综合型的超级巨头。如今阿里旗下的阿里云已经成为了国内第一,世界第三的云计算公司。
4230 0
现在到底还该不该买房?
最近楼市出了一些消息,让正打算买房的小伙伴们尴尬了。 其中包括: 新京报:厦门楼市神话破灭 中国城市房价已“阶段性见顶” 新京报评论 2018-08-04 从行业发展周期看,我国房地产行业已进入行业发展的下半场。
996 0
|
程序员
偶遇到客户的奇葩需求
@梁大折腾 部门老大:你,做个微信小游戏吧 我:啥样的? 老大:反正你就做个小游戏吧,这个火 我:????? @柔情 领导:那个运维啊,你来把他的系统破解了,要不把他服务器黑了也行。给你一天时间,搞不搞得定。
1910 0

相关实验场景

更多
下一篇
DataWorks