19.Seata 分布式事务

简介

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

官网地址:
https://seata.apache.org/
https://github.com/apache/incubator-seata/

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

{
	"branchId": 641789253,
	"undoItems": [{
		"afterImage": {
			"rows": [{
				"fields": [{
					"name": "id",
					"type": 4,
					"value": 1
				}, {
					"name": "name",
					"type": 12,
					"value": "GTS"
				}, {
					"name": "since",
					"type": 12,
					"value": "2014"
				}]
			}],
			"tableName": "product"
		},
		"beforeImage": {
			"rows": [{
				"fields": [{
					"name": "id",
					"type": 4,
					"value": 1
				}, {
					"name": "name",
					"type": 12,
					"value": "TXC"
				}, {
					"name": "since",
					"type": 12,
					"value": "2014"
				}]
			}],
			"tableName": "product"
		},
		"sqlType": "UPDATE"
	}],
	"xid": "xid:xxx"
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

单实例多数据源

为简化代码示例使用2个库实现跨库转账逻辑,使用多数据源实现:

在这里插入图片描述

create database db1;
use db1;
create table t_user(
    id int primary key auto_increment,
    name char(32) not null,
    amount int not null default 0
);

insert into t_user(name,amount) values('jack',1000);

create database db2;
use db2;
create table t_user(
    id int primary key auto_increment,
    name char(32) not null,
    amount int not null default 0
);
insert into t_user(name,amount) values('rose',2000);
<dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>


<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
server:
  port: 9010
spring:
  application:
    name: svc-test-seata
  datasource:
    dynamic:
      primary: db1
      datasource:
        db1:
          url: jdbc:mysql://xxx:3306/db1?useSSL=false&characterEncoding=UTF-8
          username: xxx
          password: xxx
          type: com.zaxxer.hikari.HikariDataSource
          driver-class-name: com.mysql.jdbc.Driver
          hikari:
            maximum-pool-size: 50
            minimum-idle: 20
        db2:
          url: jdbc:mysql://xxx:3306/db2?useSSL=false&characterEncoding=UTF-8
          username: xxx
          password: xxx
          type: com.zaxxer.hikari.HikariDataSource
          driver-class-name: com.mysql.jdbc.Driver
          hikari:
            maximum-pool-size: 50
            minimum-idle: 20

mybatis-plus:
  configuration:
    cache-enabled: false
    local-cache-scope: statement


@Data
@TableName("t_user")
public class User {
    @TableId
    private Long id;
    private String name;
    private Integer amount;
}
@Mapper
public interface UserMapper extends BaseMapper<User> {

}
public interface DB1User extends IService<User> {
    boolean updateById(User user);
}


@Service
@DS("db1")
public class DB1UserImpl extends ServiceImpl<UserMapper, User> implements DB1User {

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateById(User user) {
       return this.baseMapper.updateById(user) > 0;
    }
}
public interface DB2User extends IService<User> {
    boolean updateById(User user);
}


@Service
@DS("db2")
public class DB2UserImpl extends ServiceImpl<UserMapper, User> implements DB2User {

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateById(User user) {
        return this.baseMapper.updateById(user) > 0;
    }
}

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private DB1User db1User;

    @Autowired
    private DB2User db2User;


    @GetMapping
    public Object queryAll() throws Exception {
        List<User> users = new ArrayList<>();
        users.addAll(db1User.list());
        users.addAll(db2User.list());
        Result result = Result.ok(users);
        return result;
    }

    @GetMapping("/transfer")
    public Object transfer() throws Exception {
        User user1 = db1User.getById(1);
        User user2 = db2User.getById(1);

        user1.setAmount(user1.getAmount() - 100);
        user2.setAmount(user2.getAmount() + 100);

        db1User.updateById(user1);
        int a = 10 / 0;
        db2User.updateById(user2);
        return Result.ok(queryAll());
    }
}

访问 http://localhost:9010/user/transfer 实现跨库转账将发现数据不一致的情况。

Seata单节点

db1、db2 两个库中都创建Seata所需的undo_log表:

