天天看點

談談幂等技術(二)一、前言二、基于資料庫樂觀鎖進行幂等處理三、基于資料庫悲觀鎖進行幂等處理四、總結

一、前言

前面我們讨論了《如何基于幂等表實作幂等處理》,本文我們就來看看如何基于樂觀鎖、悲觀鎖來做幂等處理。

二、基于資料庫樂觀鎖進行幂等處理

首先我們看如何采用資料庫的行鎖+樂觀鎖來實作幂等。

在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多請求都處理了,但是處理的結果的效果和一次處理結果一樣,所謂處理結果是指對業務的影響。

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