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

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

交易的鏈路如下:

 

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

 

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章