seata 分布式事務 -- TCC模式


 

基本概念:

TCC(Try-Confirm-Cancel)分布式事務模型相對於 XA 等傳統模型,其特征在於它不依賴 RM 對分布式事務的支持,

而是通過對業務邏輯的分解來實現分布式事務。


TCC與AT模式相同,也是二階段提交,但是TCC對業務代碼侵入性很強

TCC模式下,所有事務都要手動實現Try,Confirm,Cancel三個方法

 


TCC 模型認為對於業務系統中一個特定的業務邏輯 ,其對外提供服務時,必須接受一些不確定性,

即對業務邏輯初步操作的調用僅是一個臨時性操作,調用它的主業務服務保留了后續的取消權。

如果主業務服務認為全局事務應該回滾,它會要求取消之前的臨時性操作,這就對應從業務服務的取消操作。

而當主業務服務認為全局事務應該提交時,它會放棄之前臨時性操作的取消權,這對應從業務服務的確認操作。

每一個初步操作,最終都會被確認或取消。因此,針對一個具體的業務服務,TCC 分布式事務模型需要業務系統提供

三段業務邏輯:

1. 初步操作 Try:完成所有業務檢查,預留必須的業務資源。 2. 確認操作 Confirm:真正執行的業務邏輯,不做任何業務檢查,只使用 Try 階段預留的業務資源。   因此,只要 Try 操作成功,Confirm 必須能成功。另外,Confirm 操作需滿足冪等性,保證一筆分布式事務能且只能成功一次。 3. 取消操作 Cancel:釋放 Try 階段預留的業務資源。同樣的,Cancel 操作也需要滿足冪等性。

 

 

工作機制:

一階段

TM

TM處理流程和AT模式下一樣:

1.開啟全局事務,向TC注冊全局事務並返回XID

2.如果業務執行成功,通知TC全局事務提交

3.如果業務執行失敗,通知TC全局事務回滾

4.清除內存中XID


RM

注冊分支事務,獲取branchId。並將方法調用時的上下文發送給TC。執行業務邏輯


二階段

TM

TM通知TC全局提交/回滾。TC通知各分支事務。RM處理消息邏輯是RMHandlerTCC里。

RM

提交和回滾邏輯一樣:

1.用TC發過來的ResourceId查到TCResource,找到commit/rollback方法並執行

2.通知TC執行結果

 

常見異常 :

最常見的主要是這三種異常,空回滾、冪等、懸掛。


1 空回滾:

空回滾就是對於一個分布式事務,在沒有調用 TCC 資源 Try 方法的情況下,調用了二階段的 Cancel 方法, Cancel 方法需要識別出這是一個空回滾,然后直接返回成功。 < try 未執行,Cancel 執行 > 什么樣的情形會造成空回滾呢?注冊分支事務是在調用 RPC 時,Seata 框架的切面會攔截到該次調用請求, 先向 TC 注冊一個分支事務,然后才去執行 RPC 調用邏輯。如果 RPC 調用邏輯有問題, 比如調用方機器宕機、網絡異常,都會造成 RPC 調用失敗,即未執行 Try 方法。但是分布式事務已經開啟了, 需要推進到終態,因此,TC 會回調參與者二階段 Cancel 接口,從而形成空回滾。 解決: 需要一張額外的事務控制表,其中有分布式事務 ID 和分支事務 ID,第一階段 Try 方法里會插入一條記錄, 表示一階段執行了。Cancel 接口里讀取該記錄,如果該記錄存在,則正常回滾;如果該記錄不存在,則是空回滾。

 

 

2 冪等:

冪等就是對於同一個分布式事務的同一個分支事務,重復去調用該分支事務的第二階段接口, 因此,要求 TCC 的二階段 Confirm 和 Cancel 接口保證冪等,不會重復使用或者釋放資源。如果冪等控制沒有做好, 很有可能導致資損等嚴重問題。 < 多次 執行 Confirm 或 Cancel> 什么樣的情形會造成重復提交或回滾?從圖中可以看到,提交或回滾是一次 TC 到參與者的網絡調用。因此, 網絡故障、參與者宕機等都有可能造成參與者 TCC 資源實際執行了二階段防范,但是 TC 沒有收到返回結果的情況, 這時,TC 就會重復調用,直至調用成功,整個分布式事務結束。 解決: 事務控制表的每條記錄關聯一個分支事務,那我們完全可以在這張事務控制表上加一個狀態字段, 用來記錄每個分支事務的執行狀態。 狀態字段有三個值,分別是初始化、已提交、已回滾。Try 方法插入時,是初始化狀態。 二階段 Confirm 和 Cancel 方法執行后修改為已提交或已回滾狀態。當重復調用二階段接口時, 先獲取該事務控制表對應記錄,檢查狀態,如果已執行,則直接返回成功;否則正常執行。

 

 


3.懸掛:

懸掛就是對於一個分布式事務,其二階段 Cancel 接口比 Try 接口先執行。 因為允許空回滾的原因,Cancel 接口認為 Try 接口沒執行,空回滾直接返回成功,對於 Seata 框架來說, 認為分布式事務的二階段接口已經執行成功,整個分布式事務就結束了。但是這之后 Try 方法才真正開始執行。 < Confirm 在 try 之前> 什么樣的情況會造成懸掛呢?按照前面所講,在 RPC 調用時,先注冊分支事務,再執行 RPC 調用, 如果此時 RPC 調用的網絡發生擁堵,通常 RPC 調用是有超時時間的,RPC 超時以后,發起方就會通知 TC 回滾該分布式事務, 可能回滾完成后,RPC 請求才到達參與者,真正執行,從而造成懸掛。 解決: 可以在二階段執行時插入一條事務控制記錄,狀態為已回滾,這樣當一階段執行時,先讀取該記錄, 如果記錄存在,就認為二階段已經執行,進行 空 try ,否則二階段沒執行,正常執行 try

 

 

TCC模式 需要的數據庫表:

創建數據庫: seata-server

建表 Sql語句 :

 

SET FOREIGN_KEY_CHECKS=0;


DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) NOT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`resource_group_id` varchar(32) DEFAULT NULL,
`resource_id` varchar(256) DEFAULT NULL,
`branch_type` varchar(8) DEFAULT NULL,
`status` tinyint(4) DEFAULT NULL,
`client_id` varchar(64) DEFAULT NULL,
`application_data` varchar(2000) DEFAULT NULL,
`gmt_create` datetime(6) DEFAULT NULL,
`gmt_modified` datetime(6) DEFAULT NULL,
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
`xid` varchar(128) NOT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`application_id` varchar(32) DEFAULT NULL,
`transaction_service_group` varchar(32) DEFAULT NULL,
`transaction_name` varchar(128) DEFAULT NULL,
`timeout` int(11) DEFAULT NULL,
`begin_time` bigint(20) DEFAULT NULL,
`application_data` varchar(2000) DEFAULT NULL,
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
`row_key` varchar(128) NOT NULL,
`xid` varchar(96) DEFAULT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`branch_id` bigint(20) NOT NULL,
`resource_id` varchar(256) DEFAULT NULL,
`table_name` varchar(32) DEFAULT NULL,
`pk` varchar(36) DEFAULT NULL,
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM