天天看點

幂等政策分析

幂等概念來自數學,表示N次變換和1次變換的結果是相同的。這裡讨論在某些場景下,用戶端在調用服務沒有達到預期結果時,會進行多次調用,為避免多次重複的調用對服務資源産生副作用,服務提供者會承諾滿足幂等。

幂等政策分析
舉個栗子,雙十一零點剛過,小明就迫不及待地點選送出訂單按鈕,選擇線上支付,點了确認支付按鈕,這時候網絡有些慢,小明擔心心愛的商品被搶購一空,就點了多次确認付款按鈕,如果這個訂單扣款多次,客服熱線估計會被小明打爆。

什麼是幂等性

HTTP/1.1中對幂等性的定義是:一次和多次請求某一個資源對于資源本身應該具有同樣的副作用(網絡逾時等問題除外)。也就是說,其任意多次執行對資源本身所産生的影響均與一次執行的影響相同。

Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.

這裡需要關注幾個重點:

  1. 幂等不僅僅隻是一次(或多次)請求對資源沒有副作用(比如查詢資料庫操作,沒有增删改,是以沒有對資料庫有任何影響)。
  2. 幂等還包括第一次請求的時候對資源産生了副作用,但是以後的多次請求都不會再對資源産生副作用。
  3. 幂等關注的是以後的多次請求是否對資源産生的副作用,而不關注結果。
  4. 網絡逾時等問題,不是幂等的讨論範圍。

幂等性是系統服務對外一種承諾(而不是實作),承諾隻要調用接口成功,外部多次調用對系統的影響是一緻的。聲明為幂等的服務會認為外部調用失敗是常态,并且失敗之後必然會有重試。

什麼情況下需要幂等

業務開發中,經常會遇到重複送出的情況,無論是由于網絡問題無法收到請求結果而重新發起請求,或是前端的操作抖動而造成重複送出情況。

在交易系統,支付系統這種重複送出造成的問題有尤其明顯,比如:

  1. 使用者在APP上連續點選了多次送出訂單,背景應該隻産生一個訂單;
  2. 向支付寶發起支付請求,由于網絡問題或系統BUG重發,支付寶應該隻扣一次錢。

    很顯然,聲明幂等的服務認為,外部調用者會存在多次調用的情況,為了防止外部多次調用對系統資料狀态的發生多次改變,将服務設計成幂等。

幂等VS防重

上面例子中小明遇到的問題,隻是重複送出的情況,和服務幂等的初衷是不同的。重複送出是在第一次請求已經成功的情況下,人為的進行多次操作,導緻不滿足幂等要求的服務多次改變狀态。而幂等更多使用的情況是第一次請求不知道結果(比如逾時)或者失敗的異常情況下,發起多次請求,目的是多次确認第一次請求成功,卻不會因多次請求而出現多次的狀态變化。

什麼情況下需要保證幂等性

以SQL為例,有下面三種場景,隻有第三種場景需要開發人員使用其他政策保證幂等性:

  1. SELECT col1 FROM tab1 WHER col2=2

    ,無論執行多少次都不會改變狀态,是天然的幂等。
  2. UPDATE tab1 SET col1=1 WHERE col2=2

    ,無論執行成功多少次狀态都是一緻的,是以也是幂等操作。
  3. UPDATE tab1 SET col1=col1+1 WHERE col2=2

    ,每次執行的結果都會發生變化,這種不是幂等的。

為什麼要設計幂等性的服務

幂等可以使得用戶端邏輯處理變得簡單,但是卻以服務邏輯變得複雜為代價。滿足幂等服務的需要在邏輯中至少包含兩點:

  1. 首先去查詢上一次的執行狀态,如果沒有則認為是第一次請求
  2. 在服務改變狀态的業務邏輯前,保證防重複送出的邏輯

幂等的不足

幂等是為了簡化用戶端邏輯處理,卻增加了服務提供者的邏輯和成本,是否有必要,需要根據具體場景具體分析,是以除了業務上的特殊要求外,盡量不提供幂等的接口。

  1. 增加了額外控制幂等的業務邏輯,複雜化了業務功能;
  2. 把并行執行的功能改為串行執行,降低了執行效率。

保證幂等政策

