介面服務中的冪等性設計和防重保證,詳細分析冪等性的幾種實現方法

攻城獅Chova發表於2021-06-19

什麼是冪等性

  • 冪等性定義:
    • 一次和多次請求某一個資源對於資源本身應該具有同樣的結果
    • 任意多次執行對資源本身所產生的影響均與一次執行的影響相同
  • 冪等性定義的幾個重點:
    • 冪等不僅僅只是一次或者多次請求對資源沒有副作用
      • 比如,查詢資料庫操作,沒有增刪改,無論多少次操作對資料庫都沒有任何影響
    • 冪等還包括第一次請求的時候對資源產生了副作用,但是以後的多次請求都不會再對資源產生副作用
    • 冪等關注的是以後多次請求是否對資源產生副作用,並不關注結果
    • 網路超時等問題,不是冪等的討論範圍
  • 冪等性是系統服務對外一種承諾,而不是實現
  • 承諾只要呼叫介面成功,外部多次呼叫對系統的影響是一致的
  • 宣告為冪等的服務會認為外部呼叫失敗是常態,並且失敗後必然會有重試

冪等性的使用場景

  • 業務開發中,經常遇到重複提交的情況:
    • 由於網路問題無法收到請求結果而重新發起請求
    • 前端的操作抖動而造成的重複提交的情況
  • 在交易系統中,支付系統這種重複提交造成的問題尤為明顯:
    • 使用者在APP上連續點選多次提交訂單,後臺應該只產生一個訂單
    • 向支付系統發起請求,由於網路問題或者系統Bug問題導致重發,支付系統應該只做一次扣除操作
  • 宣告冪等的服務認為,外部呼叫者會存在多次呼叫的情況,為了防止外部多次呼叫對系統的資料狀態發生多次改變,需要將服務設計為冪等

冪等和防重

  • 重複提交的情況和服務冪等的初衷是不同的
    • 重複提交是在第一次請求已經成功的情況下 ,人為地進行多次操作, 導致不滿足冪等要求的服務多次改變狀態
    • 冪等更多使用的情況是第一次請求因為某些情況,不如超時,而導致不知道結果或者請求失敗的異常情況下,發起多次請求
  • 冪等的目的是請求多次確認第一次請求成功,不會因為多次請求而出現多次的狀態變化

保證冪等性的情況

  • 在SQL中,有以下三種場景,只有第三種場景需要保證冪等性:
    • SELECT col1 FROM tab1 WHERE col2=2 : 無論執行多少次都不會改變狀態,是天然的冪等
    • UPDATE tab1 SET col1=1 WHERE col2=2 : 無論執行成功多少次狀態都是一致的,也是冪等操作
    • UPDATE tab1 SET col1=col1+1 WHERE col2=2: 每次執行的結果都會發生變化,這種不是冪等的,要採取策略保證冪等性

設計冪等性服務

  • 冪等使得客戶端邏輯處理很簡單,但是服務端邏輯會很複雜
  • 滿足冪等性服務需要包含兩點邏輯:
    • 首先去查詢上一次的執行狀態,如果沒有則認為是第一次請求
    • 在服務改變狀態的業務邏輯前保證防重複提交的邏輯

保證冪等策略

  • 冪等需要通過唯一的業務單號來保證:
    • 相同的業務單號,認為是同一業務
    • 使用唯一的業務單號確保:後面多次相同業務單號的處理邏輯和執行效果是一致的
  • 冪等實現示例-支付:
    • 先查詢訂單是否支付過
    • 如果已經支付過,返回支付成功
    • 如果沒有支付,則進行支付流程,修改訂單的狀態為已支付

防重複提交策略

  • 在保證冪等的策略中,執行是分兩步執行的,後面一步依賴上面一步的查詢結果,這樣就無法保證原子性
  • 無法保證原子性在高併發的情況下會存在問題:
    • 第二次請求在第一次請求的下一步訂單狀態沒有修改為"已支付狀態"時進行
    • 為了解決這個問題 :將查詢和變更狀態操作加鎖,並將並行操作改為序列執行
樂觀鎖
  • 如果只是更新已有的資料,沒有必要對業務進行加鎖
  • 設計表結構時使用樂觀鎖,一般通過version來實現樂觀鎖:
    • 保證執行效率
    • 保證冪等
  UPDATE tab1
  SET	col1=1,version=version+1
  WHERE	version=#version# 

由於ABA問題會導致樂觀鎖存在失效的情況,只要保證version值自增就不會出現ABA的問題

防重表
  • 使用orderNo作為去重表中的唯一索引,每次請求都根據訂單號orderNo向去重表中插入一條資料:
    • 第一次請求查詢訂單支付狀態:
      • 訂單沒有支付
      • 進行支付操作
      • 無論成功與否,執行完成之後更新訂單的狀態為成功或失敗,刪除去重表中的資料
      • 後續訂單因為表中的唯一索引插入失敗,返回操作失敗,直到第一次請求完成(成功或者失敗)
  • 防重表的作用是實現加鎖的功能
分散式鎖
  • 可以使用Redis分散式鎖代替防重表的功能
  • 示例:
    • 訂單發起支付請求
    • 支付系統會去Redis快取中查詢是否存在該訂單Key
    • 如果不存在,向Redis中增加Key為訂單號
    • 查詢訂單支付是否已經支付
    • 如果沒有則進行支付,支付完成後刪除該訂單的Key
  • 通過Redis實現分散式鎖,只有這次訂單請求完成,下次請求才會進來
  • 對比去重表,Redis分散式鎖將放併發做在快取中,效率更高
  • 同一時間只能完成一次支付請求
token令牌
  • token令牌分為兩個階段:
    • 申請token階段:
      • 在進入到提交訂單頁面之前,需要訂單系統根據使用者資訊向支付系統發起一次申請token的請求
      • 支付系統將token儲存到Redis快取中,給支付階段使用
    • 支付階段:
      • 訂單系統獲取到申請的token, 發起支付請求,
      • 支付系統檢查Redis是否存在該token
        • 如果存在,表示第一次發起支付請求,刪除快取中的token開始支付邏輯處理
        • 如果快取中不存在,表示非法請求
支付緩衝區
  • 支付緩衝區:
    • 將訂單的支付請求都快速地接收下來,是一個快速接收請求的緩衝管道
    • 使用非同步任務處理管道中的資料,過濾調掉重複的待支付的資料
  • 優點: 同步轉非同步,高吞吐
  • 缺點: 無法及時返回支付結果,需要後續監聽支付結果的非同步返回
冪等的不足:
- 冪等是為了簡化客戶端邏輯,但是增加了服務提供者的邏輯和成本
- 冪等的使用需要根據具體場景具體分析
- 增加了額外控制冪等的業務邏輯,複雜了業務功能
- 將並行的功能轉化為序列,降低了執行效率

相關文章