每個工程師都應該瞭解的:聊聊冪等

FrankYou發表於2017-06-13

轉載至:http://mp.weixin.qq.com/s/EDkuMW5-hdIxs2GXMFnj_Q

什麼是冪等(Idempotency)?簡單來說,一個操作如果具有任意多次執行所產生的影響均與一次執行的影響相同,我們就稱之為冪等。

 

這樣說來,似乎很容易理解。但要知道,這樣的定義,其實是一個語義範疇對行為結果的定義。如何用語法和規則去確保行為能達到這個結果,往往需要很謹慎的設計和實現。實際系統中,冪等是一個極為重要的概念。無論是在大型網際網路應用還是企業級架構中,我們都見到 REST API 被越來越多的採用。而正確實現冪等,往往是 API 中最難的技術點之一。

 

先說為什麼重要。舉一個簡單易懂的例子。

 

比如你要處理一次電商網站收款或者付款的交易。當你給微信支付傳送這個付款請求後,一個順利的場景,是沒有任何錯誤發生,微信支付收到你的付款請求,處理所有轉賬,然後返回一個 HTTP 200 訊息表示交易完成。

 

那如果發出請求後,有個請求超時,你再也沒有收到關於這個請求是成功還是失敗的的回執,又該如何呢?

 

這裡就有很多種可能情況:

  • 這個請求在到達微信支付端前就已經發生超時,微信支付從來沒有收到這樣的請求。

  • 這個請求到達微信支付端,但是支付交易失敗,這時發生超時,微信支付收到這樣的請求,但沒有處理成功。

  • 這個請求到達微信支付端,並且支付交易成功,這時發生超時,微信支付收到這樣的請求,處理成功,但是沒有回執。

  • 這個請求到達微信支付端,並且支付交易成功,並且發回回執,然而因為網路原因回執丟失,客戶端超時,微信支付收到這樣的請求,處理成功,發出回執,但是客戶沒有收到。

 

很直觀的一個想法,也是現實中使用者最常見的做法,是重新提交一次支付請求。但是這樣就有一個潛在的問題:請求超時是上面的哪一種情況?會不會引發多次支付的可能性?

 

這就涉及到系統中的冪等是如何實現的了。

 

那麼冪等又該如何實現呢?“多次執行所產生的影響均與一次執行的影響相同”,簡而言之,我們需要一個 Dedup(去重)的機制。這往往有很多不同的實現方法,但是有兩個很關鍵的因素:

 

一是 Idempotency Key(冪等令牌)。也就是客戶端和伺服器端通過什麼來識別這實際上是同一個請求,或是同一個請求的多次 retry(嘗試)。這往往需要雙方又一個既定的協議。往往是類似賬單號或者交易 token(令牌)這樣一個可以唯一標識同一個請求意願的元素。通常由客戶端生成。

 

二是 Uniqueness Guarantee(確保唯一性)。伺服器端用什麼機制去確保同一個請求一定不會被處理兩次,也就是微信支付怎麼確保同一筆交易不會因為客戶端傳送兩次請求就被處理多次。最通常的做法是利用資料庫。比如把冪等令牌所在的資料庫表的 Column(列)作為 unique indexed。這樣,當你試圖儲存兩個含有同樣令牌的請求時,必定有一個會報錯。注意,簡單的讀檢查並不一定行,因為讀與讀之間會有 Race Condition(競爭條件),因此還是有可能出錯。

 

如果一個系統可以正確的處理和實現上面的兩個要素,那麼基本就能達到冪等的需求。那麼現實系統中常見的問題都出在哪裡呢?

 

  1. 一是冪等令牌什麼時候產生,怎麼產生?這一點很重要。拿上面的例子來說。就算微信支付可以保證每一個請求對應的支付交易一定只會被處理一次。但是這個請求的多次重複,一定要共有某一個微信可以識別的標識。假如客戶端對同一筆交易的多次請求,產生的冪等令牌並不相同,那不論你別的地方多麼完美,都沒有可能保證 “一個操作如果具有任意多次執行所產生的影響均與一次執行的影響相同”。

  2. 二是有沒有令牌被誤刪的可能。這是上面的問題的一個特殊情況。冪等令牌是由客戶端生成的。那麼如果生成的令牌在被使用後(一次微信支付請求中使用了),不小心因為 DB rollback 等原因被刪除了。那麼客戶端就不知道自己其實已經發過一次請求。就有可能生成一個新的賬單,併產生全新的令牌,而服務端將對此一無所知。

  3. 三是各種競爭條件。上面說的用 DB 讀來確保唯一性經常因為競爭而不工作。其實一個需要冪等的系統中,保證唯一性的各個環節和實現,都要考慮 Race Condition。

  4. 四是對請求 Retry 的處理。這大部分是伺服器端要做的。一個常見的方法是區分正在處理的請求、和處理成功、處理失敗的請求。這樣當客戶端重試的時候,根據情況或者直接返回,或者再次處理。就好像前面說的微信支付的例子。微信支付服務上,需要知道每一筆交易的處理情況,才能正確處理在此轉賬請求時,是不是需要進行任何動作。

  5. 五是一個系統中需要多層冪等。什麼意思呢?A 傳送請求給 B,B 處理的一部分是要傳送請求給另一個系統 C,C 在處理的過程中還可能需要發請求給另一個系統 D…… D 處理完了返回給 C,C 返回給 B,B 返回給 A。在這個鏈條中,如果 A B C D 中任何一個系統並沒有正確實現冪等,也就是出現了 “冪等漏洞”,那麼一個請求還是有可能被多次執行,產生區別於一次執行的影響。

 

在回到本文的開頭,什麼是冪等?一個操作如果具有任意多次執行所產生的影響均與一次執行的影響相同,我們就稱之為冪等。

 

這樣的語義範疇對行為結果的定義,只有當你的實現中所有的細節都做對了,你才能得到想要的效果。任何一個地方設計有漏洞,或是實現有 Bug,那還是不成。

相關文章