Spring Cloud Nacos整合 Seata 实现分布式事务

1、Seata 介绍

        Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

2、软件版本

软件

版本

地址

JDK

1.8.0_271

Java Downloads | Oracle

Spring Boot

2.5.6

Spring Boot

Spring Cloud

2020.0.4

Spring Cloud

Nacos

2.0.3

Releases · alibaba/nacos · GitHub

Seata

1.4.2

https://github.com/seata/seata/releases

3、环境搭建

3.1、安装Nacos

3.1.1、下载nacos

        下载nacos-2.0.3.zip

        地址:Releases · alibaba/nacos · GitHub        

3.1.2、启动nacos服务

        解压nacos-2.0.3.zip,进入nacos安装目录bin目录下,执行startup.sh/startup.cmd脚本。

        CentOS 或 Mac 启动Nacos

        sh startup.sh -m standalone

        windows 启动Nacos

        startup.cmd -m standalone 

        浏览器输入:http://127.0.0.1:8848/nacos/index.html 进行访问,账号/密码:nacos/nacos 进行登录。

3.2、安装Seata

3.2.1、下载seata-server

        下载seata-server-1.4.2.zip

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

3.2.2、安装seata-server

        解压seata-server-1.4.2.zip

3.2.3、 seata数据库配置

        创建seata数据库,并创建seata服务需要的表:

        新建seata数据库,将 seata 服务端需要的表初始化到数据库中

        seata-server 需要的数据库脚本:

        https://github.com/seata/seata/tree/v1.4.2/script/server/db

        seata-client 需要的数据库脚本:

        https://github.com/seata/seata/tree/v1.4.2/script/client/at/db

        Seata AT 模式,客户端只需要 undo_log表,下面要新建业务表t_account、t_order、t_storage 三张业务表,如果三张表放到一个数据库里面,只需要新建一个 undo_log 表,如果将三张表拆分到三个数据库里面,则每个数据库都需要创建 undo_log 表,脚本文件地址

3.2.4、配置seata-server

        下载seata-server需要上传nacos相关脚本,脚本地址

        https://github.com/seata/seata/tree/develop/script/config-center

        其中config.txt 是上传nacos配置的内容,nacos目录下的nacos-config.sh是将config.txt推送到nacos的脚本。

        把confi.txt 复制到seata根目录,nacos里面的脚本复制到seata/conf目录,如下图所示:

        修改seata-server-1.4.2/config.txt,目前需要推送到nacos的只有下面这些,其他的可以暂时忽略。需要配置的内容如下:

service.vgroupMapping.my_test_tx_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&rewriteBatchedStatements=true&serverTimezone=Asia/Shanghai
store.db.user=root
store.db.password=root
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

其中 seata 是seata 服务端使用的数据库

使用conf目录下的nacos-config.sh,将txt文件里面的内容推送到nacos上

sh nacos-config.sh -h localhost -p 8848 -u nacos -w nacos

备注:windows平台可以使用Git 运行nacos-config.sh脚本

nacos-config.sh命令的参数如下:

sh nacos-config.sh -h localhost -p 8848 -u username -w password

Parameter Description:

-h: host, the default value is localhost.

-p: port, the default value is 8848.

-g: Configure grouping, the default value is 'SEATA_GROUP'.

-t: Tenant information, corresponding to the namespace ID field of Nacos, the default value is ''.

-u: username, nacos 1.2.0+ on permission control, the default value is ''.

-w: password, nacos 1.2.0+ on permission control, the default value is ''.

执行成功后,打开nacos控制台

seata-server-1.4.2/conf目录里面,file.conf和registry.conf,由于使用的是nacos注册中心,file.conf里面相关的配置已经推送到了nacos,所以只需要修改registry.conf

registry.conf

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"
  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = ""
    password = ""
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"
  nacos {
    serverAddr = "127.0.0.1"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
  }
}

3.2.5、启动Seata服务

        执行 seata-server-1.4.2\bin 目录下的 seata-server.bat/seata-server.sh 启动seata服务

        打开nacos控制台,此时seata已经成功注册到nacos注册中心

4、项目架构

4.1、项目说明

用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:

  • 采购服务(business-service):购买商品的业务逻辑,也是事务的发起者。
  • 仓储服务(storage-service):对给定的商品扣除仓储数量。
  • 订单服务(order-service):根据采购需求创建订单。
  • 帐户服务(account-service):从用户帐户中扣除余额。

4.2、项目架构

5、项目搭建

5.1、项目结构

5.2、表结构初始化

初始化订单、库存、账户三张表

-- ----------------------------
-- Table structure for t_account
-- ----------------------------
DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL DEFAULT 0,
  `money` bigint(20) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4;

-- ----------------------------
-- Records of t_account
-- ----------------------------
INSERT INTO `t_account` VALUES (1, 10001, 10000);

-- ----------------------------
-- Table structure for t_order
-- ----------------------------
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL DEFAULT 0,
  `commodity_code` varchar(20) CHARACTER SET utf8mb4 NOT NULL DEFAULT '',
  `count` int(10) NOT NULL DEFAULT 0,
  `money` bigint(20) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4;

-- ----------------------------
-- Records of t_order
-- ----------------------------

