隨機數使用不當引起的生產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生成是字符串,不是數字,有些場景不必定適用。

若是各位有好的想法,歡迎關注個人公衆號(程序員順仔)留言討論~

相關文章
相關標籤/搜索