分布式事务Seata原理
一、Seata 介紹:
1、Seata 簡介:
????????Seata 是一款開源的分布式事務解決方案,致力于提供高性能與簡單易用的分布式事務服務,為用戶提供了 AT、TCC、SAGA 和 XA 幾種不同的事務模式:
- AT模式:無侵入式的分布式事務解決方案,適合不希望對業務進行改造的場景,但由于需要添加全局事務鎖,對影響高并發系統的性能。該模式主要關注多DB訪問的數據一致性,也包括多服務下的多DB數據訪問一致性問題
- TCC模式:高性能的分布式事務解決方案,適用于對性能要求比較高的場景。該模式主要關注業務拆分,在按照業務橫向擴展資源時,解決服務間調用的一致性問題
- Saga模式:長事務的分布式事務解決方案,適用于業務流程長且需要保證事務最終一致性的業務系統。Saga 模式一階段就會提交本地事務,無鎖,長流程情況下可以保證性能,多用于渠道層、集成層業務系統,事務參與者可以是其它公司的服務也可以是遺留系統的服務,并且對于無法進行改造和提供 TCC 要求的接口,也可以使用 Saga 模式
2、Seata 的核心組件:
????????在 Seata 中主要有以下三種角色,其中 TM 和 RM 是作為 Seata 的客戶端與業務系統集成在一起,TC 作為 Seata 的服務端獨立部署:
- 事務協調器(TC):維護全局事務的運行狀態,負責協調并驅動全局提交或回滾
- 事務管理器(TM):事務發起方,控制全局事務的范圍,負責開啟一個全局事務,并最終發起全局提交或回滾全局的決議
- 資源管理器(RM):事務參與方,管理本地事務正在處理的資源,負責向 TC 注冊本地事務、匯報本地事務狀態,接收 TC 的命令來驅動本地事務的提交或回滾
?3、Seata 的整體執行流程:
?Seata 分布式事務的整體執行機制如上圖所示,可以大致分為兩階段提交:
- ① 發起方 TM 向 TC 申請開啟一個全局事務,全局事務創建成功并生成唯一的全局事務標識 XID,該 XID 在后續事務的服務調用鏈路的上下文傳播(通過Aop實現))
- ② RM 向 TC 注冊分支事務,匯報資源準備狀況,并與 XID 進行綁定(Branch分支事務指分布式事務中每個獨立的本地局部事務)
- ③ TM 向 TC 發起 XID 下的所有分支事務的全局提交或回滾請求(事務一階段結束)
- ④ TC 匯總事務信息,決定分布式事務是提交還是回滾;
- ⑤ TC 通知所有 RM 提交/回滾 資源,事務二階段結束;
二、Seata 的 AT 模式原理:
????????Seata AT模式是基于XA事務(XA是基于數據庫實現的分布式事務協議)演進而來,需要數據庫支持,如果是 MySQL,則需要5.6以上版本才支持XA協議。AT 模式的特點就是對業務無入侵式,用戶只需要關注自己的業務SQL,Seata 框架會在第一階段攔截并解析用戶的 SQL,并保存其變更前后的數據鏡像,形成undo log,并自動生成事務第二階段的提交和回滾操作。
1、AT 模式的整體執行流程:
?AT 模式 RM 驅動分支事務的行為分為以下兩個階段:
(1)執行階段:
- (1)代理 JDBC 數據源,攔截并解析業務 SQL,生成更新前后的鏡像數據,形成 UNDO LOG。
- (2)向 TC 注冊分支。
- (3)分支注冊成功后,把業務數據的更新和 UNDO LOG 放在同一個本地事務中提交。
(2)完成階段:
- 全局提交,收到 TC 的分支提交請求,異步刪除相應分支的 UNDO LOG。
- 全局回滾,收到 TC 的分支回滾請求,查詢分支對應的 UNDO LOG 記錄,生成補償回滾的 SQL 語句,執行分支回滾并返回結果給 TC
2、AT 模式兩階段詳細流程:
2.1、第一階段的詳細執行流程:
????????在第一階段,RM 寫表時,Seata 通過代理數據源(從而達到對業務無侵入的效果)攔截業務 SQL 并 解析 SQL 語義,找到該 SQL 要更新的業務數據保存成 before image(前置鏡像),然后執行業務SQL,在業務數據更新后,再將其保存成 after image(后置鏡像),最后生成行鎖。通過把更新前后的業務數據數據鏡像組織成回滾日志 undo log,并利用本地事務的 ACID 特性,將業務數據的更新和回滾日志 undo log 的寫入在同一個本地事務中提交,保證任何提交的業務數據的更新一定有相應的回滾日志存在(即操作的原子性)。
????????基于這樣的機制,分支的本地事務就可以在全局事務的第一階段提交,并馬上釋放本地事務鎖定的資源,這也是 Seata 的 AT 模式和 XA 事務的不同之處,兩階段提交往往對資源的鎖定需要持續到第二階段實際的提交或者回滾操作,而有了回滾日志之后,可以在第一階段釋放對資源的鎖定,降低了鎖范圍,提高效率,即使第二階段發生異常需要回滾,只需找對 undo log 中對應數據鏡像并反解析成 SQL 來達到回滾目的
2.2、第二階段提交的詳細執行流程:
????????如果二階段的全局表決結果是提交的話,說明所有分支事務的業務SQL已經在第一階段生效,此時 Seata 框架只需異步刪除所有分支第一階段保存的鏡像數據、回滾日志和行鎖,完成數據清理即可,這個過程是非常快速的
2.3、第二階段回滾的詳細執行流程:
?????????如果第二階段是回滾的話,Seata 就需要回滾第一階段已執行的 SQL 進行還原業務數據。由 TC 通知所有 RM 進行根據第一階段的回滾日志 undo log (即before image)進行反向補償,RM 收到 TC 發來的回滾請求后,通過 XID 和 Branch ID 找到相應的回滾日志記錄,通過undo log 生成反向的更新 SQL 并執行,以完成分支的業務數據還原,最后刪除 undo log、redo log 和行鎖。但在還原前需要校驗臟寫,對比數據庫”當前業務數據”和 “after image”,如果兩份數據完全一致就說明沒有臟寫,可以還原業務數據,如果不一致就說明有臟寫,出現臟寫就需要轉人工處理。
三、Seata 的 TCC、Saga、XA模式原理:
? ? ? ? 文章第二部分介紹了 Seata 的 AT 模式,接下來我們就介紹下 Seata 的其實幾種事務模式:
1、TCC 模式:
?TCC 模式 RM 驅動分支事務的行為分為以下兩個階段:
(1)執行階段:
- ① 向 TC 注冊分支。
- ② 執行業務定義的 Try 方法。
- ③ 向 TC 上報 Try 方法執行情況:成功或失敗。
(2)完成階段:
- 全局提交,收到 TC 的分支提交請求,執行業務定義的 Confirm 方法。
- 全局回滾,收到 TC 的分支回滾請求,執行業務定義的 Cancel 方法。
2、Saga 模式:
Saga 模式 RM 驅動分支事務的行為包含以下兩個階段:
(1)執行階段:
- ① 向 TC 注冊分支。
- ② 執行業務方法。
- ③ 向 TC 上報業務方法執行情況:成功或失敗。
(2)完成階段:
- 全局提交,RM 不需要處理。
- 全局回滾,收到 TC 的分支回滾請求,執行業務定義的補償回滾方法。
3、XA模式:
XA 模式 RM 驅動分支事務的行為包含以下兩個階段:
(1)執行階段:
- ① 向 TC 注冊分支
- ② XA Start,執行業務 SQL,XA End
- ③ XA prepare,并向 TC 上報 XA 分支的執行情況:成功或失敗
(2)完成階段:
- 收到 TC 的分支提交請求,XA Commit
- 收到 TC 的分支回滾請求,XA Rollback
參考文章:
https://help.aliyun.com/document_detail/157850.html
https://blog.csdn.net/k6T9Q8XKs6iIkZPPIFq/article/details/107273472
四、SpringCloud Alibaba 整合 Seata AT 模式:
1、搭建 Seata TC 協調者:
1.1、下載 Seata TC 協調者:
????????Seata 的協調者其實就是阿里開源的一個服務,我們只需要下載(下載地址:https://github.com/seata/seata/releases)并啟動它,下載完成后,直接解壓即可,但是此時還不能直接運行,還需要做一些配置
?1.2、創建TC所需要的表:
https://github.com/seata/seata/tree/1.4.2/script/server/db
????????TC 運行需要將事務信息保存在數據庫,因此需要創建一些表,找到 seata-1.4.2 源碼的 script\server\db 這個目錄,將會看到以下SQL文件:
?以 MySQL 數據庫為例,創建數據庫 seata,并執行 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 = InnoDBDEFAULT 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 = InnoDBDEFAULT 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 = InnoDBDEFAULT CHARSET = utf8;創建的三張表如下圖:
- global_table:全局事務表,每當有一個全局事務發起后,就會在該表中記錄全局事務的ID
- branch_table:分支事務表,記錄每一個分支事務的ID,分支事務操作的哪個數據庫等信息
- lock_table:全局鎖
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、sofatype = "nacos"nacos {application = "seata-server"serverAddr = "localhost:8848"namespace = "XXXXXXXXXX"cluster = "default"username = "nacos"password = "nacos"} }config {# file、nacos 、apollo、zk、consul、etcd3type = "nacos"nacos {serverAddr = "localhost:8848"namespace = "XXXXXXXXXX"group = "SEATA_GROUP"username = "nacos"password = "nacos"} }需要改動的地方如下:
- type:改成nacos,表示使用nacos作為注冊中心或者配置中心
- application:服務的名稱
- serverAddr:nacos的地址
- group:分組
- namespace:命名空間
- username:用戶名
- password:密碼
上述配置修改好之后,在TC啟動的時候將會自動讀取 nacos 的配置,最后這份 registry.conf 文件的配置需要與下文每個seata客戶端項目中的配置一致
1.4、導入 seata Server 配置:
建議將 seata 項目下載到本地并閱讀 https://github.com/seata/seata/tree/1.4.2/script/config-center?路徑下的README.md內容
????????在上面我們已經配置好 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 腳本能夠將 config.txt 中的全部配置自動推送到nacos中。
????????我們先啟動nacos服務,然后在 nacos-config.sh 目錄下執行下面命令,將 config.txt 中的配置信息推送到 nacos 中:
# -h 代表nacos服務的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
命令執行成功后,就可以在nacos中看到如下配置:
?1.5、修改TC的事務信息存儲方式:
????????seata 的 TC 端的事務信息存儲模式(store.mode)現有file、db、redis三種,file模式無需改動,直接啟動即可,下面專門講下db和redis啟動步驟。
注: file模式為單機模式,全局事務會話信息內存中讀寫并持久化本地文件root.data,性能較高;
(1)以DB模式存儲:
????????db模式為高可用模式,全局事務會話信息通過db共享,相應性能差些;上一節的內容已經將所有的配置信息都推送到了Nacos中,TC啟動時會從Nacos中讀取,因此我們修改也需要在Nacos中修改。需要修改的配置如下:
## 采用db的存儲形式 store.mode=db ## druid數據源 store.db.datasource=druid ## mysql數據庫 store.db.dbType=mysql ## mysql驅動 store.db.driverClassName=com.mysql.jdbc.Driver ## TC的數據庫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 及以上版本支持,性能較高,但存在事務信息丟失風險,所以需要提前配置合適當前場景的redis持久化配置,該模式需改動以下配置:
store.mode=redis store.redis.host=127.0.0.1 store.redis.port=6379 store.redis.password=1234561.6、啟動TC:
????????按照上述步驟全部配置成功后,則可以啟動TC,在 seata-server-1.4.2\bin 目錄下直接點擊 seata-server.bat(windows)運行,啟動成功后,啟動成功后,在Nacos的服務列表中則可以看到TC已經注冊進入,如下圖:
?至此,Seata 的 TC 就啟動完成了............
2、搭建Seata 客戶端(RM):
2.1、maven 添加 seata 依賴:
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><!-- 排除依賴,指定版本和服務端一致 --><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客戶端的依賴版本必須要和服務端一致。
2.2、application.yml 添加 seate 配置:
seata:# 這里要特別注意和nacos中配置的要保持一致,建議配置成 ${spring.application.name}-tx-grouptx-service-group: my_test_tx_groupregistry:type: nacosnacos:# 配置所在命名空間ID,如未配置默認public空間server-addr: 127.0.0.1:8848namespace: XXXXXXXXXXgroup: SEATA_GROUPapplication: seata-serveruserName: nacospassword: nacosconfig:type: nacosnacos:server-addr: 127.0.0.1:8848namespace: XXXXXXXXXXgroup: SEATA_GROUPuserName: nacospassword: nacostx-service-group 配置的值可以自定義,但是定義后需要在 nacos 配置中心新增 service.vgroupMapping.xxx=default 的配置,該屬性一定一定要和 seata 服務端的配置一致,否則不生效;比如上述配置中的,就需要在 nacos 配置中心新增一個配置項 service.vgroupMapping.my_test-tx-group=default,并且設置分組為SEATA_GROUP,如下圖:
注意:my_test-tx-group 僅僅是后綴,在nacos中添加配置時記得要加上前綴 service.vgroupMapping. ?
2.3、數據庫中添加回滾日志表:
https://github.com/seata/seata/tree/1.4.2/script/client/at/db
?回滾日志表:undo_log,這是Seata要求必須有的,每個業務庫都應該創建一個,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 = InnoDBAUTO_INCREMENT = 1DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';2.4、使用 seata 作為全局事務控制:
????????在分布式業務入口增加全局事務注解 @GlobalTransactional,其他 service 接口無需配置;假設A服務調用B服務,那么就在A服務的方法上面加入,B服務上面不用加
/*** seata 的 GlobalTransactional 注解只需要加載分布式業務入口處,其他service接口無需配置,從而實現分布式事務*/@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 將認為你已進行異常處理,就不會回滾數據了
- (1)比如如果你配置了@ControllerAdvice將可能導致數據不回滾
- (2)如果使用 Feign 調用分布式服務并配置了fallback,后面服務拋出異常會直接執行fallback導致無法回滾(rollbackFor = Exception.class);這時可以在fallback的實現方法內手動調用seata全局回滾,如下所示:
2.5、undo log 表介紹:
????????上面介紹過 Seata 是根據 undo_log 中的記錄來回滾的,但是異常回滾后 undo_log 表卻為空?怎么回事,這是因為 undo_log 日志被刪除了,想要看到undo_log 表中記錄,我們需要打斷點來看,在異常還沒拋出時打斷點,然后看下數據庫 undo_log 表中數據情況:
undo log 表簡單介紹:https://blog.csdn.net/hosaos/article/details/89136666
?
?
參考文章:
Seata 官方文檔:https://seata.io/zh-cn/docs/overview/what-is-seata.html
https://blog.csdn.net/qq_34162294/article/details/120984951
總結
以上是生活随笔為你收集整理的分布式事务Seata原理的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 分布式事务seate-server的do
- 下一篇: 七种常见分布式事务详解(2PC、3PC、
