分布式事務解決方案
背景
分布式事務,后端開發中比較常見啦。因為在面試的時候,總是有interviewers讓我給他普及一下分布式事務,雖然我會的也不多呀但是還是淺淺說一說;
今天心血來潮,好好地總結一下分布式事務,希望每一位后端工程師都能徹底理解分布式事務。
什么是分布式事務?
答:既然是分布式,首先必然是分布式系統中的一個概念啦。單體應用沒這個東西,也不需要這個東西。本地事務就夠啦,Spring給我們提供的注解@Transactional, InnoDB引擎會為我們保證事務的ACID特性。但是分布式系統中,目前大多數互聯網公司都在用分布式系統,微服務架構等。所以,學好分布式事務太有必要。廢話不多說,直接上原理。
總結來說,分布式事務涉及了多個獨立的數據源(數據庫)或者參與者的事務操作,這些數據源分布在不同的計算機或網絡中;分布式事務確保在不同節點之間的多個操作要么全部成功,要么全部失敗。
分布式事務解決方案
2PC(XA協議)
兩階段提交協議,也叫XA協議。主要包含兩個階段,第一個階段是預備階段,第二個階段是提交階段。
2PC協議首先有分事務協調者角色和事務參與者。協調者是事先指定好的一個節點。參與者是一些涉及到數據庫操作的表,暫時可以這樣理解。這些多個參與者一般是分布在不同的節點上。
- 準備階段。協調者向所有參與者發送事務準備請求,參與者執事務操作,并回復協調者準備就緒的消息;如果多個參與者中有一個參與者未準備就緒或者發生錯誤,那么協調者會發送中止請求。只有所有參與者都回復準備就緒,才會進入第二階段。
- 提交階段。所有參與者都已經準備就緒,協調者分別發送提交的消息,參與者收到消息以后,執行事務的提交操作,并向協調者回復提交完成。
協調者收到了所有事務參與者提交完成的消息后,整個分布式事務才算提交完成。如果有一個參與者未能提交或者發生錯誤,那么協調者會向所有參與者發送中止請求,進行事務的回滾操作。
如何評價2PC?
1、2PC有單點故障的問題。一旦事務協調者故障(因為是使用到了某個節點嘛),那么整個事務將無法繼續進行,陷入故障。
2、數據不一致。如果協調者在發送提交信息時,只有部分參與者收到了消息,并執行了提交,此時網絡異常,就導致只有部分參與者執行了事物的提交,另一部分則沒有提交,從而造成一個數據不一致性。
3、阻塞風險。如果準備階段,有一個參與者無法響應或者失敗,那么整個系統都會陷入阻塞狀態,等待超時處理。
4、性能問題。整個鏈路是串行的,響應時間較長,不適合高并發的場景。
3PC
三階段提交又稱3PC,相對于2PC來說增加了CanCommit階段和超時機制。如果某段時間內沒有收到協調者的commit請求,那么就會自動進行commit,解決了2PC單點故障的問題。
但是性能問題和數據不一致性問題還是沒解決。3PC的步驟是這樣的:
1、詢問節點。CanCommit, 首先詢問參與者,是否有能力完成此次事務?
- 如果都返回yes,則進入第二階段
- 有一個返回no或等待響應超時,則中斷事務,并向所有參與者發送abort請求。
2、準備階段;同2PC。需要注意的是,參與者收到消息后開始執行事務操作,會首先將Undo和Redo信息記錄到事務日志中。參與者執行完事務操作后,向協調者反饋ACK, 表示已經準備好提交了。
3、提交階段。同2PC。
TCC
TCC ,Try, Confirm, Cancel; 其實是采用的補償機制,其核心思想是:針對每個操作,都要注冊一個與其對應的確認和補償(撤銷)操作。它分為三個階段:
1、Try, 對業務系統做檢測及資源預留;
2、Confirm主要對業務系統做確認提交;try階段執行成功,并開始執行confirm,默認是不會出錯的;只要Try成功,那么Confirm 一定會成功。
3、Cancle, 主要是在業務執行錯誤,需要回滾的狀態下執行的業務取消,對預留資源釋放!
舉個例子,假入 Bob 要向 Smith 轉賬,思路大概是:我們有一個本地方法,里面依次調用
1、首先在 Try 階段,要先調用遠程接口把 Smith 和 Bob 的錢給凍結起來。凍結可以理解為一種特殊的扣減,以放在后續轉賬的時候,金額不夠轉。并發訪問的時候,凍結操作需要加分布式鎖,避免我在執行凍結扣減的時候,此時的金額發生了變化,導致凍結失敗或者不一致性。扣減以后,生成一條凍結交易記錄,表示該凍結操作成功。
2、在 Confirm 階段,執行遠程調用的轉賬的操作,轉賬成功;成功后查詢出凍結交易記錄,將凍結狀態變更為已解凍;
3、如果第2步執行成功,那么轉賬成功,如果第二步執行失敗,則調用遠程凍結接口對應的解凍接口,將數據恢復為凍結前的樣子。
Try部分完成業務的準備工作,confirm部分完成業務的提交,cancel部分完成事務的回滾。基本原理如下圖所示:

