分散式事務中介軟體 Fescar—RM 模組原始碼解讀

weixin_34185364發表於2019-02-14

前言

在SOA、微服務架構流行的年代,許多複雜業務上需要支援多資源佔用場景,而在分散式系統中因為某個資源不足而導致其它資源佔用回滾的系統設計一直是個難點。我所在的團隊也遇到了這個問題,為解決這個問題上,團隊採用的是阿里開源的分散式中介軟體Fescar的解決方案,並詳細瞭解了Fescar內部的工作原理,解決在使用Fescar中介軟體過程中的一些疑慮的地方,也為後續團隊在繼續使用該中介軟體奠定理論基礎。

目前分散式事務解決方案基本是圍繞兩階段提交模式來設計的,按對業務是有侵入分為:對業務無侵入的基於XA協議的方案,但需要資料庫支援XA協議並且效能較低;對業務有侵入的方案包括:TCC等。Fescar就是基於兩階段提交模式設計的,以高效且對業務零侵入的方式,解決微服務場景下面臨的分散式事務問題。Fescar設計上將整體分成三個大模組,即TM、RM、TC,具體解釋如下:

TM(Transaction Manager):

1. 全域性事務管理器,控制全域性事務邊界,負責全域性事務開啟、全域性提交、全域性回滾。

 2. RM(Resource Manager):資源管理器,控制分支事務,負責分支註冊、狀態彙報,並接收事務協調器的指令,驅動分支(本地)事務的提交和回滾。

 3. TC(Transaction Coordinator):事務協調器,維護全域性事務的執行狀態,負責協調並驅動全域性事務的提交或回滾。

14218756-1590c540ebd16317.jpg

本文將深入到Fescar的RM模組原始碼去介紹Fescar是如何在完成分支提交和回滾的基礎上又做到零侵入,進而極大方便業務方進行業務系統開發。

一、從配置開始解讀

14218756-408253b789410d5b.jpg

上圖是Fescar原始碼examples模組dubbo-order-service.xml內的配置,資料來源採用druid的DruidDataSource,但實際jdbcTemplate執行時並不是用該資料來源,而用的是Fescar對DruidDataSource的代理DataSourceProxy,所以,與RM相關的程式碼邏輯基本上都是從DataSourceProxy這個代理資料來源開始的。 Fescar採用2PC來完成分支事務的提交與回滾,具體怎麼做到的呢,下面就分別介紹Phase1、Phase2具體做了些什麼。

二、Phase1—分支(本地)事務執行

Fescar將一個本地事務做為一個分散式事務分支,所以若干個分佈在不同微服務中的本地事務共同組成了一個全域性事務,結構如下。

14218756-498cb04b18b1bcf9.jpg

那麼,一個本地事務中SQL是如何執行呢?在Spring中,本質上都是從jdbcTemplate開始的,比如下面的SQL語句:

14218756-8e2a9021abb891fb.jpg

由於在配置中,JdbcTemplate資料來源被配置成了Fescar實現DataSourceProxy,進而控制了後續的資料庫連線使用的是Fescar提供的ConnectionProxy,Statment使用的是Fescar實現的StatmentProxy,最終Fescar就順理成章地實現了在本地事務執行前後增加所需要的邏輯,比如:完成分支事務的快照記錄和分支事務執行狀態的上報等等。

14218756-18c2a354f6d8407f.jpg


14218756-26784810a737fde0.jpg


14218756-8498546c1dcb026f.jpg

1.首先會檢查當前本地事務是否處於全域性事務中,如果不處於,直接使用預設的Statment執行,避免因引入Fescar導致非全域性事務中的SQL執行效能下降。

 2.解析Sql,有快取機制,因為有些sql解析會比較耗時,可能會導致在應用啟動後剛開始的那段時間裡處理全域性事務中的sql執行效率降低。

 3.對於INSERT、UPDATE、DELETE、SELECT..FOR UPDATE這四種型別的sql會專門實現的SQL執行器進行處理,其它SQL直接是預設的Statment執行。

 4.返回執行結果,如有異常則直接拋給上層業務程式碼進行處理。 再來看一下關鍵的INSERT、UPDATE、DELETE、SELECT..FOR UPDATE這四種型別的sql如何執行的,先看一下具體類圖結構:

14218756-91382950afcdb64a.jpg

為結省篇幅,選擇UpdateExecutor實現原始碼看一下,先看入口BaseTransactionalExecutor.execute,該方法將ConnectionProxy與Xid(事務ID)進行繫結,這樣後續判斷當前本地事務是否處理全域性事務中只需要看ConnectionProxy中Xid是否為空。

14218756-8b156c69cca3918e.jpg


基本邏輯如下:

1.先判斷是否為Auto-Commit模式

2.如果非Auto-Commit模式,則先查詢Update前對應行記錄的快照beforeImage,再執行Update語句,完成後再查詢Update後對應行記錄的快照afterImage,最後將beforeImage、afterImage生成UndoLog追加到Connection上下文ConnectionContext中。(注:獲取beforeImage、afterImage方法在UpdateExecutor類下,一般是構造一條Select...For Update語句獲取執行前後的行記錄,同時會檢查是否有全域性鎖衝突,具體可參考原始碼)

 3.如果是Auto-Commit模式,先將提交模式設定成非自動Commit,再執行2中的邏輯,再執行connectionProxy.commit()方法,由於執行2過程和commit時都可能會出現全域性鎖衝突問題,增加了一個迴圈等待重試邏輯,最後將connection的模式設定成Auto-Commit模式

