Seata簡介
Seata(Simple Extensible Autonomous Transaction Architecture) 是 阿里巴巴開源的分布式事務中間件,以高效並且對業務 0 侵入的方式,解決微服務場景下面臨的分布式事務問題。
附上項目github鏈接
https://github.com/seata
目前Seata還處於不斷開源升級中,並不建議在線上使用,生產環境可以考慮使用阿里雲商用的GTS,附上Seata目前的升級計划,可以考慮在V1.0,即服務端HA集群版本進行線上使用
先來看下為什么會產生分布式事務問題
分布式事務產生背景
講到事務,又得搬出經典的銀行轉賬問題了,下面以實例說明
假設銀行(bank)中有兩個客戶(name)張三和李四
我們需要將張三的1000元存款(sal)轉到李四的賬戶上
目標就是張三賬戶減1000,李四賬戶加1000,不能出現中間步驟(張三減1000,李四沒加)
假設dao層代碼如下
public interface BankMapper {
/**
* @param userName 用戶名
* @param changeSal 余額變動值
*/
public void updateSal(String userName,int changeSal);
}
1
2
3
4
5
6
7
對應xml中sql如下
<update id="updateSal">
update bank SET sal = sal+#{changeSal} WHERE name = #{userName}
</update>
1
2
3
如果兩個用戶對應的銀行存款數據在一個數據源中,即一個數據庫中,那么service層代碼可以如下編寫
/**
* @param fromUserName 轉賬人
* @param toUserName 被轉賬人
* @param changeSal 轉賬額度
*/
@Transactional(rollbackFor = Exception.class)
public void changeSal(String fromUserName,String toUserName,int changeSal) {
bankMapper.updateSal(fromUserName, -1 * changeSal);
bankMapper.updateSal(toUserName, changeSal);
}
1
2
3
4
5
6
7
8
9
10
通過spring框架下的@Transactional注解來保證單一數據源增刪改查的一致性
但是隨着業務的不斷擴大,用戶數在不斷變多,幾百萬幾千萬用戶時數據可以存一個庫甚至一個表里,假設有10個億的用戶?
數據庫的水平分割
為了解決數據庫上的瓶頸,分庫是很常見的解決方案,不同用戶就可能落在不同的數據庫里,原來一個庫里的事務操作,現在變成了跨數據庫的事務操作。
此時@Transactional注解就失效了,這就是跨數據庫分布式事務問題
微服務化
當然,更多的情形是隨着業務不斷增長,將業務中不同模塊服務拆分成微服務后,同時調用多個微服務所產生的
微服務化的銀行轉賬情景往往是這樣的
調用交易系統服務創建交易訂單;
調用支付系統記錄支付明細;
調用賬務系統執行 A 扣錢;
調用賬務系統執行 B 加錢;
如圖所示,每個系統都對應一個獨立的數據源,且可能位於不同機房,同時調用多個系統的服務很難保證同時成功,這就是跨服務分布式事務問題
分布式事務理論基礎
兩階段提交(2pc)
兩階段提交協議(Two Phase Commitment Protocol)中,涉及到兩種角色
一個事務協調者(coordinator):負責協調多個參與者進行事務投票及提交(回滾)
多個事務參與者(participants):即本地事務執行者
總共處理步驟有兩個
(1)投票階段(voting phase):協調者將通知事務參與者准備提交或取消事務,然后進入表決過程。參與者將告知協調者自己的決策:同意(事務參與者本地事務執行成功,但未提交)或取消(本地事務執行故障);
(2)提交階段(commit phase):收到參與者的通知后,協調者再向參與者發出通知,根據反饋情況決定各參與者是否要提交還是回滾;
如果所示 1-2為第一階段,2-3為第二階段
如果任一資源管理器在第一階段返回准備失敗,那么事務管理器會要求所有資源管理器在第二階段執行回滾操作。通過事務管理器的兩階段協調,最終所有資源管理器要么全部提交,要么全部回滾,最終狀態都是一致的
圖片來自螞蟻金服公眾號
TCC
基本原理
TCC 將事務提交分為 Try - Confirm - Cancel 3個操作。其和兩階段提交有點類似,Try為第一階段,Confirm - Cancel為第二階段,是一種應用層面侵入業務的兩階段提交。
操作方法 含義
Try 預留業務資源/數據效驗
Confirm 確認執行業務操作,實際提交數據,不做任何業務檢查,try成功,confirm必定成功,需保證冪等
Cancel 取消執行業務操作,實際回滾數據,需保證冪等
其核心在於將業務分為兩個操作步驟完成。不依賴 RM 對分布式事務的支持,而是通過對業務邏輯的分解來實現分布式事務。
下面還是以銀行轉賬例子來說明
假設用戶user表中有兩個字段:可用余額(available_money)、凍結余額(frozen_money)
A扣錢對應服務A(ServiceA)
B加錢對應服務B(ServiceB)
轉賬訂單服務(OrderService)
業務轉賬方法服務(BusinessService)
ServiceA,ServiceB,OrderService都需分別實現try(),confirm(),cancle()方法,方法對應業務邏輯如下
ServiceA ServiceB OrderService
try() 校驗余額(並發控制)
凍結余額+1000
余額-1000 凍結余額+1000 創建轉賬訂單,狀態待轉賬
confirm() 凍結余額-1000 余額+1000
凍結余額-1000 狀態變為轉賬成功
cancle() 凍結余額-1000
余額+1000 凍結余額-1000 狀態變為轉賬失敗
其中業務調用方BusinessService中就需要調用
ServiceA.try()
ServiceB.try()
OrderService.try()
1、當所有try()方法均執行成功時,對全局事物進行提交,即由事物管理器調用每個微服務的confirm()方法
2、 當任意一個方法try()失敗(預留資源不足,抑或網絡異常,代碼異常等任何異常),由事物管理器調用每個微服務的cancle()方法對全局事務進行回滾
引用網上一張TCC原理的參考圖片
冪等控制
使用TCC時要注意Try - Confirm - Cancel 3個操作的冪等控制,網絡原因,或者重試操作都有可能導致這幾個操作的重復執行
業務實現過程中需重點關注冪等實現,講到冪等,以上述TCC轉賬例子中confirm()方法來說明
在confirm()方法中
余額-1000,凍結余額-1000,這一步是實現冪等性的關鍵,你會怎么做?
大家在自己系統里操作資金賬戶時,為了防止並發情況下數據不一致的出現,肯定會避免出現這種代碼
//根據userId查到賬戶
Account account = accountMapper.selectById(userId);
//取出當前資金
int availableMoney = account.getAvailableMoney();
account.setAvailableMoney(availableMoney-1000);
//更新剩余資金
accountMapper.update(account);
1
2
3
4
5
6
7
因為這本質上是一個 讀-改-寫的過程,不是原子的,在並發情況下會出現數據不一致問題
所以最簡單的做法是
update account set available_money = available_money-1000 where user_id=#{userId}
1
這利用了數據庫行鎖特性解決了並發情況下的數據不一致問題,但是TCC中,單純使用這個方法適用么?
答案是不行的,該方法能解決並發單次操作下的扣減余額問題,但是不能解決多次操作帶來的多次扣減問題,假設我執行了兩次,按這種方案,用戶賬戶就少了2000塊
那么具體怎么做?上訴轉賬例子中,可以引入轉賬訂單狀態來做判斷,若訂單狀態為已支付,則直接return
if( order!=null && order.getStatus().equals("轉賬成功")){
return;
}
1
2
3
當然,新建一張去重表,用訂單id做唯一建,若插入報錯返回也是可以的,不管怎么樣,核心就是保證,操作冪等性
空回滾
如下圖所示,事務協調器在調用TCC服務的一階段Try操作時,可能會出現因為丟包而導致的網絡超時,此時事務協調器會觸發二階段回滾,調用TCC服務的Cancel操作;
TCC服務在未收到Try請求的情況下收到Cancel請求,這種場景被稱為空回滾;TCC服務在實現時應當允許空回滾的執行;
那么具體代碼里怎么做呢?
分析下,如果try()方法沒執行,那么訂單一定沒創建,所以cancle方法里可以加一個判斷,如果上下文中訂單編號orderNo不存在或者訂單不存在,直接return
if(orderNo==null || order==null){
return;
}
1
2
3
核心思想就是 回滾請求處理時,如果對應的具體業務數據為空,則返回成功
當然這種問題也可以通過中間件層面來實現,如,在第一階段try()執行完后,向一張事務表中插入一條數據(包含事務id,分支id),cancle()執行時,判斷如果沒有事務記錄則直接返回,但是現在還不支持
防懸掛
如下圖所示,事務協調器在調用TCC服務的一階段Try操作時,可能會出現因網絡擁堵而導致的超時,此時事務協調器會觸發二階段回滾,調用TCC服務的Cancel操作;在此之后,擁堵在網絡上的一階段Try數據包被TCC服務收到,出現了二階段Cancel請求比一階段Try請求先執行的情況;
用戶在實現TCC服務時,應當允許空回滾,但是要拒絕執行空回滾之后到來的一階段Try請求;
這里又怎么做呢?
可以在二階段執行時插入一條事務控制記錄,狀態為已回滾,這樣當一階段執行時,先讀取該記錄,如果記錄存在,就認為二階段回滾操作已經執行,不再執行try方法;
事務消息
事務消息更傾向於達成分布式事務的最終一致性,適用於分布式事務的提交或回滾只取決於事務發起方的業務需求,如A給B打了款並且成功了,那么下游業務B一定需要加錢這種場景,或許下了單,用戶積分一定得增加這種場景。RocketMQ4.3中已經開源了事務消息,具體設計思路分析及demo演示,大家有興趣可以看下我寫的這篇文章
https://blog.csdn.net/hosaos/article/details/90050276
優缺點比較
事務方案 優點 缺點
2PC 實現簡單 1、需要數據庫(一般是XA支持) 2、鎖粒度大,性能差
TCC 鎖粒度小,性能好 需要侵入業務,實現較為復雜,復雜業務實現冪等有難度
消息事務 業務侵入小,無需編寫業務回滾補償邏輯 事務消息實現難度大,強依賴第三方中間件可靠性
Seata解決方案
解決分布式事務問題,有兩個設計初衷
對業務無侵入:即減少技術架構上的微服務化所帶來的分布式事務問題對業務的侵入
高性能:減少分布式事務解決方案所帶來的性能消耗
seata中有兩種分布式事務實現方案,AT及TCC
AT模式主要關注多 DB 訪問的數據一致性,當然也包括多服務下的多 DB 數據訪問一致性問題
TCC 模式主要關注業務拆分,在按照業務橫向擴展資源時,解決微服務間調用的一致性問題
AT模式(業務侵入小)
Seata AT模式是基於XA事務演進而來的一個分布式事務中間件,XA是一個基於數據庫實現的分布式事務協議,本質上和兩階段提交一樣,需要數據庫支持,Mysql5.6以上版本支持XA協議,其他數據庫如Oracle,DB2也實現了XA接口
角色如下
Transaction Coordinator (TC): 事務協調器,維護全局事務的運行狀態,負責協調並驅動全局事務的提交或回滾
Transaction Manager ™: 控制全局事務的邊界,負責開啟一個全局事務,並最終發起全局提交或全局回滾的決議
Resource Manager (RM): 控制分支事務,負責分支注冊、狀態匯報,並接收事務協調器的指令,驅動分支(本地)事務的提交和回滾
基本處理邏輯如下
Branch就是指的分布式事務中每個獨立的本地局部事務
第一階段
Seata 的 JDBC 數據源代理通過對業務 SQL 的解析,把業務數據在更新前后的數據鏡像組織成回滾日志,利用 本地事務 的 ACID 特性,將業務數據的更新和回滾日志的寫入在同一個 本地事務 中提交。
這樣,可以保證:任何提交的業務數據的更新一定有相應的回滾日志存在
基於這樣的機制,分支的本地事務便可以在全局事務的第一階段提交,並馬上釋放本地事務鎖定的資源
這也是Seata和XA事務的不同之處,兩階段提交往往對資源的鎖定需要持續到第二階段實際的提交或者回滾操作,而有了回滾日志之后,可以在第一階段釋放對資源的鎖定,降低了鎖范圍,提高效率,即使第二階段發生異常需要回滾,只需找對undolog中對應數據並反解析成sql來達到回滾目的
同時Seata通過代理數據源將業務sql的執行解析成undolog來與業務數據的更新同時入庫,達到了對業務無侵入的效果
第二階段
如果決議是全局提交,此時分支事務此時已經完成提交,不需要同步協調處理(只需要異步清理回滾日志),Phase2 可以非常快速地完成
如果決議是全局回滾,RM 收到協調器發來的回滾請求,通過 XID 和 Branch ID 找到相應的回滾日志記錄,通過回滾記錄生成反向的更新 SQL 並執行,以完成分支的回滾
TCC(高性能)
seata也針對TCC做了適配兼容,支持TCC事務方案,原理前面已經介紹過,基本思路就是使用侵入業務上的補償及事務管理器的協調來達到全局事務的一起提交及回滾,詳情參考demo回滾
Demo上手-AT模式Dubbo集成Seata
Demo的github項目名稱是fescar-example,鏈接如下
https://github.com/fescar-group/fescar-samples
要跑demo例子,首先需要下載上述鏈接官方demo,我下面以IDEA為例子演示demo中dubbo的分布式事務例子,另外還需要一個fescar-server,我下的版本是0.4.1,鏈接如下
https://github.com/seata/seata/releases
先看下fescar-example的項目結構
其中dubbo模塊就是dubbo的demo
模塊中代碼結構如下
配置修改
由於我本地啟動了Zookeeper服務端做dubbo注冊中心,所以我修改了4個dubbo配置文件中的注冊中心為zk,官方默認的是用的廣播,沒有Zk用廣播或者Redis做注冊中心都可以
數據庫地址,由於我們是針對Rpc遠程服務做分布式事務測試,所以數據庫用一個也能達到測試效果,本地啟動Mysql服務,並新建名為fescar的數據庫
同時修改jdbc.properties中url地址
jdbc.account.url=jdbc:mysql://localhost:3306/fescar
jdbc.account.username=root
jdbc.account.password=123456
jdbc.account.driver=com.mysql.jdbc.Driver
# storage db config
jdbc.storage.url=jdbc:mysql://localhost:3306/fescar
jdbc.storage.username=root
jdbc.storage.password=123456
jdbc.storage.driver=com.mysql.jdbc.Driver
# order db config
jdbc.order.url=jdbc:mysql://localhost:3306/fescar
jdbc.order.username=root
jdbc.order.password=123456
jdbc.order.driver=com.mysql.jdbc.Driver
1
2
3
4
5
6
7
8
9
10
11
12
13
14
再執行dubbo_biz.sql中的建表語句,創建storage_tbl,order_tbl,account_tbl 3個業務表
再執行undo_log.sql 創建seata所需記錄undolog的回滾日志表
啟動測試
先啟動fescar-server,啟動方式
sh fescar-server.sh $LISTEN_PORT $PATH_FOR_PERSISTENT_DATA
1
參數有兩個,LISTEN_PORT代表端口號,PATH_FOR_PERSISTENT_DATA表示Seata持久化數據存放路徑
將安裝包解壓后cd到bin目錄下,我這里指定端口號為8091,data路徑為我自己創建的一個目錄
sh fescar-server.sh 8091 /Users/chenyin/fescar/data
1
啟動成功效果圖如下
回到項目代碼中
先啟動DubboAccountServiceStarter,其初始化時向account_tbl表中插入一個用戶編號為
U100001的用戶,初始金額為999
再啟動DubboOrderServiceStarter,DubboStorageServiceStarter,DubboStorageServiceStarter中默認初始化一個商品編號為C00321的商品,初始庫存100
看下BusinessService業務處理類,里面調用了庫存類(StorageService)扣減庫存,調用訂單類(OrderService)下單,其中手動拋出RuntimeException模擬分布式事務中的異常情況
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")
public void purchase(String userId, String commodityCode, int orderCount) {
LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
storageService.deduct(commodityCode, orderCount);
orderService.create(userId, commodityCode, orderCount);
throw new RuntimeException("xxx");
}
1
2
3
4
5
6
7
8
如果沒有throw new RuntimeException(“xxx”); 正確的業務操作結果是用戶賬戶余額減400變成599,庫存減2變成98,具體為什么是余額-400,庫存-2大家看下demo中具體業務類的代碼就知道,不多說。異常的情況,也就是分布式事務回滾的情況,應該是余額還是999,庫存還是100
先看下有拋出異常的情況,啟動業務類,DubboBusinessTester,執行結果如下
檢查下數據庫中數據是否正確,有沒發生數據未回滾的情況
數據正確無誤,證明數據正確的回滾了。上面介紹過了,Seata是根據undolog中記錄來回滾的,但是異常回滾后undolog表卻為空?怎么回事,這是因為undolog日志被刪除了,想要看到undolog表中記錄,我們打斷點來看,在異常還沒拋出時打斷點,看下數據庫undolog表中數據情況
斷點處觸發后,查看undolog表,可以看到3條記錄,3個branch_id對應3個rpc分支事務,也就對應3個業務表的回滾日志,一個xid標識這3個分支事務處於一個全局分布式事務中
其中rollbackinfo字段是bytes類型,看不到具體數據,怎么辦?我們把數據導成txt格式
數據內容如下,可以看到是BASE64加密過的,進行解密
最終內容如下,我只貼出第一行數據中的rollback_info,可以看到其中記錄了數據操作前后的鏡像數據beforeImage,afterImage,如果發生回滾,可以通過xid,branchid定位到undolog中的rollback_info,並將beforeImage中內容反解析成sql來達到回滾目的的
{
"branchId": 2008522332,
"sqlUndoLogs": [
{
"afterImage": {
"rows": [
{
"fields": [
{
"keyType": "PrimaryKey",
"name": "id",
"type": 4,
"value": 3
},
{
"keyType": "NULL",
"name": "count",
"type": 4,
"value": 98
}
]
}
],
"tableName": "storage_tbl"
},
"beforeImage": {
"rows": [
{
"fields": [
{
"keyType": "PrimaryKey",
"name": "id",
"type": 4,
"value": 3
},
{
"keyType": "NULL",
"name": "count",
"type": 4,
"value": 100
}
]
}
],
"tableName": "storage_tbl"
},
"sqlType": "UPDATE",
"tableName": "storage_tbl"
}
],
"xid": "192.168.202.197:8091:2008522331"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
至於不拋出異常的情況,這里就不多做演示了,注釋掉拋出異常的代碼,重新運行一下就行
Demo上手-TCC模式Dubbo集成Seata
tcc模塊下有個transfer-tcc-sample項目,不過數據庫不是Mysql的,下面進行部分修改並演示
先看下代碼結構
核心是action包下的2個類,都暴露成了dubbo服務,同時使用注解標記為TCC服務,並實現try-commit-cancle方法
FirsetAction-對應扣錢service
SecondAction-對應加錢service
看下FirsetAction源碼,@TwoPhaseBusinessAction是TCC服務參與者必須加的注解,指定服務名稱,提交方法commitMethod及回滾方法rollbackMethod,SecondAction同理
public interface FirstTccAction {
/**
* 一階段方法
*
* @param businessActionContext
* @param accountNo
* @param amount
*/
@TwoPhaseBusinessAction(name = "firstTccAction", commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepareMinus(BusinessActionContext businessActionContext,
@BusinessActionContextParameter(paramName = "accountNo") String accountNo,
@BusinessActionContextParameter(paramName = "amount") double amount);
/**
* 二階段提交
* @param businessActionContext
* @return
*/
public boolean commit(BusinessActionContext businessActionContext);
/**
* 二階段回滾
* @param businessActionContext
* @return
*/
public boolean rollback(BusinessActionContext businessActionContext);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
transfer包下TransferService是具體業務實現類(即轉賬操作類)
看下TransferServiceImpl源碼,轉賬方法上加了 @GlobalTransactional 將方法納入事務管理器管理范圍
public class TransferServiceImpl implements TransferService {
private FirstTccAction firstTccAction;
private SecondTccAction secondTccAction;
/**
* 轉賬操作
* @param from 扣錢賬戶
* @param to 加錢賬戶
* @param amount 轉賬金額
* @return
*/
@Override
@GlobalTransactional
public boolean transfer(final String from, final String to, final double amount) {
//扣錢參與者,一階段執行
boolean ret = firstTccAction.prepareMinus(null, from, amount);
if(!ret){
//扣錢參與者,一階段失敗; 回滾本地事務和分布式事務
throw new RuntimeException("賬號:["+from+"] 預扣款失敗");
}
//加錢參與者,一階段執行
ret = secondTccAction.prepareAdd(null, to, amount);
if(!ret){
throw new RuntimeException("賬號:["+to+"] 預收款失敗");
}
System.out.println(String.format("transfer amount[%s] from [%s] to [%s] finish.", String.valueOf(amount), from, to));
return true;
}
public void setFirstTccAction(FirstTccAction firstTccAction) {
this.firstTccAction = firstTccAction;
}
public void setSecondTccAction(SecondTccAction secondTccAction) {
this.secondTccAction = secondTccAction;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
配置修改
由於我本地啟動的Mysql服務,而demo是以h2Database為例,所以pom中引入mysql相關包
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
1
2
3
4
修改from-datasource-bean.xml,from-datasource-bean.xml中數據源配置,我這里轉出賬戶對應xa1數據庫,轉入賬戶對應xa2數據庫
<bean id="fromAccountDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/xa1</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value>123456</value>
</property>
</bean>
<bean id="toAccountDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/xa2</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value>123456</value>
</property>
</bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
啟動測試
1、配置修改完畢,先在本地啟動fescar-server
2、然后啟動 TransferProviderStarter 暴露dubbo服務並初始化數據庫,數據庫初始化完后,數據如下
xa1庫account表
xa2庫account表
3、啟動TransferApplication,其main方法執行2個子方法
/**
* 執行轉賬成功 demo
*
* @param initAmount 初始化余額
* @param transferAmount 轉賬余額
*/
private static void doTransferSuccess(double initAmount, double transferAmount) throws SQLException {
//執行轉賬操作
doTransfer("A", "C", transferAmount);
//校驗A賬戶余額:initAmount - transferAmount
checkAmount(fromAccountDAO, "A", initAmount - transferAmount);
//校驗C賬戶余額:initAmount + transferAmount
checkAmount(toAccountDAO, "C", initAmount + transferAmount);
}
/**
* 執行轉賬 失敗 demo, 'B' 向未知用戶 'XXX' 轉賬,轉賬失敗分布式事務回滾
* @param initAmount 初始化余額
* @param transferAmount 轉賬余額
*/
private static void doTransferFailed(int initAmount, int transferAmount) throws SQLException {
// 'B' 向未知用戶 'XXX' 轉賬,轉賬失敗分布式事務回滾
try{
doTransfer("B", "XXX", transferAmount);
}catch (Throwable t){
System.out.println("從賬戶B向未知賬號XXX轉賬失敗.");
}
//校驗A2賬戶余額:initAmount
checkAmount(fromAccountDAO, "B", initAmount);
//賬戶XXX 不存在,無需校驗
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
執行結果應該是doTransferSuccess()執行成功,A賬戶變成90,C賬戶變成110
doTransferFailed()執行失敗(secondTccAction的try方法中有對轉賬接收賬戶做校驗,賬戶不存在,拋異常),B賬戶數據還是100
執行下看下結果
xa1庫數據如下
xa2庫數據如下
說明TCC分布式事務生效,如果不是微服務帶來的分布式事務問題,而是本地分庫操作帶來的事務問題,可以看下local-tcc-sample例子
該demo中並未對3個方法做冪等控制,實際業務實現中需多加注意
最后貼上Seata中AT、TCC模式源碼的分析,有興趣的可以看一下哦
Seata實戰-AT模式源碼分析
Seata實戰-TCC模式源碼分析
————————————————
版權聲明:本文為CSDN博主「hosaos」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/hosaos/article/details/89136666
