seata分布式事务

注意: nacos版本与seata版本一定要匹配,否则会有很多莫名其妙的错误
docker-compose up -d
docker-compose down
本文选择的版本 nacos2.0.3、seata选择的版本 在docker-compose.yaml已经写明

1、在home目录下新建 seata目录

编写 docker-compose.yaml

version: "3.1"
services:
  seata-server:
    image: seataio/seata-server:1.4.2
    hostname: seata-server
    ports:
      - "8091:8091"
    environment:
      - SEATA_PORT=8091
      - SEATA_CONFIG_NAME=file:/root/seata-config/registry
      - SEATA_IP=192.168.1.8
    volumes:
    # 需要把file.conf和registry.conf都放到./seata-server/config文件夹中
      - "/home//seata/config:/root/seata-config"

2、在seata目录新建 config目录
新建2个配置
file.conf、registry.conf

file.conf配置如下:
store {
## store mode: file、db、redis
mode = "db"
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://192.168.1.8:3306/seata?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"
user = "root"
password = "wangjing"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
registry.conf
registry {
  type = "nacos"
  nacos {
  # seata服务注册在nacos上的别名,客户端通过该别名调用服务
    application = "seata-server"
  # 请根据实际生产环境配置nacos服务的ip和端口
    serverAddr = "192.168.1.8:8848"
  # nacos上指定的namespace
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
}

config {
  type = "file"
  file{
      name = "file:/home/seata/config/file.conf"
    }
}

3、数据库脚本

CREATE TABLE `branch_table` (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `resource_group_id` varchar(32) DEFAULT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `branch_type` varchar(8) DEFAULT NULL,
  `status` tinyint(4) DEFAULT NULL,
  `client_id` varchar(64) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime(6) DEFAULT NULL,
  `gmt_modified` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `distributed_lock` (
  `lock_key` char(20) NOT NULL,
  `lock_value` varchar(20) NOT NULL,
  `expire` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`lock_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `global_table` (
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) DEFAULT NULL,
  `transaction_service_group` varchar(32) DEFAULT NULL,
  `transaction_name` varchar(128) DEFAULT NULL,
  `timeout` int(11) DEFAULT NULL,
  `begin_time` bigint(20) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`xid`),
  KEY `idx_status_gmt_modified` (`status`,`gmt_modified`),
  KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(128) DEFAULT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `branch_id` bigint(20) NOT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `table_name` varchar(32) DEFAULT NULL,
  `pk` varchar(36) DEFAULT NULL,
  `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`row_key`),
  KEY `idx_status` (`status`),
  KEY `idx_branch_id` (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意:每个参与分布式事务的数据库 都需要加入 undo_log表

CREATE TABLE `undo_log` (
  `branch_id` bigint(20) 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`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='AT transaction mode undo table';

服务端的seata-server到此结束,此时在nacos中,应该就能查看到seata-server已经注册到nacos里面了

4、接下来 就是在项目中如何集成
首先肯定需要引入相关包。 需要进行分布式事务控制的项目 都需要引用

<!-- Seata -->
       <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>${seata.version}</version> <!-- 该版本在主项目中定义的为1.4.2 -->
        </dependency>

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>${seata.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <version>2021.0.1.0</version> <!-- 注意服务端用的1.4.2 然后2021.0.1.0是能匹配到1.4.2版本的 -->
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

5、接下来 就是yml的配置了

seata:
  enabled: true
  application-id: ${spring.application.name}
  tx-service-group: sas_group  #事务组名称,需要进行同一组事务控制,就需要一样的配置,如果A项目用了 a_group  B项目需要加入到事务中,也用a_group
  service:
    vgroup-mapping:
      sas_group: default
    enable-degrade: false # 是否启用降级
    disable-global-transaction: false # 是否禁用全局事务
  config:
    type: file # 配置中心为file模式
  registry:
    type: nacos
    nacos:
      server-addr: 192.168.1.8:8848
      application: seata-server
      username: nacos
      password: nacos
      namespace:
      group: DEFAULT_GROUP  # 此处需要看你的nacos注册在那个分组,此处我踩了坑,默认的分组也需要写,我没写。
  enable-auto-data-source-proxy: true
  use-jdk-proxy: false

6、启动类 注解

一个是远程调用,一个是事务控制,已添加的忽视
@EnableDiscoveryClient
@EnableTransactionManagement

7、代码块使用
@GlobalTransactional 即为开启分布式事务。
在此可以打印出 事务id ,在调用oaApiClient的时候 也同样打印,如果发现xid为null,表示开启事务是不成功的。如果xid有值,表示开启分布式事务id是成功了。但是在oaApiClient里面xid与开启的xid不一致 表示oaApiClient里面的事务是没有加入到分布式事务中,因此就需要看一下配置了

@Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public void test1(String type) throws BizException{
        System.out.println("事务id---------------------->" + RootContext.getXID());
        JSONObject resModel = oaApiClient.saveMessage();
        log.info(resModel.toString());
        if("0".equals(type)){
            // 远程调用成功 也回滚
            throw new BizException(ResponseErrorEnum.OPERATION_FAILED);
        }
        LedgerResult ledgerResult = new LedgerResult();
        ledgerResult.setDetails("测试");
        ledgerResultService.save(ledgerResult);
        // 等于1则 两边执行完了 再回滚
        if("1".equals(type)){
            throw new BizException(ResponseErrorEnum.OPERATION_FAILED);
        }
    }

oaApiClient.saveMessage的代码片段,注意 也要开启自身事务@Transactional

@Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveMessage() throws Exception {
        System.out.println("事务id---------------------->" + RootContext.getXID());
        MessageInfo m = new MessageInfo();
        m.setTitle("分布式测试1");
        m.setContext("分布式测试1");
        m.setSendUid(1L);
        m.setType(15);
         messageInfoService.saveMessageInfoTest(m);
         return true;
    }

到此seata的部署,并且与spring cloud的集成已经完成。
至于理论知识,可以看官网。以及不同类型事务模式