如上圖所示,每個分支事務都需要實現Try,Confirm,Cancel接口。TCC事務開始時,業務應用會向事務協調器注冊啟動事務。之后業務應用會調用所有服務的Try接口,完成一階段準備。之后事務協調器會根據Try接口返回情況,決定調用Confirm接口或者Cancel接口。如果接口調用失敗,會進行重試。總結的說,所有分支的Try操作成功,會進入到Confirm; 有一個分支未成功,已經執行的操作都要回滾。從而恢復到嘗試階段之前的狀態,以確保數據的一致性。
TCC的優點:1、降低了鎖的粒度,減少了并發沖突;從而提高了吞吐量;每個業務執行操作都有相應的補償操作,不需要人工干預進行補償。
不足之處也很明顯, 1、對業務有一定的入侵;改造成本高,代碼冗長;
2、實現難度較大。需要按照網絡狀態、系統故障等不同的失敗原因實現不同的回滾策略。為了滿足一致性的要求,confirm和cancel接口必須實現冪等。
本地消息表+MQ最終一致性事務

本地消息表的思想是將分布式事務拆分為本地事務來處理,通過引入一個額外的消息表,存儲消息的處理狀態,從而實現消息可靠性和一致性。
如上圖中的訂單系統和庫存系統是兩個獨立的系統,所以數據庫也是獨立的。如果我們想要保證下訂單以及訂單中商品庫存扣減的原子性,就必然要使用分布式事務。使用本地消息表解決分布式事務的思路是這樣。首先,訂單系統正常進行訂單業務數據存DB,同時將這個消息實體記錄存入消息表內,消息實體至少要有消息的id以及消息的處理狀態;然后向MQ發送這條消息。
訂單系統不斷往MQ中生產消息,庫存系統去消費這個MQ中的消息,取到消息以后,執行庫存相關業務操作,保存至DB,最終返回一個成功處理的響應給MQ。訂單系統發完消息以后,也會去讀取該消息的響應處理消息,讀到以后,如果是成功處理,那么就更新訂單庫中本地消息表中的該消息狀態,更新為已完成。
整個簡單的思路是這樣,需要注意的是,我們要保證下訂單和訂單消息存入消息表是一個原子性的,使用本地事務處理,要么都成功,要么都失敗。
第二,如果最終消息表中的消息都是已完成就沒什么問題,但是有時候, 由于數據庫網絡等不穩定因素導致消息的狀態還是未完成。這種問題,我們必須要解決。
可以使用定時任務,定時任務周期性去輪詢本地消息表中的消息,如果某消息是未完成,那么可以觸發重試操作,繼續往MQ中發送這條消息,交給庫存系統處理。
如果1步驟訂單消息在保存的時候失敗,那么直接觸發回滾即可。1和2是同時成功,同時回滾的。如果3失敗了,由于本地消息表中已經存在消息體,這時只需要輪詢消息重新通過消息中間件發送一次。
第三,如果庫存系統中在業務處理上失敗了,此時再重試已無效,可以發消息給事務主動方訂單系統回滾事務。
如果庫存系統已經消費了消息,訂單系統需要回滾事務的話,需要發消息通知庫存進行回滾事務。
本地消息表+MQ由于消息是異步發送,實際上是一種最終一致性方案,即滿足base理論,所以在一些要求實時性的業務場景就不那么適用的,適用于實時性不高,能接受最終一致性的場景。
優缺點總結
優點
1、簡單可靠。通過本地消息表,將消息先存儲到本地數據庫中,再進行異步發送,可以保證消息的可靠性和一致性。
2、高可用性。相對XA協議,由于消息是異步發送的,所以即使其他分支事務出現了服務不可用或者故障,也不會影響當前事務的提交,保證了系統的高可用。
3、提升性能:將消息發送過程異步化,減少了事務的等待時間,提升了系統的性能。
缺點
1、需要維護額外的消息表,增加了系統的復雜性。
2、依賴于數據庫。由于本地消息表依賴于數據庫,如果數據庫出現故障或性能問題,會對整個系統的可用性和性能產生影響。
3、業務耦合。本地消息表與業務耦合在一起,難于做成通用性,不可獨立伸縮。
4、無法保證實時性:由于消息發送是異步的,無法保證消息的實時性,存在一定的延遲。
最后,對于實時性要求較高、對數據一致性要求更嚴格的場景,可能需要考慮使用分布式事務管理框架或消息中間件等更復雜的方案。
Seata
Seata的設計思路是將一個分布式事務理解為一個全局事務,全局事務下面掛著若干個分支事務。每個分支事務又相當于是本地事務,滿足ACID的特性,因此我們操作分布式事務就像操作本地事務一樣。Seata 內部定義了 3個模塊來處理全局事務和分支事務的關系和處理過程,這三個組件分別是:
Transaction Coordinator (TC): 事務協調器,維護全局事務的運行狀態,負責協調并驅動全局事務的提交或回滾。
Transaction Manager (TM): 控制全局事務的邊界,負責開啟一個全局事務,并最終發起全局提交或全局回滾的決議。
Resource Manager (RM): 控制分支事務,負責分支注冊、狀態匯報,并接收事務協調器的指令,驅動分支(本地)事務的提交和回滾。

