随机数使用不当引起的生产bug

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

交易的链路以下: 数据库

出现该问题后,咱们对日志进行了分析,发现主站收到的两笔流水号彻底相同,然而主站却没有作重复校验,将两笔订单(A和B)都发往基金系统,基金系统作了重复校验,收到A以后开始处理,收到B以后直接报错返回,A处理完后又正常返回。可是主站根据流水号更新数据库状态,却将两笔订单更新错了,致使客户的交易记录出错。并发

该问题虽然不会形成用户的资金损失或记帐出错,可是交易记录出错会带来极差的用户体验,引起客户投诉,并对公司声誉带来不良影响。所以主站经过增长重复校验来解决此问题。分布式

可是问题的根源在于为什么会产生重复的流水号,只有从源头上消灭重复的流水号,该问题才算完全解决,所以咱们对代码进行了分析。高并发

流水号由APP -server产生,并传入后续的交易。流水号生成代码以下: 性能

能够看出,流水号由13位时间戳+3位随机数+固定数字“38”组成。通常状况下,该规则生成的流水号是不会重复的,由于时间戳是精确到毫秒的。可是在高并发的状况下,同一毫秒收到多个请求,此时只能由三位随机数来保证流水号的惟一性。3d

虽然就单次请求来讲,与同一毫秒内其它请求的流水号重复的概率极小,能够忽略。假设每一毫秒有2个请求,那么这两个请求的3位随机数重复的几率为1/1000,不重复的几率为999/1000(假设是这么大的几率,没有通过数学计算)。咱们经过程序来看下流水号的重复几率:日志

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

程序运行屡次,也没法复现流水号重复的问题。但没法复现不表明没有问题,只能说明发生几率较小,所以须要调大循环次数。server

循环次数调大后,log输出已没法靠肉眼去看是否重复,须要将每一个流水号出现的次数存入Map,最后再看有多少个次数大于1的流水号。代码片断以下:

执行以上代码,结果以下:
能够看出,随着统计样本的扩大,出现重复的流水号的概率也在增长。也就是说,在系统长时间处于高并发的状况下,每一毫秒都会有重复的几率产生(如1/1000),随着时间的推移,在至关长的一段时间内,不发生重复的几率为999/1000 * 999/1000 * ........,不重复的几率愈来愈小,发生重复的几率愈来愈大。

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

  • 使用数据库的自增id做为流水号,但这样会增长数据库IO开销,下降性能;
  • 使用Redis存储流水号,每次使用时到Redis获取并加1,配合着分布式锁一同使用。同方案1同样,会增长IO开销,下降性能;
  • 使用开源的发号器,如Snowflake等;
  • 使用UUID,但UUID生成是字符串,不是数字,有些场景不必定适用。

若是各位有好的想法,欢迎关注个人公众号(程序员顺仔)留言讨论~

相关文章
相关标签/搜索