系統冪等設計
前言
冪等簡單的定義:
系統中的多次操作,不管多少次,都應該產生一樣的效果,或返回一樣的效果。
比如實際的業務請求為建立一個活動,理論上需要根據業務形態開發冪等建立活動的介面,這樣在相同引數呼叫介面多次建立活動時,只可以建立成功一次。
由於查詢天生的是冪等請求,所以針對於查詢場景可以不做業務角度的冪等約束,查詢冪等的約束多是針對於資源控制,安全防刷,流控來做的。
一個場景
試想有這樣一個場景: A系統傳遞userId和活動Id呼叫B系統發券,如果B系統發券成功,需要返回A系統本次發券userId和發券code。 由於B系統需要對自己發出去的券進行限制防止超發,所以會根據userId和code建立冪等攔截。 但是A系統的呼叫次數是不受信的,B系統會對多次重複的請求做攔截,這樣造成一部分A的請求為無效請求,被直接打回。 但是A系統接受B系統的返回值中是需要code的,如果沒有收到code,A系統會認為呼叫B系統失敗,進行重試,結果就造成了A系統不停被重試,B系統攔截無效請求,返回預設值,A再重試的死迴圈。
解決這個場景問題有兩種方法:
- 在B系統識別到A重複請求時,需要查詢流水錶,返回已經發送的code,組裝引數返回A系統,A系統識別到code,做本地記錄,不再呼叫B系統傳送。
- A系統呼叫B系統發券這個邏輯拆分成兩個介面,發券介面呼叫和查詢發券記錄。
第一種方案明顯的缺點在於,針對於重複傳送的請求都會轉化成一次查詢操作,這樣無形中加大了對於B系統資源的浪費,同時由於發券介面邏輯中引入了查詢邏輯,造成此介面違反了“單一職能原則”,在未來圍繞這個介面的新業務邏輯造成的程式碼修改時,比如允許對同一個使用者傳送多張券,可能出現潛在的bug問題。
第二種方案則是我選擇的更好的方案,也是更支援的方案,一個介面最好只做一件事,這樣一個介面只做發券,同時對於多次重複發券做請求攔截,沒有必要放無效請求到系統核心邏輯中,更沒有必要因此引入查詢邏輯消耗系統資源。 在呼叫B系統發券介面因為攔截重複請求,返回重複請求狀態碼後,系統A呼叫B系統的查詢介面,進行已傳送code的查詢,這樣在使用角度和後期業務迭代角度及系統資源使用和未來優化角度來說,都存在一定的空間,而不會造成程式碼複雜度提升引入隱患bug。
總結
針對於冪等操作還有如下幾種方案:
- 刪除/修改操作,一定要帶入版本號和原始修改引數,萬不可直接在下游邏輯中直接i++,i--
- 為進一步攔截真實資料羅庫,需要在資料庫表中建立唯一約束,防止因為分散式系統鎖問題或資料不一致問題導致攔截不到,這樣在DB層建立最後一次兜底策略
- token機制,可以做類似於頁面重複提交的功能,token可以放到redis中,並自帶實效
- 悲觀鎖/樂觀鎖,一般分為分散式鎖,單機鎖,update where
- 有限狀態機,訂單系統一般都會設計一個訂單狀態流轉的狀態機,表述在不同狀態下的狀態變更,只有在上一個狀態滿足時,才會進行接下來的狀態變更,這樣保證了狀態變更的冪等性
- 介面呼叫最好引入來源source,序列號seq等資訊,可以用source+seq做唯一索引,也可以將這兩個值上報做好監控
- 監控和開關,為可以更直觀的觀察系統冪等情況,可以建立對應的監控大盤,及告警配置,這樣可以更直觀的發現問題,同時配置相應的開關,在發現問題時比如被刷時,通過調控開關