3. 分布式事務解決方案之2PC(兩階段提交)
針對不同的分布式場景業界常見的解決方案有2PC、TCC、可靠消息最終一致性、最大努力通知這幾種。
2PC即兩階段提交協議,是將整個事務流程分為兩個階段,准備階段(Prepare phase)、提交階段(commit phase),2是指兩階段,P是指准備階段,C是提交階段。
舉例 :張三和李四好久不見,老友約起聚餐,飯店老板要求先買單,才能出票。這時張三和李四分別抱怨近況不如意,囊腫羞澀,都不願意請客,這時只能AA。只有張三和李四都付款,老板才能出票安排就餐。但由於張三和李四都是鐵公雞,形成兩尷尬的一幕 :
准備階段 :老板要求張三付款,張三付款。老板要求李四付款,李四付款。
提交階段 :老板出票,兩人拿票紛紛落座就餐。
例子中形成兩一個事務,若張三或李四其中一個拒絕付款,或錢不夠,店老板都不會給出票,並且會把已收款退回。
整個事務過程由事務管理器和參與者組成,店老板就是事務管理器,張三、李四就是事務參與者,事務管理器負責決策整個分布式事務的提交和回滾,事務參與者負責自己本地事務的提交和回滾。
在計算機中部分關系數據庫如Oracle、MySQL支持兩階段提交協議,如下圖 :
1. 准備階段(Prepare phase):事務管理器給每個參與者發送Prepare消息,每個數據庫參與者在本地執行事務,並寫本地的Undo/Redo日志,此時事務沒有提交。
(Undo日志是記錄修改前的數據,用於數據庫回滾,Redo日志是記錄修改后的數據,用於提交事務后寫入數據文件)
2. 提交階段(commit phase):如果事務管理器收到兩參與者的執行失敗或者超時消息時,直接給每個參與者發送回滾(Rollback)消息;否則,發送提交(Commit)消息;參與者根據事務管理器的指令執行提交或者回滾操作,並釋放事務處理過程中使用的鎖資源。注意 :必須在最后階段釋放鎖資源。
下圖展示兩2PC的兩個階段,分成功和失敗兩個情況說明 :
成功情況 :
失敗情況 :
2PC的傳統方案是在數據庫層面實現的,如Oracle、MySQL都支持2PC協議,為了統一標准減少行業內不必要的對接成本,需要制定標准化的處理模型及接口標准,國際開放標准組織Open Group定義分布式事務處理模型DTP(Distributed Transaction Processing Reference Model)。
為了讓大家更明確XA方案的內容,下面新用戶注冊送積分為例來說明 :
執行流程如下 :
1、應用程序(AP)持有用戶庫和積分庫兩個數據源。
2、應用程序(AP)通過TM通知用戶庫RM新增用戶,同時通知積分庫RM為該用戶新增積分,RM此時並未提交事務,此時用戶和積分資源鎖定。
3、TM收到執行回復,只要有一方失敗則分別向其他RM發起回滾事務,回滾完畢,資源鎖釋放。
4、TM收到執行回復,全部成功,此時向所有RM發起提交事務,提交完畢,資源鎖釋放。
DTP模型定義如下角色 :
- AP(Application Program) : 既應用程序,可以理解為使用DTP分布式事務的程序。
- RM(Resource Manager) : 即資源管理器,可以理解為事務的參與者,一般情況下是指一個數據庫實例,通過資源管理器對該數據庫進行控制,資源管理器控制着分支事務。
- TM(Transaction Manager) : 事務管理器,負責協調和管理事務,事務管理器控制着全局事務,管理事務生命周期,並協調各個RM。全局事務是指分布式事務處理環境中,需要操作多個數據庫共同完成一個工作,這個工作即是一個全局事務。
- DTP模型定義TM和RM之間通訊的接口規范叫XA,簡單理解為數據庫提供的2PC接口協議,基於數據庫的XA協議來實現2PC又稱為XA方案。
- 以上三個角色之間的交互方式如下 :
1)TM向AP提供應用程序編程接口,AP通過TM提交及回滾事務。
2)TM交易中間件通過XA接口來通知RM數據庫事務的開始、結束以及提交、回滾等。
總結 :
整個2PC的事務流程涉及到三個角色AP、RM、TM。AP指的是使用2PC分布式事務的應用程序;RM指的是資源管理器,它控制着分支事務;TM指的是事務管理器,它控制着整個全局事務。
1)在准備階段RM執行實際的業務操作,但不提交事務,資源鎖定;
2)在提交階段TM會接收RM在准備階段的執行回復,只要有任一個RM執行失敗,TM會通知所有RM執行回滾操作,否則,TM將會通知所有RM提交該事務。提交階段結束資源鎖釋放。
XA方案的問題 :
1、需要本地數據庫支持XA協議。
2、資源鎖需要等到兩個階段結束才釋放,性能較差。
Seata是阿里中間件團隊發起的開源項目Fescar,后更名Seata,它是一個是開源的分布式事務框架。傳統2PC的問題在Seata中得到了解決,它通過對本地關系數據庫的分支事務的協調來驅動完成全局事務,是工作在應用層的中間件。主要優點是性能較好,且不長時間占用連接資源,它以高效並且對業務0入侵的方式解決微服務場景下面臨的分布式事務問題,它目前提供AT模式(即2PC)及TCC模式的分布式事務解決方案。
Seata的設計思想如下 :
Seata的設計目標其一是對業務無入侵,因此從業務無入侵的2PC方案着手,在傳統2PC的基礎上演進,並解決2PC方案面臨的問題。
Seata把一個分布式事務理解成一個包含來若干分支事務的全局事務。全局事務的職責是協調其下管轄的分支事務達成一致,要么一起成功提交,要么一起失敗回滾。此外,通常分支事務本身就是一個關系數據庫的本地事務,下圖是全局事務與分支事務的關系圖 :
與傳統2PC的模型類似,Seata定義了三個組件來協議分布式事務的處理過程 :
- Transaction Coordinator(TC):事務協調器,它是獨立的中間件,需要獨立部署運行,它維護全局事務的運行狀態,接收TM指令發起全局事務的提交與回滾,負責與RM通信協調各個分支事務的提交或回滾。
- Transaction Manager(TM):事務管理器,TM需要嵌入應用程序中工作,它負責開啟一個全局事務,並最終向TC發起全局提交或全局回滾的指令。
- Resource Manager(RM):控制分支事務,負責分支注冊、狀態匯報,並接收事務協調器TC的指令,驅動分支(本地)事務的提交和回滾。
還拿新用戶注冊送積分舉例Seata的分布式事務過程 :
具體的執行流程如下 :
- 用戶服務的TM向TC申請開啟一個全局事務,全局事務創建成功並生成一個全局唯一的XID。
- 用戶服務的RM向TC注冊分支事務,該分支事務在用戶服務執行新增用戶邏輯,並將其納入XID對應全局事務的管轄。
- 用戶服務執行分支事務,向用戶表插入一條記錄。
- 邏輯執行到遠程調用積分服務時(XID在微服務調用鏈路的上下文中傳播)。積分服務的RM向TC注冊分支事務,該分支事務執行增加積分的邏輯,並將其納入XID對應全局事務的管轄。
- 積分服務執行分支事務,向積分記錄表插入一條記錄,執行完畢后,返回用戶服務。
- 用戶服務分支事務執行完畢。
- TM向TC發起針對XID的全局提交或回滾決議。
- TC調度XID下管轄的全部分支事務完成提交或回滾請求。
Seata實現2PC與傳統2PC的差別 :
架構層次方面,傳統2PC方案的RM實際上是在數據庫層,RM本質上就是數據庫自身,通過XA協議實現,而Seata的RM是以jar包的形式作為中間件層部署在應用程序的這一側的。
兩階段提交方面,傳統2PC無論第二階段的決議是commit還是rollbcak,事務性資源的鎖都要保持到Phase2完成才釋放。而Seata的做法是在Phase1就將本地事務提交,這樣就可以省去Phase2持鎖的時間,整體提高效率。
本實例通過Seata中間件實現分布式事務,模擬兩個賬戶的轉賬交易過程。兩個賬戶在兩個不同的銀行(張三在bank1、李四在bank2),bank1和bank2是兩個微服務。交易過程中,張三給李四轉賬制定金額。
上述交易步驟,要么一起成功,要么一起失敗,必須是一個整體性的事務。
本實例程序組成 部分如下 :
數據庫 :MySQL-5.7.25
包括bank1和bank2兩個數據庫。
JDK:1.8
微服務框架 :spring-boot-2.1.3、spring-cloud-Greenwich.RELEASE
seata客戶端(RM、TM):spring-cloud-alibaba-seata-2.1.0RELEASE
seata服務端(TC):seata-server-0.7.1
微服務及數據庫的關系 :
dtx/dtx-seata-demo/seata-demo-bank1 銀行1,操作張三賬戶,鏈接數據庫bank1
dtx/dtx-seata-demo/seata-demo-bank2 銀行2,操作李四賬戶,鏈接數據庫bank2
服務注冊中興 :dtx/discover-server
本實例程序技術架構如下 :
交互流程如下 :
1、請求bank1進行轉賬,傳入轉賬金額。
2、bank1減少轉賬金額,調用bank2,傳入轉賬金額。
bank1庫,包含張三賬戶
CREATE DATABASE /*!32312 IF NOT EXISTS*/`bank1` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `bank1`;
/*Table structure for table `account_info` */
DROP TABLE IF EXISTS `account_info`;
CREATE TABLE `account_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`account_name` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '戶主姓名',
`account_no` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '銀行卡號',
`account_password` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '帳戶密碼',
`account_balance` double DEFAULT NULL COMMENT '帳戶余額',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;
/*Data for the table `account_info` */
insert into `account_info`(`id`,`account_name`,`account_no`,`account_password`,`account_balance`) values (2,'張三','1',NULL,1000);
/*Table structure for table `de_duplication` */
DROP TABLE IF EXISTS `de_duplication`;
CREATE TABLE `de_duplication` (
`tx_no` varchar(64) COLLATE utf8_bin NOT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`tx_no`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;
/*Data for the table `de_duplication` */
/*Table structure for table `local_cancel_log` */
DROP TABLE IF EXISTS `local_cancel_log`;
CREATE TABLE `local_cancel_log` (
`tx_no` varchar(64) NOT NULL COMMENT '事務id',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`tx_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `local_cancel_log` */
/*Table structure for table `local_confirm_log` */
DROP TABLE IF EXISTS `local_confirm_log`;
CREATE TABLE `local_confirm_log` (
`tx_no` varchar(64) NOT NULL COMMENT '事務id',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`tx_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `local_confirm_log` */
/*Table structure for table `local_trade_log` */
DROP TABLE IF EXISTS `local_trade_log`;
CREATE TABLE `local_trade_log` (
`tx_no` bigint(20) NOT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`tx_no`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;
DROP TABLE IF EXISTS `local_try_log`;
CREATE TABLE `local_try_log` (
`tx_no` varchar(64) NOT NULL COMMENT '事務id',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`tx_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `local_try_log` */
/*Table structure for table `undo_log` */
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=167 DEFAULT CHARSET=utf8;
/*Data for the table `undo_log` */
insert into `undo_log`(`id`,`branch_id`,`xid`,`context`,`rollback_info`,`log_status`,`log_created`,`log_modified`,`ext`) values (166,2019228885,'192.168.1.101:8888:2019228047','serializer=jackson','{}',1,'2019-08-11 15:16:43','2019-08-11 15:16:43',NULL);
bank2庫,包含李四賬戶
CREATE DATABASE /*!32312 IF NOT EXISTS*/`bank2` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `bank2`;
/*Table structure for table `account_info` */
DROP TABLE IF EXISTS `account_info`;
CREATE TABLE `account_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`account_name` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '戶主姓名',
`account_no` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '銀行卡號',
`account_password` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '帳戶密碼',
`account_balance` double DEFAULT NULL COMMENT '帳戶余額',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;
/*Data for the table `account_info` */
insert into `account_info`(`id`,`account_name`,`account_no`,`account_password`,`account_balance`) values (3,'李四的賬戶','2',NULL,0);
/*Table structure for table `de_duplication` */
DROP TABLE IF EXISTS `de_duplication`;
CREATE TABLE `de_duplication` (
`tx_no` varchar(64) COLLATE utf8_bin NOT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`tx_no`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;
/*Data for the table `de_duplication` */
/*Table structure for table `local_cancel_log` */
DROP TABLE IF EXISTS `local_cancel_log`;
CREATE TABLE `local_cancel_log` (
`tx_no` varchar(64) NOT NULL COMMENT '事務id',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`tx_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `local_cancel_log` */
/*Table structure for table `local_confirm_log` */
DROP TABLE IF EXISTS `local_confirm_log`;
CREATE TABLE `local_confirm_log` (
`tx_no` varchar(64) NOT NULL COMMENT '事務id',
`create_time` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `local_confirm_log` */
/*Table structure for table `local_trade_log` */
DROP TABLE IF EXISTS `local_trade_log`;
CREATE TABLE `local_trade_log` (
`tx_no` bigint(20) NOT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`tx_no`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;
DROP TABLE IF EXISTS `local_try_log`;
CREATE TABLE `local_try_log` (
`tx_no` varchar(64) NOT NULL COMMENT '事務id',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`tx_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `local_try_log` */
/*Table structure for table `undo_log` */
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
(1)下載seata服務器
下載地址 :seata服務器
(2)解壓並啟動
winodws :【seata服務端解壓路徑】/bin/seata-server.bat -p 8888 -m file
mac/linux : 【seata服務端解壓路徑】nohup sh seata-server.sh -p 8888 -h 127.0.0.1 -m file &> seata.log &
注 :其中8888為服務端口號;file為啟動模式,這里指seata服務將采用文件的方式存儲信息。
如上圖出現“Server started。。。“的字樣則表示啟動成功。
discover-server是服務注冊中心,測試工程將自己注冊至discover-server。
dtx-seata-demo是seata的測試工程,根據業務需求需要創建兩個dex-seata-demo工程。
(1)父工程maven依賴說明
在dtx父工程中指定了SpringBoot和SpringCloud版本
在dtx-seata-demo父工程中指定了spring-cloud-alibaba-dependencies的版本。
(3)配置seata
在src/main/resource中,新增registry.conf、file.conf文件,內容可拷貝seata-server-0.7.1中的配置文件子。 在registry.conf中registry.type使用file:
在file.conf中更改service.vgroup_mapping.[springcloud服務名]-fescar-service-group = “default”,並修改 service.default.grouplist =[seata服務端地址]
關於vgroup_mapping的配置:
vgroup_mapping.事務分組服務名=Seata Server集群名稱(默認名稱為default) default.grouplist = Seata Server集群地址
在 org.springframework.cloud:spring-cloud-starter-alibaba-seata 的 org.springframework.cloud.alibaba.seata.GlobalTransactionAutoConfiguration 類中,默認會使用 ${spring.application.name}-fescar-service-group 作為事務分組服務名注冊到 Seata Server上,如果和 file.conf 中的配置不一致,會提示 no available server to connect 錯誤
也可以通過配置 spring.cloud.alibaba.seata.tx-service-group 修改后綴,但是必須和 file.conf 中的配置保持 一致。
(4)創建代理數據源
新增DatabaseConfiguration.java,Seata的RM通過DataSourceProxy才能在業務代碼的事務提交時,通過這個切 入點,與TC進行通信交互、記錄undo_log等。
@Configuration
public class DatabaseConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.ds0")
public DruidDataSource ds0() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
@Primary
@Bean
public DataSource dataSource(DruidDataSource ds0) {
DataSourceProxy pds0 = new DataSourceProxy(ds0);
return pds0;
}
}
1、正常提交流程
2、回滾流程
回滾流程省略前的RM注冊過程。
要點說明 :
1、每個RM使用DataSourceProxy連接數據庫,其目的是使用ConnectionProxy,使用數據源和數據連接代理的目的就是第一階段將undo_log和業務數據放在一個本地事務提交,這樣就保存了只要有業務操作就一定有undo_log.
2、在第一階段undo_log中存放了數據修改前和修改后的值,為事務回滾作好准備,所以第一階段完成就已經將分支事務提交,也就釋放了鎖資源。
3、TM開啟全局事務開始,將XID全局事務id放在事務上下午中,通過feign調用也將XID傳入下游分支事務,每個分支事務將自己的Branch ID分支事務ID與XID關聯。
4、第二階段全局事務提交,TC會通知各個分支參與者提交分支事務,在第一階段就已經提交了分支事務,這里各個參與者只需要刪除undo_log即可,並且可以異步執行,第二階段很快可以完成。
5、第二階段全局事務回滾,TC會通知各個分支參與者回滾分支事務,通過XID和Branch ID找到相應的回滾日志,通過回滾日志生成反向的SQL並執行,以完成分支事務回滾到之前的狀態,如果回滾失敗則會重試回滾操作。
dtx-seata-demo-bank1實現如下功能:
1、張三賬戶減少金額,開啟全局事務。
2、遠程調用bank2向李四轉賬。
(1)DAO
@Mapper
@Component
public interface AccountInfoDao {
//更新賬戶金額
@Update("update account_info set account_balance = account_balance + #{amount} where account_no = #{accountNo}")
int updateAccountBalance(@Param("accountNo") String accountNo, @Param("amount") Double amount);
}
(2) FeignClient
遠程調用bank2的客戶端
@FeignClient(value = "seata‐demo‐bank2",fallback = Bank2ClientFallback.class) public interface Bank2Client {
@GetMapping("/bank2/transfer")
String transfer(@RequestParam("amount") Double amount);
}
@Component
public class Bank2ClientFallback implements Bank2Client{
@Override
public String transfer(Double amount) {
return "fallback"; }
}
(3)Service
@Service
public class AccountInfoServiceImpl implements AccountInfoService {
private Logger logger = LoggerFactory.getLogger(AccountInfoServiceImpl.class);
@Autowired
AccountInfoDao accountInfoDao;
@Autowired
Bank2Client bank2Client;
//張三轉賬
@Override
@GlobalTransactional
@Transactional
public void updateAccountBalance(String accountNo, Double amount) {
logger.info("******** Bank1 Service Begin ... xid: {}" , RootContext.getXID()); //張三扣減金額
accountInfoDao.updateAccountBalance(accountNo,amount*‐1);
//向李四轉賬
String remoteRst = bank2Client.transfer(amount); //遠程調用失敗
if(remoteRst.equals("fallback")){
throw new RuntimeException("bank1 下游服務異常"); }
//人為制造錯誤 if(amount==3){
throw new RuntimeException("bank1 make exception 3"); }
}
}
將@GlobalTransactional注解標注在全局事務發起的Service實現方法上,開啟全局事務 :
GlobalTransactionalInterceptor會攔截@GlobalTransactional注解的方法,生成全局事務ID(XID),XID會在整個分布式事務中傳遞。
在遠程調用時,spring-cloud-alibaba-seata會攔截Feign調用將XID傳遞到下游服務。
(6)Controller
@RestController
public class Bank1Controller {
@Autowired
AccountInfoService accountInfoService;
//轉賬
@GetMapping("/transfer")
public String transfer(Double amount){
accountInfoService.updateAccountBalance("1",amount);
return "bank1"+amount; }
}
dtx-seata-demo-bank2實現如下功能:
1、李四賬戶增加金額。
dtx-seata-demo-bank2在本賬戶事務中作為分支事務不使用@GlobalTransactional。
(1)DAO
@Mapper
@Component
public interface AccountInfoDao {
//向李四轉賬
@Update("UPDATE account_info SET account_balance = account_balance + #{amount} WHERE account_no = #{accountNo}")
int updateAccountBalance(@Param("accountNo") String accountNo, @Param("amount") Double amount);
}
(2)Service
@Service
public class AccountInfoServiceImpl implements AccountInfoService {
private Logger logger = LoggerFactory.getLogger(AccountInfoServiceImpl.class);
@Autowired
AccountInfoDao accountInfoDao;
@Override
@Transactional
public void updateAccountBalance(String accountNo, Double amount) { logger.info("******** Bank2 Service Begin ... xid: {}" , RootContext.getXID()); //李四增加金額
accountInfoDao.updateAccountBalance(accountNo,amount);
//制造異常
if(amount==2){
throw new RuntimeException("bank1 make exception 2"); }
}
}
(3)Controller
@RestController
public class Bank2Controller {
@Autowired
AccountInfoService accountInfoService;
@GetMapping("/transfer")
public String transfer(Double amount){
accountInfoService.updateAccountBalance("2",amount);
return "bank2"+amount;
}
}
- 張三向李四轉賬成功。
- 李四事務失敗,張三事務回滾成功。
- 張三事務失敗,李四事務回滾成功。
- 分支事務超時測試。
傳統2PC(基於數據庫XA協議)和Seata實現2PC的兩種2PC方案,由於Seata的零入侵並且解決了傳統2PC長期鎖資源的問題,所以推薦采用Seata實現2PC。
Seata實現2PC要點 :
1、全局事務開始使用GlobalTransactional標識。
2、每個本地事務方案仍然使用@Transactional標識。
3、每個數據都需要創建undo_log表,此表是Seata保證本地事務一致性的關鍵。