6 张图带你彻底搞懂分布式事务 XA 模式
作者 | 朱晉君
來(lái)源 | 阿里巴巴云原生公眾號(hào)
XA 協(xié)議是由 X/Open 組織提出的分布式事務(wù)處理規(guī)范,主要定義了事務(wù)管理器 TM 和局部資源管理器 RM 之間的接口。目前主流的數(shù)據(jù)庫(kù),比如 oracle、DB2 都是支持 XA 協(xié)議的。
mysql 從 5.0 版本開(kāi)始,innoDB 存儲(chǔ)引擎已經(jīng)支持 XA 協(xié)議,今天的源碼介紹實(shí)驗(yàn)環(huán)境使用的是 mysql 數(shù)據(jù)庫(kù)。
兩階段提交
分布式事務(wù)的兩階段提交是把整個(gè)事務(wù)提交分為 prepare 和 commit 兩個(gè)階段。以電商系統(tǒng)為例,分布式系統(tǒng)中有訂單、賬戶和庫(kù)存三個(gè)服務(wù),如下圖:
第一階段,事務(wù)協(xié)調(diào)者向事務(wù)參與者發(fā)送 prepare 請(qǐng)求,事務(wù)參與者收到請(qǐng)求后,如果可以提交事務(wù),回復(fù) yes,否則回復(fù) no。
第二階段,如果所有事務(wù)參與者都回復(fù)了 yes,事務(wù)協(xié)調(diào)者向所有事務(wù)參與者發(fā)送 commit 請(qǐng)求,否則發(fā)送 rollback 請(qǐng)求。
兩階段提交存在三個(gè)問(wèn)題:
- 同步阻塞,本地事務(wù)在 prepare 階段鎖定資源,如果有其他事務(wù)也要修改 xiaoming 這個(gè)賬戶,就必須等待前面的事務(wù)完成。這樣就造成了系統(tǒng)性能下降。
- 協(xié)調(diào)節(jié)點(diǎn)單點(diǎn)故障,如果第一個(gè)階段 prepare?成功了,但是第二個(gè)階段協(xié)調(diào)節(jié)點(diǎn)發(fā)出 commit 指令之前宕機(jī)了,所有服務(wù)的數(shù)據(jù)資源處于鎖定狀態(tài),事務(wù)將無(wú)限期地等待。
- 數(shù)據(jù)不一致,如果第一階段 prepare 成功了,但是第二階段協(xié)調(diào)節(jié)點(diǎn)向某個(gè)節(jié)點(diǎn)發(fā)送 commit 命令時(shí)失敗,就會(huì)導(dǎo)致數(shù)據(jù)不一致。
三階段提交
為了解決兩階段提交的問(wèn)題,三階段提交做了改進(jìn):
- 在協(xié)調(diào)節(jié)點(diǎn)和事務(wù)參與者都引入了超時(shí)機(jī)制。
- 第一階段的 prepare 階段分成了兩步,canCommi 和 preCommit。
如下圖:
引入 preCommit 階段后,協(xié)調(diào)節(jié)點(diǎn)會(huì)在 commit 之前再次檢查各個(gè)事務(wù)參與者的狀態(tài),保證它們的狀態(tài)是一致的。但是也存在問(wèn)題,那就是如果第三階段發(fā)出 rollback 請(qǐng)求,有的節(jié)點(diǎn)沒(méi)有收到,那沒(méi)有收到的節(jié)點(diǎn)會(huì)在超時(shí)之后進(jìn)行提交,造成數(shù)據(jù)不一致。
XA 事務(wù)語(yǔ)法介紹
xa 事務(wù)的語(yǔ)法如下:
結(jié)束 xa 事務(wù):
XA END xid [SUSPEND [FOR MIGRATE]]seata?XA 簡(jiǎn)介
seata 是阿里推出的一款開(kāi)源分布式事務(wù)解決方案,目前有 AT、TCC、SAGA、XA 四種模式。
seata 的 XA 模式是利用分支事務(wù)中數(shù)據(jù)庫(kù)對(duì) XA 協(xié)議的支持來(lái)實(shí)現(xiàn)的。我們看一下 seata 官網(wǎng)的介紹:[1]
從上面的圖可以看到,seata XA 模式的流程跟其他模式一樣:
這里介紹一下 RM 客戶端初始化關(guān)聯(lián)的 UML 類圖:[2]
這個(gè)圖中有一個(gè)類是 AbstractNettyRemotingClient,這個(gè)類的內(nèi)部類 ClientHandler 來(lái)處理 TC 發(fā)來(lái)的請(qǐng)求并委托給父類 AbstractNettyRemoting 的 processMessage 方法來(lái)處理。processMessage 方法調(diào)用 RmBranchCommitProcessor 類的 process 方法。
需要注意的是,「seata 的 xa 模式對(duì)傳統(tǒng)的三階段提交做了優(yōu)化,改成了兩階段提交」:
- 第一階段首執(zhí)行 XA 開(kāi)啟、執(zhí)行 sql、XA 結(jié)束三個(gè)步驟,之后直接執(zhí)行 XA prepare。
- 第二階段執(zhí)行 XA commit/rollback。
mysql 目前是支持 seata xa 模式的兩階段優(yōu)化的。
「但是這個(gè)優(yōu)化對(duì) oracle 不支持,因?yàn)?oracle 實(shí)現(xiàn)的是標(biāo)準(zhǔn)的 xa 協(xié)議,即 xa end 后,協(xié)調(diào)節(jié)點(diǎn)向事務(wù)參與者統(tǒng)一發(fā)送 prepare,最后再發(fā)送 commit/rollback。這也導(dǎo)致了 seata 的 xa 模式對(duì) oracle 支持不太好。」
seata XA 源碼
seata 中的 XA 模式是使用數(shù)據(jù)源代理來(lái)實(shí)現(xiàn)的,需要手動(dòng)配置數(shù)據(jù)源代理,代碼如下:
@Bean @ConfigurationProperties(prefix = "spring.datasource") public DruidDataSource druidDataSource() {return new DruidDataSource(); }@Bean("dataSourceProxy") public DataSource dataSource(DruidDataSource druidDataSource) {return new DataSourceProxyXA(druidDataSource); }- 也可以根據(jù)普通 DataSource 來(lái)創(chuàng)建 XAConnection,但是這種方式有兼容性問(wèn)題(比如 oracle),所以 seata 使用了開(kāi)發(fā)者自己配置 XADataSource。
- seata 提供的 XA 數(shù)據(jù)源代理,要求代碼框架中必須使用 druid 連接池。
1. XA 第一階段
當(dāng) RM 收到 DML 請(qǐng)求后,seata 會(huì)使用 ExecuteTemplateXA來(lái)執(zhí)行,執(zhí)行方法 execute 中有一個(gè)地方很關(guān)鍵,就是把 autocommit 屬性改為了 false,而 mysql 默認(rèn) autocommit 是 true。事務(wù)提交之后,還要把 autocommit 改回默認(rèn)。
下面我們看一下 XA 第一階段提交的主要代碼。
1)開(kāi)啟 XA
上面代碼標(biāo)注[1]處,調(diào)用了 ConnectionProxyXA 類的 setAutoCommit 方法,這個(gè)方法的源代碼中,XA start 主要做了三件事:
- 向 TC 注冊(cè)分支事務(wù)
- 調(diào)用數(shù)據(jù)源的 XA Start
- 把 xaActive 設(shè)置為 true
RM 并沒(méi)有直接使用 TC 返回的 branchId 作為 xa 數(shù)據(jù)源的 branchId,而是使用全局事務(wù) id(xid) 和 branchId 重新構(gòu)建了一個(gè)。
2)執(zhí)行 sql
調(diào)用 PreparedStatementProxyXA 的 execute 執(zhí)行 sql。
3)XA end/prepare
public void commit() throws SQLException {//省略部分源代碼try {// XA End: SuccessxaResource.end(xaBranchXid, XAResource.TMSUCCESS);// XA PreparexaResource.prepare(xaBranchXid);// Keep the Connection if necessarykeepIfNecessary();} catch (XAException xe) {try {// Branch Report to TC: FailedDefaultResourceManager.get().branchReport(BranchType.XA, xid, xaBranchXid.getBranchId(),BranchStatus.PhaseOne_Failed, null);} catch (TransactionException te) {//這兒只打印了一個(gè)warn級(jí)別的日志}throw new SQLException("Failed to end(TMSUCCESS)/prepare xa branch on " + xid + "-" + xaBranchXid.getBranchId() + " since " + xe.getMessage(), xe);} finally {cleanXABranchContext();} }從這個(gè)源碼我們看到,commit 主要做了三件事:
- 調(diào)用數(shù)據(jù)源的 XA end
- 調(diào)用數(shù)據(jù)源的 XA prepare
- 向 TC 報(bào)告分支事務(wù)狀態(tài)
到這里我們就可以看到,seata 把 xa 協(xié)議的前兩個(gè)階段合成了一個(gè)階段。
2. XA commit
這里的調(diào)用關(guān)系用一個(gè)時(shí)序圖來(lái)表示:
看一下 RmBranchCommitProcessor 類的 process 方法,代碼如下:
@Override public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception {String remoteAddress = NetUtil.toStringAddress(ctx.channel().remoteAddress());Object msg = rpcMessage.getBody();if (LOGGER.isInfoEnabled()) {LOGGER.info("rm client handle branch commit process:" + msg);}handleBranchCommit(rpcMessage, remoteAddress, (BranchCommitRequest) msg); }從調(diào)用關(guān)系時(shí)序圖可以看出,上面的 handleBranchCommit 方法最終調(diào)用了 AbstractRMHandler 的 handle 方法,最后通過(guò) branchCommit 方法調(diào)用了 ResourceManagerXA 類的 finishBranch 方法。
ResourceManagerXA 類是 XA 模式的資源管理器,看下面這個(gè)類圖,也就是 seata 中資源管理器(RM)的 UML 類圖:
上面的 finishBranch 方法調(diào)用了 connectionProxyXA.xaCommit 方法,我們最后看一下 xaCommit 方法:
public void xaCommit(String xid, long branchId, String applicationData) throws XAException {XAXid xaXid = XAXidBuilder.build(xid, branchId);//因?yàn)槭褂胢ysql,這里xaResource是MysqlXAConnectionxaResource.commit(xaXid, false);releaseIfNecessary(); }上面調(diào)用了數(shù)據(jù)源的 commit 方法,提交了 RM 分支事務(wù)。
到這里,整個(gè) RM 分支事務(wù)就結(jié)束了。Rollback 的代碼邏輯跟 commit 類似。
最后要說(shuō)明的是,上面的 xaResource,是 mysql-connector-java.jar 包中的 MysqlXAConnection 類實(shí)例,它封裝了 mysql 提供的 XA 協(xié)議接口。
總結(jié)
seata 中 XA 模式的實(shí)現(xiàn)是使用數(shù)據(jù)源代理完成的,底層使用了數(shù)據(jù)庫(kù)對(duì) XA 協(xié)議的原生支持。
mysql 的 java 驅(qū)動(dòng)庫(kù)中,MysqlXAConnection 類封裝類 XA 協(xié)議的底層接口供外部調(diào)用。
跟 TCC 和 SAGA 模式需要在業(yè)務(wù)代碼中實(shí)現(xiàn) prepare/commit/rollback 邏輯相比,XA 模式對(duì)業(yè)務(wù)代碼無(wú)侵入。
Reference
[1]:http://seata.io/zh-cn/docs/overview/what-is-seata.html
[2]:https://github.com/seata/seata
總結(jié)
以上是生活随笔為你收集整理的6 张图带你彻底搞懂分布式事务 XA 模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: What‘s new in dubbo-
- 下一篇: 边开飞机边换引擎?我们造了个新功能保障业