代碼改變世界

系統冪等設計

2019-06-06 11:18 春哥大魔王 閱讀(...) 評論(...) 編輯 收藏

前言

冪等簡單的定義:

系統中的多次操作,不管多少次,都應該產生一樣的效果,或返回一樣的效果。

比如實際的業務請求為創建一個活動,理論上需要根據業務形態開發冪等創建活動的接口,這樣在相同參數調用接口多次創建活動時,只可以創建成功一次。

由于查詢天生的是冪等請求,所以針對于查詢場景可以不做業務角度的冪等約束,查詢冪等的約束多是針對于資源控制,安全防刷,流控來做的。

一個場景

試想有這樣一個場景:
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做唯一索引,也可以將這兩個值上報做好監控
  • 監控和開關,為可以更直觀的觀察系統冪等情況,可以建立對應的監控大盤,及告警配置,這樣可以更直觀的發現問題,同時配置相應的開關,在發現問題時比如被刷時,通過調控開關及時止損。

四川金7乐历史开奖号码查询