IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring Cloud Eureka整合 Seata 实现分布式事务 -> 正文阅读

[Java知识库]Spring Cloud Eureka整合 Seata 实现分布式事务

一、Seata 介绍

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

????????Seata相关名词

????????TC (Transaction Coordinator) - 事务协调者

????????维护全局和分支事务的状态,驱动全局事务提交或回滚。

????????TM (Transaction Manager) - 事务管理器

????????定义全局事务的范围:开始全局事务、提交或回滚全局事务。

????????RM (Resource Manager) - 资源管理器

????????管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

二、软件版本

软件

版本

地址

JDK

1.8.0_271

Java Downloads | Oracle

Spring Boot

2.5.6

Spring Boot

Spring Cloud

2020.0.4

Spring Cloud

Seata

1.4.2

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

????????代码地址(Seata 和 SQL?脚本在resource目录下):
? ? ? ? spring-cloud-seata

三、环境搭建

3.1、安装Seata

3.1.1、下载seata-server

????????下载seata-server-1.4.2.zip,地址:https://github.com/seata/seata/releases

3.1.2、安装seata-server

????????解压seata-server-1.4.2.zip

?3.1.3、 seata数据库配置

????????创建数据库spring-cloud-seata-eureka ,并创建seata服务需要的表:

????????global_table、branch_table、lock_table

????????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.1.4、配置seata-server

? ? ? ? 我们需要修改 seata-server-1.4.2/conf 目录里面file.conf和registry.conf,这里我们使用的是eureka注册中心,存储使用的MySQL数据库,配置如下

????????file.conf 配置如下:

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

? ## database store property
? db {
? ? ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
? ? datasource = "druid"
? ? ## mysql/oracle/postgresql/h2/oceanbase etc.
? ? dbType = "mysql"
? ? driverClassName = "com.mysql.cj.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/spring-cloud-seata-eureka?rewriteBatchedStatements=true&characterEncoding=utf8&useSSL=false"
? ? user = "root"
? ? password = "root"
? ? minConn = 5
? ? maxConn = 30
? ? globalTable = "global_table"
? ? branchTable = "branch_table"
? ? lockTable = "lock_table"
? ? queryLimit = 100
? }
}

????????registry.conf 配置如下

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

? eureka {
? ? serviceUrl = "http://127.0.0.1:8761/eureka"
? ? application = "default"
? ? weight = "1"
? }
}

config {
? # file、nacos 、apollo、zk、consul、etcd3
? type = "file"
??
? file {
? ? name = "file.conf"
? }
}

3.2、启动服务

3.2.1、启动Eureka注册中心

? ? ? ? eureka配置如下:

server:
  port: 8761

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

????????启动eureka

3.2.2、启动Seata服务

????????执行 seata-server-1.4.2\bin 目录下的 seata-server.bat (windows)/ seata-server.sh (linux或mac)启动seata服务 。

????????打开?http://localhost:8761/,我们可以看出,seata 服务已经成功注册到eureka注册中心。

四、项目架构

4.1、项目说明

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

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

4.2、项目架构

五、项目搭建

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, 1000);

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>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</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

????????order-service 服务yml配置

server:
  port: 9001

spring:
  application:
    name: order-service
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/spring-cloud-seata-eureka?characterEncoding=utf-8
    username: root
    password: root

eureka:
  instance:
    instance-id: order-service
    prefer-ip-address: true
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

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:
  application-id: order-service
  tx-service-group: my_test_tx_group
  service:
    vgroup-mapping:
      # 此处配置对应Server端配置registry.eureka.application的值
      my_test_tx_group: default 
  registry:
    type: eureka
    eureka:
      service-url: http://localhost:8761/eureka
      weight: 1

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

server:
  port: 9000

spring:
  application:
    name: business-service

eureka:
  instance:
    instance-id: business-service
    prefer-ip-address: true
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

# Seata Config
seata:
  application-id: business-service
  tx-service-group: my_test_tx_group
  service:
    vgroup-mapping:
      # 此处配置对应Server端配置registry.eureka.application的值
      my_test_tx_group: default
  registry:
    type: eureka
    eureka:
      service-url: http://localhost:8761/eureka
      weight: 1

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

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

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

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

????????其中?registry.conf 修改后如下:

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

  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    weight = "1"
  }
}

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

  file {
    name = "file.conf"
  }
}

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、采购业务逻辑

    /**
     * 扣减库存-》创建订单
     *
     * @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、查看服务启动情况

????????启动business服务,“register RM success.?”,表示RM已经注册到TC。

????????查看order服务的启动情况:

????????打开eureka注册中心,我们会清楚的看到各个服务的健康状态:

? ? ? ? 我们也可以从Seata服务端查看各个RM是否注册成功,如下图所示:?

5.3 分布式事务测试

5.3.1、正常业务逻辑测试

????????postman测试:

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

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

????????发起创建订单的请求后,日志里面可以清楚的看到本次全局事务的情况,

????????xid=172.20.97.98:8091:5395550782355771430,branchId=5395550782355771432,branchType=AT

????????business 服务的事务执行情况?

????????order 服务执行情况?

????????在Seata服务端,我们也能清楚的看到xid=172.20.97.98:8091:5395550782355771430这个全局事务的提交情况

5.3.1、异常业务逻辑测试

????????账户服务中,如果userId = 1000,就抛出异常,进行异常测试

    @Override
    public Long deductAccount(Long userId, Long money) {
        log.info("[账户服务]>------>扣减账户开始");
        if (10000L == userId) {
            throw new RuntimeException("[账户服务]>------>扣减账户异常");
        }
        accountMapper.deductAccount(userId, money);
        log.info("[账户服务]>------>扣减账户结束");
        return money;
    }

? ? ? ? http://localhost:9000/business/purchase?userId=10000&commodityCode=10001&count=1

????????这次全局事务ID 172.20.97.98:8091:5395550782355771442,开启全局事务

????????创建订单的时候,在order服务调用account服务的时候发生了异常?,下面日志可以看出,数据已经开始回滚。

????????Branch Rollbacking: 172.20.97.98:8091:5395550782355771442 5395550782355771447 jdbc:mysql://127.0.0.1:3306/spring-cloud-seata-eureka

????????xid 172.20.97.98:8091:5395550782355771442 branch 5395550782355771447, undo_log deleted with GlobalFinished

????????Branch Rollbacked result: PhaseTwo_Rollbacked

????????回滚的时候,是将之前保存的undo_log 取出来,进行数据恢复,在发生异常的地方,我们打上断点,再次请求,会发现undo_log表会将前面执行的日志保存下来

????????undo_log保存之前的日志

源码地址:

GitHub - jeespring/spring-cloud-seata

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-08 22:14:58  更:2022-03-08 22:16:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:52:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码