本文主要选择nacos作为注册中心使用,下面记录一下操作的步骤。主要是网上有一些坑,并且网上有些文章不合理

1.下载seata

我下载的seata的版本是1.4.2

下载地址:https://github.com/seata/seata/releases

下载完成之后解压就行(我下载的是压缩包)

2.seata配置

(1).配置config/registry.conf

registry.config文件主要是seata注册中心地址的配置,里面包括了各种注册中心的配置样例,我们这里选择了nacos为注册中心,所以主需要保留nacos的配置,其他的都删掉。我配置的文件如下:

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    application = "seata-server" #seata注册到nacos上面的应用名
    serverAddr = "127.0.0.1:8848" #注册中心的地址
    group = "SEATA_GROUP" #分组
    namespace = "abc34810-6e99-41dd-a5c3-c9f09fcced39" #命名空间,强烈建议新建一个namespage单独用于seata,填写命名空间id
    cluster = "default" #集群名
    username = "" #nacos的用户名
    password = "" #nacos密码
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = "abc34810-6e99-41dd-a5c3-c9f09fcced39"
    group = "SEATA_GROUP"
    username = ""
    password = ""
    dataId = "seataServer.properties"
  }
}

上面主要是注册中心的配置,这一提醒一点,最好是新建一个单独的namespace专门用于做seata分布式事务使用,不然后面配置多了很难查找,并且很难维护!

seata配合postgresql seata配置nacos_bc

(2).配置file.config

file.config主要用于配置存储事务日记,可以是file,redis,db,我选择的是db

## transaction log store, only used in seata-server
store {
  ## store mode: file、db、redis
  mode = "db"
  ## rsa decryption public key
  ##publicKey = ""
  ## file store property

  ## 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"
    ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "root"
    password = "123456"
    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }
}

上面是我的配置,大家可以按照自己的需求配置

(3).运行脚本

新版本的seata里面没有nacos-conf.sh和config.txt两个文件,可以去github上面下载,下载地址如下

nacos-config.sh:https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh
config.txt:https://github.com/seata/seata/blob/develop/script/config-center/config.txt

运行nacos-conf.sh,其实就是将config.txt的配置信息上传到nacos上面,后面seata直接从nacos上面去数据了。如果你是windows操作系统,直接运行不了nacos-conf.sh,可以通过git-bash运行

运行命令之前可以修改conf.txt的内容,也可以上传到nacos后,在nacos的管理台修改

修改的内容如下:

#1.我这里配置了我的db的连接信息
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
#2.修改分布式事务的分组
service.vgroupMapping.my_test_tx_group=default
#我增加了下面两个
service.vgroupMapping.device=default #这里是固定写法将device修改成你的应用里面的application.yml里面的seata.tx-service-group名称
service.vgroupMapping.task=default #

例如我device微服务的配置

seata配合postgresql seata配置nacos_spring_02

运行命令

sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -u nacos -w nacos -t abc34810-6e99-41dd-a5c3-c9f09fcced39

#-h nacos的地址
#-p nacos服务的端口
#-g group
#-u nacos用户名
#-w nacos密码
#-t 租户,这里就是上面创建的namespace id,这里非常重要,一定要小心,不然后面启动应用会一直报警告信息

运行完成之后:

seata配合postgresql seata配置nacos_分布式_03

这里显示失败了几个,没关系。上传到nacos后的样子

seata配合postgresql seata配置nacos_spring_04

(4)创建数据库和表

在自己微服务数据库里面创建undo_log表,sql在源码里面有

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=5 DEFAULT CHARSET=utf8

在配置的seata库里面创建下面三张表

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
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_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store BranchSession data
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`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(96),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

到这里SEATA的配置基本上配置完了,我们直接运行SEATA,直接运行点击bin/seata-server.bat,若是linux版本,运行sh脚本即可。到这里SEATA服务的配置基本上结束了,我们下面配置自己的微服务应用了

(5)配置微服务

我的微服务是device和task,我的springcloud的版本和springcloudalibaba的版本如下

下面是dependencyManagement的配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.6.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>Hoxton.SR3</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>


<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.3.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>


<spring-cloud-starter-seata.version>2.2.0.RELEASE</spring-cloud-starter-seata.version>
<seata.springboot.starter.version>1.4.2</seata.springboot.starter.version>
<seata-all.version>1.4.2</seata-all.version>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>${spring-cloud-starter-seata.version}</version>
</dependency>

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>${seata-all.version}</version>
</dependency>

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>${seata.springboot.starter.version}</version>
</dependency>

下面是我每个微服务的pom关于seata的配置

<!--seata-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <!-- 排除依赖 指定版本和服务器端一致 -->
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </exclusion>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>

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

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
</dependency>

这里我期望在yml中配置seata的配置,可以动态的适配环境的变化(测试环境、预发布环境、生产环境),网上很多帖子描述将SEATA文件夹里面的registry.conf复制到微服务的resources文件夹下面,我觉得不合理,这样每次不同的环境都需要修改这个文件

配置依赖之后,就配置bootstrap.yml文件了,可以根据不同的环境配置不同的bootstrap.yml

seata:
  application-id: device
  enabled: true
  tx-service-group: device #和上面的nacos的上面的配置一致
  enable-auto-data-source-proxy: true
  config:
    type: nacos
    nacos:
      namespace: abc34810-6e99-41dd-a5c3-c9f09fcced39 #配置上面的namespace
      serverAddr: http://10.10.29.155:8848 #nacos地址
      group: SEATA_GROUP
      userName: nacos
      password: nacos
  registry:
    type: nacos
    nacos:
      application: seata-server
      serverAddr: http://10.10.29.155:8848
      group: SEATA_GROUP
      namespace: abc34810-6e99-41dd-a5c3-c9f09fcced39
      userName: nacos
      password: nacos
      cluster: default

在其他的微服务里面类似配置

(6)配置动态数据源代理

SEATA需要创建动态数据源代理,用于创建分支事务id,我的配置如下

@Slf4j
@Configuration
public class DbConfig {


    @ConfigurationProperties(prefix = "spring.datasource.druid")
    @Bean
    public DataSource druidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        log.info("create druid dataSource ......");
        return dataSource;
    }

    @Bean
    @Primary
    public DataSource dataSource (){
        log.info("create seata dataSourceProxy");
        return new DataSourceProxy(druidDataSource());
    }

}

最后还需要springboot不能自动装配数据源,所以我在启动类那里exclude掉了,如下:

/**
 * @Author tyler.hong
 * @Date 2021/7/29 14:45
 * @Version 1.0
 * @Description
 */
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})//这里排除掉
@MapperScan({"com.xxx.xxx.device.mapper*"})
@EnableTransactionManagement
@EnableFeignClients(basePackages = {"com.xxx.xxx.api.task"})
public class DeviceApp {

    public static void main(String[] args) {
        SpringApplication.run(DeviceApp.class,args);
    }
}

好了到这里基本上所有的配置就结束了,下面就是测试代码了,很简单,我用的是AT模式

@GetMapping("/testTransaction")
//@GlobalTransactional 加上这个注解就行了
public R testTransaction(){
    Test test = new Test();
    test.setAge(300)
        .setId(333)
        .setName("tyler2333");
    testService.insert(test);

    //模拟一场
    int a = 1/0;
    testFeignService.testTransaction();
    return success();
}