CREATE TABLE IF NOT EXISTS `undo_log`(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
);

ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
Usage: sh seata-server.sh(for linux and mac) or cmd seata-server.bat(for windows) [options]
  Options:
    --host, -h
      The address is expose to registration center and other service can access seata-server via this ip.
      Default: 0.0.0.0
    --port, -p
      The port to listen.
      Default: 8091
    --storeMode, -m
      log store mode : file、db
      Default: file
    --help

e.g.

# file模式是为了快速搭建验证demo
sh seata-server.sh -p 8091 -h 127.0.0.1 -m file

pom.xml 添加seata依赖(seata-spring-boot-starter 依赖非常老的seata 库会导致启动失败):

<dependency>
   <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.3.0</version>
    <exclusions>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>1.3.0</version>
</dependency>

application.yaml

server:
  port: 9010
spring:
  application:
    name: svc-test-seata
  datasource:
    dynamic:
      primary: db1
      datasource:
        db1:
          url: jdbc:mysql://xxx:3306/db1?useSSL=false&characterEncoding=UTF-8
          username: xxx
          password: xxx
          type: com.zaxxer.hikari.HikariDataSource
          driver-class-name: com.mysql.jdbc.Driver
          hikari:
            maximum-pool-size: 50
            minimum-idle: 20
        db2:
          url: jdbc:mysql://xxx:3306/db2?useSSL=false&characterEncoding=UTF-8
          username: xxx
          password: xxx
          type: com.zaxxer.hikari.HikariDataSource
          driver-class-name: com.mysql.jdbc.Driver
          hikari:
            maximum-pool-size: 50
            minimum-idle: 20
      seata: true
      seata-mode: at      # 事务模式为AT模式

mybatis-plus:
  configuration:
    cache-enabled: false
    local-cache-scope: statement

logging:
  level:
    root: info
    com.test.seata.mapper: trace

seata:
  enable-auto-data-source-proxy: false
  application-id: ${spring.application.name}
  tx-service-group: my-test-tx-group          # 配置事务组
  service:
    vgroup-mapping:
      my-test-tx-group: default                     # 配置事务组关联的TC集群名
    grouplist:
      default: xxx:8091    # Seata 服务器地址信
  config:
    type: file
  registry:
    type: file
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private DB1User db1User;

    @Autowired
    private DB2User db2User;


    @GetMapping
    public Object queryAll() throws Exception {
        List<User> users = new ArrayList<>();
        users.addAll(db1User.list());
        users.addAll(db2User.list());
        Result result = Result.ok(users);
        return result;
    }

    @GetMapping("/transfer")
    @GlobalTransactional
    public Object transfer() throws Exception {
        User user1 = db1User.getById(1);
        User user2 = db2User.getById(1);

        user1.setAmount(user1.getAmount() - 100);
        user2.setAmount(user2.getAmount() + 100);

        db1User.updateById(user1);
        int a = 10 / 0;
        db2User.updateById(user2);
        return Result.ok(queryAll());
    }
}

  • 断点验证AT模式:

在这里插入图片描述
db1库中的数据被减少100,undo_log 表中记录了数据:

在这里插入图片描述

在这里插入图片描述

放行断点后数据达到一致性:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 事务回滚时会读取数据库最新数据同undo_log表中数据对比,不相等则回滚失败(需要人工介入):

在这里插入图片描述

在这里插入图片描述

  • XA事务模式:

修改事务模式为XA模式(需要使用支持XA的驱动):

server:
  port: 9010
spring:
  application:
    name: svc-test-seata
  datasource:
    dynamic:
      primary: db1
      datasource:
        db1:
          url: jdbc:mysql://xxx:3306/db1?useSSL=false&characterEncoding=UTF-8
          username: xxx
          password: xxx
          type: com.alibaba.druid.pool.xa.DruidXADataSource    # 使用XA数据源
          driver-class-name: com.mysql.jdbc.Driver
          hikari:
            maximum-pool-size: 50
            minimum-idle: 20
        db2:
          url: jdbc:mysql://xxx:3306/db2?useSSL=false&characterEncoding=UTF-8
          username: xxx
          password: xxx
          type: com.alibaba.druid.pool.xa.DruidXADataSource    # 使用XA的数据源
          driver-class-name: com.mysql.jdbc.Driver
          hikari:
            maximum-pool-size: 50
            minimum-idle: 20
      seata: true
      seata-mode: xa    # 修改为XA模式

