天天看點

分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務

一、什麼是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的兩個階段,分成功和失敗兩個情況說明:

成功情況: 
分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務
 失敗情況:
分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務

二、解決方案

2.1 XA方案

2PC的傳統方案是在資料庫層面實作的,如Oracle、MySQL都支援2PC協定,為了統一标準減少行業内不必要的對接成本,需要制定标準化的處理模型及接口标準,

國際開放标準組織Open Group定義了分布式事務處理模型DTP(Distributed Transaction Processing Reference Model)。

為了讓大家更明确XA方案的内容程,下面新使用者注冊送積分為例來說明:

分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務

執行流程如下:

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一、什麼是2PC二、解決方案三、seata實作2PC事務
與 傳統2PC 的模型類似,Seata定義了3個元件來協調分布式事務的處理過程:T
分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務

Transaction Coordinator (TC): 事務協調器,它是獨立的中間件,需要獨立部署運作,它維護全局事務的運作狀态,接收TM指令發起全局事務的送出與復原,

負責與RM通信協調各各分支事務的送出或復原。

Transaction Manager (TM): 事務管理器,TM需要嵌入應用程式中工作,它負責開啟一個全局事務,并最終向TC發起全局送出或全局復原的指令。

Resource Manager (RM): 資料總管,控制分支事務,負責分支注冊、狀态彙報,并接收事務協調器TC的指令,驅動分支(本地)事務的送出和復原。

還拿新使用者注冊送積分舉例Seata的分布式事務過程:
分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務

具體的執行流程如下:

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是兩個微服務。

交易過程是,張三給李四轉賬指定金額。

上述交易步驟,要麼一起成功,要麼一起失敗,必須是一個整體性的事務。

分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務

 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

本示例程式技術架構如下:

分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務

互動流程如下:

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服務将采用檔案的方式存儲資訊。

分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務
 如上圖出現“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:預設

分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務
在file.conf中更改service.vgroup_mapping.[springcloud服務名]-fescar-service-group = "default",并修改service.default.grouplist =[seata服務端位址]
分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務

關于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 正常送出流程

分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務

3.7.2 復原流程

復原流程省略前面的RM注冊過程

分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務

 要點說明:

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;
    }

}
           
分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務
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;
    }
}
           
分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務
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

分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務
轉賬前張三賬戶
分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務
轉賬後張三賬戶
分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務
轉賬前李四賬戶
分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務
轉賬後李四賬戶

李四事務失敗,張三事務復原成功。

張三事務失敗,李四事務復原成功。

分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務
分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務

http://localhost:5555/bank1/transfer?amount=2

http://localhost:5555/bank1/transfer?amount=3

分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務

 斷點打到上圖出,檢視資料庫,李四事務已經送出,發現生成了一條undo log日志

分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務
分布式事務解決方案之2PC一、什麼是2PC二、解決方案三、seata實作2PC事務
{
    "@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源碼

繼續閱讀