在SOA、微服務架構流行的年代,許多複雜業務上須要支持多資源佔用場景,而在分佈式系統中由於某個資源不足而致使其它資源佔用回滾的系統設計一直是個難點。我所在的團隊也遇到了這個問題,爲解決這個問題上,團隊採用的是阿里開源的分佈式中間件Fescar的解決方案,並詳細瞭解了Fescar內部的工做原理,解決在使用Fescar中間件過程當中的一些疑慮的地方,也爲後續團隊在繼續使用該中間件奠基理論基礎。git
目前分佈式事務解決方案基本是圍繞兩階段提交模式來設計的,按對業務是有侵入分爲:對業務無侵入的基於XA協議的方案,但須要數據庫支持XA協議而且性能較低;對業務有侵入的方案包括:TCC等。Fescar就是基於兩階段提交模式設計的,以高效且對業務零侵入的方式,解決微服務場景下面臨的分佈式事務問題。Fescar設計上將總體分紅三個大模塊,即TM、RM、TC,具體解釋以下:github
本文將深刻到Fescar的RM模塊源碼去介紹Fescar是如何在完成分支提交和回滾的基礎上又作到零侵入,進而極大方便業務方進行業務系統開發。sql
上圖是Fescar源碼examples模塊dubbo-order-service.xml內的配置,數據源採用druid的DruidDataSource,但實際jdbcTemplate執行時並非用該數據源,而用的是Fescar對DruidDataSource的代理DataSourceProxy,因此,與RM相關的代碼邏輯基本上都是從DataSourceProxy這個代理數據源開始的。數據庫
Fescar採用2PC來完成分支事務的提交與回滾,具體怎麼作到的呢,下面就分別介紹Phase一、Phase2具體作了些什麼。緩存
Fescar將一個本地事務作爲一個分佈式事務分支,因此若干個分佈在不一樣微服務中的本地事務共同組成了一個全局事務,結構以下。
微信
那麼,一個本地事務中SQL是如何執行呢?在Spring中,本質上都是從jdbcTemplate開始的,好比下面的SQL語句:架構
jdbcTemplate.update("update storage_tbl set count = count - ? where commodity_code = ?", new Object[] {count, commodityCode});
通常JdbcTemplate執行流程以下圖所示:
app
因爲在配置中,JdbcTemplate數據源被配置成了Fescar實現DataSourceProxy,進而控制了後續的數據庫鏈接使用的是Fescar提供的ConnectionProxy,Statment使用的是Fescar實現的StatmentProxy,最終Fescar就瓜熟蒂落地實現了在本地事務執行先後增長所須要的邏輯,好比:完成分支事務的快照記錄和分支事務執行狀態的上報等等。異步
DataSourceProxy獲取ConnectionProxy:
async
ConnectionProxy獲取StatmentProxy:
在獲取到StatmentProxy後,能夠調用excute方法執行sql了
而真正excute實現邏輯以下:
再來看一下關鍵的INSERT、UPDATE、DELETE、SELECT..FOR UPDATE這四種類型的sql如何執行的,先看一下具體類圖結構:
爲結省篇幅,選擇UpdateExecutor實現源碼看一下,先看入口BaseTransactionalExecutor.execute,該方法將ConnectionProxy與Xid(事務ID)進行綁定,這樣後續判斷當前本地事務是否處理全局事務中只須要看ConnectionProxy中Xid是否爲空。
而後,執行AbstractDMLBaseExecutor中實現的doExecute方法
基本邏輯以下:
若是本地事務執行過程當中發生異常,業務上層會接收到該異常,至因而給TM模塊返回成功仍是失敗,由業務上層實現決定,若是返回失敗,則TM裁決對全局事務進行回滾;若是本地事務執行過程未發生異常,不論是非Auto-Commit仍是Auto-Commit模式,最後都會調用connectionProxy.commit()對本地事務進行提交,在這裏會建立分支事務、上報分支事務的狀態以及將UndoLog持久化到undo_log表中,具體代碼以下圖:
基本邏輯:
綜上所述,RM模塊經過對JDBC數據源進行代理,干預業務SQL執行過程,加入了不少流程,好比業務SQL解析、業務SQL執行先後的數據快照查詢並組織成UndoLog、全局鎖檢查、分支事務註冊、UndoLog寫入並隨本地事務一塊兒Commit、分支事務狀態上報等。經過這種方式,Fescar真正作到了對業務代碼無侵入,只須要經過簡單的配置,業務方就能夠輕鬆享受Fescar所帶來的功能。Phase1總體流程引用Fescar官方圖總結以下:
階段2完成的是全局事物的最終提交或回滾,當全局事務中全部分支事務所有完成而且都執行成功,這時TM會發起全局事務提交,TC收到全全局事務提交消息後,會通知各分支事務進行提交;同理,當全局事務中全部分支事務所有完成而且某個分支事務失敗了,TM會通知TC協調全局事務回滾,進而TC通知各分支事務進行回滾。
在業務應用啓動過程當中,因爲引入了Fescar客戶端,RmRpcClient會隨應用一塊兒啓動,該RmRpcClient採用Netty實現,能夠接收TC消息和向TC發送消息,所以RmRpcClient是與TC收發消息的關鍵模塊。
public class RMClientAT { public static void init(String applicationId, String transactionServiceGroup) { RmRpcClient rmRpcClient = RmRpcClient.getInstance(applicationId, transactionServiceGroup); AsyncWorker asyncWorker = new AsyncWorker(); asyncWorker.init(); DataSourceManager.init(asyncWorker); rmRpcClient.setResourceManager(DataSourceManager.get()); rmRpcClient.setClientMessageListener(new RmMessageListener(new RMHandlerAT())); rmRpcClient.init(); } }
上述代碼展現是的RmRpcClient初始化過程,有三個關鍵類RMHandlerAT、AsyncWorker和DataSourceManager。RMHandlerAT具備了分支提交和回滾兩個方法,分支提交或回滾的邏輯能夠從這裏開始看;AsyncWorker是一個異步Worker,主要是完成分支事務異步提交的功能,具備失敗重試功能;DataSourceManager對數據源管理和維護。
下面分紅兩部分來說:分支事務提交、分去事務回滾。
在接收到TC發起的全局提交消息後,經RmRpcClient對通訊協議的處理,再交由RMHandlerAT來完成對分支事務的提交,分支事務提交從RMHandlerAT.doBranchCommit()開始,但最後由AsyncWorker異步Worker完成,直接看AsyncWorker中的代碼實現:
分支事務提交關鍵邏輯在doBranchCommits方法中:
該方法主要是批量刪除UndoLog日誌,但並未使用ConnectionProxy去執行刪除SQL,可能緣由是:一、徹底不必 二、考慮效率優先
一樣,對於分支事務提交也引用Fescar官方一張圖來結尾:
一樣,分支事務回滾是從RMHandlerAT.doBranchRollback開始的,而後到了dataSourceManager.branchRollback,最後完成分支事務回滾邏輯的是UndoLogManager.undo方法。
@Override protected void RMHandlerAT:doBranchRollback(BranchRollbackRequest request, BranchRollbackResponse response) throws TransactionException { String xid = request.getXid(); long branchId = request.getBranchId(); String resourceId = request.getResourceId(); String applicationData = request.getApplicationData(); LOGGER.info("AT Branch rolling back: " + xid + " " + branchId + " " + resourceId); BranchStatus status = dataSourceManager.branchRollback(xid, branchId, resourceId, applicationData); response.setBranchStatus(status); LOGGER.info("AT Branch rollback result: " + status); } @Override public BranchStatus DataSourceManager:branchRollback(String xid, long branchId, String resourceId, String applicationData) throws TransactionException { DataSourceProxy dataSourceProxy = get(resourceId); if (dataSourceProxy == null) { throw new ShouldNeverHappenException(); } try { UndoLogManager.undo(dataSourceProxy, xid, branchId); } catch (TransactionException te) { if (te.getCode() == TransactionExceptionCode.BranchRollbackFailed_Unretriable) { return BranchStatus.PhaseTwo_RollbackFailed_Unretryable; } else { return BranchStatus.PhaseTwo_RollbackFailed_Retryable; } } return BranchStatus.PhaseTwo_Rollbacked; }
UndoLogManager.undo方法源碼以下:
從上圖能夠看出,整個回滾到全局事務以前狀態的代碼邏輯集中在以下代碼中:
AbstractUndoExecutor undoExecutor = UndoExecutorFactory.getUndoExecutor(dataSourceProxy.getDbType(), sqlUndoLog); undoExecutor.executeOn(conn);
首先經過UndoExecutorFactory獲取到對應的UndoExecutor,而後再執行UndoExecutor的executeOn方法完成回滾操做。目前三種類型的UndoExecutor結構以下:
undoExecutor.executeOn源碼以下:
至此,整個分支事務回滾就結束了,分支事務回滾總體時序圖以下:
引入Fescar官方對分支事務回滾原理介紹圖做爲結尾:
綜合上述,Fescar在Phase2經過UndoLog自動完成分支事務提交與回滾,在這個過程當中不須要業務方作任何處理,業務方無感知,因些在該階段對業務代碼也是無侵入的。
本文主要介紹了RM模塊的相關代碼,將RM模塊按2PC模式分紅Phase1和Phase2分別進行介紹,從Fescar源碼上看,整個源碼結構清晰,有利於研發人員快速學習Fescar的原理。在使用方面,只需進行簡單的配置,就能夠享受Fescar帶來的便捷功能,對業務作到了無侵入;同時在性能方面,Fescar在分支事務提交過程當中採用異步模式,減小了全局鎖的佔用時間,進而提高了總體性能。後續,將繼續學習Fescar的其它模塊(TM、TC)與全局鎖的實現邏輯,並作相關總結介紹。
原文連接 更多技術乾貨 請關注阿里云云棲社區微信號 :yunqiinsight