基于SpringBoot,Nacos和Seata1.4.2实现分布式事务的示例

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:新手向,有异议请不吝赐教:

搭建项目时遇到了一些问题,在网上找到了不少关于seata的配置文章,有的博主用老版本的思路在发文章,结果做了很多无用功,踩了不少坑总算是实现了seata的分布式事务,在此将这个试错demo项目分享出来,供各位摸索的同学参考。


以下不罗嗦,直接从nacos,seata搭建开始

一、Nacos环境

从官网下载的nacos,配置standlone模式运行,完毕后环境如下:

访问地址:http://192.168.5.210:8848/nacos
用户名:nacos
密码:nacos

二、Seata环境

1.下载Seata

来到阿里巴巴Github站,找到“版本说明”页:

本次使用的SpringBoot版本是2.6.8,SpringCloud版本是2021.0.1.0,所以选定的Seata版本是1.4.2,不要擅自尝试修改其他版本,太浪费时间了。

Seata下载页,找到1.4.2版本进行下载,这里任意选择合适的版本下载,我的运行环境在linux选择tar.gz。

2.配置Seata

核心配置文件是file.conf和registry.conf

由于是1.4.2,不需要执行所谓的sh脚本,只需要贴入官方的配置到nacos即可,这里的弯路着重强调,没有必要走。

file.conf代码如下:

store {
  mode = "db"
  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://192.168.5.210:3306/seata?rewriteBatchedStatements=true"
    user = "xxx"
    password = "xxx"
    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }

registry.conf代码如下

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

  nacos {
    application = "seata-server"
    serverAddr = "192.168.5.210:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
}

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

  nacos {
    serverAddr = "192.168.5.210:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
  }
}

请务必留意上文配置中的dataId = seataServer.properties

这个dataId需要配到nacos中,下面我们开始配置这个

由于namespace未指定特殊值,留空表示使用public

我们在nacos配置列表项的public命名空间中,新增配置文件"seataServer.properties",配置类型选择为"Properties",新建时确保名称如下:

Data ID: seataServer.properties
Group: SEATA_GROUP

然后在编辑区贴入示例内容页中的内容。

贴入的内容有部分关键地方需要修改,请留意:

...省略配置...

#修改数据驱动
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://数据库服务ip:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=xxx
store.db.password=xxx

...省略配置...

# 自定义命名vgroupMapping   default-tx-group java项目要用,切记
service.vgroupMapping.default-tx-group=default
service.default.grouplist=seta服务ip:8091

...省略配置...

到这里为止seta服务的配置OK了,接下来需要注意seata数据库的表创建。

seata数据库表分为2个部分,一个部分是上文中配置的数据库持有,另一个部分是使用seata的每一个数据库必须持有的表。

我们先确认第一部分,建表语句如下:

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;
 
-- 分支表
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;
 
-- 锁定表
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),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
    ) ENGINE = INNODB
    DEFAULT CHARSET = utf8;
 
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`)
    ) ENGINE = INNODB
    DEFAULT CHARSET = utf8mb4;
 
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);

第二部分,用到seata的数据库中,需要创建表,建表语句如下:

--日志文件表--
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`)
    ) ENGINE = INNODB
    AUTO_INCREMENT = 1
    DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

至此关于seata的配置告一段落,接下来我们开始配置示例项目。

三、基于SpringBoot项目使用Seata

很多同学在这里面遇到了麻烦,要不就是找不到服务,要不就是无法启动。

核心部分就是application.yml文件,java代码暂且不表,相信你们肯定不会有问题。

spring:
  application:
    name: seataServer
  redis:
    host: 192.168.5.210
    port: 6379
    password: xxx
  datasource:
    dynamic:
      druid:
        initial-size: 5
        min-idle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall,slf4j