-- ----------------------------
-- Table structure for t_storage
-- ----------------------------
DROP TABLE IF EXISTS `t_storage`;
CREATE TABLE `t_storage`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(50) CHARACTER SET utf8mb4  NOT NULL DEFAULT '',
  `commodity_name` varchar(255) CHARACTER SET utf8mb4 NOT NULL DEFAULT '',
  `count` int(11) NOT NULL DEFAULT 0,
  `price` bigint(20) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `commodity_code`(`commodity_code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4;

-- ----------------------------
-- Records of t_storage
-- ----------------------------
INSERT INTO `t_storage` VALUES (1, '10001', '苹果手机', 100, 100);

5.3、服务搭建

5.3.1、pom依赖

order-service、storage-service、account-service三个服务的pom.xml的依赖一样,business-service服务不需要连接数据库,所以不需要引用mybatis-plus-boot-starter和mysql-connector-java

<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>

5.3.2、application.yml

order-service、storage-service、account-service三个服务的application.yml基本一致,其中需要修改的有以下几个地方:

server.port

spring.application.name

mybatis-plus.type-aliases-package

seata.application-id

business-service服务不需要连接数据库,所以不用配置datasource和mybatis-plus节点

server:
  port: 9001

spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        username: nacos
        password: nacos
        # Nacos 服务发现与注册配置
        server-addr: 127.0.0.1:8848
        # 注册到 nacos 的指定 namespace,默认为 public
        namespace: public
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/seata?characterEncoding=utf-8
    username: root
    password: root
mybatis-plus:
  global-config:
    banner: false
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志
    map-underscore-to-camel-case: true # 开启驼峰
  type-aliases-package: com.seata.order.entity  #定义所有操作类的别名所在包
  mapper-locations: classpath:mapper/*Mapper.xml

# seata config
seata:
  enabled: true
  application-id: order-service
  tx-service-group: my_test_tx_group # 事务群组(可以每个应用单独取名,也可以使用相同名字,独立起名需要配置nacos)
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace:
      cluster: default
  config:
    type: nacos
    nacos:
      namespace:
      server-addr: 127.0.0.1:8848

5.3.3、seata配置说明(这一步不用配置,只是说明一下)

registry.conf中内容已经配置到application.yml中,因此不需要引用registry.conf文件

如果不想正application.yml中配置seata相关内容,只需要将seata相关的脚本拷贝到项目resource目录下

文件地址https://github.com/seata/seata/tree/v1.4.2/script/client/conf

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

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

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

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

5.3.4、仓储服务

/**
     * 扣减库存
     */
    int deductStorage(String commodityCode, int count);
    /**
     * 扣减库存
     *
     * @param commodityCode 商品编码
     * @param count         数量
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deductStorage(String commodityCode, int count) {
        log.info("[库存服务]>------>扣减库存开始");
        storageMapper.deductStorage(commodityCode, count);
        log.info("[库存服务]>------>扣减库存结束");
        return count;
    }

5.3.5、订单服务

创建订单服务接口: 

    /**
     * 创建订单
     */
    Long createOrder(Long userId, String commodityCode, int count);

创建订单服务实现: 

    /**
     * 创建订单
     *
     * @param userId
     * @param commodityCode
     * @param count
     * @return
     */
    @Override
    public Long createOrder(Long userId, String commodityCode, int count) {
        log.info("[订单服务]>------>创建订单开始");
        //扣减账户余额
        Long price = storageService.selectPrice(commodityCode);
        Long money = price * count;
        // 创建订单
        Order order = new Order();
        order.setUserId(userId);
        order.setCommodityCode(commodityCode);
        order.setMoney(money);
        order.setCount(count);
        orderMapper.insert(order);
        // 扣减账户
        log.info("[订单服务]>------>扣减账户开始");
        accountService.deductAccount(order.getUserId(), money);
        log.info("[订单服务]>------>扣减账户结束");

        log.info("[订单服务]>------>创建订单结束");
        return order.getId();
    }

5.3.6、帐户服务

    /**
     * 扣减账户
     */
    Long deductAccount(Long userId, Long money);
    /**
     * 扣减账户
     * @param userId
     * @param money
     * @return
     */
    @Override
    public Long deductAccount(Long userId, Long money) {
        log.info("[账户服务]>------>扣减账户开始");
        if (10000 == userId) {
            throw new RuntimeException("[库存服务]>------>扣减库存异常");
        }
        accountMapper.deductAccount(userId, money);
        log.info("[账户服务]>------>扣减账户失败");
        return money;
    }

5.3.7、采购业务逻辑

采购业务逻辑,@GlobalTransactional 注解表示开启全局事务

    /**
     * 扣减库存-》创建订单
     *
     * @param userId        用户Id
     * @param commodityCode 商品编码
     * @param count         数量
     */
    @Override
    @GlobalTransactional(timeoutMills = 10000, name = "spring-cloud-seata", rollbackFor = Exception.class)
    public Long purchase(Long userId, String commodityCode, int count) {
        log.info("开始全局事务,XID = " + RootContext.getXID());

        log.info("[采购服务]>------>扣减库存开始");
        storageService.deductStorage(commodityCode, count);
        log.info("[采购服务]>------>扣减库存结束");

        log.info("[采购服务]>------>创建订单开始");
        Long orderId = orderService.createOrder(userId, commodityCode, count);
        log.info("[采购服务]>------>创建订单结束");

        return orderId;
    }

5.3.8、查看服务启动情况

5.3 分布式事务测试

5.3.1、正常业务逻辑测试

postman测试:

请求地址:http://localhost:9000/business/purchase?userId=10001&commodityCode=10001&count=1

数据库中:订单表已经有一条数据,库存表商品库存数量减1

5.3.1、异常业务逻辑测试

项目完整地址:

https://github.com/jeespring/spring-cloud-seata

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值