随机数使用不当引发的生产bug
前几天负责的理财产品线上出现问题:一客户赎回失败,查询交易记录时显示某条交易记录为其他人的卡号。
交易的链路如下:

出现该问题后,我们对日志进行了分析,发现主站收到的两笔流水号完全相同,然而主站却没有做重复校验,将两笔订单(A和B)都发往基金系统,基金系统做了重复校验,收到A之后开始处理,收到B之后直接报错返回,A处理完后又正常返回。但是主站根据流水号更新数据库状态,却将两笔订单更新错了,导致客户的交易记录出错。
该问题虽然不会造成用户的资金损失或记账出错,但是交易记录出错会带来极差的用户体验,引发客户投诉,并对公司声誉带来不良影响。因此主站通过增加重复校验来解决此问题。
但是问题的根源在于为何会产生重复的流水号,只有从源头上消灭重复的流水号,该问题才算彻底解决,因此我们对代码进行了分析。
流水号由APP -server产生,并传入后续的交易。流水号生成代码如下:

可以看出,流水号由13位时间戳+3位随机数+固定数字“38”组成。一般情况下,该规则生成的流水号是不会重复的,因为时间戳是精确到毫秒的。但是在高并发的情况下,同一毫秒收到多个请求,此时只能由三位随机数来保证流水号的唯一性。
虽然就单次请求来说,与同一毫秒内其它请求的流水号重复的几率极小,可以忽略。假设每一毫秒有2个请求,那么这两个请求的3位随机数重复的概率为1/1000,不重复的概率为999/1000(假设是这么大的概率,没有经过数学计算)。我们通过程序来看下流水号的重复概率:

程序运行结果如下(为了方便查看,随机数加了-用来分隔):

程序运行多次,也无法复现流水号重复的问题。但无法复现不代表没有问题,只能说明发生概率较小,因此需要调大循环次数。
循环次数调大后,log输出已无法靠肉眼去看是否重复,需要将每个流水号出现的次数存入Map,最后再看有多少个次数大于1的流水号。代码片段如下:



执行以上代码,结果如下:

可以看出,随着统计样本的扩大,出现重复的流水号的几率也在增加。也就是说,在系统长时间处于高并发的情况下,每一毫秒都会有重复的概率产生(如1/1000),随着时间的推移,在相当长的一段时间内,不发生重复的概率为999/1000 * 999/1000 * ........,不重复的概率越来越小,发生重复的概率越来越大。
如何避免发生重复呢?目前我想到的有以下几种方法:
- 使用数据库的自增id作为流水号,但这样会增加数据库IO开销,降低性能;
- 使用Redis存储流水号,每次使用时到Redis获取并加1,配合着分布式锁一同使用。同方案1一样,会增加IO开销,降低性能;
- 使用开源的发号器,如Snowflake等(有机会单独介绍)。
- 使用UUID,但UUID生成是字符串,不是数字,有些场景不一定适用。
如果各位有好的想法,欢迎关注我的公众号(程序员顺仔)或留言讨论~

