談談冪等技術(二)

一、前言

前面我們討論了《如何基於冪等表實現冪等處理》,本文我們就來看看如何基於樂觀鎖、悲觀鎖來做冪等處理。

二、基於數據庫樂觀鎖進行冪等處理

首先我們看如何採用數據庫的行鎖+樂觀鎖來實現冪等。

在mysql Innodb存儲引擎裡面實現了行鎖功能,當我們根據id去更新記錄時就會獲取到行鎖。多個線程根據同一個記錄id去更新行記錄時只有一個線程可以獲取到鎖,其他線程會阻塞。

樂觀鎖的實現方式常見有兩種:

  • 在業務表裡面添加一個version版本字段
  • 使用業務表裡面自帶的狀態機字段
    比如訂單流程,每個訂單狀態有:創建->支付->發貨->驗貨等等。但是需要注意狀態機不能出現迴路,因為這會導致ABA問題。

上面兩種方式本質一樣,不同在於如果業務表裡面自帶的狀態機字段,那麼我們就不必額外加一個version字段了。下面我們統一稱version和狀態字段為冪等字段。

基於樂觀鎖實現冪等流程:

  • 根據select ... from biz_table where id = #id and 冪等字段=冪等字段值拿到DO對象
  • 根據DO對象進行處理:可能是修改DO對象裡面的某些值
  • 進行樂觀鎖冪等:update biz_table set 冪等字段=新冪等值... where id = #id and 冪等字段= #DO對象.冪等字段;

如果使用version作為冪等處理字段,則上面第三步可以修改為:update biz_table set version=version+1... where id = #id and version= #DO對象.version;

如果使用業務狀態作為冪等處理字段,則上面第三步可以修改為:update biz_table set 狀態字段=狀態機的下一個狀態... where id = #id and 狀態字段= #DO對象.狀態字段;

可知基於樂觀鎖時,我們基於第三步做冪等處理。當多個相同id的請求同時(併發)或者先後(順序)過來後,第一和第二步可能是併發或者順序執行,但是第三步只有一個請求會返回1,其他都返回0,這就實現了冪等處理.

需要注意的是樂觀鎖方式在下面這種場景才用(以基於版本方案實現樂觀鎖為例):


談談冪等技術(二)

image.png


也就是服務B內可以實現冪等處理前提是,調用方A把記錄行id和行記錄對應的版本號以參數形式傳遞過來了。

如下時序圖中,服務A調用服務B時候如果只是把記錄id傳遞給服務B,則當服務A順序多次以相同記錄id調用服務B時候,服務B是實現不了冪等的(因為多次調用時步驟2,3,4都會被執行)。


談談冪等技術(二)

image.png

三、基於數據庫悲觀鎖進行冪等處理

恕我直言,基於悲觀鎖實現不了通用的冪等處理,為何那?且讓我們一一道來。

我們且來回憶一下冪等技術用來保證唯一性,就是相同參數的多次請求和一次請求對業務效果都一樣。

而悲觀鎖處理流程一般為:

  • 開啟事務
  • select ...from biz_table where id = #id for update 對行記錄加鎖,並返回DO對象
  • 對DO對象進行處理
  • update biz_table set ... where id = #id
  • 提交或者回滾事務

那麼當多個id一樣的請求順序或者並行過來後,會導致上面五個步驟都執行(雖然併發過來時候,可能多個請求會暫時hold到步驟2),如果步驟三本身不是冪等的,那麼這就起不到冪等作用了。

四、總結

這裡我們補充下,冪等技術不是簡單的對N多相同請求參數的請求,只處理其中一個,其他的請求忽略,直接返回。而是要保證即使這N多請求都處理了,但是處理的結果的效果和一次處理結果一樣,所謂處理結果是指對業務的影響。

本節講解的樂觀鎖方式相比基於冪等表方式,對業務入侵比較大,需要在業務表添加一個版本字段或者強依賴業務狀態字段。


分享到:


相關文章: