SpringBoot Seata 分布式事务

205 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

SpringBoot Seata 分布式事务

SpringBoot + Nacos + Dubbo + Seata

1 环境准备

1 Nacos 服务搭建

以本地 windows 环境为例

nacos GitHub下载安装服务:github.com/alibaba/nac…

我用的是 2.0.3 版本

解压后修改 conf 目录下的配置文件,主要修改 application.properties 文件中数据库相关配置

对应 SQL 在 nacos-mysql.sql

clipboard.png

bin 目录下找到 startup.cmd 脚本启动,访问 localhost:8848/nacos ,默认 nacos/nacos 登录

clipboard.png

2 Seata 服务搭建

同样 github 上安装服务 github.com/seata/seata…

我用的是 1.4.1 版本

同样是修改 conf 目录下的配置文件,主要 registry.conf 和 file.conf

目前都采用文件配置模式,未来可以把配置放到 nacos里

registry.conf

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "file"
  loadBalance = "RandomLoadBalance"
  loadBalanceVirtualNodes = 10

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = ""
    password = ""
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

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

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = ""
    password = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    appId = "seata-server"
    apolloMeta = "http://192.168.1.204:8801"
    namespace = "application"
    apolloAccesskeySecret = ""
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

持久化存储方式放到 db 数据库里,这里注意 service 块,里面的 vgroup_mapping 极为重要

file.conf

service {
  #transaction service group mapping
  vgroup_mapping.my_test_tx_group = "default"

  #only support when registry.type=file, please don't set multiple addresses
  default.grouplist = "127.0.0.1:8091"
  #disable seata
  disableGlobalTransaction = false
}
## transaction log store, only used in seata-server
store {
  ## store mode: file、db、redis
  mode = "db"

  ## file store property
  file {
    ## store location dir
    dir = "sessionStore"
    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    maxBranchSessionSize = 16384
    # globe session size , if exceeded throws exceptions
    maxGlobalSessionSize = 512
    # file buffer size , if exceeded allocate new buffer
    fileWriteBufferCacheSize = 16384
    # when recover batch read size
    sessionReloadReadSize = 100
    # async, sync
    flushDiskMode = async
  }

  ## 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.cj.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata?serverTimezone=GMT%2B8"
    user = "root"
    password = "root"
    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }

  ## redis store property
  redis {
    host = "127.0.0.1"
    port = "6379"
    password = ""
    database = "0"
    minConn = 1
    maxConn = 10
    maxTotal = 100
    queryLimit = 100
  }

}

bin 目录下seata-server.bat 启动

3 SpringBoot + Mybatis-plus 服务搭建

后端服务采用 SpringBoot+ Mybatis-plus 框架实现

整体模块

clipboard.png

其中 account,order,storage 分别对应账户,订单,库存模块,api 提供 rpc 的接口,web 提供对外 web 服务。

各个模块可以单独提供对外 web 服务,我这边为了清晰,仅让 web 模块提供对外服务

2 整合 dubbo ,以 nacos 作为注册中心

dubbo 最初是 阿里 维护的,后来卖给了 apache,我这边用的也是 apache 对应的 dubbo 包

<dependencies>
    <!-- Dubbo Nacos registry dependency -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-registry-nacos</artifactId>
        <version>${dubbo.version}</version>
    </dependency>
    <!-- Keep latest Nacos client version -->
    <dependency>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-client</artifactId>
        <version>1.1.4</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba.spring</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>1.0.8</version>
    </dependency>
    <!-- dubbo依赖 -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo</artifactId>
        <version>${dubbo.version}</version>
    </dependency>

    <!-- dubbo-start依赖 -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>${dubbo.version}</version>
    </dependency>

    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>4.2.0</version>
    </dependency>
</dependencies>

各个模块同时作为消费者和提供者,注册到 nacos 上

以 account 模块为例,其余模块同理

application.yml

server:
  port: 8090
  tomcat:
    uri-encoding: UTF-8
  servlet:
    context-path: /account
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/seata-demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&allowMultiQueries=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  mapper-locations: classpath:sqlmap/auto/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

dubbo:
  application:
    name: account
  consumer:
    check: false
  registry:
    check: false
    protocol: nacos
    address: nacos://127.0.0.1:8848?namespace=f3972b84-6d85-4b31-b0d3-88e8079cc23a
  protocol:
    name: dubbo
    port: 18080

主要关注 dubbo.registry.address ,nacos 指定协议, namespace 指定命名空间

3 整合 seata

直接引入 seata 对应包

<dependency> <!-- 主要想使用 seata 1.1.0 版本 -->
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.1.0</version>
</dependency>

application.yml 添加配置

seata:
  enabled: true
  application-id: account  #可自定义
  tx-service-group: my_test_tx_group #事务组名可自定义,但必须与配置中心参数一致
  enable-auto-data-source-proxy: true #数据源自动代理
  service:
    grouplist:
      default: 127.0.0.1:8091
    vgroup-mapping:
      my_test_tx_group: default
    enable-degrade: false
    disable-global-transaction: false

使用也很简单,只需要在业务层方法上添加 @GlobalTransactional 注解

@GlobalTransactional(name = "my_test_tx_group")
public void purchase(String userId, String commodityCode, int orderCount) {
    storageService.deduct(commodityCode, orderCount);
    orderService.create(userId, commodityCode, orderCount);
}

4 整体测试

初始化数据库

设置一个 code 为 code 的商品,库存为 10 份

INSERT INTO `seata-demo`.`storage_tbl`(`id`, `commodity_code`, `count`) VALUES (1, 'code', 10);

设置 userId 为 1 的用户的账号,金额为 1000

INSERT INTO `seata-demo`.`account_tbl`(`id`, `user_id`, `money`) VALUES (1, '1', 1000);

用户 id 为 1 的用户,消费商品编码为 code 的商品 1 份,可以正常消费

http://localhost:8094/seata/business/v1/purchase?userId=1&commodityCode=code&orderCount=1

clipboard.png

用户 id 改为 2 ,返回异常

http://localhost:8094/seata/business/v1/purchase?userId=2&commodityCode=code&orderCount=1

clipboard.png

clipboard.png

查询数据库,商品库存理应 -1 的,也进行了回滚

clipboard.png

最后附上源码地址 seata-demo