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