幂等需要通過唯一的業務單号來保證。也就是說相同的業務單号,認為是同一筆業務。使用這個唯一的業務單号來確定,後面多次的相同的業務單号的處理邏輯和執行效果是一緻的。

下面以支付為例,在不考慮并發的情況下,實作幂等很簡單:①先查詢一下訂單是否已經支付過,②如果已經支付過,則傳回支付成功;如果沒有支付,進行支付流程,修改訂單狀态為‘已支付’。

防重複送出政策

上述的保證幂等方案是分成兩步的,第②步依賴第①步的查詢結果,無法保證原子性的。在高并發下就會出現下面的情況:第二次請求在第一次請求第②步訂單狀态還沒有修改為‘已支付狀态’的情況下到來。既然得出了這個結論,餘下的問題也就變得簡單:把查詢和變更狀态操作加鎖,将并行操作改為串行操作。

樂觀鎖

如果隻是更新已有的資料,沒有必要對業務進行加鎖,設計表結構時使用樂觀鎖,一般通過version來做樂觀鎖,這樣既能保證執行效率,又能保證幂等。例如:

UPDATE tab1 SET col1=1,version=version+1 WHERE version=#version#

不過,樂觀鎖存在失效的情況,就是常說的ABA問題,不過如果version版本一直是自增的就不會出現ABA的情況。(從網上找了一張圖檔很能說明樂觀鎖,引用過來,出自Mybatis對樂觀鎖的支援)

幂等政策分析

防重表

使用訂單号orderNo做為去重表的唯一索引,每次請求都根據訂單号向去重表中插入一條資料。第一次請求查詢訂單支付狀态,當然訂單沒有支付,進行支付操作,無論成功與否,執行完後更新訂單狀态為成功或失敗,删除去重表中的資料。後續的訂單因為表中唯一索引而插入失敗,則傳回操作失敗,直到第一次的請求完成(成功或失敗)。可以看出防重表作用是加鎖的功能。

幂等政策分析

分布式鎖

這裡使用的防重表可以使用分布式鎖代替,比如Redis。訂單發起支付請求,支付系統會去Redis緩存中查詢是否存在該訂單号的Key,如果不存在,則向Redis增加Key為訂單号。查詢訂單支付已經支付,如果沒有則進行支付,支付完成後删除該訂單号的Key。通過Redis做到了分布式鎖,隻有這次訂單訂單支付請求完成,下次請求才能進來。相比去重表,将放并發做到了緩存中,較為高效。思路相同,同一時間隻能完成一次支付請求。

幂等政策分析

token令牌

這種方式分成兩個階段:申請token階段和支付階段。

第一階段,在進入到送出訂單頁面之前,需要訂單系統根據使用者資訊向支付系統發起一次申請token的請求,支付系統将token儲存到Redis緩存中,為第二階段支付使用。

第二階段,訂單系統拿着申請到的token發起支付請求,支付系統會檢查Redis中是否存在該token,如果存在,表示第一次發起支付請求,删除緩存中token後開始支付邏輯處理;如果緩存中不存在,表示非法請求。

實際上這裡的token是一個信物,支付系統根據token确認,你是你媽的孩子。不足是需要系統間互動兩次,流程較上述方法複雜。

幂等政策分析

支付緩沖區

把訂單的支付請求都快速地接下來,一個快速接單的緩沖管道。後續使用異步任務處理管道中的資料,過濾掉重複的待支付訂單。優點是同步轉異步,高吞吐。不足是不能及時地傳回支付結果,需要後續監聽支付結果的異步傳回。

我是葛一凡,希望對你有幫助。

幂等政策分析

微信公衆号

參考

    1. 高并發的核心技術-幂等的實作方案
    2. 防重複請求處理的實踐與總結
    3. 分布式服務協調—幂等(Idempotent)機制
    4. 分布式系統接口幂等性
    5. 幂等性 個人了解及應用
    6. 程式設計中的幂等性 —— HTTP幂等性
    7. 系統幂等以及常用實作方式
    8. 高并發系統資料幂等的技術嘗試
    9. Mybatis對樂觀鎖的支援

作者:葛一凡

出處:http://geyifan.cn/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

繼續閱讀