zoukankan      html  css  js  c++  java
  • 随机数使用不当引发的生产bug

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

    交易的链路如下:

     

    出现该问题后,我们对日志进行了分析,发现主站收到的两笔流水号完全相同,然而主站却没有做重复校验,将两笔订单(AB)都发往基金系统,基金系统做了重复校验,收到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生成是字符串,不是数字,有些场景不一定适用。

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

  • 相关阅读:
    973. K Closest Points to Origin
    919. Complete Binary Tree Inserter
    993. Cousins in Binary Tree
    20. Valid Parentheses
    141. Linked List Cycle
    912. Sort an Array
    各种排序方法总结
    509. Fibonacci Number
    374. Guess Number Higher or Lower
    238. Product of Array Except Self java solutions
  • 原文地址:https://www.cnblogs.com/sunnick/p/9736900.html
Copyright © 2011-2022 走看看