一、前言
前面我們讨論了《如何基于幂等表實作幂等處理》,本文我們就來看看如何基于樂觀鎖、悲觀鎖來做幂等處理。
二、基于資料庫樂觀鎖進行幂等處理
首先我們看如何采用資料庫的行鎖+樂觀鎖來實作幂等。
在mysql Innodb存儲引擎裡面實作了行鎖功能,當我們根據id去更新記錄時就會擷取到行鎖。多個線程根據同一個記錄id去更新行記錄時隻有一個線程可以擷取到鎖,其他線程會阻塞。
樂觀鎖的實作方式常見有兩種:
- 在業務表裡面添加一個version版本字段
-
使用業務表裡面自帶的狀态機字段
比如訂單流程,每個訂單狀态有:建立->支付->發貨->驗貨等等。但是需要注意狀态機不能出現回路,因為這會導緻ABA問題。
上面兩種方式本質一樣,不同在于如果業務表裡面自帶的狀态機字段,那麼我們就不必額外加一個version字段了。下面我們統一稱version和狀态字段為幂等字段。
基于樂觀鎖實作幂等流程:
- 根據
拿到DO對象select ... from biz_table where id = #id and 幂等字段=幂等字段值
- 根據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多請求都處理了,但是處理的結果的效果和一次處理結果一樣,所謂處理結果是指對業務的影響。
本節講解的樂觀鎖方式相比基于幂等表方式,對業務入侵比較大,需要在業務表添加一個版本字段或者強依賴業務狀态字段。