随机数使用不当引发的生产bug的更多相关文章
- 一次 Redis 事务使用不当引发的生产事故
这是悟空的第 170 篇原创文章 官网:http://www.passjava.cn 你好,我是悟空. 本文主要内容如下: 一.前言 最近项目的生产环境遇到一个奇怪的问题: 现象:每天早上客服人员在后 ...
- 为什么我没有拔出钥匙 ——开锁引发的程序bug解决方案的思考
http://blog.csdn.net/wojiushiwo987/article/details/8851204为什么我没有拔出钥匙 ——开 ...
- 清缓存的姿势不对,真的会出生产bug哦
最近解决了一个生产bug,bug的原因很简单,就是清理缓存的方式不对.本来没啥好说的,但是考虑到我们有时候确实会在一些小问题上栽跟头,最终决定把这个小故事拿出来跟大家分享下. 风起有一天在撸代码,突然 ...
- jedis参数不当引发的问题总结
jedis参数不当引发dubbo服务线程池耗尽异常 现象:一个dubbo服务偶发性的出现个别机器甚至整个集群大量报线程池耗尽的问题.一开始对问题的处理比较粗暴,直接增加了10倍的线程数.但是问题依然偶 ...
- Erlang 程序引发共享内存 bug 的一个例子
虽然 Erlang 的广告说得非常好,functional.share-nothing.消息传递,blah blah 的,好像用 Erlang 写并发程序就高枕无忧了,但是由于 Erlang 信奉高度 ...
- 一次 select for update 的悲观锁使用引发的生产事故
1.事故描述 本月 8 日上午十点多,我们的基础应用发生生产事故.具体表象为系统出现假死无响应.查看事发时间段的基础应用 error 日志,没发现明显异常.查看基础应用业务日志,银行结果处理的部分普遍 ...
- mybatis中resultMap引发的吐血bug
简单的讲: 问题背景:如果在写mybatis中的resultMap时,不下心将resultMapde id写成映射接口的名字,会发生什么? 结论:单元测试进度条卡住但不报错, Tomcat运行不报错, ...
- js 记录几个因惯性思维引发的代码BUG,开发思维方式的自我反省
壹 ❀ 引 在写这篇文章之前,对于取什么标题其实让我纠结了好几天,这篇文章中我想说的东西与引用类型数据有关,也与我们的惯性思维有关.本文中展示的几段代码都非常简单,原型都来自于我的日常开发,但让你立 ...
- [bug]——vue 组件状态外置引发的一个 bug
背景 在编写 .vue 组件时,可以将状态外置来获取一些额外的好处,譬如有这么一个组件(global-components.vue): <template> <div> < ...
随机推荐
- nodejs的socket.io学习笔记
socket.io学习笔记 1.服务器信息传输: 2.不分组,数据传输: 3.分组数据传输: 4.Socket.io难点大放送(暂时没有搞定): 服务器信息传输 1. // send to curre ...
- JavaScript this指向相关内容
1,默认绑定this指向windw对象 看代码: function test(C){ var a = 123 function b(){}; } 在预编译环节当中. OA{ arguments:[1] ...
- csharp:Learn how to post JSON string to generic Handler using jQuery in ASP.Net C#.
/// <summary> ///參考: http://james.newtonking.com/json/help/index.html# /// 塗聚文(Geovin Du) 2014 ...
- Codeforces Round #413 B. T-shirt buying
B. T-shirt buying time limit per test 3 seconds memory limit per test 256 megabytes A new pack ...
- 第4课 简易浏览器-WebViewer组件的使用方法
做一个手机浏览器,需要哪些组件呢? 一.组件设计 二.组件属性及命名修改 三.逻辑设计 1.导航按钮代码:前进.后退.主页 2.访问网页按钮 1)根据用户在地址栏输入的地址书写,判断书写中是否含有“h ...
- 如何一次性下载某个类库依赖的所有jar包"
** 经常碰到这种事情: ** 在一些非maven工程中(由于某种原因这种工程还是手工添加依赖的),需要用到某个新的类库(假设这个类库发布在maven库中),而这个类库又间接依赖很多其他类库,如果依赖 ...
- 五、mariadb遇到的坑——Linux学习笔记
C#连接MySQL异常:The host localhost does not support SSL connections. 解决方案: 连接字符串添加如下语句. SslMode = none; ...
- M-wordL-图
典型的需要使用图模型 将start 和 end 以及字典一同构建成图,然后探究从start到end的最短路径
- npm创建react项目
1:win+R 输入cmd ,打开cmd面板 2:安装cnpm npm install -g cnpm --registry=https://registry.npm.taobao.org 3:安装r ...
- NET对象的跨应用程序域
NET对象的跨应用程序域 转眼就到了元宵节,匆匆忙忙的脚步是我们在为生活奋斗的写照,新的一年,我们应该努力让自己有不一样的生活和追求.生命不息,奋斗不止.在上篇博文中主要介绍了.NET的AppDoma ...