概念重覆請求是指一個請求因為某些原因被多次提交,場景簡述如下:1)用戶快速多次點擊按鈕2)Nginx失敗重試機制3)服務框架失敗重試機制4)MQ消息重覆消費5)第三方支付支付成功後,因為異常原因導致的多次非同步回調; 冪等性是指同樣的請求參數,多次請求返回的結果相同。一般是因為重覆請求導致的重覆操作等 ...
概念
重覆請求是指一個請求因為某些原因被多次提交,場景簡述如下:
1)用戶快速多次點擊按鈕
2)Nginx失敗重試機制
3)服務框架失敗重試機制
4)MQ消息重覆消費
5)第三方支付支付成功後,因為異常原因導致的多次非同步回調;
冪等性是指同樣的請求參數,多次請求返回的結果相同。一般是因為重覆請求導致的重覆操作等,但重覆請求不只包含併發時的重覆請求還包括並併發情況下的業務重試。
基本原理
實現冪等需要兩個條件1、同一請求參數(併發請求或非併發請求);2、多次請求返回的結果一致。一般大家講的都是併發情況下的,使用併發控制解決,但還有一點是要滿足返回的結果一致,這個一般根據場景來定,是返回相同結果還是返回失敗。
發生原因
1)分散式系統中網路的三態性:成功,失敗,未知,未知時一般三方系統會定期重試。
2)用戶重覆提交或系統重試機制導致的多次請求;
常見解決方案
解決思路:併發控制+返回相同結果
分類:按是否更改數據可以把介面分為查詢類介面和更新類介面,查詢類介面天然支持冪等,因此冪等性主要是解決更新類介面冪等。
1)唯一索引
描述:比如訂單號做唯一索引,同一訂單號只能插入一條記錄;
應用場景:適用於單庫單表的新增場景。
2)Select+[Insert/Update]
描述:先進行查詢,根據查詢結果判斷是否符合更新條件,符合則更新;
應用場景:因為兩條Sql非原子操作,適合併發量不高的新增或修改場景。
3)資料庫樂觀鎖
描述:根據某一欄位做為更新條件,如何不滿足,則更新失敗,比如狀態欄位或增加自增版本號欄位。【版本號欄位可以解決ABA問題】
應用場景:適合非高併發的更新,且有版本控制欄位的場景。如果高併發更新,評估利弊後可使用悲觀鎖。
4)防重Token
描述:頁面載入時,先請求服務端返回防重Token,用戶提交時將token一起提交到服務端,服務端判斷token是否存在,存在則執行,不存在則異常處理。【可根據業務規則是更新token的狀態值還是直接刪除token來標識已處理過】
應用場景:適用於沒有唯一性欄位的添加或修改類場景。
5)防重表
描述:基於資料庫的方式進行併發控制,此表通過唯一欄位+唯一索引來保障不重覆處理數據。
應用場景:簡單分散式情況下對添加或修改類場景,進行併發或防重控制(也適用於老系統不想新增併發控制欄位,統一進行併發欄位存儲的場景)。【複雜分散式因為請求量或數據量太大,超過了單表的限制,此時防重表可能存在出錯的情況】
6)分散式鎖
描述:以唯一欄位作為key進行加鎖,請求處理時先判斷是否有鎖,無鎖則先加鎖再處理邏輯,重覆請求因為已經加鎖,則說明重覆,則不處理。
場景:適合分散式高併發場景或不適用其它方式的場景,比如發驗證簡訊60秒控制,因為控制信息是記錄在緩存中的,無法使用樂觀鎖等方式,因此只能使用分散式鎖。
小結:解決方案的核心是根據資源(數據)的唯一性或唯一條件進行併發控制。
應用場景舉例
以訂單流程為例,介紹下冪等實現的常規解決方案。
訂單流程:
1、用戶提交訂單->待支付
2、用戶付款成功->待出庫
3、商品出庫->等待收貨
4、買家收貨->完成
其中:提交訂單為添加類介面,付款成功,商品出庫,等待收貨,完成為修改類介面。
1)訂單提交->待支付
單機環境:訂單號唯一索引或Select+Insert
分散式環境:Redis分散式鎖或防重token或防重表
2)用戶付款成功->待出庫,商品出庫-等待收貨,買家收貨->完成
單機環境:樂觀鎖
分散式環境:Redis鎖或防重token或防重表或Select+Update或樂觀鎖
降級方案
分散式鎖+唯一索引或樂觀鎖
分散式環境下,1)如果只加分散式鎖可能會存在鎖失效的情況,2)業務層鎖控制後,數據操作服務可能會超時重試;因此,依舊需要有唯一索引或資料庫樂觀鎖來進行併發控制,保障最後一道防線。
文章小結
本文對重覆請求和需要冪等控制的場景進行了介紹,並講解了常見的解決方案,最後以訂單流程為例介紹了方案的具體應用。希望對大家在防重和冪等控制設計上有幫助,不足之處,請批評指正,歡迎一起交流討論。