一、什麼是2PC
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的兩個階段,分成功和失敗兩個情況說明:
成功情況:
失敗情況:
二、解決方案
2.1 XA方案
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、資源鎖需要等到兩個階段結束才釋放,性能較差。
2.2 Seata方案
Seata是由阿裡中間件團隊發起的開源項目 Fescar,後更名為Seata,它是一個是開源的分布式事務架構。
傳統2PC的問題在Seata中得到了解決,它通過對本地關系資料庫的分支事務的協調來驅動完成全局事務,是工作在應用層的中間件。
主要優點是性能較好,且不長時間占用連接配接資源,它以高效并且對業務0侵入的方式解決微服務場景下面臨的分布式事務問題,
它目前提供AT模式(即2PC)及TCC模式的分布式事務解決方案。
Seata的設計思想如下:
Seata的設計目标其一是對業務無侵入,是以從業務無侵入的2PC方案着手,在傳統2PC的基礎上演進,并解決2PC方案面臨的問題。
Seata把一個分布式事務了解成一個包含了若幹分支事務的全局事務。
全局事務的職責是協調其下管轄的分支事務達成一緻,要麼一起成功送出,要麼一起失敗復原。
此外,通常分支事務本身就是一個關系資料庫的本地事務,
下圖是全局事務與分支事務的關系圖:
與 傳統2PC 的模型類似,Seata定義了3個元件來協調分布式事務的處理過程:T
Transaction Coordinator (TC): 事務協調器,它是獨立的中間件,需要獨立部署運作,它維護全局事務的運作狀态,接收TM指令發起全局事務的送出與復原,
負責與RM通信協調各各分支事務的送出或復原。
Transaction Manager (TM): 事務管理器,TM需要嵌入應用程式中工作,它負責開啟一個全局事務,并最終向TC發起全局送出或全局復原的指令。
Resource Manager (RM): 資料總管,控制分支事務,負責分支注冊、狀态彙報,并接收事務協調器TC的指令,驅動分支(本地)事務的送出和復原。
還拿新使用者注冊送積分舉例Seata的分布式事務過程:
具體的執行流程如下:
1. 使用者服務的 TM 向 TC 申請開啟一個全局事務,全局事務建立成功并生成一個全局唯一的XID。
2. 使用者服務的 RM 向 TC 注冊 分支事務,該分支事務在使用者服務執行新增使用者邏輯,并将其納入 XID 對應全局事務的管轄。
3. 使用者服務執行分支事務,向使用者表插入一條記錄。
4. 邏輯執行到遠端調用積分服務時(XID 在微服務調用鍊路的上下文中傳播)。
積分服務的RM 向 TC 注冊分支事務,該分支事務執行增加積分的邏輯,并将其納入 XID 對應全局事務的管轄。
5. 積分服務執行分支事務,向積分記錄表插入一條記錄,執行完畢後,傳回使用者服務。
6. 使用者服務分支事務執行完畢。
7. TM 向 TC 發起針對 XID 的全局送出或復原決議。
8. TC 排程 XID 下管轄的全部分支事務完成送出或復原請求。
Seata實作2PC與傳統2PC的差别:
架構層次方面
傳統2PC方案的 RM 實際上是在資料庫層,RM 本質上就是資料庫自身,通過 XA 協定實作。
而Seata的 RM 是以jar包的形式作為中間件層部署在應用程式這一側的。
兩階段送出方面
傳統2PC無論第二階段的決議是commit還是rollback,事務性資源的鎖都要保持到Phase2完成才釋放。
而Seata的做法是在Phase1 就将本地事務送出,這樣就可以省去Phase2持鎖的時間,整體提高效率。
三、seata實作2PC事務
3.1 業務說明
本示例通過Seata中間件實作分布式事務,模拟兩個賬戶的轉賬交易過程。
兩個賬戶在兩個不同的銀行(張三在bank1、李四在bank2),bank1和bank2是兩個微服務。
交易過程是,張三給李四轉賬指定金額。
上述交易步驟,要麼一起成功,要麼一起失敗,必須是一個整體性的事務。
3.2 程式組成部分
本示例程式組成部分如下:
資料庫:MySQL-5.7.25 包括bank1和bank2兩個資料庫。
JDK:64位 jdk1.8.0_201
微服務架構:spring-boot-2.1.3、spring-cloud-Greenwich.RELEASE
seata用戶端(RM、TM):spring-cloud-alibaba-seata-2.1.0.RELEASE
seata服務端(TC):seata-server-0.7.1
微服務及資料庫的關系 :
seata-demo-bank1 銀行1,操作張三賬戶, 連接配接資料庫bank1
seata-demo-bank2 銀行2,操作李四賬戶,連接配接資料庫bank2
服務注冊中心:eureka-server
本示例程式技術架構如下:
互動流程如下:
1、請求bank1進行轉賬,傳入轉賬金額。
2、bank1減少轉賬金額,調用bank2,傳入轉賬金額。
3.3 建立資料庫
包括如下資料庫:
bank1庫,包含張三賬戶
create database `bank1` character set 'utf8' collate 'utf8_general_ci';
drop table if exists `account_info`;
create table `account_info` (
`id` bigint(20) not null auto_increment,
`account_name` varchar(100) character set utf8 collate utf8_bin null default null comment '戶
主姓名',
`account_no` varchar(100) character set utf8 collate utf8_bin null default null comment '銀行
卡号',
`account_password` varchar(100) character set utf8 collate utf8_bin null default null comment
'帳戶密碼',
`account_balance` double null default null comment '帳戶餘額',
primary key (`id`) using btree
) engine = innodb auto_increment = 5 character set = utf8 collate = utf8_bin row_format =
dynamic;
insert into `account_info` values (2, '張三的賬戶', '1', '', 10000);
bank2庫,包含李四賬戶
create database `bank2` character set 'utf8' collate 'utf8_general_ci';
create table `account_info` (
`id` bigint(20) not null auto_increment,
`account_name` varchar(100) character set utf8 collate utf8_bin null default null comment '戶
主姓名',
`account_no` varchar(100) character set utf8 collate utf8_bin null default null comment '銀行
卡号',
`account_password` varchar(100) character set utf8 collate utf8_bin null default null comment
'帳戶密碼',
`account_balance` double null default null comment '帳戶餘額',
primary key (`id`) using btree
) engine = innodb auto_increment = 5 character set = utf8 collate = utf8_bin row_format =
dynamic;
insert into `account_info` values (3, '李四的賬戶', '2', null, 0);
分别在bank1、bank2庫中建立undo_log表,此表為seata架構使用:
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=1 default charset=utf8;
3.4 啟動TC(事務協調器)
3.4.1 下載下傳seata伺服器
一定注意版本,各種坑
下載下傳位址:https://github.com/seata/seata/releases/download/v0.7.1/seata-server-0.7.1.zip
3.4.2 解壓并啟動
[seata服務端解壓路徑]/bin/seata-server.bat -p 8888 -m file
注:其中8888為服務端口号;file為啟動模式,這裡指seata服務将采用檔案的方式存儲資訊。
如上圖出現“Server started...”的字樣則表示啟動成功。
3.5 eureka-server
eureka-server是服務注冊中心,測試工程将自己注冊進去,基于Eureka實作
3.6.1 導入案例工程
1.兩個測試工程如下
seata-demo-bank1 ,操作張三賬戶,連接配接資料庫bank1
seata-demo-bank2 ,操作李四賬戶,連接配接資料庫bank2
2.父工程maven依賴說明
在父工程中指定了SpringBoot和SpringCloud版本和SpringCloudAlibaba版本
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.best</groupId>
<artifactId>best-eureka-demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>seata-demo-bank1</module>
<module>seata-demo-bank2</module>
<module>eureka-server</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
3.6.2 配置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 中的配置保持一緻。
3.6.3 建立代理資料源
新增DatabaseConfiguration.java,Seata的RM通過DataSourceProxy才能在業務代碼的事務送出時,通過這個切入點,與TC進行通信互動、記錄undo_log等。
@Configuration
public class DatabaseConfiguration {
private final ApplicationContext applicationContext;
public DatabaseConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@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;
}
}
3.7 Seata執行流程
3.7.1 正常送出流程
3.7.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 并執行,
以完成分支事務復原到之前的狀态,如果復原失敗則會重試復原操作。
3.8 seata-demo-bank1
seata-demo-bank1實作如下功能:
1、張三賬戶減少金額,開啟全局事務。
2、遠端調用bank2向李四轉賬。
踩坑、參考gitee、踩坑指南
3.8.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);
}
3.8.2 FeignClient
遠端調用bank2的用戶端
@FeignClient(value = "provider",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.8.3 Service
public interface AccountInfoService {
// 張三扣減金額
void updateAccountBalance(String accountNo, Double amount);
}
@Service
@Slf4j
public class AccountInfoServiceImpl implements AccountInfoService {
private Logger logger = LoggerFactory.getLogger(AccountInfoServiceImpl.class);
@Resource
private AccountInfoDao accountInfoDao;
@Resource
private Bank2Client bank2Client;
//張三轉賬
@Override
@Transactional
@GlobalTransactional
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傳遞到下遊服務。
3.8.4 Controller
@RestController
public class Bank1Controller {
@Resource
AccountInfoService accountInfoService;
//轉賬
@GetMapping("/transfer")
public String transfer(Double amount){
accountInfoService.updateAccountBalance("1",amount);
return "bank1"+amount;
}
}
server:
port: 5555
# 注冊到eureka服務端的微服務名稱
spring:
application:
name: seata-demo-bank1
datasource:
ds0:
url: jdbc:mysql://localhost:3306/bank1?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT user()
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
connection-properties: druid.stat.mergeSql:true;druid.stat.slowSqlMillis:5000
eureka:
instance:
# 将ip注冊到eureka server上,ip替代hostname
prefer-ip-address: true
# 顯示微服務的服務執行個體id
instance-id: seata-demo-bank1-${server.port}
client:
register-with-eureka: true
fetchRegistry: true
service-url:
# 注冊到eureka服務端的位址
defaultZone: http://127.0.0.1:2222/eureka
3.9 seata-demo-bank2
seata-demo-bank2實作如下功能:
1、李四賬戶增加金額。
seata-demo-bank2在本賬号事務中作為分支事務不使用@GlobalTransactional。
3.9.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);
}
3.9.2 Service
@Service
@Slf4j
public class AccountInfoServiceImpl implements AccountInfoService {
private Logger logger = LoggerFactory.getLogger(AccountInfoServiceImpl.class);
@Resource
private 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.9.3 Controller
@RestController
public class Bank2Controller {
@Resource
private AccountInfoService accountInfoService;
// 接收張三的轉賬
@GetMapping("/transfer")
public String transfer(Double amount) {
// 李四增加金額
accountInfoService.updateAccountBalance("2", amount);
return "bank2" + amount;
}
}
server:
port: 3333
servlet:
context-path: /bank2
spring:
application:
name: seata-demo-bank2
datasource:
ds0:
url: jdbc:mysql://localhost:3306/bank1?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT user()
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
connection-properties: druid.stat.mergeSql:true;druid.stat.slowSqlMillis:5000
eureka:
instance:
# 将ip注冊到eureka server上
prefer-ip-address: true
# 顯示微服務的服務執行個體id
instance-id: seata-demo-bank2-${server.port}
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://127.0.0.1:2222/eureka
3.10 測試場景
張三向李四轉賬成功。
http://localhost:5555/bank1/transfer?amount=1
轉賬前張三賬戶
轉賬後張三賬戶
轉賬前李四賬戶
轉賬後李四賬戶
李四事務失敗,張三事務復原成功。
張三事務失敗,李四事務復原成功。
http://localhost:5555/bank1/transfer?amount=2
http://localhost:5555/bank1/transfer?amount=3
斷點打到上圖出,檢視資料庫,李四事務已經送出,發現生成了一條undo log日志
{
"@class":"io.seata.rm.datasource.undo.BranchUndoLog",
"xid":"10.67.146.58:8888:2081705533",
"branchId":2081705534,
"sqlUndoLogs":[
"java.util.ArrayList",
[
{
"@class":"io.seata.rm.datasource.undo.SQLUndoLog",
"sqlType":"UPDATE",
"tableName":"account_info",
"beforeImage":{
"@class":"io.seata.rm.datasource.sql.struct.TableRecords",
"tableName":"account_info",
"rows":[
"java.util.ArrayList",
[
{
"@class":"io.seata.rm.datasource.sql.struct.Row",
"fields":[
"java.util.ArrayList",
[
{
"@class":"io.seata.rm.datasource.sql.struct.Field",
"name":"id",
"keyType":"PrimaryKey",
"type":-5,
"value":[
"java.lang.Long",
3
]
},
{
"@class":"io.seata.rm.datasource.sql.struct.Field",
"name":"account_balance",
"keyType":"NULL",
"type":8,
"value":22
}
]
]
}
]
]
},
"afterImage":{
"@class":"io.seata.rm.datasource.sql.struct.TableRecords",
"tableName":"account_info",
"rows":[
"java.util.ArrayList",
[
{
"@class":"io.seata.rm.datasource.sql.struct.Row",
"fields":[
"java.util.ArrayList",
[
{
"@class":"io.seata.rm.datasource.sql.struct.Field",
"name":"id",
"keyType":"PrimaryKey",
"type":-5,
"value":[
"java.lang.Long",
3
]
},
{
"@class":"io.seata.rm.datasource.sql.struct.Field",
"name":"account_balance",
"keyType":"NULL",
"type":8,
"value":24
}
]
]
}
]
]
}
}
]
]
}
斷點執行完,由于異常,bank1,bank2根據undo log反向操作,進行復原,結束後,undo log日志會被seata清理掉
bank1 bank2模拟異常,都可復原
分支事務逾時測試
總結
本節講解了傳統2PC(基于資料庫XA協定)和Seata實作2PC的兩種2PC方案,由于Seata的0侵入性并且解決了傳統2PC長期鎖資源的問題,是以推薦采用Seata實作2PC。
Seata實作2PC要點:
1、全局事務開始使用 @GlobalTransactional辨別 。
2、每個本地事務方案仍然使用@Transactional辨別。
3、每個資料都需要建立undo_log表,此表是seata保證本地事務一緻性的關鍵。
視訊教程、gitee源碼