分布式事務(wù)(Seata) 四大模式詳解
前言
在上一節(jié)中我們講解了,關(guān)于分布式事務(wù)和seata的基本介紹和使用,感興趣的小伙伴可以回顧一下《別再說你不知道分布式事務(wù)了!》 最后小農(nóng)也說了,下期會(huì)帶給大家關(guān)于Seata中關(guān)于seata中AT、TCC、SAGA 和 XA 模式的介紹和使用,今天就來講解關(guān)于Seata中分布式四種模型的介紹。
文中文件資源和項(xiàng)目在結(jié)尾都有資源鏈接
Seata分為三大模塊,分別是 TM、RM 和 TC
TC (Transaction Coordinator) - 事務(wù)協(xié)調(diào)者:
維護(hù)全局和分支事務(wù)的狀態(tài),驅(qū)動(dòng)全局事務(wù)提交或回滾。
TM (Transaction Manager) - 事務(wù)管理器:
定義全局事務(wù)的范圍:開始全局事務(wù)、提交或回滾全局事務(wù)。
RM (Resource Manager) - 資源管理器:
管理分支事務(wù)處理的資源,與TC交談以注冊(cè)分支事務(wù)和報(bào)告分支事務(wù)的狀態(tài),并驅(qū)動(dòng)分支事務(wù)提交或回滾。
在 Seata 中,分布式事務(wù)的執(zhí)行流程:
- TM 開啟分布式事務(wù)(TM 向 TC 注冊(cè)全局事務(wù)記錄);
- 按業(yè)務(wù)場(chǎng)景,編排數(shù)據(jù)庫、服務(wù)等事務(wù)內(nèi)資源(RM 向 TC 匯報(bào)資源準(zhǔn)備狀態(tài) );
- TM 結(jié)束分布式事務(wù),事務(wù)一階段結(jié)束(TM 通知 TC 提交/回滾分布式事務(wù));
- TC 匯總事務(wù)信息,決定分布式事務(wù)是提交還是回滾;
- TC 通知所有 RM 提交/回滾 資源,事務(wù)二階段結(jié)束。
TM 和 RM 是作為 Seata 的客戶端與業(yè)務(wù)系統(tǒng)集成在一起,TC 作為 Seata 的服務(wù)端獨(dú)立部署。
服務(wù)端存儲(chǔ)模式支持三種:
file: 單機(jī)模式,全局事務(wù)會(huì)話信息內(nèi)存中讀寫并持久化本地文件root.data,性能較高(默認(rèn))
DB: 高可用模式,全局事務(wù)會(huì)話信息通過DB共享,相對(duì)性能差一些
redis: Seata-Server1.3及以上版本支持,性能較高,存在事務(wù)信息丟失風(fēng)險(xiǎn),需要配合實(shí)際場(chǎng)景使用
TC環(huán)境搭建詳解
這里我們使用DB高可用模式,找到conf/file.conf
文件
修改以上中的信息,找到對(duì)應(yīng)的db配置,修改其中的jdbc連接,要注意其中涉及到三個(gè)表(global_table,branch_table,lock_table
),同時(shí) mysql5和mysql8的驅(qū)動(dòng)是不一樣的
mysql5:com.mysql.jdbc.Driver
mysql8:com.mysql.cj.jdbc.Driver
建表語句地址:https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql
global_table: 全局事務(wù)表,每當(dāng)有一個(gè)全局事務(wù)發(fā)起后,就會(huì)在該表中記錄全局事務(wù)的ID
branch_table: 分支事務(wù)表,記錄每一個(gè)分支事務(wù)的 ID,分支事務(wù)操作的哪個(gè)數(shù)據(jù)庫等信息
lock_table: 全局鎖
當(dāng)上述配置好以后,重啟Seata即可生效。
Seata 配置 Nacos
Seata支持注冊(cè)服務(wù)到Nacos,以及支持Seata所有配置放到Nacos配置中心,在Nacos中統(tǒng)一維護(hù);在
高可用模式下就需要配合Nacos來完成
首先找到 conf/registry.conf
,修改registry
信息
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server" # 這里的配置要和客戶端保持一致
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP" # 這里的配置要和客戶端保持一致
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
......
}
修改好后,將seata中的一些配置上傳到Nacos中,因?yàn)榕渲庙?xiàng)比較多,所以官方提供了一個(gè)config.txt
,只下載并且修改其中某些參數(shù)后,上傳到Nacos中即可。
下載地址:https://github.com/seata/seata/tree/develop/script/config-center
修改項(xiàng)如下:
service.vgroupMapping.mygroup=default # 事務(wù)分組
store.mode=db
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
store.db.user=root
store.db.password=123456
修改好這個(gè)文件以后,把這個(gè)文件放到seata目錄下
把這些配置都加入到Nacos配置中,要借助一個(gè)腳本來進(jìn)行執(zhí)行,官方已經(jīng)提供好。
地址為:https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh
新建一個(gè)nacos-config.sh
文件,將腳本內(nèi)容復(fù)制進(jìn)去,修改congfig.txt
的路徑
上述文件修改好后,打開git工具,將nacos-config.sh
工具拖拽到窗體中即可或者使用命令
sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -t 88b8f583-43f9-4272-bd46-78a9f89c56e8 -u nacos -w nacos
-h:nacos地址
-p:端口,默認(rèn)8848
-g:seata的服務(wù)列表分組名稱
-t:nacos命名空間id
-u和-w:nacos的用戶名和密碼
最后會(huì)有四個(gè)執(zhí)行失敗,是因?yàn)閞edis報(bào)錯(cuò)的關(guān)系,這個(gè)可以忽略,不影響正常使用。最后可以看到在Nacos中有很多的配置項(xiàng),說明導(dǎo)入成功。再重新其中seata,成功監(jiān)聽到8091端口,表示前置工作都已經(jīng)準(zhǔn)備完成。
Seata的事務(wù)模式
Seata 定義了全局事務(wù)的框架,主要分為以下幾步
- TM 向 TC請(qǐng)求 發(fā)起(Begin)、提交(Commit)、回滾(Rollback)等全局事務(wù)
- TM把代表全局事務(wù)的XID綁定到分支事務(wù)上
- RM向TC注冊(cè),把分支事務(wù)關(guān)聯(lián)到XID代表的全局事務(wù)中
- RM把分支事務(wù)的執(zhí)行結(jié)果上報(bào)給TC
- TC發(fā)送分支提交(Branch Commit)或分支回滾(Branch Rollback)命令給RM
Seata 的 全局事務(wù) 處理過程,分為兩個(gè)階段:
- 執(zhí)行階段 :執(zhí)行分支事務(wù),并保證執(zhí)行結(jié)果滿足是可回滾的(Rollbackable) 和持久化的(Durable)。
- 完成階段: 根據(jù) 執(zhí)行階段 結(jié)果形成的決議,應(yīng)用通過 TM 發(fā)出的全局提交或回滾的請(qǐng)求給 TC,TC 命令 RM 驅(qū)動(dòng) 分支事務(wù) 進(jìn)行 Commit 或 Rollback。
Seata 的所謂事務(wù)模式是指:運(yùn)行在 Seata 全局事務(wù)框架下的 分支事務(wù) 的行為模式。準(zhǔn)確地講,應(yīng)該叫作 分支事務(wù)模式。
不同的 事務(wù)模式 區(qū)別在于 分支事務(wù) 使用不同的方式達(dá)到全局事務(wù)兩個(gè)階段的目標(biāo)。即,回答以下兩個(gè)問題:
- 執(zhí)行階段 :如何執(zhí)行并 保證 執(zhí)行結(jié)果滿足是可回滾的(Rollbackable) 和持久化的(Durable)。
- 完成階段: 收到 TC 的命令后,如何做到分支的提交或回滾?
我們以AT模式為例:
- 執(zhí)行階段:
- 可回滾:根據(jù) SQL 解析結(jié)果,記錄回滾日志
- 持久化:回滾日志和業(yè)務(wù) SQL 在同一個(gè)本地事務(wù)中提交到數(shù)據(jù)庫
- 完成階段:
- 分支提交:異步刪除回滾日志記錄
- 分支回滾:依據(jù)回滾日志進(jìn)行反向補(bǔ)償更新
接下來就進(jìn)入重頭戲,Seata四大模式的介紹。
Seata-XA模式
Seata 1.2.0 版本發(fā)布了新的事務(wù)模型:XA模式,實(shí)現(xiàn)了對(duì)XA協(xié)議的支持。對(duì)于XA模式我們需要從三個(gè)點(diǎn)去解析它。
- XA模式是什么
- 為什么支持XA
- XA模式如何實(shí)現(xiàn)和使用
XA模式簡介
首先需要知道XA模型是什么,XA 規(guī)范早在上世紀(jì) 90 年代初就被提出,用于解決分布式事務(wù)領(lǐng)域的問題,他也是最早的分布式事務(wù)處理方案,因?yàn)樾枰獢?shù)據(jù)庫內(nèi)部也是支持XA模式的,比如MYSQL,XA模式具有強(qiáng)一致性的特點(diǎn),因此他對(duì)數(shù)據(jù)庫占用時(shí)間比較長,所以性能比較低。
XA模式屬于兩階段提交。
-
第一階段進(jìn)行事務(wù)注冊(cè),將事務(wù)注冊(cè)到TC中,執(zhí)行SQL語句。
-
第二階段TC判斷無事務(wù)出錯(cuò),通知所有事務(wù)提交,否則回滾。
-
在第一到第二階段過程中,事務(wù)一直占有數(shù)據(jù)庫鎖,因此性能比較低,但是所有事務(wù)要么一起提交,要么一起回滾,所以能實(shí)現(xiàn)強(qiáng)一致性。
無論是AT模式、TCC還是SAGA,這些模式的提出,都是源于XA規(guī)范對(duì)某些業(yè)務(wù)場(chǎng)景無法滿足
什么是XA協(xié)議
XA規(guī)范是X/OPEN組織定義的分布式事務(wù)處理(DTP,Distributed Transaction Processing)標(biāo)準(zhǔn),XA規(guī)范描述了全局事務(wù)管理器和局部資源管理器之間的接口,XA規(guī)范的目的是允許多個(gè)資源(如數(shù)據(jù)庫,應(yīng)用服務(wù)器,消息隊(duì)列等)在同一事務(wù)中訪問,這樣可以使 ACID 屬性跨越應(yīng)用程序而保持有效。
XA 規(guī)范 使用兩階段提交(2PC,Two-Phase Commit)來保證所有資源同時(shí)提交或回滾任何特定的事務(wù)。因?yàn)閄A規(guī)范最早被提出,所以幾乎所有的主流數(shù)據(jù)庫都保有對(duì)XA規(guī)范的支持。
分布式事務(wù)DTP模型定義的角色如下:
- AP:即應(yīng)用程序,可以理解為使用DTP分布式事務(wù)的程序,例如訂單服務(wù)、庫存服務(wù)
- RM:資源管理器,可以理解為事務(wù)的參與者,一般情況下是指一個(gè)數(shù)據(jù)庫的實(shí)例(MySql),通過資源管理器對(duì)該數(shù)據(jù)庫進(jìn)行控制,資源管理器控制著分支事務(wù)
- TM:事務(wù)管理器,負(fù)責(zé)協(xié)調(diào)和管理事務(wù),事務(wù)管理器控制著全局事務(wù),管理實(shí)務(wù)生命周期,并協(xié)調(diào)各個(gè)RM。全局事務(wù)是指分布式事務(wù)處理環(huán)境中,需要操作多個(gè)數(shù)據(jù)庫共同完成一個(gè)工作,這個(gè)工作即是一個(gè)全局事務(wù)。
DTP模式定義TM和RM之間通訊的接口規(guī)范叫XA,簡單理解為數(shù)據(jù)庫提供的2PC接口協(xié)議,基于數(shù)據(jù)庫的XA協(xié)議來實(shí)現(xiàn)的2PC又稱為XA方案。
現(xiàn)在有應(yīng)用程序(AP)持有訂單庫和庫存庫,應(yīng)用程序(AP)通過TM通知訂單庫(RM)和庫存庫(RM),進(jìn)行扣減庫存和生成訂單,這個(gè)時(shí)候RM并沒有提交事務(wù),而且鎖定資源。
當(dāng)TM收到執(zhí)行消息,如果有一方RM執(zhí)行失敗,分別向其他RM也發(fā)送回滾事務(wù),回滾完畢,釋放鎖資源
當(dāng)TM收到執(zhí)行消息,RM全部成功,向所有RM發(fā)起提交事務(wù),提交完畢,釋放鎖資源。
分布式通信協(xié)議XA規(guī)范,具體執(zhí)行流程如下所示:
第一步:AP創(chuàng)建了RM1,RM2的JDBC連接。
第二步:AP通知生成全局事物ID,并把RM1,RM2注冊(cè)到全局事務(wù)ID
第三步:執(zhí)行二階段協(xié)議中的第一階段prepare
第四步:根據(jù)prepare請(qǐng)求,決定整體提交或回滾。
但是對(duì)于XA而言,如果一個(gè)參與全局事務(wù)的資源“失聯(lián)”了,那么就意味著TM收不到分支事務(wù)結(jié)束的命令,那么它鎖定的數(shù)據(jù),將會(huì)一直被鎖定,從而產(chǎn)生死鎖,這個(gè)也是Seata需要重點(diǎn)解決的問題。
在Seata定義的分布式事務(wù)架構(gòu)中,利用事務(wù)資源(數(shù)據(jù)局、消息)等對(duì)XA協(xié)議進(jìn)行支持,用XA協(xié)議的機(jī)制來管理分支事務(wù)。
-
執(zhí)行階段:
- 可回滾:業(yè)務(wù)SQL操作在XA分支中進(jìn)行,有資源管理器對(duì)XA協(xié)議的支持來保證可回滾
- 持久化:ZA分支完成以后,執(zhí)行 XA prepare,同樣,由資源對(duì)XA協(xié)議的支持來保證持久化
-
完成階段:
- 分支提交:執(zhí)行XA分支的commit
- 分支回滾:執(zhí)行XA分支的rollback
XA存在的意義
Seata 已經(jīng)支持了三大事務(wù)模式:AT\TCC\SAGA,這三個(gè)都是補(bǔ)償型事務(wù),補(bǔ)償型事務(wù)處理你機(jī)制構(gòu)建在 事務(wù)資源 之上(要么中間件層面,要么應(yīng)用層),事務(wù)資源本身對(duì)于分布式的事務(wù)是無感知的,這種對(duì)于分布式事務(wù)的無感知存在有一個(gè)根本性的問題,無法做到真正的全局一致性。
例如一個(gè)庫存記錄,在補(bǔ)償型事務(wù)處理過程中,用80扣減為60,這個(gè)時(shí)候倉庫管理員查詢數(shù)據(jù)結(jié)果,看到的是60,之后因?yàn)楫惓;貪L,庫存回滾到原來的80,那么這個(gè)時(shí)候庫存管理員看到的60,其實(shí)就是臟數(shù)據(jù),而這個(gè)中間狀態(tài)就是補(bǔ)償型事務(wù)存在的臟數(shù)據(jù)。
和補(bǔ)償型事務(wù)不同,XA協(xié)議要求事務(wù)資源 本身提供對(duì)規(guī)范和協(xié)議的支持,因?yàn)槭聞?wù)資源感知并參與分布式事務(wù)處理過程中,所以事務(wù)資源可以保證從任意視角對(duì)數(shù)據(jù)的訪問有效隔離性,滿足全局?jǐn)?shù)據(jù)的一致性。
XA模式的使用
官方案例:https://github.com/seata/seata-samples
項(xiàng)目名:seata-samples
業(yè)務(wù)開始: business-xa
庫存服務(wù): stock-xa
訂單服務(wù): order-xa
賬號(hào)服務(wù): account-xa
把這個(gè)項(xiàng)目案例下載下來以后,找到項(xiàng)目名為seata-xa
的目錄,里面有測(cè)試數(shù)據(jù)庫的鏈接,如果不想用測(cè)試數(shù)據(jù)庫,只需要修改官方文檔中數(shù)據(jù)庫配置信息即可。
首先關(guān)注的是 business-xa
項(xiàng)目,更多的關(guān)注BusinessService.purchase()
方法
@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount, boolean rollback) {
String xid = RootContext.getXID();
LOGGER.info("New Transaction Begins: " + xid);
//調(diào)用庫存減庫存
String result = stockFeignClient.deduct(commodityCode, orderCount);
if (!SUCCESS.equals(result)) {
throw new RuntimeException("庫存服務(wù)調(diào)用失敗,事務(wù)回滾!");
}
//生成訂單
result = orderFeignClient.create(userId, commodityCode, orderCount);
if (!SUCCESS.equals(result)) {
throw new RuntimeException("訂單服務(wù)調(diào)用失敗,事務(wù)回滾!");
}
if (rollback) {
throw new RuntimeException("Force rollback ... ");
}
}
其實(shí)現(xiàn)方法較之前差不多,我們只需要在order-xa
里面(OrderService.create
),添加人為錯(cuò)誤(int i = 1/0;
)
public void create(String userId, String commodityCode, Integer count) {
String xid = RootContext.getXID();
LOGGER.info("create order in transaction: " + xid);
int i = 1/0;
// 定單總價(jià) = 訂購數(shù)量(count) * 商品單價(jià)(100)
int orderMoney = count * 100;
// 生成訂單
jdbcTemplate.update("insert order_tbl(user_id,commodity_code,count,money) values(?,?,?,?)",
new Object[] {userId, commodityCode, count, orderMoney});
// 調(diào)用賬戶余額扣減
String result = accountFeignClient.reduce(userId, orderMoney);
if (!SUCCESS.equals(result)) {
throw new RuntimeException("Failed to call Account Service. ");
}
}
里面有一個(gè)方法可以進(jìn)行XA模式和AT模式的轉(zhuǎn)換OrderXADataSourceConfiguration.dataSource
@Bean("dataSourceProxy")
public DataSource dataSource(DruidDataSource druidDataSource) {
// DataSourceProxy for AT mode
// return new DataSourceProxy(druidDataSource);
// DataSourceProxyXA for XA mode
return new DataSourceProxyXA(druidDataSource);
}
我們啟動(dòng)這四個(gè)服務(wù),訪問地址 http://localhost:8084/purchase
我們可以其中報(bào)錯(cuò),然后再去看對(duì)應(yīng)數(shù)據(jù)庫的數(shù)據(jù),沒有發(fā)生更改,說明我們的XA模式生效了,當(dāng)你dubug去看里面的庫存服務(wù)的時(shí)候,當(dāng)操作數(shù)據(jù)更改的時(shí)候,數(shù)據(jù)庫里面其實(shí)也是沒有記錄的,因?yàn)閄A是強(qiáng)一致性,只有當(dāng)事務(wù)結(jié)束完成以后,才會(huì)更改其中的數(shù)據(jù)。
XA模式的加入,補(bǔ)齊了Seata在全局一致性場(chǎng)景下的缺口,形成了AT、TCC、Saga、XA 四大事務(wù)模式的版圖,基本滿足了所有場(chǎng)景分布式事務(wù)處理的需求。
其中XA和AT是無業(yè)務(wù)侵入的,而TCC和Saga是有一定業(yè)務(wù)侵入的。
Seata-AT模式
先來介紹一下AT模式,AT模式是一種沒有侵入的分布式事務(wù)的解決方案,在AT模式下,用戶只需關(guān)注自己的業(yè)務(wù)SQL,用戶的業(yè)務(wù)SQL作為一階段,Seata框架會(huì)自動(dòng)生成事務(wù)進(jìn)行二階段提交和回滾操作。
兩階段提交協(xié)議的演變:
-
一階段:業(yè)務(wù)數(shù)據(jù)和回滾日志記錄在同一個(gè)本地事務(wù)中提交,釋放本地鎖和連接資源。
-
二階段:
提交異步化,非??焖俚赝瓿?。
回滾通過一階段的回滾日志進(jìn)行反向補(bǔ)償。
AT模式主要特點(diǎn)
- 最終一致性
- 性能較XA高
- 只在第一階段獲取鎖,在第一階段進(jìn)行提交后釋放鎖。
在一階段中,Seata會(huì)攔截 業(yè)務(wù)SQL ,首先解析SQL語義,找到要操作的業(yè)務(wù)數(shù)據(jù),在數(shù)據(jù)被操作前,保存下來記錄 undo log,然后執(zhí)行 業(yè)務(wù)SQL 更新數(shù)據(jù),更新之后再次保存數(shù)據(jù) redo log,最后生成行鎖,這些操作都在本地?cái)?shù)據(jù)庫事務(wù)內(nèi)完成,這樣保證了一階段的原子性。
相對(duì)一階段,二階段比較簡單,負(fù)責(zé)整體的回滾和提交,如果之前的一階段中有本地事務(wù)沒有通過,那么就執(zhí)行全局回滾,否在執(zhí)行全局提交,回滾用到的就是一階段記錄的 undo Log ,通過回滾記錄生成反向更新SQL并執(zhí)行,以完成分支的回滾。當(dāng)然事務(wù)完成后會(huì)釋放所有資源和刪除所有日志。
AT流程分為兩階段,主要邏輯全部在第一階段,第二階段主要做回滾或日志清理的工作。流程如下:
從上圖中我們可以看到,訂單服務(wù)中TM向TC申請(qǐng)開啟一個(gè)全局事務(wù),一般通過@GlobalTransactional
標(biāo)注開啟,TC會(huì)返回一個(gè)全局事務(wù)ID(XID),訂單服務(wù)在執(zhí)行本地事務(wù)之前,RM會(huì)先向TC注冊(cè)一個(gè)分支事務(wù),
訂單服務(wù)依次生成undo log 執(zhí)行本地事務(wù),生成redo log 提交本地事務(wù),向TC匯報(bào),事務(wù)執(zhí)行OK。
訂單服務(wù)發(fā)起遠(yuǎn)程調(diào)用,將事務(wù)ID傳遞給庫存服務(wù),庫存服務(wù)在執(zhí)行本地事務(wù)之前,先向TC注冊(cè)分支事務(wù),庫存服務(wù)同樣生成undo Log和redo Log,向TC匯報(bào),事務(wù)狀態(tài)成功。
如果正常全局提交,TC通知RM一步清理掉本地undo和redo日志,如果存在一個(gè)服務(wù)執(zhí)行失敗,那么發(fā)起回滾請(qǐng)求。通過undo log進(jìn)行回滾。
在這里還會(huì)存在一個(gè)問題,因?yàn)槊總€(gè)事務(wù)從本地提交到通知回滾這段時(shí)間里面,可能這條數(shù)據(jù)已經(jīng)被其他事務(wù)進(jìn)行修改,如果直接用undo log進(jìn)行回滾,可能會(huì)導(dǎo)致數(shù)據(jù)不一致的情況,
這個(gè)時(shí)候 RM會(huì)用 redo log進(jìn)行驗(yàn)證,對(duì)比數(shù)據(jù)是否一樣,從而得知數(shù)據(jù)是否有別的事務(wù)進(jìn)行修改過,undo log是用于被修改前的數(shù)據(jù),可以用來回滾,redolog是用于被修改后的數(shù)據(jù),用于回滾校驗(yàn)。
如果數(shù)據(jù)沒有被其他事務(wù)修改過,可以直接進(jìn)行回滾,如果是臟數(shù)據(jù),redolog校驗(yàn)后進(jìn)行處理。
實(shí)戰(zhàn)
了解了AT模型的基本操作,接下來就來實(shí)戰(zhàn)操作一下,關(guān)于AT模型具體是如何實(shí)現(xiàn)的。首先設(shè)計(jì)兩個(gè)服務(wù) cloud-alibaba-seata-order
和 cloud-alibaba-seata-stock
表結(jié)構(gòu)t_order
、t_stock
和undo_log
三張表,項(xiàng)目源碼和表結(jié)構(gòu),加上undo_log
表,此表用于數(shù)據(jù)的回滾,文末有鏈接。
cloud-alibaba-seata-order
核心代碼如下:
controller
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("order/create")
@GlobalTransactional //開啟分布式事務(wù)
public String create(){
orderService.create();
return "訂單創(chuàng)建成功!";
}
}
OrderService
public interface OrderService {
void create();
}
StockClient
@FeignClient(value = "seata-stock")
public interface StockClient {
@GetMapping("/stock/reduce")
String reduce();
}
OrderServiceImpl
@Service
public class OrderServiceImpl implements OrderService{
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockClient stockClient;
@Override
public void create() {
//扣減庫存
stockClient.reduce();
System.out.println("扣減庫存成功!");
//手工異常 用于回滾庫存信息
int i = 1/0;
System.err.println("異常!");
//創(chuàng)建訂單
orderMapper.createOrder();
System.out.println("創(chuàng)建訂單成功!");
}
}
OrderMapper
@Mapper
public interface OrderMapper {
@Insert("insert into t_order (order_no,order_num) value (order_no+1,1)")
void createOrder();
}
cloud-alibaba-seata-stock
核心代碼如下:
@RestController
public class StockController {
@Autowired
private StockService stockService;
@GetMapping("stock/reduce")
public String reduce(){
stockService.reduce();
return "庫存數(shù)量已扣減:"+ new Date();
}
}
public interface StockService {
void reduce();
}
@Service
public class StockServiceImpl implements StockService{
@Autowired
StockMapper stockMapper;
@Override
public void reduce() {
stockMapper.reduce();
}
}
@Mapper
@Repository
public interface StockMapper {
@Update("update t_stock set order_num = order_num - 1 where order_no = 1 ")
void reduce();
}
代碼都比較簡單,我們就不做過多的描述,基本注釋也都有,,首先我們需要將order和stock服務(wù)都跑起來,在之前我們的Nacos和Seata都要啟動(dòng)起來,這個(gè)時(shí)候我們?cè)L問order的Rest接口,http://localhost:8087/order/create
,為了驗(yàn)證undo_log的表是用于存儲(chǔ)回滾數(shù)據(jù),我們?cè)?code>OrderServiceImpl.create()中添加斷點(diǎn),用debug的方式啟動(dòng)
然后訪問http://localhost:8087/order/create
,當(dāng)程序卡在這個(gè)節(jié)點(diǎn)的時(shí)間,我們?nèi)タ?code>undo_log和庫存表,會(huì)發(fā)現(xiàn),庫存確實(shí)減少了,而且undo_log
也出現(xiàn)了對(duì)應(yīng)的快照記錄修改當(dāng)前的數(shù)據(jù)信息,這個(gè)數(shù)據(jù)就是用來回滾的數(shù)據(jù),
但是當(dāng)我們F9通過以后,庫存數(shù)量恢復(fù),并且undo_log表的數(shù)據(jù)行也沒有了,這個(gè)時(shí)候證明我們的Seata事務(wù)生效,回滾成功。
到這里我們就驗(yàn)證了AT事務(wù)的執(zhí)行過程,相比于XA和TCC等事務(wù)模型,Seata的AT模型可以應(yīng)對(duì)大多數(shù)的業(yè)務(wù)場(chǎng)景,并且可以做到無業(yè)務(wù)侵入,開發(fā)者無感知,對(duì)于整個(gè)事務(wù)的協(xié)調(diào)、提交或者回滾操作,都可以通過AOP完成,開發(fā)者只需要關(guān)注業(yè)務(wù)即可。
由于Seata需要在不同的服務(wù)之間傳遞全局唯一的事務(wù)ID,和Dubbo等框架集成會(huì)比較友好,例如Dubbo可以用過隱士傳參來進(jìn)行事務(wù)ID的傳遞,整個(gè)事務(wù)ID的傳播過程對(duì)開發(fā)者也可以做到無感知。
Seata-TCC模式
具體使用案例:https://seata.io/zh-cn/blog/integrate-seata-tcc-mode-with-spring-cloud.html
什么是TCC
TCC 是分布式事務(wù)中的二階段提交協(xié)議,它的全稱為 Try-Confirm-Cancel,即資源預(yù)留(Try)、確認(rèn)操作(Confirm)、取消操作(Cancel),他們的具體含義如下:
- Try:對(duì)業(yè)務(wù)資源的檢查并預(yù)留;
- Confirm:對(duì)業(yè)務(wù)處理進(jìn)行提交,即 commit 操作,只要 Try 成功,那么該步驟一定成功;
- Cancel:對(duì)業(yè)務(wù)處理進(jìn)行取消,即回滾操作,該步驟回對(duì) Try 預(yù)留的資源進(jìn)行釋放。
TCC 是一種侵入式的分布式事務(wù)解決方案,以上三個(gè)操作都需要業(yè)務(wù)系統(tǒng)自行實(shí)現(xiàn),對(duì)業(yè)務(wù)系統(tǒng)有著非常大的入侵性,設(shè)計(jì)相對(duì)復(fù)雜,但優(yōu)點(diǎn)是 TCC 完全不依賴數(shù)據(jù)庫,能夠?qū)崿F(xiàn)跨數(shù)據(jù)庫、跨應(yīng)用資源管理,對(duì)這些不同數(shù)據(jù)訪問通過侵入式的編碼方式實(shí)現(xiàn)一個(gè)原子操作,更好地解決了在各種復(fù)雜業(yè)務(wù)場(chǎng)景下的分布式事務(wù)問題。
TCC和AT區(qū)別
AT 模式基于 支持本地 ACID 事務(wù) 的 關(guān)系型數(shù)據(jù)庫:
- 一階段 prepare 行為:在本地事務(wù)中,一并提交業(yè)務(wù)數(shù)據(jù)更新和相應(yīng)回滾日志記錄。
- 二階段 commit 行為:馬上成功結(jié)束,自動(dòng) 異步批量清理回滾日志。
- 二階段 rollback 行為:通過回滾日志,自動(dòng) 生成補(bǔ)償操作,完成數(shù)據(jù)回滾。
相應(yīng)的,TCC 模式,不依賴于底層數(shù)據(jù)資源的事務(wù)支持:
- 一階段 prepare 行為:調(diào)用自定義 的 prepare 邏輯。
- 二階段 commit 行為:調(diào)用自定義 的 commit 邏輯。
- 二階段 rollback 行為:調(diào)用自定義 的 rollback 邏輯。
所謂 TCC 模式,是指支持把 自定義 的分支事務(wù)納入到全局事務(wù)的管理中。
特點(diǎn):
- 侵入性比較強(qiáng),并且需要自己實(shí)現(xiàn)相關(guān)事務(wù)控制邏輯
- 在整個(gè)過程基本沒有鎖,性能較強(qiáng)
Seata-Saga模式
Saga模式是SEATA提供的長事務(wù)解決方案,在Saga模式中,業(yè)務(wù)流程中每個(gè)參與者都提交本地事務(wù),當(dāng)出現(xiàn)某一個(gè)參與者失敗則補(bǔ)償前面已經(jīng)成功的參與者,一階段正向服務(wù)和二階段補(bǔ)償服務(wù)(執(zhí)行處理時(shí)候出錯(cuò)了,給一個(gè)修復(fù)的機(jī)會(huì))都由業(yè)務(wù)開發(fā)實(shí)現(xiàn)。
Saga 模式下分布式事務(wù)通常是由事件驅(qū)動(dòng)的,各個(gè)參與者之間是異步執(zhí)行的,Saga 模式是一種長事務(wù)解決方案。
之前我們學(xué)習(xí)的Seata分布式三種操作模型中所使用的的微服務(wù)全部可以根據(jù)開發(fā)者的需求進(jìn)行修改,但是在一些特殊環(huán)境下,比如老系統(tǒng),封閉的系統(tǒng)(無法修改,同時(shí)沒有任何分布式事務(wù)引入),那么AT、XA、TCC模型將全部不能使用,為了解決這樣的問題,才引用了Saga模型。
比如:事務(wù)參與者可能是其他公司的服務(wù)或者是遺留系統(tǒng),無法改造,可以使用Saga模式。
Saga模式是Seata提供的長事務(wù)解決方案,提供了異構(gòu)系統(tǒng)的事務(wù)統(tǒng)一處理模型。在Saga模式中,所有的子業(yè)務(wù)都不在直接參與整體事務(wù)的處理(只負(fù)責(zé)本地事務(wù)的處理),而是全部交由了最終調(diào)用端來負(fù)責(zé)實(shí)現(xiàn),而在進(jìn)行總業(yè)務(wù)邏輯處理時(shí),在某一個(gè)子業(yè)務(wù)出現(xiàn)問題時(shí),則自動(dòng)補(bǔ)償全面已經(jīng)成功的其他參與者,這樣一階段的正向服務(wù)調(diào)用和二階段的服務(wù)補(bǔ)償處理全部由總業(yè)務(wù)開發(fā)實(shí)現(xiàn)。
Saga狀態(tài)機(jī)
目前Seata提供的Saga模式只能通過狀態(tài)機(jī)引擎來實(shí)現(xiàn),需要開發(fā)者手工的進(jìn)行Saga業(yè)務(wù)流程繪制,并且將其轉(zhuǎn)換為Json配置文件,而后在程序運(yùn)行時(shí),將依據(jù)子配置文件實(shí)現(xiàn)業(yè)務(wù)處理以及服務(wù)補(bǔ)償處理,而要想進(jìn)行Saga狀態(tài)圖的繪制,一般需要通過Saga狀態(tài)機(jī)來實(shí)現(xiàn)。
基本原理:
- 通過狀態(tài)圖來定義服務(wù)調(diào)用的流程并生成json定義文件
- 狀態(tài)圖中一個(gè)節(jié)點(diǎn)可以調(diào)用一個(gè)服務(wù),節(jié)點(diǎn)可以配置它的補(bǔ)償節(jié)點(diǎn)
- 狀態(tài)圖 json 由狀態(tài)機(jī)引擎驅(qū)動(dòng)執(zhí)行,當(dāng)出現(xiàn)異常時(shí)狀態(tài)引擎反向執(zhí)行已成功節(jié)點(diǎn)對(duì)應(yīng)的補(bǔ)償節(jié)點(diǎn)將事務(wù)回滾
- 可以實(shí)現(xiàn)服務(wù)編排需求,支持單項(xiàng)選擇、并發(fā)、子流程、參數(shù)轉(zhuǎn)換、參數(shù)映射、服務(wù)執(zhí)行狀態(tài)判斷、異常捕獲等功能
Saga狀態(tài)機(jī)的應(yīng)用
官方文檔地址:https://seata.io/zh-cn/docs/user/saga.html
Seata Safa狀態(tài)機(jī)可視化圖形設(shè)計(jì)器使用地址:https://github.com/seata/seata/blob/develop/saga/seata-saga-statemachine-designer/README.zh-CN.md
總結(jié)
總的來說在Seata的中AT模式基本可以滿足百分之80的分布式事務(wù)的業(yè)務(wù)需求,AT模式實(shí)現(xiàn)的是最終一致性,所以可能存在中間狀態(tài),而XA模式實(shí)現(xiàn)的強(qiáng)一致性,所以效率較低一點(diǎn),而Saga可以用來處理不同開發(fā)語言之間的分布式事務(wù),所以關(guān)于分布式事務(wù)的四大模型,基本可以滿足所有的業(yè)務(wù)場(chǎng)景,其中XA和AT沒有業(yè)務(wù)侵入性,而Saga和TCC具有一定的業(yè)務(wù)侵入。
到這里我們的Seata分布式事務(wù)就講完了,如果有不懂或者有疑問的小伙伴記得評(píng)論區(qū)留言。
關(guān)于資料和項(xiàng)目源碼,公眾號(hào)后臺(tái): seata
,即可獲取
我是牧小農(nóng)怕什么真理無窮,進(jìn)一步有進(jìn)一步的歡喜,大家加油!
作者:牧小農(nóng)
公眾號(hào):牧小農(nóng),微信掃碼關(guān)注或搜索公眾號(hào)名稱