1. 程式人生 > >【問題】如何避免併發情況下的重複提交

【問題】如何避免併發情況下的重複提交

背景

在業務開發中,我們常會面對防止重複請求的問題。當服務端對於請求的響應涉及資料的修改,或狀態的變更時,可能會造成極大的危害。重複請求的後果在交易系統、售後維權,以及支付系統中尤其嚴重。

重複請求的一致性問題又稱冪等性問題。

先弄清楚啥叫冪等性。

比如
1. 一個使用者把他的性別設定為男,無論他設定多少次,他的性別都是男。
2. 比如我們查詢餘額(假設並沒有任何使餘額發生變化的行為),那麼我們點選多少次查詢餘額得到的結果都應該是一樣的。

以上都是冪等的。

但是,如果我們進行一筆交易,這筆交易實際已經正常的插入了資料庫,但由於前臺操作的抖動,快速操作,網路通訊或者後端響應慢等原因,又來了一次。導致使用者平白無故支付了兩次。 這種情況我們該如何避免呢??

首先前端優化是必不可少的,這裡暫且不談。。。

我們談談後端

唯一鍵法

併發並不意味著每個request都處理的很快,也不意味著機器之間就不共享資料了。可以把所有帶有副作用的task都給一個GUID,最後寫進資料庫之前查詢一下這個GUID是否已經被執行過了。

訂單狀態法

使用者呼叫支付,扣款成功後,更新對應訂單狀態,然後再儲存流水。

(支付狀態:未支付,已支付)

步驟:
1、查詢訂單支付狀態
2、如果已經支付,直接返回結果
3、如果未支付,則支付扣款並且儲存流水
4、返回支付結果

理論上,只要在資料狀態更新前完成了查詢操作,則業務邏輯的重複處理就依舊會發生。

基於快取的資料驗證

Redis儲存查詢輕量快速。在request進來的時候,可以先記錄在快取中。後續進來的request每次進行驗證。整個流程處理完成,清除快取。

I.  每次交易發起申請,讀取快取中是否有以orderId為key的值
II. 沒有,則往快取中寫入以orderId為key的value
III.有,則說明有該訂單正在進行。
IV. 操作完清快取,或者快取存值的時候設定生命週期

利用資料庫的主鍵唯一

同一筆訂單進來的話,資料庫會報唯一索引的錯誤。這個時候後臺對這個異常進行處理並返回給前端提示。

(那麼如何保證誤操作的交易的訂單號都是一個訂單號呢,如何和正常的多次交易情況區分開?)

快取計數器

由於資料庫的操作比較消耗效能,瞭解到redis的計數器也是原子性操作。果斷採用計數器。既可以提高效能,還不用儲存,而且能提升qps的峰值。
還是以支付為例子:

每次request進來則新建一個以orderId為key的計數器,然後+1。

如果>1(不能獲得鎖): 說明有操作在進行,刪除。
如果=1(獲得鎖): 可以操作。
操作結束(刪除鎖):刪除這個計數器。