【問題】如何避免併發情況下的重複提交
背景
在業務開發中,我們常會面對防止重複請求的問題。當服務端對於請求的響應涉及資料的修改,或狀態的變更時,可能會造成極大的危害。重複請求的後果在交易系統、售後維權,以及支付系統中尤其嚴重。
重複請求的一致性問題又稱冪等性問題。
先弄清楚啥叫冪等性。
比如
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(獲得鎖): 可以操作。
操作結束(刪除鎖):刪除這個計數器。