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

交易的链路如下:

出现该问题后,我们对日志进行了分析,发现主站收到的两笔流水号完全相同,然而主站却没有做重复校验,将两笔订单(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. thinkphp session设置

    <?php namespace Home\Controller; use think\Controller; /*登录*/ class LoginController extends Publi ...

  2. 小白学flask之hello,world

    from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "He ...

  3. JS_1

    学习JS分为哪几步: 1.学习基础语法 JS写在哪 JS输出 JS变量 JS函数 JS分支 JS循环 2.学习JS操作网页DOM树 获取Dom节点 触发Dom事件 对Dom进行修改 3.学习JS对象及 ...

  4. 欣欣的留言板项目====超级触动的dbUtil实现留言板

    留言板管理系统 我的完成效果图: 提交后: 我的留言板基本架构如图: 创建留言板数据库: 刚开始我的前台主页中写留言信息表单: <body> <h1>留言板</h1> ...

  5. Activiti 配置Oracle不能自动创建表解决方法

    使用配置文件创建工作流表 <bean id="processEngineConfiguration" class="org.activiti.engine.impl ...

  6. Java入门到精通——调错篇之Astah Community打开报需要jre1.7运行环境

    1.问题概述     Astah Community安装完以后点击运行Astah Community的时候报此应用需要jdk1.7如下图     但是我的电脑在D盘装了jdk1.8了为什么这个软件为什 ...

  7. Python 练习项目1 弹球游戏

    这几天学习了python的基础知识,然后参考了网上的一些资料,完成了一个自己的小游戏,弹球游戏比较简单,但却具备了一些游戏的普遍特征,对于初学者是一个比较合适的锻炼的项目. 下面是效果图: 完整程序: ...

  8. 【Leetcode】【Medium】Remove Duplicates from Sorted Array II

    Follow up for "Remove Duplicates":What if duplicates are allowed at most twice? For exampl ...

  9. 华为HCNP实验 DHCP配置

    HCNP实验 DHCP配置 学习目的 1.掌握ip pool的配置方法2.掌握DHCP服务器的配置方法3.掌握DHCP客户端的配置方法4.掌握DHCP中继的配置方法5.掌握DHCP Snooping的 ...

  10. QA-IDEA中用maven配置项目无法加载JDBC

    java.lang.ClassNotFoundException: com.mysql.jdbc.Driver Im building Maven Java Web application and w ...