前言
一、Seata 介紹
1.1、Seata 簡介
Seata 是一款開源的分布式事務(wù)解決方案,致力于提供高性能與簡單易用的分布式事務(wù)服務(wù),為用戶提供了 AT、TCC、SAGA 和 XA 幾種不同的事務(wù)模式:
- AT模式:無侵入式的分布式事務(wù)解決方案,適合不希望對業(yè)務(wù)進行改造的場景,但由于需要添加全局事務(wù)鎖,對影響高并發(fā)系統(tǒng)的性能。該模式主要關(guān)注多DB訪問的數(shù)據(jù)一致性,也包括多服務(wù)下的多DB數(shù)據(jù)訪問一致性問題
- TCC模式:高性能的分布式事務(wù)解決方案,適用于對性能要求比較高的場景。該模式主要關(guān)注業(yè)務(wù)拆分,在按照業(yè)務(wù)橫向擴展資源時,解決服務(wù)間調(diào)用的一致性問題
- Saga模式:長事務(wù)的分布式事務(wù)解決方案,適用于業(yè)務(wù)流程長且需要保證事務(wù)最終一致性的業(yè)務(wù)系統(tǒng)。Saga 模式一階段就會提交本地事務(wù),無鎖,長流程情況下可以保證性能,多用于渠道層、集成層業(yè)務(wù)系統(tǒng),事務(wù)參與者可以是其它公司的服務(wù)也可以是遺留系統(tǒng)的服務(wù),并且對于無法進行改造和提供 TCC 要求的接口,也可以使用 Saga 模式
1.2、Seata 的核心組件
在 Seata 中主要有以下三種角色,其中 TM 和 RM 是作為 Seata 的客戶端與業(yè)務(wù)系統(tǒng)集成在一起,TC 作為 Seata 的服務(wù)端獨立部署:
- 事務(wù)協(xié)調(diào)器(TC):維護全局事務(wù)的運行狀態(tài),負責(zé)協(xié)調(diào)并驅(qū)動全局提交或回滾
- 事務(wù)管理器(TM):事務(wù)發(fā)起方,控制全局事務(wù)的范圍,負責(zé)開啟一個全局事務(wù),并最終發(fā)起全局提交或回滾全局的決議
- 資源管理器(RM):事務(wù)參與方,管理本地事務(wù)正在處理的資源,負責(zé)向 TC 注冊本地事務(wù)、匯報本地事務(wù)狀態(tài),接收 TC 的命令來驅(qū)動本地事務(wù)的提交或回滾
1.3、Seata 的整體執(zhí)行流程
Seata 分布式事務(wù)的整體執(zhí)行機制如上圖所示,可以大致分為兩階段提交:
- 發(fā)起方 TM 向 TC 申請開啟一個全局事務(wù),全局事務(wù)創(chuàng)建成功并生成唯一的全局事務(wù)標(biāo)識 XID,該 XID 在后續(xù)事務(wù)的服務(wù)調(diào)用鏈路的上下文傳播(通過Aop實現(xiàn)))
- RM 向 TC 注冊分支事務(wù),匯報資源準(zhǔn)備狀況,并與 XID 進行綁定(Branch分支事務(wù)指分布式事務(wù)中每個獨立的本地局部事務(wù))
- TM 向 TC 發(fā)起 XID 下的所有分支事務(wù)的全局提交或回滾請求(事務(wù)一階段結(jié)束)
- TC 匯總事務(wù)信息,決定分布式事務(wù)是提交還是回滾;
- TC 通知所有 RM 提交/回滾 資源,事務(wù)二階段結(jié)束;
二、Seata 的 AT 模式原理
Seata AT模式是基于XA事務(wù)(XA是基于數(shù)據(jù)庫實現(xiàn)的分布式事務(wù)協(xié)議)演進而來,需要數(shù)據(jù)庫支持,如果是 MySQL,則需要5.6以上版本才支持XA協(xié)議。AT 模式的特點就是對業(yè)務(wù)無入侵式,用戶只需要關(guān)注自己的業(yè)務(wù)SQL,Seata 框架會在第一階段攔截并解析用戶的 SQL,并保存其變更前后的數(shù)據(jù)鏡像,形成undo log,并自動生成事務(wù)第二階段的提交和回滾操作。
2.1、AT 模式的整體執(zhí)行流程
AT 模式 RM 驅(qū)動分支事務(wù)的行為分為以下兩個階段:
-
執(zhí)行階段:
- 代理 JDBC 數(shù)據(jù)源,攔截并解析業(yè)務(wù) SQL,生成更新前后的鏡像數(shù)據(jù),形成 UNDO LOG。
- 向 TC 注冊分支。
- 分支注冊成功后,把業(yè)務(wù)數(shù)據(jù)的更新和 UNDO LOG 放在同一個本地事務(wù)中提交。
-
完成階段:
- 全局提交,收到 TC 的分支提交請求,異步刪除相應(yīng)分支的 UNDO LOG。
- 全局回滾,收到 TC 的分支回滾請求,查詢分支對應(yīng)的 UNDO LOG 記錄,生成補償回滾的 SQL 語句,執(zhí)行分支回滾并返回結(jié)果給 TC
2.2、AT 模式兩階段詳細流程
2.2.1、第一階段的詳細執(zhí)行流程
在第一階段,RM 寫表時,Seata 通過代理數(shù)據(jù)源(從而達到對業(yè)務(wù)無侵入的效果)攔截業(yè)務(wù) SQL 并 解析 SQL 語義,找到該 SQL 要更新的業(yè)務(wù)數(shù)據(jù)保存成 before image(前置鏡像),然后執(zhí)行業(yè)務(wù)SQL,在業(yè)務(wù)數(shù)據(jù)更新后,再將其保存成 after image(后置鏡像),最后生成行鎖。通過把更新前后的業(yè)務(wù)數(shù)據(jù)數(shù)據(jù)鏡像組織成回滾日志 undo log,并利用本地事務(wù)的 ACID 特性,將業(yè)務(wù)數(shù)據(jù)的更新和回滾日志 undo log 的寫入在同一個本地事務(wù)中提交,保證任何提交的業(yè)務(wù)數(shù)據(jù)的更新一定有相應(yīng)的回滾日志存在(即操作的原子性)。
基于這樣的機制,分支的本地事務(wù)就可以在全局事務(wù)的第一階段提交,并馬上釋放本地事務(wù)鎖定的資源,這也是 Seata 的 AT 模式和 XA 事務(wù)的不同之處,兩階段提交往往對資源的鎖定需要持續(xù)到第二階段實際的提交或者回滾操作,而有了回滾日志之后,可以在第一階段釋放對資源的鎖定,降低了鎖范圍,提高效率,即使第二階段發(fā)生異常需要回滾,只需找對 undo log 中對應(yīng)數(shù)據(jù)鏡像并反解析成 SQL 來達到回滾目的
2.2.2、第二階段提交的詳細執(zhí)行流程
如果二階段的全局表決結(jié)果是提交的話,說明所有分支事務(wù)的業(yè)務(wù)SQL已經(jīng)在第一階段生效,此時 Seata 框架只需異步刪除所有分支第一階段保存的鏡像數(shù)據(jù)、回滾日志和行鎖,完成數(shù)據(jù)清理即可,這個過程是非??焖俚?/p>
2.2.3、第二階段回滾的詳細執(zhí)行流程
如果第二階段是回滾的話,Seata 就需要回滾第一階段已執(zhí)行的 SQL 進行還原業(yè)務(wù)數(shù)據(jù)。由 TC 通知所有 RM 進行根據(jù)第一階段的回滾日志 undo log (即before image)進行反向補償,RM 收到 TC 發(fā)來的回滾請求后,通過 XID 和 Branch ID 找到相應(yīng)的回滾日志記錄,通過undo log 生成反向的更新 SQL 并執(zhí)行,以完成分支的業(yè)務(wù)數(shù)據(jù)還原,最后刪除 undo log、redo log 和行鎖。但在還原前需要校驗臟寫,對比數(shù)據(jù)庫”當(dāng)前業(yè)務(wù)數(shù)據(jù)”和 “after image”,如果兩份數(shù)據(jù)完全一致就說明沒有臟寫,可以還原業(yè)務(wù)數(shù)據(jù),如果不一致就說明有臟寫,出現(xiàn)臟寫就需要轉(zhuǎn)人工處理。
三、Seata 的 TCC、Saga、XA模式原理
文章第二部分介紹了 Seata 的 AT 模式,接下來我們就介紹下 Seata 的其實幾種事務(wù)模式:
3.1、TCC 模式
TCC 模式 RM 驅(qū)動分支事務(wù)的行為分為以下兩個階段:
-
執(zhí)行階段:
① 向 TC 注冊分支。
② 執(zhí)行業(yè)務(wù)定義的 Try 方法。
③ 向 TC 上報 Try 方法執(zhí)行情況:成功或失敗。 -
完成階段:
① 全局提交,收到 TC 的分支提交請求,執(zhí)行業(yè)務(wù)定義的 Confirm 方法。
② 全局回滾,收到 TC 的分支回滾請求,執(zhí)行業(yè)務(wù)定義的 Cancel 方法。
3.2、Saga 模式
Saga 模式 RM 驅(qū)動分支事務(wù)的行為包含以下兩個階段:
-
執(zhí)行階段:
① 向 TC 注冊分支。
② 執(zhí)行業(yè)務(wù)方法。
③ 向 TC 上報業(yè)務(wù)方法執(zhí)行情況:成功或失敗。 -
完成階段:
全局提交,RM 不需要處理。
全局回滾,收到 TC 的分支回滾請求,執(zhí)行業(yè)務(wù)定義的補償回滾方法。
3.3、XA模式
XA 模式 RM 驅(qū)動分支事務(wù)的行為包含以下兩個階段:
-
執(zhí)行階段:
① 向 TC 注冊分支
② XA Start,執(zhí)行業(yè)務(wù) SQL,XA End
③ XA prepare,并向 TC 上報 XA 分支的執(zhí)行情況:成功或失敗 -
完成階段:
收到 TC 的分支提交請求,XA Commit
收到 TC 的分支回滾請求,XA Rollback
參考文章:
https://help.aliyun.com/document_detail/157850.html
四、SpringCloud Alibaba 整合 Seata AT 模式
4.1、搭建 Seata TC 協(xié)調(diào)者
4.1.1、下載 Seata TC 協(xié)調(diào)者
Seata 的協(xié)調(diào)者其實就是阿里開源的一個服務(wù),我們只需要下載(下載地址:https://github.com/seata/seata/releases)并啟動它,下載完成后,直接解壓即可,但是此時還不能直接運行,還需要做一些配置
4.1.2、創(chuàng)建TC所需要的表
https://github.com/seata/seata/tree/1.4.2/script/server/db
TC 運行需要將事務(wù)信息保存在數(shù)據(jù)庫,因此需要創(chuàng)建一些表,找到 seata-1.4.2 源碼的 script\server\db 這個目錄,將會看到以下SQL文件:
以 MySQL 數(shù)據(jù)庫為例,創(chuàng)建數(shù)據(jù)庫 seata,并執(zhí)行 mysql.sql 文件中的sql語句:
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
創(chuàng)建的三張表如下圖:
- global_table:全局事務(wù)表,每當(dāng)有一個全局事務(wù)發(fā)起后,就會在該表中記錄全局事務(wù)的ID
- branch_table:分支事務(wù)表,記錄每一個分支事務(wù)的ID,分支事務(wù)操作的哪個數(shù)據(jù)庫等信息
- lock_table:全局鎖
4.1.3、修改TC的注冊中心和配置中心
找到 seata-server-1.4.2\seata\conf 目錄,其中有一個 registry.conf 文件,其中配置了TC的注冊中心和配置中心。默認的注冊中心是 file 形式,實際使用中肯定不能使用,需要改成Nacos形式;同樣 Seate 的 TC 的配置中心默認也是使用 file 形式,需要修改為 nacos 作為配置中心:
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "localhost:8848"
namespace = "XXXXXXXXXX"
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "localhost:8848"
namespace = "XXXXXXXXXX"
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
}
需要改動的地方如下:
- type:改成nacos,表示使用nacos作為注冊中心或者配置中心
- application:服務(wù)的名稱
- serverAddr:nacos的地址
- group:分組
- namespace:命名空間
- username:用戶名
- password:密碼
上述配置修改好之后,在TC啟動的時候?qū)詣幼x取 nacos 的配置,最后這份 registry.conf 文件的配置需要與下文每個seata客戶端項目中的配置一致
4.1.4、導(dǎo)入 seata Server 配置
建議將 seata 項目下載到本地并閱讀 https://github.com/seata/seata/tree/1.4.2/script/config-center 路徑下的README.md內(nèi)容
在上面我們已經(jīng)配置好 seata TC 的配置中心,那么 TC 需要存儲到 Nacos 的配置都有哪些,如何推送過去呢?在 https://github.com/seata/seata/tree/1.4.2/script/config-center 中有一個 confIg.txt 文件,其中就是 TC 需要的全部配置,而 https://github.com/seata/seata/tree/1.4.2/script/config-center/nacos 中有一個 nacos-config.sh 腳本能夠?qū)?config.txt 中的全部配置自動推送到nacos中。
我們先啟動nacos服務(wù),然后在 nacos-config.sh 目錄下執(zhí)行下面命令,將 config.txt 中的配置信息推送到 nacos 中:
# -h 代表nacos服務(wù)的IP;-p 代表nacos的端口號;-g 分組信息;-t 命名空間ID;-u 用戶名,-p 密碼
$ sh nacos-config.sh -h 127.0.0.1 -p 8080 -g SEATA_GROUP -t 7a7581ef-433d-46f3-93f9-5fdc18239c65 -u nacos -w nacos
命令執(zhí)行成功后,就可以在nacos中看到如下配置:
4.1.5、修改TC的事務(wù)信息存儲方式
seata 的 TC 端的事務(wù)信息存儲模式(store.mode)現(xiàn)有file、db、redis三種,file模式無需改動,直接啟動即可,下面專門講下db和redis啟動步驟。
注: file模式為單機模式,全局事務(wù)會話信息內(nèi)存中讀寫并持久化本地文件root.data,性能較高;
(1)以DB模式存儲:
db模式為高可用模式,全局事務(wù)會話信息通過db共享,相應(yīng)性能差些;上一節(jié)的內(nèi)容已經(jīng)將所有的配置信息都推送到了Nacos中,TC啟動時會從Nacos中讀取,因此我們修改也需要在Nacos中修改。需要修改的配置如下:
## 采用db的存儲形式
store.mode=db
## druid數(shù)據(jù)源
store.db.datasource=druid
## mysql數(shù)據(jù)庫
store.db.dbType=mysql
## mysql驅(qū)動
store.db.driverClassName=com.mysql.jdbc.Driver
## TC的數(shù)據(jù)庫url
store.db.url=jdbc:mysql://127.0.0.1:3306/seata_server?useUnicode=true
## 用戶名
store.db.user=root
## 密碼
store.db.password=Nov2014
在nacos中搜索上述的配置,直接修改其中的值,比如修改 store.mode,如下圖:
(2)以Redis模式存儲:
redis模式 Seata-Server 1.3 及以上版本支持,性能較高,但存在事務(wù)信息丟失風(fēng)險,所以需要提前配置合適當(dāng)前場景的redis持久化配置,該模式需改動以下配置:
store.mode=redis
store.redis.host=127.0.0.1
store.redis.port=6379
store.redis.password=123456
4.1.6、啟動TC
按照上述步驟全部配置成功后,則可以啟動TC,在 seata-server-1.4.2\bin 目錄下直接點擊 seata-server.bat(windows)運行,啟動成功后,啟動成功后,在Nacos的服務(wù)列表中則可以看到TC已經(jīng)注冊進入,如下圖:
至此,Seata 的 TC 就啟動完成了…
4.2、搭建Seata 客戶端(RM)
4.2.1、maven 添加 seata 依賴
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<!-- 排除依賴,指定版本和服務(wù)端一致 -->
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.4.2</version>
</dependency>
注意:seata客戶端的依賴版本必須要和服務(wù)端一致。
4.2.2、application.yml 添加 seate 配置
seata:
# 這里要特別注意和nacos中配置的要保持一致,建議配置成 ${spring.application.name}-tx-group
tx-service-group: my_test_tx_group
registry:
type: nacos
nacos:
# 配置所在命名空間ID,如未配置默認public空間
server-addr: 127.0.0.1:8848
namespace: XXXXXXXXXX
group: SEATA_GROUP
application: seata-server
userName: nacos
password: nacos
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: XXXXXXXXXX
group: SEATA_GROUP
userName: nacos
password: nacos
tx-service-group 配置的值可以自定義,但是定義后需要在 nacos 配置中心新增 service.vgroupMapping.xxx=default 的配置,該屬性一定一定要和 seata 服務(wù)端的配置一致,否則不生效;比如上述配置中的,就需要在 nacos 配置中心新增一個配置項 service.vgroupMapping.my_test-tx-group=default,并且設(shè)置分組為SEATA_GROUP,如下圖:
注意:my_test-tx-group 僅僅是后綴,在nacos中添加配置時記得要加上前綴 service.vgroupMapping.
4.2.3、數(shù)據(jù)庫中添加回滾日志表
https://github.com/seata/seata/tree/1.4.2/script/client/at/db
回滾日志表:undo_log,這是Seata要求必須有的,每個業(yè)務(wù)庫都應(yīng)該創(chuàng)建一個,SQL如下:
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
4.2.4、使用 seata 作為全局事務(wù)控制
在分布式業(yè)務(wù)入口增加全局事務(wù)注解 @GlobalTransactional,其他 service 接口無需配置;假設(shè)A服務(wù)調(diào)用B服務(wù),那么就在A服務(wù)的方法上面加入,B服務(wù)上面不用加
/**
* seata 的 GlobalTransactional 注解只需要加載分布式業(yè)務(wù)入口處,其他service接口無需配置,從而實現(xiàn)分布式事務(wù)
*/
@GlobalTransactional
@PostMapping (value = "addOrder",produces = {"application/json;charset=utf-8"})
public String addOrder(@RequestParam String title, @RequestParam int price, @RequestParam int shopId, @RequestParam int userId)
{
shopOrderService.save(ShopOrderPojo.builder().price(price).title(title).shopId(shopId).userId(userId).build());
storageFeignService.reduceStorage(shopId, 1);
return "success";
}
備注:如果你進行異常捕捉,seata 將認為你已進行異常處理,就不會回滾數(shù)據(jù)了
(1)比如如果你配置了@ControllerAdvice將可能導(dǎo)致數(shù)據(jù)不回滾
(2)如果使用 Feign 調(diào)用分布式服務(wù)并配置了fallback,后面服務(wù)拋出異常會直接執(zhí)行fallback導(dǎo)致無法回滾(rollbackFor = Exception.class);這時可以在fallback的實現(xiàn)方法內(nèi)手動調(diào)用seata全局回滾,如下所示:文章來源:http://www.zghlxwxcb.cn/news/detail-480120.html
@Override
public BizResponse insertAge(Integer age) {
//feign調(diào)用接口fallback后需要手動調(diào)用全局事務(wù)回滾
try {
String xid = RootContext.getXID();
GlobalTransactionContext.reload(xid).rollback();
} catch (TransactionException e) {
e.printStackTrace();
}
return BizResponse.fail("客戶端降級處理insertAge," + Thread.currentThread().getName());
}
4.2.5、undo log 表介紹
上面介紹過 Seata 是根據(jù) undo_log 中的記錄來回滾的,但是異?;貪L后 undo_log 表卻為空?怎么回事,這是因為 undo_log 日志被刪除了,想要看到undo_log 表中記錄,我們需要打斷點來看,在異常還沒拋出時打斷點,然后看下數(shù)據(jù)庫 undo_log 表中數(shù)據(jù)情況。文章來源地址http://www.zghlxwxcb.cn/news/detail-480120.html
到了這里,關(guān)于【Spring Cloud】Spring Cloud Alibaba-- 分布式事務(wù)Seata原理的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!