mybatis-plus:
  configuration:
    cache-enabled: false
    local-cache-scope: statement

logging:
  level:
    root: info
    com.test.seata.mapper: trace

seata:
  enable-auto-data-source-proxy: false
  application-id: ${spring.application.name}
  tx-service-group: my-test-tx-group
  service:
    vgroup-mapping:
      my-test-tx-group: default
    grouplist:
      default: xxx:8091
  config:
    type: file
  registry:
    type: file

在这里插入图片描述

t_user表加锁阻塞(XA事务中该记录已经处于锁定状态):
在这里插入图片描述

异常后事务回滚,数据处于一致性:
在这里插入图片描述
在这里插入图片描述

事务组 & TC高可用

在这里插入图片描述

  • 修改Seata conf/registry.conf 注册中心、配置中心,参照说明如下:
registry {
  # file, nacos, eureka, redis, zk, consul, etcd3, sofa
  type = "nacos"                  ---------------> Use Nacos as the registry center
  nacos {
    application = "seata-server"  ---------------> Specify the service name registered in Nacos registry center
    group = "SEATA_GROUP"         ---------------> Specify the group name registered in Nacos registry center
    serverAddr = "localhost"      ---------------> Nacos registry center IP:port
    namespace = ""                ---------------> Nacos namespace ID, "" represents the reserved public namespace in Nacos, users should not configure namespace = "public"
    cluster = "default"           ---------------> Specify the cluster name registered in Nacos registry center
  }
}
config {
  # file, nacos, apollo, zk, consul, etcd3
  type = "nacos"                  ------------> Use Nacos as the configuration center
  nacos {
    serverAddr = "localhost"      ---------------> Nacos registry center IP:port
    namespace = ""
    group = "SEATA_GROUP"         ---------------> Nacos configuration center group name
  }
}

在这里插入图片描述

上传成功后查看nacos 配置信息如下:
在这里插入图片描述

修改配置中心数据库连接配置:
在这里插入图片描述

config.txt 文件配置的事务组是default_tx_group,如果application.yaml 配置文件中的事务组不是default_tx_group则会导致失败:
在这里插入图片描述

CREATE TABLE IF NOT EXISTS `global_table`(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
    KEY `idx_transaction_id` (`transaction_id`)
);


CREATE TABLE IF NOT EXISTS `branch_table`(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
);

