深入理解冪等技術

JavaDog發表於2019-03-03

什麼是冪等

冪等(idempotent)是一個數學與計算機學概念,常見於抽象代數中。

在程式設計中,一個冪等操作的特點是,其任意多次執行所產生的影響均與一次執行的影響相同。冪等函式,或冪等方法,是指可以使用相同引數重複執行,並能獲得相同結果的函式。冪等函式可以改變系統的狀態。例如,setTrue()就是一個冪等函式,因為無論執行多少次,其結果都是一樣的。

冪等的場景有很多,例如:

  • 前端重複提交選中的資料,後臺只產生對應這個資料的一個反應結果;
  • 我們發起一筆付款請求,應該只扣使用者賬戶一次錢,當遇到網路重發或系統bug重發,也應該只扣一次錢;
  • 傳送訊息,也應該只發一次,同樣的簡訊發給使用者,使用者會崩潰;
  • 建立業務訂單,一次業務請求只能建立一個,建立多個就會出大問題。

冪等的技術手段

冪等並不是併發場景下的特有問題。冪等處理的是多次執行的問題,而併發僅僅是多次執行的一種形式。不管是依次執行,還是併發執行,都需要做好冪等。有些技術人員將解決併發問題的技術手段,例如悲觀鎖、樂觀鎖和分散式鎖,當成冪等的技術手段,這是不對的。

再次強調,冪等的核心是確保唯一性。

唯一索引

在資料庫中建立唯一索引,用作冪等記錄,可以防止插入重複的資料。 在冪等函式中,先執行一次查詢操作,如存在冪等記錄則返回第一次執行的結果,如不存在冪等記錄則繼續執行。在併發場景下,可能存在多個執行緒同時插入冪等記錄,這時候唯一索引可以確保只有一個執行緒插入成功,其它執行緒丟擲異常。

除了插入冪等記錄,應該還要插入其它的業務資料,這個時候務必使用事務。在實際工作中,冪等記錄與事務經常同時出現,如影相隨。

唯一資料

使用redis、memcache和zookeeper都可以實現唯一資料,這裡僅用redis的SETNX舉例。筆者從redis的官方文件摘抄了SETNX的用法,如下所示。

SETNX key value

將 key 的值設為 value ,當且僅當 key 不存在。

若給定的 key 已經存在,則 SETNX 不做任何動作。

SETNX 是『SET if Not eXists』(如果不存在,則 SET)的簡寫。

可用版本:
>= 1.0.0
時間複雜度:
O(1)
返回值:
設定成功,返回 1 。
設定失敗,返回 0 。
複製程式碼

在冪等函式中,將唯一標識作為key,任取value,呼叫SETNX。如果返回1,說明當前是第一次執行,繼續執行冪等函式;如果返回0,取出第一次執行的結果並返回給呼叫方。在併發場景下,可能因尚未完成第一次執行而取不到結果,這時候可以稍作等待。

除了redis、memcache和zookeeper,還有其它手段可以實現唯一資料,讀者可自行探索。只要可以實現唯一資料,就可以用來做冪等。

狀態機約束

在單據相關的業務,或者是任務相關的業務,基本會涉及到狀態機。業務單據上面有個狀態,這個狀態根據一個有限狀態機進行跳轉。如果狀態機已經處於下一個狀態,這時候是不能往回跳轉到上一個狀態的。通過狀態機的跳轉約束,可以做到有限狀態機的冪等。

冪等的場景

冪等經常與事務同時出現,而事務適合小任務場景、不適合大任務場景,因此筆者將冪等場景分為以下兩類進行介紹。為了方便描述,我們假設bizId可以唯一標識一筆業務。

小任務場景

這個場景的處理方式很簡單,可以追求強一致性。在冪等函式中,先判斷冪等記錄是否存在。如果存在,直接返回;如果不存在,開啟一個事務。在事務中,採用任務名+bizId作為冪等組合欄位,插入冪等記錄和業務資料。它的流程圖如下。

image.png

大任務場景

在大任務場景下,需要將大任務拆成多個小任務分別執行。在每個小任務中,都可以有事務。但是,沒有事務保證所有的小任務同時成功。因此,存在部分成功的場景。針對部分成功的場景,可以利用重試機制做到最終一致性。重試機制意味著多次執行,回到了冪等問題。這裡只介紹需要冪等的場景。如果同時存在需要冪等和不需要冪等的場景,請加入一個判斷標。

同步執行小任務

同步執行小任務的流程圖如下。各小任務依次執行,中間的小任務不返回結果,僅在最後一個小任務或之後返回結果。

image.png

非同步執行小任務

非同步執行小任務的流程圖如下。各小任務單獨執行,互相不感知,也沒有地方返回結果。

image.png

冪等欄位

不管是同步執行小任務,還是非同步執行小任務,都需要為每個小任務設定一個冪等欄位或冪等組合欄位。筆者推薦採用小任務名+bizId作為冪等組合欄位。一方面,bizId可以標識這一批小任務屬於同一筆業務;另一方便,小任務名可以區分不同的小任務。

碼字不易,如有建議請掃碼深入理解冪等技術

相關文章