冪等的實現方案

冪等的實現方案

背景

在軟件系統的開發過程中,我們可能有如下需求:

  1. 創建業務訂單,一次業務請求只能創建一個;
  2. 單個訂單請求調用支付接口,當遇到網絡或系統故障請求重發,也應該只支付一次;
  3. 單個訂單完成時,給用戶發送消息應該只發一次;

等等很多情況下,都需要冪等的特性來支持。

冪等的概念

冪等(idempotence)一詞原為數學上的概念,用一個最直觀的數學式子表達為:

f(f(x)) = f(x)

對應到軟件開發領域,即為同樣的請求被執行一次與連續執行多次的效果是一樣的,服務器的狀態也是一樣的,實際上就是接口的可重複調用(包括時間和空間上兩個維度)。不是要求返回值完全相同,而且是指後續多餘的調用對系統的數據一致性不造成破壞。對於寫入類操作,如果第一次寫入是成功的,後續的寫入應該拋出異常或者空操作,或者執行了寫入但是未對數據造成變化。對於讀取類操作,需要保證其實現上是真正的讀取,不能在讀操作中夾帶寫操作。

冪等實現方案

冪等性不能脫離業務來討論。在不同的需求場景下,實現冪等的思路和方案也會不同,一般有如下通用方案:

1. MVCC(多版本併發控制)

這是樂觀鎖的一種實現,用於在數據庫併發訪問時的情況。當數據更新時需要去判斷版本號是否一致,如果不一致,則無法對數據進行更新。例如請求支付接口進行扣款時:

<code>update table_name set deposit = deposit-#{payment}, version = version + 1 where orderId = #{orderId} and version = #{version}/<code>

這裡orderId可進一步設為主鍵或唯一索引,因為這樣是行鎖,否則更新操作時會鎖表。樂觀鎖在對已有數據進行更新時既能保證效率,又能保證冪等。

2. 去重表

這是利用數據庫表單的特性來實現冪等。以訂單請求支付場景為例:將訂單號orderId設為去重表的唯一索引,每次請求支付都根據訂單號向去重表中插入一條數據,只有插入成功才繼續執行支付操作,相當於在事務的開始階段加鎖。考慮兩種失敗的情況:

  1. Insert去重表失敗,事務回滾,無任何影響;
  2. Insert去重表成功,支付業務操作失敗,事務回滾,刪除之前插入去重表的記錄,無任何影響;

以上兩種失敗的情況下,事務的冪等性是可以保持的,避免了單個訂單同時多次進行支付的情況。下圖為該支付場景下的時序圖:

冪等的實現方案

3. 分佈式鎖

與去重表思路相同,只是將對數據庫的的訪問轉移到了對緩存(如redis)的訪問,提高了效率。具體操作如下:訂單發起支付請求,支付系統會去redis緩存中查詢是否存在該訂單號orderId的key,如果不存在,則向redis增加key為訂單號,然後開始實際支付操作;如果查詢到存在該訂單號的key,則不進行實際支付操作。無論支付操作成功或失敗,在支付操作結果返回後,在緩存中刪除該訂單號key。


分享到:


相關文章: