如何實現冪等性

什麼是冪等

冪等本來是數學上的概念,它的定義是這樣的: 如果一個函數 f(x) 滿足:f(f(x)) = f(x),則函數 f(x) 滿足冪等性。比如,求絕對值的函數,abs(x) = abs(abs(x))。

在計算機領域用來描述一個操作、方法或者服務。一個冪等操作的特點是,其任意多次執行所產生的影響均與一次執行的影響相同。

非冪等接口帶來的問題

我們把系統解耦隔離後,服務間的調用可能會有三個狀態,一個是成功(Success),一個是失敗(Failed),一個是超時(Timeout)。前兩者都是明確的狀態,而超時則是完全不知道是什麼狀態。比如,超時原因是網絡傳輸丟包的問題,可能是請求時就沒有請求到,也有可能是請求到了,返回結果時沒有正常返回等等情況。於是我們完全不知道下游系統是否收到了請求,而收到了請求是否處理了,成功 / 失敗的狀態在返回時是否遇到了網絡問題。總之,請求方完全不知道是怎麼回事。
舉幾個例子:

  • 訂單創建接口,第一次調用超時了,然後調用方重試了一次。是否會多創建一筆訂單?
  • 訂單創建時,我們需要去扣減庫存,這時接口發生了超時,調用方重試了一次。是否會多扣一次庫存?
  • 當這筆訂單開始支付,在支付請求發出之後,在服務端發生了扣錢操作,接口響應超時了,調用方重試了一次。是否會多扣一次錢?

因爲系統超時,而調用方重試一下,會給我們的系統帶來不一致的副作用。
在這種情況下,一般有兩種處理方式。

  • 一種是需要下游系統提供相應的查詢接口。上游系統在 timeout 後去查詢一下。如果查到了,就表明已經做了,成功了就不用做了,失敗了就走失敗流程。
  • 另一種是通過冪等性的方式。也就是說,把這個查詢操作交給下游系統,我上游系統只管重試,下游系統保證一次和多次的請求結果是一樣的。

對於第一種方式,需要對方提供一個查詢接口來做配合。而第二種方式則需要下游的系統提供支持冪等性的交易接口。


場景

將林志玲賬戶的餘額加 100 元

方法一(推薦使用): 令牌機制(全局ID) (記錄並檢查操作)

在發送消息時,給每條消息指定一個全局唯一的ID,消費時,先根據這個ID檢查這條消息是否有被消費過,如果沒有消費過更新數據,然後將消費狀態置爲已消費。

  1. 服務器端派發token並將token記錄在緩存中, 客戶端攜帶此token請求服務, 如果此token有效證明是有效請求處理請求, 並刪除token。token無效忽略此請求。
  2. 客戶端請求攜帶一個唯一標識(流水號),服務器判斷此唯一標識是否已經存在,如果已存在忽略此請求。

方法二:利用數據庫的唯一約束實現冪等(利用數據庫實現冪等性)

我們在數據庫中建一張轉賬流水錶,這個表有三個字段:轉賬單 ID、賬戶 ID 和變更金額,然後給轉賬單 ID 和賬戶 ID 這兩個字段聯合起來創建一個唯一約束,這樣對於相同的轉賬單 ID 和賬戶 ID,表裏至多隻能存在一條記錄。

方法三:爲更新的數據設置前置條件(將方法實現冪等)

  1. 方法入參傳入林志玲的賬戶餘額,拿參數中的餘額與數據庫中的餘額做比較如果相同則執行變更操作。
  2. 最簡單的做法給數據增加一個版本號屬性,每次更改數據前,比較當前數據的版本號是否和消息中的版本一致,如果不一致就拒絕更新數據,更新數據的同時將版本號 1。(和樂觀鎖原理一樣)

實現冪等的核心是判斷請求是否重複,具體實現方式比較多,想設計一個高可用的冪等方法還需要我們具體業務具體分析具體設計。


HTTP的冪等性

HTTP GET方法用於獲取資源,不應有副作用,所以是冪等的。 比如: GET

http: / /www . bank . com/ account/123456,不會改變資源的狀態,不論調用一次還是 N次都沒有副作用。請注意,這裏強調的是- -次和N次具有相同的副作用,而不是每次GET的結果相同。GET http:/ /www. news . com/latest-news這個HTTP請求可能會每次得到不同的結果,但它本身並沒有產生任何副作用,因而是滿足冪等性的。

HTTP HEAD和GET本質是一樣的,區別在於HEAD不含有呈現數據,而僅僅是HTTP頭信息,不應用有副作用,也是冪等的。 有的人可能覺得這個方法沒什麼用,其實不是這樣的。想象一個業務情景:欲判斷某個資源是否存在,我們通常使用GET,但這裏用HEAD則意義更加明確。也就是說,HEAD 方法可以用來做探活使用。

HTTP OPTIONS主要用於獲取當前URL所支持的方法,所以也是冪等的。 若請求成功,則.它會在HTTP頭中包含一個名爲“Allow”的頭,值是所支持的方法,如“GET, POST"。

HTTP DELETE方法用於刪除資源,有副作用,但它應該滿足冪等性。 比如: DELETE
http://www. forum. com/article/4231,調用一次和N次對系統產生的副作用是相同的,即刪掉ID爲4231的帖子。因此,調用者可以多次調用或刷新頁面而不必擔心引起錯誤。

HTTP POST方法用於創建資源,所對應的URI並非創建的資源本身,而是去執行創建動作的操作者,有副作用,不滿足冪等性。 比如: POST http:/ /www. forum. com/ articles的語義是在http: //www. forum. com/articles下創建一篇帖子, HTTP響應中應包含帖子的創建狀態以及帖子的URI。兩次相同的POST請求會在服務器端創建兩份資源,它們具有不同的URI;所以,POST方法不具備冪等性。

HTTP PUT方法用於創建或更新操作,所對應的URI是要創建或更新的資源本身,有副作用,它應該滿足冪等性。 比如: PUT http: / /www. forum/articles/4231的語義是創建或更新ID爲4231的帖子。對同一URI進行多次PUT的副作用和一次PUT是相同的;因此,PUT方法具有冪等性。

所以,對於POST的方式,很可能會出現多次提交的問題,就好比,我們在論壇中發貼時,有時候因爲網絡有問題,可能會對同一篇貼子出現多次提交的情況。對此的一-般的冪等性的設計如下。

  • 首先,在表單中需要隱藏一個token,這個token可以是前端生成的一個唯一的ID。用 於防止用戶多次點擊了表單提交按鈕,而導致後端收到了多次請求,卻不能分辨是否是重複的提交。這個token是表單的唯一標識。 (這種情況其實是 通過前端生成ID把POST變成了PUT。)
  • 然後,當用戶點擊提交後,後端會把用戶提示的數據和這個token保存在數據庫中。如果有重複提交,那麼數據庫中的token會做排它限制,從而做到冪等性。
  • 當然,更爲穩妥的做法是,後端成功後向前端返回302跳轉,把用戶的前端頁跳轉到GET請求,把剛剛POST的數據給展示出來。如果是Web.上的最好還把之前的表單設置成過期,這樣用戶不能通過瀏覽器後退按鈕來重新提交。這個模式又叫做OPRG模式(Post/Redirect/Get)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章