#        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      datasource:
          # 主库数据源
          master:
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://192.168.5.210:3306/cloud-xxx?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
            username: xxx
            password: xxx          # seata_order数据源
          order:
            username: xxx
            password: xxx
            url: jdbc:mysql://192.168.5.210:3306/seata_order?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
            driver-class-name: com.mysql.cj.jdbc.Driver
          # seata_account数据源
          account:
            username: xxx
            password: xxx
            url: jdbc:mysql://192.168.5.210:3306/seata_account?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
            driver-class-name: com.mysql.cj.jdbc.Driver
          # seata_product数据源
          product:
            username: xxx
            password: xxx
            url: jdbc:mysql://192.168.5.210:3306/seata_product?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
            driver-class-name: com.mysql.cj.jdbc.Driver
      seata: true
  cloud:
    nacos:
      server-addr: 192.168.5.210
      username: nacos
      password: nacos
server:
  port: 8081
# seata配置
seata:
  enabled: true
  # Seata 应用编号,默认为 ${spring.application.name}
  application-id: ${spring.application.name}
  # Seata 事务组编号,用于 TC 集群名
  tx-service-group: default-tx-group
  # 关闭自动代理
  enable-auto-data-source-proxy: false
  # 服务配置项
  service:
    # 虚拟组和分组的映射
    vgroup-mapping:
      default-tx-group: default
    vgroupMapping:
      default-tx-group: default
    # 分组和 Seata 服务的映射
    grouplist:
      default: 192.168.5.210:8091
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 192.168.5.210:8848
      username: nacos
      password: nacos
      cluster: default
      group: SEATA_GROUP
  config:
    type: nacos
    nacos:
      data-id: seataServer.properties
      server-addr: 192.168.5.210:8848
      group: SEATA_GROUP
      username: nacos
      password: nacos
# mybatis配置
mybatis:
    # 搜索指定包别名
    typeAliasesPackage: com.xxx.seata
    # 配置mapper的扫描,找到所有的mapper.xml映射文件
    mapperLocations: classpath:mapper/**/*.xml
  

下面开始说核心配置:

  1. druid数据源,由于本示例项目在1个项目中模拟多个数据源,所以这里配置了动态数据源,如果没有这个需求可以无视本配置
  2. tx-service-group: default-tx-group,这个很关键,算是核心入口了,这个default-tx-group在nacos配置文件中指定过,所以此处需要指定这个组,包括下面的seata.vgroup-mapping也是如此,不能错不能少。
  3. seata.registry配置,这个说的是项目在nacos中,怎么找seata服务器,所以这里的application一定要亲眼在nacos的“服务管理”->“服务列表”中找到启动中的seata服务,名字要一样,比如我这里叫做seata-server,在nacos服务列表中至少要有1个示例,不然会出现找不到服务。
  4. seata.config配置,这里说的是seata客户端要读取的配置地址,所以指定的data-id必须要在nacos中的“配置管理”->“配置列表”中找到,data-id要一模一样,包括命名空间和组名,不能乱不能少。

此外,不少同学在配置完成后,会出现Type id handling not implemented for type java.lang.Object这样的报错,这个是Seata1.4版本自身的问题,据说1.5版会修复。解决方法如下:

  1. 在nacos中修改seata的序列化方式,修改properties内容中client.undo.logSerialization=jackson设置为kryo,同时需要配置项目依赖
    <dependency>
        <groupId>com.esotericsoftware.kryo</groupId>
        <artifactId>kryo</artifactId>
        <version>2.24.0</version>
    </dependency>
    <dependency>
        <groupId>com.esotericsoftware</groupId>
        <artifactId>kryo</artifactId>
        <version>4.0.2</version>
    </dependency>
    <dependency>
        <groupId>de.javakaffee</groupId>
        <artifactId>kryo-serializers</artifactId>
        <version>0.42</version>
    </dependency>

  2. 参考https://github.com/seata/seata/pull/3228/files 或者看这个pr的做法,通过spi,自定义jackson序列化器
  3. 数据库日期字段从datetime修改成timestamp,不过可能还需要考虑2038年问题


总结

至此踩坑的干货已经分享完毕,各位还有不同的意见欢迎留言交流。

示例代码下载地址

基于SpringBoot的seata1.4.2项目,Nacos分布式事务实现-Java文档类资源-CSDN下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值