前几天负责的理财产品线上出现问题:一客户赎回失败,查询交易记录时显示某条交易记录为其他人的卡号。

交易的链路如下:

出现该问题后,我们对日志进行了分析,发现主站收到的两笔流水号完全相同,然而主站却没有做重复校验,将两笔订单(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 * ........,不重复的概率越来越小,发生重复的概率越来越大。

如何避免发生重复呢?目前我想到的有以下几种方法:

  1. 使用数据库的自增id作为流水号,但这样会增加数据库IO开销,降低性能;
  2. 使用Redis存储流水号,每次使用时到Redis获取并加1,配合着分布式锁一同使用。同方案1一样,会增加IO开销,降低性能;
  3. 使用开源的发号器,如Snowflake等(有机会单独介绍)。
  4. 使用UUID,但UUID生成是字符串,不是数字,有些场景不一定适用。

如果各位有好的想法,欢迎关注我的公众号(程序员顺仔)或留言讨论~

随机数使用不当引发的生产bug的更多相关文章

  1. 一次 Redis 事务使用不当引发的生产事故

    这是悟空的第 170 篇原创文章 官网:http://www.passjava.cn 你好,我是悟空. 本文主要内容如下: 一.前言 最近项目的生产环境遇到一个奇怪的问题: 现象:每天早上客服人员在后 ...

  2. 为什么我没有拔出钥匙 ——开锁引发的程序bug解决方案的思考

    http://blog.csdn.net/wojiushiwo987/article/details/8851204为什么我没有拔出钥匙                             ——开 ...

  3. 清缓存的姿势不对,真的会出生产bug哦

    最近解决了一个生产bug,bug的原因很简单,就是清理缓存的方式不对.本来没啥好说的,但是考虑到我们有时候确实会在一些小问题上栽跟头,最终决定把这个小故事拿出来跟大家分享下. 风起有一天在撸代码,突然 ...

  4. jedis参数不当引发的问题总结

    jedis参数不当引发dubbo服务线程池耗尽异常 现象:一个dubbo服务偶发性的出现个别机器甚至整个集群大量报线程池耗尽的问题.一开始对问题的处理比较粗暴,直接增加了10倍的线程数.但是问题依然偶 ...

  5. Erlang 程序引发共享内存 bug 的一个例子

    虽然 Erlang 的广告说得非常好,functional.share-nothing.消息传递,blah blah 的,好像用 Erlang 写并发程序就高枕无忧了,但是由于 Erlang 信奉高度 ...

  6. 一次 select for update 的悲观锁使用引发的生产事故

    1.事故描述 本月 8 日上午十点多,我们的基础应用发生生产事故.具体表象为系统出现假死无响应.查看事发时间段的基础应用 error 日志,没发现明显异常.查看基础应用业务日志,银行结果处理的部分普遍 ...

  7. mybatis中resultMap引发的吐血bug

    简单的讲: 问题背景:如果在写mybatis中的resultMap时,不下心将resultMapde id写成映射接口的名字,会发生什么? 结论:单元测试进度条卡住但不报错, Tomcat运行不报错, ...

  8. js 记录几个因惯性思维引发的代码BUG,开发思维方式的自我反省

     壹 ❀ 引 在写这篇文章之前,对于取什么标题其实让我纠结了好几天,这篇文章中我想说的东西与引用类型数据有关,也与我们的惯性思维有关.本文中展示的几段代码都非常简单,原型都来自于我的日常开发,但让你立 ...

  9. [bug]——vue 组件状态外置引发的一个 bug

    背景 在编写 .vue 组件时,可以将状态外置来获取一些额外的好处,譬如有这么一个组件(global-components.vue): <template> <div> < ...

随机推荐

  1. v-model的双向数据绑定(表单)

    可以用 v-model 指令在表单 <input>.<textarea> 及 <select> 元素上创建双向数据绑定.它会根据控件类型自动选取正确的方法来更新元素 ...

  2. java unsupported major.minor version 51.0 解决

    1.概述 出现如题所述异常 是因为jdk高版本 编译后的class文件 运行在低版本的jre环境下(如jdk7编译 运行在jdk6环境下) 2. 解决方案 在eclipse等ide中重新编译 指定编译 ...

  3. easyui numberbox输入框 编辑不可编辑的切换

    背景:申请单里需要选费用类型,费用类型有的有子明细项,有个合计项    当有子明细项的时候,合计项的值是通过弹出的子明细项价格的总和(设置为可编辑没问题,因为点击出来弹框,编辑不了)    没有子明细 ...

  4. OSGEarth加载 geoserver 发布 TMS

    geoserver配好数据并用自带的gwc切片好后, 访问 http://localhost:9999/geoserver/gwc/service/tms/1.0.0/ 在OsgEarth的earth ...

  5. Python爬虫教程-29-验证码识别-Tesseract-OCR

    本篇是关于验证码识别问题,也是Python爬虫笔记的一个结尾,使用 Tesseract Python爬虫教程-29-验证码识别-Tesseract-OCR 常见反爬虫手段: 验证码 1.简单图片,扭曲 ...

  6. 【Udacity】朴素贝叶斯

    机器学习就像酿制葡萄酒--好的葡萄(数据)+好的酿酒方法(机器学习算法) 监督分类 supervised classification Features -->Labels 保留10%的数据作为 ...

  7. JavaScript中filter()方法

    方法概述 用于把数组(Array)的某些元素过滤掉,然后返回剩下的元素组成的数组. 语法: var filteredArray = array.filter(callback[, thisObject ...

  8. JS高级程序设计第三版——变量、作用域和内存问题

    JavaScript变量: 由于JavaScript变量松散类型的本质,决定了它只是在特定时间用于保存特定值的一个名字而已.由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可 ...

  9. Opencv2.4.13与Visual Studio2013环境搭建配置教程

    转载:http://www.jb51.net/article/108943.htm 一.安装包的下载与安装 Opencv可免费到官网上去下载,opencv是国外软件,在下载是由于受资源的限制,可能会出 ...

  10. 如果Android的jar包导入错误,怎么修改呢?

    如果jar包导入错误,怎么修改呢? 右键工程---->properties---->Java Build Path --->Libraries-->选择android-supp ...