承接上文
上一篇文章說到了Seata 為用戶提供了 AT、TCC馒索、SAGA 和 XA 事務(wù)模式名船,為用戶打造一站式的分布式解決方案渠驼。那么接下來我們將要針對(duì)于AT模式下進(jìn)行分布式事務(wù)開發(fā)的原理進(jìn)行介紹以及實(shí)戰(zhàn)。
Seata AT模式
在AT百揭、TCC、SAGA 和 XA 這四種事務(wù)模式中使用最多课锌,最方便的就是 AT 模式渺贤。與其他事務(wù)模式相比请毛,AT 模式可以應(yīng)對(duì)大多數(shù)的業(yè)務(wù)場景获印,且基本可以做到無業(yè)務(wù)入侵,開發(fā)人員能夠有更多的精力關(guān)注于業(yè)務(wù)邏輯開發(fā)玻孟。
使用AT模式的前提
任何應(yīng)用想要使用Seata的 AT 模式對(duì)分布式事務(wù)進(jìn)行控制黍翎,必須滿足以下 2 個(gè)前提:
- 必須使用支持本地 ACID 事務(wù)特性的關(guān)系型數(shù)據(jù)庫艳丛,例如 MySQL氮双、Oracle 等;
- 應(yīng)用程序必須是使用 JDBC 對(duì)數(shù)據(jù)庫進(jìn)行訪問的 JAVA 應(yīng)用送爸。
Seata安裝使用
下載地址
Seata服務(wù)進(jìn)行下載的地址:https://seata.io/zh-cn/blog/download.html袭厂,訪問之后可以看到下面的資源中球匕,可以直接進(jìn)行下載亮曹,如下圖所示秘症。
但是由于官方維護(hù)的稍微緩慢,所以并不是最新的版本衷佃,如果你想要下載較新的版本氏义,可以去官方的Git倉庫中進(jìn)行下載對(duì)應(yīng)的版本文件包图云。地址為:https://github.com/seata/seata/releases竣况,可以看到下面的最新版本已經(jīng)到了1.6.1了
我們選擇下載對(duì)應(yīng)的可執(zhí)行包即可丹泉。
創(chuàng)建UNDO_LOG表
SEATA AT模式需要針對(duì)業(yè)務(wù)中涉及的各個(gè)數(shù)據(jù)庫表摹恨,分別創(chuàng)建一個(gè)UNDO_LOG(回滾日志)表。不同數(shù)據(jù)庫在創(chuàng)建 UNDO_LOG 表時(shí)會(huì)略有不同睁宰,以 MySQL 為例柒傻,其 UNDO_LOG 表的創(chuàng)表語句如下:
-- 注意此處0.3.0+ 增加唯一索引 ux_undo_log
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
啟動(dòng)服務(wù)
下載服務(wù)器軟件包后诅愚,將其解壓縮劫映。主要通過腳本進(jìn)行啟動(dòng)Seata服務(wù)
Seata Server 目錄中包含以下子目錄:
- bin:用于存放Seata Server可執(zhí)行命令泳赋。
- conf:用于存放Seata Server的配置文件祖今。
- lib:用于存放Seata Server依賴的各種 Jar 包拣技。
- logs:用于存放Seata Server的日志膏斤。
Seata Server的執(zhí)行腳本
- seata-server.sh:主要是為Linux和Mac系統(tǒng)準(zhǔn)備的啟動(dòng)腳本邪驮。執(zhí)行
sh seata-server.sh
啟動(dòng)服務(wù)毅访。 - seata-server.bat:主要是為Windows系統(tǒng)準(zhǔn)備的啟動(dòng)腳本喻粹。執(zhí)行
cmd seata-server.bat
啟動(dòng)服務(wù)。
其中參數(shù)的選擇范圍如下所示
--host, -h(簡略指令)該地址向注冊(cè)中心公開守呜,其他服務(wù)可以通過該ip訪問seata-server型酥,默認(rèn): 0.0.0.0
--port, -p(簡略指令) 監(jiān)聽的端口,默認(rèn)值為8091
--storeMode, -m(簡略指令)日志存儲(chǔ)模式 : file(文件)查乒、db(數(shù)據(jù)庫)冕末,默認(rèn)為:file
--help 幫助指令
例如執(zhí)行shell腳本
sh seata-server.sh -p 8091 -h 127.0.0.1 -m file
AT 模式的工作機(jī)制
Seata的AT模式工作時(shí)大致可以分為以兩個(gè)階段,下面我們就結(jié)合一個(gè)實(shí)例來對(duì) AT 模式的工作機(jī)制進(jìn)行介紹侣颂。
整體機(jī)制
兩階段提交協(xié)議的演變:
- 一階段:業(yè)務(wù)數(shù)據(jù)和回滾日志記錄在同一個(gè)本地事務(wù)中提交档桃,釋放本地鎖和連接資源。
- 二階段:提交異步化憔晒,非常快速地完成拒担∴谕停回滾通過一階段的回滾日志進(jìn)行反向補(bǔ)償。
AT模式一階段
Seata AT模式一階段的工作流程如下圖所示
業(yè)務(wù)數(shù)據(jù)和回滾日志記錄在同一個(gè)本地事務(wù)中提交从撼,釋放本地鎖和連接資源州弟。
第一子階段-獲取SQL的基本信息
Seata攔截并解析業(yè)務(wù)SQL,得到SQL 的操作類型(INSERT/UPDATE/DELETE)低零、表名(tableXXX)婆翔、判斷條件(where condition = value)等相關(guān)信息。
第二子階段-查詢并備份【執(zhí)行之前】的數(shù)據(jù)快照
根據(jù)得到的業(yè)務(wù)SQL信息掏婶,生成“前鏡像查詢語句”啃奴。
select * from tableXX where condition=value;
執(zhí)行“前鏡像查詢語句”,得到即將執(zhí)行操作的數(shù)據(jù)雄妥,并將其保存為“前鏡像數(shù)據(jù)(beforeImage)”最蕾。
第三子階段-執(zhí)行業(yè)務(wù)操作的SQL語句
執(zhí)行業(yè)務(wù)SQL依溯,例如(update tableXX set parameter = 'value' where condition = value;),將這條記錄的進(jìn)行修改瘟则。
第四子階段-查詢業(yè)務(wù)操作之后的數(shù)據(jù)黎炉,并且保存下來
查詢后鏡像:根據(jù)“前鏡像數(shù)據(jù)”的主鍵(id : X),生成“后鏡像查詢語句”醋拧。
select * from tableXX where condition=value;
執(zhí)行“后鏡像查詢語句”慷嗜,得到執(zhí)行業(yè)務(wù)操作后的數(shù)據(jù),并將其保存為“后鏡像數(shù)據(jù)(afterImage)”趁仙。
第五子階段-插入保存回滾日志記錄到undo_log表中
將前后鏡像數(shù)據(jù)和業(yè)務(wù)SQL的信息組成一條回滾日志記錄洪添,插入到 UNDO_LOG 表中垦页,示例回滾日志如下雀费。
{
"branchId": 641789253,
"undoItems": [{
"afterImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "GTS"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"beforeImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "TXC"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"sqlType": "UPDATE"
}],
"xid": "xid:xxx"
}
提交前需要獲取申請(qǐng)本地鎖
- 提交前,向TC注冊(cè)分支:申請(qǐng)TableXXX表中痊焊,id主鍵等于N的記錄的全局鎖 盏袄。需要確保先拿到全局鎖 。
- 拿不到全局鎖 薄啥,不能提交本地事務(wù)辕羽。
- 拿到全局鎖,會(huì)被限制在一定范圍內(nèi)垄惧,超出范圍將放棄刁愿,并回滾本地事務(wù),釋放本地鎖到逊。
示例說明:
兩個(gè)全局事務(wù)tx1和tx2铣口,分別對(duì)a表的m字段進(jìn)行更新操作,m的初始值1000觉壶。
tx1先開始脑题,開啟本地事務(wù),拿到本地鎖铜靶,更新操作 m = 1000 - 100 = 900叔遂。本地事務(wù)提交前,先拿到該記錄的全局鎖 争剿,本地提交釋放本地鎖已艰。
tx2后開始,開啟本地事務(wù)蚕苇,拿到本地鎖旗芬,更新操作 m = 900 - 100 = 800。本地事務(wù)提交前捆蜀,嘗試拿該記錄的全局鎖 疮丛,tx1 全局提交前幔嫂,該記錄的全局鎖被 tx1 持有,tx2需要重試等待 全局鎖 誊薄。
-
tx1二階段全局提交履恩,釋放全局鎖 。tx2 拿到全局鎖提交本地事務(wù)
image 如果tx1的二階段全局回滾呢蔫,則tx1需要重新獲取該數(shù)據(jù)的本地鎖切心,進(jìn)行反向補(bǔ)償?shù)母虏僮鳎瑢?shí)現(xiàn)分支的回滾片吊。
此時(shí)绽昏,如果tx2仍在等待該數(shù)據(jù)的全局鎖,同時(shí)持有本地鎖俏脊,則tx1的分支回滾會(huì)失敗全谤。分支的回滾會(huì)一直重試,直到tx2的全局鎖等鎖超時(shí)爷贫,放棄全局鎖并回滾本地事務(wù)釋放本地鎖认然,tx1 的分支回滾最終成功。因?yàn)檎麄€(gè)過程全局鎖在tx1結(jié)束前一直是被tx1持有的漫萄,所以不會(huì)發(fā)生臟寫的問題卷员。
數(shù)據(jù)庫隔離級(jí)別
在數(shù)據(jù)庫本地事務(wù)隔離級(jí)別,讀已提交(Read Committed)或以上的基礎(chǔ)上腾务,Seata(AT 模式)的默認(rèn)全局隔離級(jí)別是讀未提交(Read Uncommitted) 毕骡。
如果應(yīng)用在特定場景下,必需要求全局的讀已提交 岩瘦,目前Seata的方式是通過 SELECT FOR UPDATE 語句的代理未巫。
SELECT FOR UPDATE 語句的執(zhí)行會(huì)申請(qǐng)全局鎖 ,如果全局鎖被其他事務(wù)持有担钮,則釋放本地鎖(回滾 SELECT FOR UPDATE 語句的本地執(zhí)行)并重試橱赠。這個(gè)過程中,查詢是被 block 住的箫津,直到全局鎖拿到狭姨,即讀取的相關(guān)數(shù)據(jù)是已提交的,才返回苏遥。
出于總體性能上的考慮饼拍,Seata目前的方案并沒有對(duì)所有 SELECT 語句都進(jìn)行代理,僅針對(duì) FOR UPDATE 的 SELECT 語句田炭。
本地事務(wù)提交
業(yè)務(wù)數(shù)據(jù)的更新和前面步驟中生成的UNDO LOG一并提交师抄,將本地事務(wù)提交的結(jié)果上報(bào)給TC。
AT模式二階段-回滾操作
收到TC的分支回滾請(qǐng)求教硫,開啟一個(gè)本地事務(wù)叨吮。
通過XID和Branch ID查找到相應(yīng)的UNDO LOG 記錄辆布。
數(shù)據(jù)校驗(yàn):拿 UNDO LOG 中的后鏡與當(dāng)前數(shù)據(jù)進(jìn)行比較,如果有不同茶鉴,說明數(shù)據(jù)被當(dāng)前全局事務(wù)之外的動(dòng)作做了修改锋玲。這種情況,需要根據(jù)配置策略來做處理涵叮,詳細(xì)的說明在另外的文檔中介紹惭蹂。
根據(jù) UNDO LOG 中的前鏡像和業(yè)務(wù)SQL的相關(guān)信息生成并執(zhí)行回滾的語句:
update TableXXX set parameter = 'XXX' where condition = value;
- 提交本地事務(wù),并把本地事務(wù)的執(zhí)行結(jié)果(即分支事務(wù)回滾的結(jié)果)上報(bào)給 TC割粮。
AT模式二階段-提交操作
收到TC的分支提交請(qǐng)求盾碗,把請(qǐng)求放入一個(gè)異步任務(wù)的隊(duì)列中,馬上返回提交成功的結(jié)果給 TC舀瓢。
異步任務(wù)階段的分支提交請(qǐng)求將異步和批量地刪除相應(yīng) UNDO LOG 記錄廷雅。