CREATE TABLE IF NOT EXISTS `lock_table`(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(128),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_status` (`status`),
    KEY `idx_branch_id` (`branch_id`),
    KEY `idx_xid` (`xid`)
);

CREATE TABLE IF NOT EXISTS `distributed_lock`(
    `lock_key`       CHAR(20) NOT NULL,
    `lock_value`     VARCHAR(20) NOT NULL,
    `expire`         BIGINT,
    primary key (`lock_key`)
);

  • 启动seata后可以看到数据库连接成功:

在这里插入图片描述
多个seata服务成功注册到nacos注册中心:
在这里插入图片描述

pom.xml 配置:


<dependencies>
	<dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	
	<dependency>
	    <groupId>com.baomidou</groupId>
	    <artifactId>mybatis-plus-boot-starter</artifactId>
	    <version>3.5.1</version>
	</dependency>
	
	
	<dependency>
	    <groupId>com.baomidou</groupId>
	    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
	    <version>3.5.1</version>
	</dependency>
	
	<dependency>
	    <groupId>mysql</groupId>
	    <artifactId>mysql-connector-java</artifactId>
	    <version>5.1.47</version>
	</dependency>
	
	<dependency>
	    <groupId>com.example</groupId>
	    <artifactId>alibaba-common</artifactId>
	</dependency>
	
	
	<dependency>
	    <groupId>io.seata</groupId>
	    <artifactId>seata-spring-boot-starter</artifactId>
	    <version>1.3.0</version>
	    <exclusions>
	        <exclusion>
	            <groupId>io.seata</groupId>
	            <artifactId>seata-all</artifactId>
	        </exclusion>
	    </exclusions>
	</dependency>
	
	
	<dependency>
	    <groupId>io.seata</groupId>
	    <artifactId>seata-all</artifactId>
	    <version>1.3.0</version>
	</dependency>
	
	<!-- 增加seata客户端为了读取nacos中配置信息,否则会启动失败 -->
	<dependency>
	    <groupId>com.alibaba.nacos</groupId>
	    <artifactId>nacos-client</artifactId>
	    <version>1.3.0</version>
	</dependency>
</dependencies>

application.yaml:

server:
  port: 9010
spring:
  application:
    name: svc-test-seata
  datasource:
    dynamic:
      primary: db1
      datasource:
        db1:
          url: jdbc:mysql://xxx:3306/db1?useSSL=false&characterEncoding=UTF-8
          username: xxx
          password: xxx
          type: com.alibaba.druid.pool.xa.DruidXADataSource
          driver-class-name: com.mysql.jdbc.Driver
          hikari:
            maximum-pool-size: 50
            minimum-idle: 20
        db2:
          url: jdbc:mysql://xxx:3306/db2?useSSL=false&characterEncoding=UTF-8
          username: xxx
          password: xxx
          type: com.alibaba.druid.pool.xa.DruidXADataSource
          driver-class-name: com.mysql.jdbc.Driver
          hikari:
            maximum-pool-size: 50
            minimum-idle: 20
      seata: true
      seata-mode: xa


mybatis-plus:
  configuration:
    cache-enabled: false
    local-cache-scope: statement

logging:
  level:
    root: info
    com.test.seata.mapper: trace

seata:
  enable-auto-data-source-proxy: false
  application-id: ${spring.application.name}
  tx-service-group: default_tx_group       # 同配置中心 service.vgroupMapping.default_tx_group 保持一致,否则会有错误信息
  service:
    vgroup-mapping:
      default_tx_group: default
  config:
    type: nacos
    nacos:
      group: SEATA_GROUP
      server-addr: xxx:8848
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: xxx:8848
      group: SEATA_GROUP

application.yaml 配置项 seata.service.vgroup-mapping 与配置中心 service.vgroupMapping.default_tx_group 不匹配错误信息如下:
在这里插入图片描述

  • 数据一致性验证:

在这里插入图片描述

在这里插入图片描述

进入业务断点后更新数据库数据发现被阻塞,事务回滚后数据处于一致性:
在这里插入图片描述

在这里插入图片描述

  • TC高可用验证
    停止业务日志回滚节点TC:
    在这里插入图片描述
    在这里插入图片描述
    再次访问 http://localhost:9010/user/transfer 发现由另一TC节点处理:
    在这里插入图片描述

微服务之间XID传递

为方便简化说明使用RestRemplate调用服务实现转账操作。

pom.xml:

<dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>

       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>mybatis-plus-boot-starter</artifactId>
           <version>3.5.1</version>
       </dependency>


       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
           <version>3.5.1</version>
       </dependency>

       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>5.1.47</version>
       </dependency>

       <dependency>
           <groupId>com.example</groupId>
           <artifactId>alibaba-common</artifactId>
       </dependency>


       <dependency>
           <groupId>io.seata</groupId>
           <artifactId>seata-spring-boot-starter</artifactId>
           <version>1.3.0</version>
           <exclusions>
               <exclusion>
                   <groupId>io.seata</groupId>
                   <artifactId>seata-all</artifactId>
               </exclusion>
           </exclusions>
       </dependency>


       <dependency>
           <groupId>io.seata</groupId>
           <artifactId>seata-all</artifactId>
           <version>1.3.0</version>
       </dependency>


       <!-- 增加seata客户端为了读取nacos中配置信息,否则会启动失败 -->
       <dependency>
           <groupId>com.alibaba.nacos</groupId>
           <artifactId>nacos-client</artifactId>
           <version>1.3.0</version>
       </dependency>

       <!-- 增加cloud alibaba seata 以传递 xid 到其它服务节点 -->
       <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
       </dependency>
   </dependencies>

application.yaml

server:
  port: 9010
spring:
  application:
    name: svc-test-seata
  datasource:
    dynamic:
      primary: db1
      datasource:
        db1:
          url: jdbc:mysql://xxx:3306/db1?useSSL=false&characterEncoding=UTF-8
          username: xxx
          password: xxx
          type: com.alibaba.druid.pool.xa.DruidXADataSource
          driver-class-name: com.mysql.jdbc.Driver
          hikari:
            maximum-pool-size: 50
            minimum-idle: 20
        db2:
          url: jdbc:mysql://xxx:3306/db2?useSSL=false&characterEncoding=UTF-8
          username: xxx
          password: xxx
          type: com.alibaba.druid.pool.xa.DruidXADataSource
          driver-class-name: com.mysql.jdbc.Driver
          hikari:
            maximum-pool-size: 50
            minimum-idle: 20
      seata: true
      seata-mode: xa

mybatis-plus:
  configuration:
    cache-enabled: false
    local-cache-scope: statement

logging:
  level:
    root: info
    com.test.seata.mapper: trace


seata:
  enable-auto-data-source-proxy: false
  application-id: ${spring.application.name}
  tx-service-group: default_tx_group
  service:
    vgroup-mapping:
      default_tx_group: default
  config:
    type: nacos
    nacos:
      group: SEATA_GROUP
      server-addr: xxx:8848
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: xxx:8848
      group: SEATA_GROUP

@SpringBootApplication(exclude = GlobalTransactionAutoConfiguration.class) 启动类排除GlobalTransactionAutoConfiguration 自动配置:

package com.test.seata;


import com.alibaba.cloud.seata.GlobalTransactionAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication(exclude = GlobalTransactionAutoConfiguration.class)
public class TestSeataApp {
    public static void main(String[] args) {
        SpringApplication.run(TestSeataApp.class,args);
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

参考文档如下(如果不排除则会导致启动异常):
在这里插入图片描述

UserController.java 模拟实现业务逻辑:

package com.test.seata.controller;



import com.alibaba.fastjson.JSON;
import com.example.alibaba.common.dto.Result;
import com.test.seata.domain.pojo.User;
import com.test.seata.service.DB1User;
import com.test.seata.service.DB2User;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private DB1User db1User;

    @Autowired
    private DB2User db2User;

    @Autowired
    private RestTemplate restTemplate;


    @GetMapping
    public Object queryAll() throws Exception {
        List<User> users = new ArrayList<>();
        users.addAll(db1User.list());
        users.addAll(db2User.list());
        Result result = Result.ok(users);
        return result;
    }

    @GetMapping("/transfer")
    @GlobalTransactional
    public Object transfer() throws Exception {
//        User user1 = db1User.getById(1);
//        User user2 = db2User.getById(1);
//
//        user1.setAmount(user1.getAmount() - 100);
//        user2.setAmount(user2.getAmount() + 100);
//
//        db1User.updateById(user1);
//        int a = 10 / 0;
//        db2User.updateById(user2);


        // 先给db2增加金额,再减去db1中的金额
        Result result = restTemplate.getForObject("http://localhost:9011/user/add-amount?dbName=db2&amount=100",Result.class);

        User user2 = null;
        if (result != null && result.getStatus() == 200) {
            user2 = JSON.parseObject(JSON.toJSONString(result.getData()),User.class);
        }
        int a = 10 / 0;
        this.transfer("db1",-100);

        return Result.ok(queryAll());
    }

    @GetMapping("/add-amount")
    public Object transfer(@RequestParam String dbName,@RequestParam Integer amount) throws Exception {
        if (!Arrays.asList("db1","db2").contains(dbName)) {
            return Result.error(null,String.format("非法操作数据库 %s",dbName));
        }

        User user = null;
        if (dbName.equals("db1")) {
            user = db1User.getById(1);
            user.setAmount(user.getAmount() + amount);
            db1User.updateById(user);
        } else if (dbName.equals("db2")) {
            user = db2User.getById(1);
            user.setAmount(user.getAmount() + amount);
            db2User.updateById(user);
        }
        return Result.ok(user);
    }
}

  • 一致性验证

异常回滚情况:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

正常提交:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • TM宕机后TC超时会回滚事务:

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值