通過這三個模塊,完成全局事務的執行流程,執行步驟如下:
首先,TM向TC申請一個全局事務,TC創建一個全局事務,并返回一個XID,XID是全局事務的唯一標識。然后,RM向TC注冊分支事務,該分支歸屬于該XID的全局事務。RM注冊本地事務,會完成一系列的本地事務操作,用于全局事務的提交或者回滾。
最后,TM將全局事務提交或者回滾的決策發送TC,TC調度 XID 全局事務下的分支事務完成提交或者回滾。
一般的,我們都使用Seate的AT模式,其實也是分為兩個階段,類似于XA協議的方式。第一階段是準備階段,第二階段是分布式事務的提交或者回滾。
第一階段
分支事務主要利用RM模塊中的JDBC代理,在業務數據提交時,自動攔截業務SQL,把業務數據在更新前后的數據鏡像組織成回滾日志,進而生成undoLog; 然后利用本地事務的特性,將業務SQL和undolog寫入同一個事務中,一起提交到數據庫中,保證業務SQL必定存在回滾日志,最后對分支事務狀態向TC進行上報。
第二階段
1、TM決議全局事務提交。首先通知TC全局事務提交,說明各個RM分支事務已經完成了第一階段;此時,TC會異步調度各個RM分支事務刪除對應的undolog日志。這個過程是異步的,所以速度也比較快。
2、TM決議全局事務回滾。同樣,首先通知TC全局事務回滾,RM會收到TC發送的回滾請求,RM會通過XID找到對應的undolog日志,利用本地事務 ACID 特性,執行回滾日志完成回滾操作,并刪除 undo log 日志,最后向TC進行回滾結果上報。
業務對以上所有的流程都無感知,業務完全不關心全局事務的具體提交和回滾,而且最重要的一點是 Seata 將兩段式提交的同步協調分解到各個分支事務中了,分支事務與普通的本地事務無任何差異,這意味著我們使用 Seata 后,分布式事務就像使用本地事務一樣。
然后想說一下,將數據庫層的事務協調機制交給了中間件層 Seata , Seata為什么是個中間層,這是因為,XA協議依賴的是數據庫層面來保障事務的一致性,也即是說 XA 的各個分支事務是在數據庫層面上驅動的。而Seata在數據庫做了一層代理層,所以我們使用 Seata 時,使用的數據源實際上用的是Seata自帶的數據源代理 DataSourceProxy。這個代理層具體體現在RM模塊。
其主要作用是解析 SQL,把業務數據在更新前后的數據鏡像組織成回滾日志,并將 undo log 日志插入 undo log 表中,保證每條更新數據的業務 sql 都有對應的回滾日志存在。這樣做的好處是:本地事務執行完以后,可以立即釋放資源,然后向 TC上報分支狀態。TM決議全局提交時,也不需要同步協調處理。
總接的來說,Seate有如下優點:
1、較高的性能表現。它極大減少了分支事務對資源的鎖定時間,完美避免了 XA 協議需要同步協調導致資源鎖定時間過長的問題。
2、易集成。 Seata 提供了與各種主流框架和中間件的集成支持,方便在現有系統中集成和使用。
3、支持多種存儲后端: Seata 支持多種存儲后端,可以根據實際需求選擇合適的存儲方式。
4、靈活配置: Seata 提供了豐富的配置選項,可以根據需求進行靈活配置,滿足不同的分布式事務需求。
但是因為在使用時需要部署Seata服務端,集成Seata客戶端,所以也存在一些缺點,比如:
部署和維護成本: 部署和維護分布式事務解決方案需要一定的成本和精力,特別是在大規模系統中。
依賴性: 引入 Seata 可能會增加系統的依賴性,需要謹慎評估是否真正需要使用分布式事務解決方案。
配置復雜性: 配置 Seata 可能需要一定的復雜性,特別是針對復雜的分布式系統和場景,需要仔細配置各種參數。
所以,可按需看業務情況是否真的需要使用Seate實現分布式事務。

浙公網安備 33010602011771號