开发知识

SpringCloud整合Seata实现分布式事务通过nacos实现注册和配置

来源: 实战案例锦集  日期:2023-07-26 17:43:32  点击:218  属于:开发知识

环境:springboot2.3.11.RELEASE + spring cloud Hoxton.SR8 + spring cloud alibaba 2.2.5.RELEASE + seata1.3.0

前提:安装并启动了nacos服务

Seata注册中心及配置中心说明

配置中心

什么是配置中心?配置中心可以说是一个"大衣柜",内部放置着各种配置文件,你可以通过自己所需进行获取配置加载到对应的客户端.比如Seata Client端(TM,RM),Seata Server(TC),会去读取全局事务开关,事务会话存储模式等信息.

Seata的配置中心与Spring cloud的配置中心区别是?在广义上来说,并无区别,只不过Spring cloud的配置中心仅是作用于它们自身的组件,而Seata的配置中心也是一样是作用于Seata自身.(注:Spring cloud的配置中心与Seata无关)

注册中心

什么是注册中心?注册中心可以说是微服务架构中的”通讯录“,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址,进行调用.比如Seata Client端(TM,RM),发现Seata Server(TC)集群的地址,彼此通信.

Seata的注册中心与Dubbo,Spring cloud的注册中心区别是?在广义上来说,并无区别,只不过Dubbo与Spring cloud的注册中心仅是作用于它们自身的组件,而Seata的注册中心也是一样是作用于Seata自身.(注:Dubbo与Spring cloud的注册中心与Seata无关)

Seata服务配置

1、在%SEATA_HOME%目录下新建config.txt文件

service.vgroupMapping.dt-group=default
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&serverTimezone=GMT%2B8
store.db.user=root
store.db.password=xxxxxx
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

2、执行脚本

通过如下地址下载脚本执行

图片

在windows系统下可以通过 git bash 来执行shell脚本

sh nacos-config.sh -h nacos的ip -p nacos端口 -g nacos配置文件的组 -t 你的namespace号(若是public可省略此选项) -u nacos用户名 -w nacos密码

3、数据库初始化脚本

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

完成以上步骤后查看nacos配置

接下来进行项目开发及配置接下来进行项目开发及配置

项目结构

图片图片

两个子模块:

dt-account-service 用户模块
dt-storage-service 库存模块

父工程依赖

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
      <exclusion>
        <groupId>io.seata</groupId>
        <artifactId>seata-all</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
  </dependency>
  <dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>${seata.version}</version>
  </dependency>
</dependencies>
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-alibaba-dependencies</artifactId>
      <version>${spring-cloud-alibaba.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>${spring-cloud.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

account帐号模块

核心配置文件

spring:
  cloud:
    nacos:
      password: nacos
      username: nacos
      discovery:
        server-addr: 118.24.111.33:8848
        namespace: ""
        group: dt-group
---
seata:
  tx-service-group: dt-group
  registry:
    type: nacos
    nacos:
      application: seata-server #这里要与seata中的registry.conf配置的application一致
      group: dt-group
      namespace: ""
      server-addr: 118.24.111.33:8848
      username: nacos
      password: nacos
  config:
    type: nacos
    nacos:
      namespace: ""
      group: dt-group
      server-addr: 118.24.111.33:8848
      username: nacos
      password: nacos

AccountService服务类

@Service
public class AccountService {


  private static final String ERROR_USER_ID = "1002";
  @Resource
  private AccountMapper accountMapper ;
  @Resource
  private StorageFeignClient storageFeignClient;


  @Transactional(rollbackFor = Exception.class)
  @GlobalTransactional
  public void debit(String userId, BigDecimal num, String commodityCode, int orderCount) {
    System.out.println(RootContext.getXID()) ;
    accountMapper.updateAccount(num, userId) ;
    storageFeignClient.deduct(commodityCode, orderCount);
    try {
      TimeUnit.MILLISECONDS.sleep(new Random().nextInt(100)) ;
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
        
    if (ERROR_USER_ID.equals(userId)) {
      throw new RuntimeException("account branch exception");
    }
  }
    
}

这里模拟了抛出异常当输入的userId为1002时抛出异常,就为了测试所有服务的事务是否回滚了。注意这里需要添加@GlobalTransactional注解

Feign接口

@FeignClient(name = "storage-service", url = "127.0.0.1:8802")
public interface StorageFeignClient {


  @GetMapping("/storage/deduct")
  void deduct(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count);


}

storage库存子模块

该子模块的配置与account模块的配置基本一致

StorageService

@Service
public class StorageService {
  @Resource
  private StorageMapper storageMapper ;


  @Transactional
  public void deduct(String commodityCode, int count) {
    System.out.println(RootContext.getXID()) ;
    storageMapper.updateStorage(count, commodityCode) ;
  }
}

测试

启动seata,nacos,account,storage服务后查看nacos

图片图片


seata服务也已经注册上来了。

数据库初始化数据

图片图片


图片图片

正常请求

图片图片

数据变化

图片图片


当传入userId=1002时

图片图片


account模块控制台

图片图片


storage模块控制台

图片图片


数据库数据

图片图片


数据没有任何变化说明回滚了