1. 程式人生 > >隨機數使用不當引發的生產bug

隨機數使用不當引發的生產bug

兩個 體驗 spa 技術 人的 多個 導致 關註 做了

前幾天負責的理財產品線上出現問題:一客戶贖回失敗,查詢交易記錄時顯示某條交易記錄為其他人的卡號。

交易的鏈路如下:

技術分享圖片

出現該問題後,我們對日誌進行了分析,發現主站收到的兩筆流水號完全相同,然而主站卻沒有做重復校驗,將兩筆訂單(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生成是字符串,不是數字,有些場景不一定適用。

如果各位有好的想法,歡迎關註我的公眾號(程序員順仔)或留言討論~

技術分享圖片

隨機數使用不當引發的生產bug