1. 程式人生 > 實用技巧 >分散式事務的5種解決方案

分散式事務的5種解決方案

分散式事務的幾種解決方式

分散式事務指事務的操作位於不同的節點上,需要保證事務的AICD特性

1、 兩階段提交方案/XA方案

兩階段提交,通過引入協調者,來協調參與者的行為,並最終決定這些參與者是否要真正執行事務。

執行過程

(1) 準備階段
協調者詢問參與者事務是否執行成功,參與者發回事務執行結果
在這裡插入圖片描述

(2) 提交階段

如果事務在每個參與者上都執行成功,事務協調者傳送通知讓參與者提交事務;否則,協調者傳送通知讓參與者回滾事務。

需要注意的是,在準備階段,參與者執行了事務,但是還未提交。只有在提交階段接收到協調者發來的通知後,才進行提交或者回滾。
在這裡插入圖片描述

存在的問題:

(1) 同步阻塞:所有事務參與者在等待其他參與者響應的時候都處於同步阻塞狀態,無法進行其他操作。

(2) 單點問題:協調者在方案中起到非常大的作用,發生故障將會造成很大的影響,特別是在二階段發生故障,所有參與者會一直等待狀態,無法完成其他操作。

(3) 資料不一致:在階段二,如果協調者只發送了部分Commit訊息,此時網路發生異常,那麼只有部分參與者接收到commit訊息,也就是說只有部分參與者提交了事務,使得系統資料不一致。

(4) 太過保守:任意一個節點失敗就會導致整個事務失敗,沒有完善的容錯機制。

這種分散式事務方案,比較適合單塊應用裡,跨多個庫的分散式事務,而且因為嚴重依賴於資料庫層面來搞定複雜的事務,效率很低,絕對不適合高併發的場景,如果要使用,那麼基於Spring+JTA就可以搞定。

這個方案很少用,一般來說某個系統內部如果出現跨多個庫的這麼一個操作,是不合規矩的。現在為服務,一般來說我們的規定和規範,是要求說每個服務只能操作自己對應的一個數據庫。

如果你要操作別的服務對應的庫,不允許直連別的服務的庫,違反微服務架構的規範,你隨便交叉胡亂訪問,幾百個服務的話,全體亂套,這樣的一套服務是沒法管理的,沒法治理的,經常資料被別人改錯,自己的資料庫又被別人寫掛。

2、 補償事務(TCC)

TCC的全稱是:Try、Confirm、Cancel

這個其實是用了補償的概念,分為了三個階段:

(1) Try階段:這個階段說的是對各個服務的資源做檢測以及對資源進行鎖定或者預留

(2) Confirm階段

:這個階段說的是在各個服務中執行實際的操作

(3) Cancel階段:如果任何一個服務的業務方法執行出錯,那麼這裡就需要進行補償,就是執行已經執行成功的業務邏輯的回滾操作,插入的要刪除,刪除要插入,修改了的改回來。

舉個例子,假入 Bob 要向 Smith 轉賬,思路大概是:我們有一個本地方法,裡面依次呼叫

(1) 首先在 Try 階段,要先呼叫遠端介面把 Smith 和 Bob 的錢給凍結起來。

(2) 在 Confirm 階段,執行遠端呼叫的轉賬的操作,轉賬成功進行解凍。

(3) 如果第2步執行成功,那麼轉賬成功,如果第二步執行失敗,則呼叫遠端凍結介面對應的解凍方法 (Cancel)。

這種方案几乎很少人使用,因為這個事務回滾實際上是嚴重依賴於你自己寫程式碼來回滾和補償了,會造成補償程式碼巨大,非常噁心,在一些場景中,一些業務流程可能用TCC不太好定義及處理。

比較適合的場景

這個就是除非你是真的一致性要求太高,是你係統中核心之核心的場景,比如常見的就是資金類的場景,那你可以用TCC方案了,自己編寫大量的業務邏輯,自己判斷一個事務中的各個環節是否OK,不OK就執行補償/回滾程式碼,而且最好是你的各個業務執行的時間都比較短。

比如說:一般跟錢相關的,支付、交易的場景,可以使用TCC,嚴格保證分散式事務要麼全部成功,要麼全部自動回滾,嚴格保證資金的正確性。

3、 本地訊息表(非同步確保)

本地訊息表這種實現方式應該是業界使用最多的,其核心思想是將分散式事務拆分成本地事務進行處理,這種思路是來源於ebay。

基本思路就是:

訊息生產方,需要額外建一個訊息表,並記錄訊息傳送狀態。訊息表和業務資料要在一個事務裡提交,也就是說他們要在一個數據庫裡面。然後訊息會經過MQ傳送到訊息的消費方。如果訊息傳送失敗,會進行重試傳送。

訊息消費方,需要處理這個訊息,並完成自己的業務邏輯。此時如果本地事務處理成功,表明已經處理成功了,如果處理失敗,那麼就會重試執行。如果是業務上面的失敗,可以給生產方傳送一個業務補償訊息,通知生產方進行回滾等操作。

生產方和消費方定時掃描本地訊息表,把還沒處理完成的訊息或者失敗的訊息再發送一遍。如果有靠譜的自動對賬補賬邏輯,這種方案還是非常實用的。

這種方案遵循BASE理論,採用的是最終一致性,筆者認為是這幾種方案裡面比較適合實際業務場景的,即不會出現像2PC那樣複雜的實現(當呼叫鏈很長的時候,2PC的可用性是非常低的),也不會像TCC那樣可能出現確認或者回滾不了的情況。

優點: 一種非常經典的實現,避免了分散式事務,實現了最終一致性。在 .NET中 有現成的解決方案。

缺點: 訊息表會耦合到業務系統中,如果沒有封裝好的解決方案,會有很多雜活需要處理。

4、 可靠訊息最終一致性方案(MQ事務訊息)

這個就是乾脆不用本地訊息表了,直接基於MQ來實現事務,比如RocketMQ就支援訊息事務
大概實現:

(1) A系統先發送一個prepared訊息到MQ,如果這個prepared訊息傳送失敗那麼就直接取消操作別執行了。

(2) 如果這個訊息傳送成功過了,那麼接著執行本地事務,如果成功就告訴MQ傳送確認訊息,如果失敗就告訴MQ回滾訊息。

(3) 如果傳送了確認訊息,那麼此時B系統會接收到確認訊息,然後執行本地的事務。

(4) MQ會自動定時輪詢所有prepared訊息回撥你的介面,問你,這個訊息是不是本地事務處理失敗了,所以沒有傳送確認訊息?那麼是繼續重試還是回滾?一般來說這裡你就可以查下資料庫看之前本地事務是否執行,如果回滾了,那麼這裡也回滾吧。這個就是避免可能本地事務執行成功了,別確認訊息傳送失敗了。

(5) 這個方案裡,要是系統B的事務失敗了咋辦?重試嘍,自動不斷重試直到成功,如果實在是不行,要麼就是針對重要的資金類業務進行回滾,比如B系統本地回滾後,想辦法通知系統A也回滾(可以使用zookeeper的通知監聽機制來進行通知),或者是傳送報警由人工來手工回滾和補償。

5、 最大努力通知方案

(1) 系統A本地事務執行完之後,傳送個訊息到MQ

(2) 這裡會有個專門消費MQ的最大努力通知服務,這個服務會消費MQ然後寫入資料庫中記錄下來,或者是放入記憶體佇列也可以,接著呼叫系統B的介面。

(3) 要是系統B執行成功就ok了,要是系統B執行失敗了,那麼最大努力通知服務就定時嘗試重新呼叫系統B,反覆N次後,最後還是不行就放棄。