1. 程式人生 > 其它 >防重冪等

防重冪等

前言:

在分散式系統下,服務之間相互呼叫,必然會存在呼叫失敗並且進行重試的情況,在某些情況下就需要做好防重冪等。

防重和冪等是什麼?

防重:避免產生重複資料

冪等:除了避免產生重複資料之外,還要求每次請求都返回一樣的結果

什麼情況會導致重複?

傳送方傳送相同的請求到服務端。

  • 前端多次傳送相同的請求到後端
  • 超時重發導致的重複
  • MQ異常導致的重複消費

如何防重?

  • insert之前先select,通常情況下有效,但是在高併發情況下,也會導致重複

  • 建立唯一索引,資料庫兜底,防止重複新增

  • 某些業務表在特定的場景下才不允許重複,不能直接建立唯一鍵,就可以增加一張防重表(為此類業務),將此類資料在同一事務下先insert進防重表成功,在insert業務表,假如insert進防重表失敗,證明此類資料重複,就不用再處理業務表了

  • 加分散式鎖(針對單據來鎖):需要合理設定過期時間。不能太短,導致業務沒有處理完,鎖失效,防重失敗;也不能不設定過期時間,解鎖異常導致鎖一直被佔,阻塞後續處理。

什麼情況要做冪等?

使用者對於同一操作發起的一次請求或者多次請求的結果是一致的,不會因為多次點選而產生了副作用。

例如:

  • 比如使用者對一筆訂單發起付款,因為網路問題沒有返回結果,就多次點選付款按鈕,此時只能發起一筆真實的交易,生成一條交易記錄。
  • 分散式系統中,因為介面超時,導致的重試,第一次請求介面超時,沒有獲取到返回結果(有可能已經成功了),第二次重試,接收方不能直接返回失敗,要根據第一次處理的結果進行返回。

怎麼解決?

  1. 新增資料類介面,通過防重解決。
  2. 更新類介面,比如更新庫存,更改狀態等,通過狀態,加樂觀鎖解決。

根據狀態判斷

很多業務是有狀態的,比如一個訂單表。有下單0、支付中1、已支付2、取消支付3等狀態,

假如id=123的訂單狀態是0,現在要變成支付中狀態。

update order set status=1 where id=123 and status=0;

第一次請求時,該訂單的狀態可以正常更新,sql執行結果的影響行數是1,訂單狀態變成了1。後面有相同的請求過來,再執行相同的sql時,由於訂單狀態變成了1,再用status=0作為條件,最終sql執行結果的影響行數是0,即不會真正的更新資料。但為了保證介面冪等性,介面也需要直接返回成功。

加樂觀鎖,在表中增加一個version欄位。

在更新資料之前先查詢一下資料:

select id,amount,version from user id=123;

如果資料存在,假設查到的version等於1,再使用id和version欄位作為查詢條件更新資料:

update user set amount=amount+100,version=version+1 where id=123 and version=1;

更新資料的同時version+1,然後判斷本次update操作的影響行數,如果大於0,則說明本次更新成功,如果等於0,則說明本次更新沒有讓資料變更。

由於第一次請求version等於1是可以成功的,操作成功後version變成2了。這時如果併發的請求過來,再執行相同的sql:

update user set amount=amount+100,version=version+1 where id=123 and version=1;

該update操作不會真正更新資料,最終sql的執行結果影響行數是0,因為version已經變成2了,為了保證介面冪等性,介面可以直接返回成功,因為version值已經修改了,那麼前面必定已經成功過一次,後面都是重複的請求。

總結

  • 網路延遲問題:先發的不一定先到
  • 資料庫操作延遲:先到的不一定先執行完
  • 不能依賴上游或下游去做防重冪等,自己本身也要把控好
  • 對於外部介面,沒有明確返回可重試狀態的,不要輕易重試