如果本地事務執行過程中發生異常,業務上層會接收到該異常,至於是給TM模組返回成功還是失敗,由業務上層實現決定,如果返回失敗,則TM裁決對全域性事務進行回滾;如果本地事務執行過程未發生異常,不管是非Auto-Commit還是Auto-Commit模式,最後都會呼叫connectionProxy.commit()對本地事務進行提交,在這裡會建立分支事務、上報分支事務的狀態以及將UndoLog持久化到undo_log表中,具體程式碼如下圖:      

14218756-56558123240d2749.jpg


基本邏輯:

1.判斷當前本地事務是否處於全域性事務中(也就判斷ConnectionContext中的xid是否為空)。

 2.如果不處於全域性事務中,則呼叫targetConnection對本地事務進行commit。

 3.如果處於全域性事務中,首先建立分支事務,再將ConnectionContext中的UndoLog寫入到undo_log表中,然後呼叫targetConnection對本地事務進行commit,將UndoLog與業務SQL一起提交,最後上報分支事務的狀態(成功 or 失敗),並將ConnectionContext上下文重置。

 綜上所述,RM模組通過對JDBC資料來源進行代理,干預業務SQL執行過程,加入了很多流程,比如業務SQL解析、業務SQL執行前後的資料快照查詢並組織成UndoLog、全域性鎖檢查、分支事務註冊、UndoLog寫入並隨本地事務一起Commit、分支事務狀態上報等。通過這種方式,Fescar真正做到了對業務程式碼無侵入,只需要通過簡單的配置,業務方就可以輕鬆享受Fescar所帶來的功能。Phase1整體流程引用Fescar官方圖總結如下:

14218756-5d6e6e46f0fc7784.jpg

三、Phase2-分支事務提交或回滾

 階段2完成的是全域性事物的最終提交或回滾,當全域性事務中所有分支事務全部完成並且都執行成功,這時TM會發起全域性事務提交,TC收到全全域性事務提交訊息後,會通知各分支事務進行提交;同理,當全域性事務中所有分支事務全部完成並且某個分支事務失敗了,TM會通知TC協調全域性事務回滾,進而TC通知各分支事務進行回滾。

在業務應用啟動過程中,由於引入了Fescar客戶端,RmRpcClient會隨應用一起啟動,該RmRpcClient採用Netty實現,可以接收TC訊息和向TC傳送訊息,因此RmRpcClient是與TC收發訊息的關鍵模組。

14218756-c15698443451ed21.jpg

上述程式碼展示是的RmRpcClient初始化過程,有三個關鍵類RMHandlerAT、AsyncWorker和DataSourceManager。RMHandlerAT具有了分支提交和回滾兩個方法,分支提交或回滾的邏輯可以從這裡開始看;AsyncWorker是一個非同步Worker,主要是完成分支事務非同步提交的功能,具有失敗重試功能;DataSourceManager對資料來源管理和維護。

 下面分成兩部分來講:分支事務提交、分去事務回滾。

3.1、分支事務提交

在接收到TC發起的全域性提交訊息後,經RmRpcClient對通訊協議的處理,再交由RMHandlerAT來完成對分支事務的提交,分支事務提交從RMHandlerAT.doBranchCommit()開始,但最後由AsyncWorker非同步Worker完成,直接看AsyncWorker中的程式碼實現

14218756-935bfa19ead2dafa.jpg

該方法主要是批量刪除UndoLog日誌,但並未使用ConnectionProxy去執行刪除SQL,可能原因是:1、完全沒必要 2、考慮效率優先 同樣,對於分支事務提交也引用Fescar官方一張圖來結尾:

14218756-5516e8a97ed1e309.jpg

3.2、分支事務回滾 同樣,分支事務回滾是從RMHandlerAT.doBranchRollback開始的,然後到了dataSourceManager.branchRollback,最後完成分支事務回滾邏輯的是UndoLogManager.undo方法。 @Override

14218756-876dd18f3f6c4350.jpg


14218756-57394eca70018d2a.jpg

首先通過UndoExecutorFactory獲取到對應的UndoExecutor,然後再執行UndoExecutor的executeOn方法完成回滾操作。目前三種型別的UndoExecutor結構如下:

14218756-07628971134c1df4.jpg


14218756-c7104e6dea7284dc.jpg


14218756-4c9a233e9e24f768.jpg

四、總結

 本文主要介紹了RM模組的相關程式碼,將RM模組按2PC模式分成Phase1和Phase2分別進行介紹,從Fescar原始碼上看,整個原始碼結構清晰,有利於研發人員快速學習Fescar的原理。在使用方面,只需進行簡單的配置,就可以享受Fescar帶來的便捷功能,對業務做到了無侵入;同時在效能方面,Fescar在分支事務提交過程中採用非同步模式,減少了全域性鎖的佔用時間,進而提升了整體效能。後續,將繼續學習Fescar的其它模組(TM、TC)與全域性鎖的實現邏輯,並做相關總結介紹。

相關文章