Seata 概念

业务的不断发展,单体架构无法满足我们的需求,分布式微服务架构逐渐成为大型互联网平台的首选,所有使用分布式微服务架构的应用都必须面临一个十分棘手的问题,那就是“分布式事务”问题。在分布式微服务架构中,几乎所有业务操作都需要多个服务协作才能完成。对于其中的某个服务而言,它的数据一致性可以交由其自身 数据库事务 来保证,但从整个分布式微服务架构来看,其全局数据的一致性却是无法保证的。 如:用户在某电商系统下单购买了一件商品后,电商系统会执行下 4 步:
  1. 调用订单服务创建订单数据
  2. 调用库存服务扣减库存
  3. 调用账户服务扣减账户金额
  4. 最后调用订单服务修改订单状态
为保证数据的正确性和一致性,我们必须保证所有这些操作要么全部成功,要么全部失败,否则就可能出现类似于商品库存已扣减,但用户账户资金尚未扣减等数据不一致的情况。各服务自身的事务特性显然是无法实现这一目标的,此时我们可以通过分布式事务框架来解决这个问题。 Seata 是这样一个分布式事务处理框架,它是由阿里巴巴和蚂蚁金服共同开源的分布式事务解决方案,作用在微服务架构下提供高性能且简单易用的分布式事务服务。官网地址 分布式事务主要涉及以下概念: + 事务:由一组操作构成的可靠、独立的工作单元,事务具备 ACID 的特性,即原子性、一致性、隔离性和持久性。 + 本地事务:本地事务由本地资源管理器(通常指数据库管理系统 DBMS,例如 MySQL、Oracle ……)管理,严格地支持 ACID 特性,高效可靠。本地事务不具备分布式事务的处理能力,隔离的最小单位受限于资源管理器,即本地事务只能对自己数据库的操作进行控制,对于其他数据库的操作则无能为力。 + 全局事务:全局事务指的是一次性操作多个资源管理器完成的事务,由一组分支事务组成。 + 分支事务:在分布式事务中,就是一个个受全局事务管辖和协调的本地事务。 我们可以将分布式事务理解成一个包含了若干个分支事务的全局事务。全局事务的职责是协调其管辖的各个分支事务达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个满足 ACID 特性的本地事务 Seata 工作流程 Seata 对分布式事务的协调和控制,主要是通过 XID 3 个核心组件实现的。 XID:XID 是全局事务的唯一标识,它可以在服务的调用链路中传递,绑定到服务的事务上下文中。 Seata 定义了 3 个核心组件:
  • TC(Transaction Coordinator):事务协调器,它是事务的协调者(这里指的是 Seata 服务器),主要负责维护全局事务和分支事务的状态,驱动全局事务提交或回滚。
  • TM(Transaction Manager):事务管理器,它是事务的发起者,负责定义全局事务的范围,并根据 TC 维护的全局事务和分支事务状态,做出开始事务、提交事务、回滚事务的决议。
  • RM(Resource Manager):资源管理器,它是资源的管理者(这里可以将其理解为各服务使用的数据库)。它负责管理分支事务上的资源,向 TC 注册分支事务,汇报分支事务状态,驱动分支事务的提交或回滚。
以上三个组件相互协作,TC 以 Seata 服务器(Server)形式独立部署,TM 和 RM 则是以 Seata Client 的形式集成在微服务中运行

画板

Seata 的整体工作流程如下:
  1. TM 向 TC 申请开启一个全局事务,全局事务创建成功后,TC 会针对这个全局事务生成一个全局唯一的 XID;
  2. XID 通过服务的调用链传递到其他服务;
  3. RM 向 TC 注册一个分支事务,并将其纳入 XID 对应全局事务的管辖;
  4. TM 根据 TC 收集的各个分支事务的执行结果,向 TC 发起全局事务提交或回滚决议;
  5. TC 调度 XID 下管辖的所有分支事务完成提交或回滚操作。

Seata下载安装

官方下载:Releases · seata/seata

Spring Cloud Alibaba Seata - 图4

解压后,目录如下

Spring Cloud Alibaba Seata - 图5

:::color1

目录说明如下:
  • <font style="color:rgb(68, 68, 68);">bin</font>:用于存放 <font style="color:rgb(68, 68, 68);">Seata Server</font> 可执行命令。
  • <font style="color:rgb(68, 68, 68);">conf</font>:用于存放 <font style="color:rgb(68, 68, 68);">Seata Server</font> 的配置文件。
  • <font style="color:rgb(68, 68, 68);">lib</font>:用于存放 <font style="color:rgb(68, 68, 68);">Seata Server</font> 依赖的各种 Jar 包。
  • <font style="color:rgb(68, 68, 68);">logs</font>:用于存放 <font style="color:rgb(68, 68, 68);">Seata Server</font> 的日志。

:::

Seata 事务模式

Seata 提供了 AT、TCC、SAGA 和 XA 四种事务模式,可以快速有效地对分布式事务进行控制。详细可参考官网
在这四种事务模式中使用最多,最方便的就是 AT 模式。与其他事务模式相比,AT 模式可以应对大多数的业务场景,且基本可以做到无业务入侵,开发人员能够有更多的精力关注于业务逻辑开发。

Seata AT

要使用 Seata 的 AT 模式对分布式事务进行控制,必须满足以下 2 个前提:
  • 须使用支持本地 ACID 事务特性的关系型数据库,例如 <font style="color:rgb(68, 68, 68);">MySQL</font><font style="color:rgb(68, 68, 68);">Oracle </font>
  • 应用程序必须是使用 JDBC 对数据库进行访问的 JAVA 应用
此外,我们还需要针对业务中涉及的各个数据库表,分别创建一个 UNDO_LOG(回滚日志)表。不同数据库在创建 UNDO_LOG 表时会略有不同,以 MySQL 为例,其 UNDO_LOG 表的创表语句如下:
  1. CREATE TABLE `undo_log` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `branch_id` bigint(20) NOT NULL,
  4. `xid` varchar(100) NOT NULL,
  5. `context` varchar(128) NOT NULL,
  6. `rollback_info` longblob NOT NULL,
  7. `log_status` int(11) NOT NULL,
  8. `log_created` datetime NOT NULL,
  9. `log_modified` datetime NOT NULL,
  10. PRIMARY KEY (`id`),
  11. UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
  12. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

Seata 配置中心

Seata 配置中心 所谓“配置中心”,就像是一个“大衣柜”,内部存放着各种各样的配置文件,我们可以根据自己的需要从其中获取指定的配置文件,加载到对应的客户端中。详细参考官网
Seata 支持多种配置中心:
  • nacos
  • consul
  • apollo
  • etcd
  • zookeeper
  • file (读本地文件,包含 conf、properties、yml 等配置文件)

Seata 整合Nacos配置中心

对于 Seata 来说,Nacos 是一种重要的配置中心实现。官网参考:Seata部署指南,使用步骤如下:

添加Maven依赖

在 Spring Cloud 项目中使用Seata,通常只需要在 <font style="color:rgb(68, 68, 68);">pom.xml</font> 中添加 <font style="color:rgb(68, 68, 68);">spring-cloud-starter-alibaba-seata</font> 依赖即可
  1. <!--引入 seata 依赖-->
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  5. </dependency>

Seata Server 配置

在 Seata Server 安装目录下的 <font style="color:rgb(68, 68, 68);">config/registry.conf</font> 中,将配置方式<font style="color:rgb(68, 68, 68);">config.type</font>修改为 Nacos,并对 Nacos 配置中心的相关信息进行配置,示例配置如下(默认的是用文件(file)配置,本来里面放了很多选项,既然我们用nacos,其他选项就可以删掉)。

Spring Cloud Alibaba Seata - 图6

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa(seata支持多种配置中心,我们主要使用nacos)
  type = "nacos"

  nacos {
    application = "seata-server"
    #修改为使用的 nacos 服务器地址
    serverAddr = "127.0.0.1:8848"
    #配置中心所在的分组
    group = "SEATA_GROUP"
    #配置中心的命名空间
    namespace = ""
    cluster = "default"
    #Nacos 配置中心的用户名
    username = "nacos"
     #Nacos 配置中心的密码
    password = "nacos"
    }
}

Seata Client 配置

在 Seata Client(即微服务架构中的服务,就是项目中的配置),通过 <font style="color:rgb(68, 68, 68);">application.yml</font> 等配置文件对 Nacos 配置中心进行配置,示例代码如下。
server:
  port: 8501

spring:
  application:
    name: chen-seata-server
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

# seata配置文件
seata:
  registry:
    type: nacos
    nacos:
      application: chen-seata-server
      server-addr: 127.0.0.1:8848 # Nacos 注册中心的地址
      group: "SEATA_GROUP"  #分组
      namespace: ""
      username: nacos
      password: nacos
@SpringBootApplication
@EnableDiscoveryClient//开启服务注册与发现功能(不要忘记哦)
public class SpringCloudAlibabaSeata8501Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringCloudAlibabaSeata8501Application.class, args);
    }

}
完成以上步骤,先启动 Nacos Server 再启动 Seata Server,登录 Nacos 控制台查看服务列表如下:

Spring Cloud Alibaba Seata - 图7

如上图 <font style="color:rgb(68, 68, 68);">chen-seata-server</font> 服务已经注册到了 Nacos 注册中心。如果运行报错,请检查是否导入Nacos依赖,或者Nacos配置

上传配置到 Nacos 配置中心

在完成了 Seata 服务端客户端的相关配置后,就可以将配置上传到Nacos 配置中心了,操作步骤如下。

  1. 我们需要获取一个名为 config.txt 的文本文件,该文件包含了 Seata 配置的所有参数明细。去官网下载源码里面查找

Spring Cloud Alibaba Seata - 图8

Spring Cloud Alibaba Seata - 图9

  1. /script/config-center/nacos 目录中,有以下 2 个 Seata 脚本:
  • nacos-config.py:Python 脚本
  • nacos-config.sh:为 Linux 脚本,可以在 Windows 下通过 Git 命令,将 config.txt 中的 Seata 配置上传到 Nacos 配置中心

Spring Cloud Alibaba Seata - 图10

  1. seata-1.4.2\script\config-center\nacos 目录下,右键鼠标选择 Git Bush Here,并在弹出的 Git 命令窗口中执行以下命令,将 config.txt 中的配置上传到 Nacos 配置中心。注意:上传时Nacos服务一定要启动
sh nacos-config.sh -h 127.0.0.1 -p 8848  -g SEATA_GROUP -u nacos -w nacos
Git 命令各参数说明:
  • -h:Nacos 的 host,默认取值为 localhost
  • -p:端口号,默认取值为 <font style="color:rgb(68, 68, 68);">8848</font>
  • -g:Nacos 配置的分组,默认取值为 <font style="color:rgb(68, 68, 68);">SEATA_GROUP</font>
  • -u:Nacos 用户名
  • -w:Nacos 密码

Spring Cloud Alibaba Seata - 图11

看到如上图所示代表上传成功,可以到Nacos配置中心验证是否上传成功

Spring Cloud Alibaba Seata - 图12

Seata事务分组(待完善……)

事务分组是 Seata 提供的一种 TC(Seata Server) 服务查找机制。Seata 通过事务分组获取 TC 服务,流程如下:
  1. 在应用中配置事务分组。
  2. 应用通过配置中心去查找配置:service.vgroupMapping.{事务分组},该配置的值就是 TC 集群的名称。
  3. 获得集群名称后,应用通过一定的前后缀 + 集群名称去构造服务名。
  4. 得到服务名后,去注册中心去拉取服务列表,获得后端真实的 TC 服务列表。
下面我们以 Nacos 服务注册中心为例,介绍 Seata 事务的使用。

Seata Server配置

在 Seata Server 的 config/registry.conf 中,进行如下配置。
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos" #使用 Nacos作为注册中心

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

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"  # 使用nacos作为配置中心

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

Seata Cline配置

seata:
  registry:
    type: nacos #从 Nacos 获取 TC 服务
    nacos:
      application: chen-seata-server
      server-addr: 127.0.0.1:8848 # Nacos 注册中心的地址
      group: "SEATA_GROUP"
      namespace: ""
      username: nacos
      password: nacos
  config:
    type: nacos #使用 Nacos 作为配置中心
    nacos:
      application: chen-seata-server
      server-addr: 127.0.0.1:8848
      username: nacos
      password: nacos
      namespace: ""
  tx-service-group: service-order-group

启动Seata Server

建表

Seata Server 目前有以下 3 种存储模式(store.mode)(官网后续标明将引入<font style="color:rgb(36, 41, 46);">raft</font>,<font style="color:rgb(36, 41, 46);">mongodb</font>):详细参考官网Seata部署指南

模式 说明 准备工作
file 文件存储模式:默认存储模式; 该模式为单机模式,全局事务的会话信息在内存中读写,并持久化本地文件 <font style="color:rgb(51, 51, 51);">root.data</font>,性能较高 *
db 数据库存储模式:该模式为高可用模式,全局事务会话信息通过数据库共享,相应性能差些 建数据库表
redis 缓存处处模式: Seata Server 1.3 及以上版本支持该模式,性能较高,但存在事务信息丢失风险 配置 redis 持久化配置
实际开发中我们都是存在数据库中的。在 db 模式下,我们需要针对全局事务的会话信息创建以下 3 张数据库表。
  • 全局事务,对应的表为:<font style="color:rgb(68, 68, 68);">global_table</font>
  • 分支事务,对应的表为:<font style="color:rgb(68, 68, 68);">branch_table</font>
  • 全局锁,对应的表为:<font style="color:rgb(68, 68, 68);">lock_table</font>
在 MySQL 中,创建一个名为<font style="color:rgb(68, 68, 68);">seata</font>的数据库实例,并在该数据库内执行以下 SQL
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;

上方SQL可以在相应版本的源码包里面的 seata-1.4.2\script\server\db路径下面找到

Spring Cloud Alibaba Seata - 图13

修改配置 修改 Seata Server 配置。在 seata-server-1.4.2/conf/ 目录下的 registry.conf 中,将 Seata Server 的服务注册方式(registry.type)和配置方式(config.type)都修改为 Nacos,修改内容如下。
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

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

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

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

修改配置文件 <font style="color:rgb(68, 68, 68);">config.txt</font>中的数据库连接

#将 Seata Server 的存储模式修改为 db
store.mode=db
# 数据库驱动
store.db.driverClassName=com.mysql.cj.jdbc.Driver
# 数据库 url
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&characterEncoding=UTF-8&useUnicode=true&serverTimezone=UTC 
# 数据库的用户名
store.db.user=root 
# 数据库的密码
store.db.password=cj123456789
# 自定义事务分组
service.vgroupMapping.service-order-group=default
service.vgroupMapping.service-storage-group=default
service.vgroupMapping.service-account-group=default
在 seata-1.4.2\script\config-center\nacos 目录下,右键鼠标选择 Git Bush Here,在弹出的 Git 命令窗口中执行以下命令,将 config.txt 中的配置上传到 Nacos 配置中心。
sh nacos-config.sh -h 127.0.0.1 -p 8848  -g SEATA_GROUP -u nacos -w nacos
当 Git 命令窗口出现以下执行日志时,则说明配置上传成功。
Spring Cloud Alibaba Seata - 图14 ## 启动 Seata Server 双击 Seata Server 端 bin 目录下的启动脚本 seata-server.bat ,启动 Seata Server。
Spring Cloud Alibaba Seata - 图15 # 业务系统集成 Seata 我们就以电商系统为例,演示下业务系统是如何集成 Seata 的。 在电商系统中,用户下单购买一件商品,需要以下 3 个服务提供支持:
  • Order(订单服务):创建和修改订单;
  • Storage(库存服务):对指定的商品扣除仓库库存;
  • Account(账户服务) :从用户帐户中扣除商品金额。
这三个微服务分别使用三个不同的数据库,架构图如下所示。

画板

当用户从平台购买一件商品时,触发的步骤如下:

  1. 调用订单服务Order,创建一条订单记录,状态未完成
  2. 调用库存服务Storage,执行库存的扣减
  3. 调用账户服务Account,从当前账户扣减相应产品金额
  4. 上述步骤完成后调用订单服务Order,调整订单状态为已完成

创建订单服务

建表

在 MySQL 数据库中,新建一个名为 seata-order 的数据库实例,并通过以下 SQL 语句创建 2 张表:order(订单表)和 undo_log(回滚日志表)。

-- 创建订单服务数据库
CREATE DATABASE IF NOT EXISTS seata_order
DROP TABLE IF EXISTS `s_order`;
CREATE TABLE `s_order` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint DEFAULT NULL COMMENT '用户id',
  `product_id` bigint DEFAULT NULL COMMENT '产品id',
  `count` int DEFAULT NULL COMMENT '数量',
  `money` decimal(11,0) DEFAULT NULL COMMENT '金额',
  `status` int DEFAULT NULL COMMENT '订单状态:0:未完成;1:已完结',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `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 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 DEFAULT CHARSET=utf8 ;

创建Module spring-cloud-alibaba-seata-order-8005

Spring Cloud Alibaba Seata - 图17

添加依赖 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--父工程-->
    <parent>
        <groupId>com.chen</groupId>
        <artifactId>spring-cloud-alibaba-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.chen</groupId>
    <artifactId>spring-cloud-alibaba-seata-order-8005</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-cloud-alibaba-seata-order-8005</name>
    <description>spring-cloud-alibaba-seata-order-8005</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--引入 seata 依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
        <!--引入Nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--配置中心读取seata配置-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--添加 Spring Boot 的监控模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--引入MyBatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <!--引入 OpenFeign 的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

application.yaml

server:
  port: 8005  #端口

spring:
  application:
    name: spring-cloud-alibaba-seata-order-8005 #服务吗
  #数据源
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver #数据库驱动,如果是8.0以下使用 com.mysql.jdbc.Driver
    name: defaultDataSource
    url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC #???????
    username: root  #账号
    password: cj123456789  #密码
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 #nacos连接地址
        namespace: public #nacos命名空间
        username: nacos
        password: nacos
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080  #Sentinel地址
        port: 8719
    alibaba:
      seata:
              #自定义服务群组,该值必须与 Nacos 配置中的 service.vgroupMapping.{my-service-group}=default 中的 {my-service-group}相同
        tx-service-group: service-order-group

seata:
  application-id: ${spring.application.name}
  #自定义服务群组,该值必须与 Nacos 配置中的 service.vgroupMapping.{my-service-group}=default 中的 {my-service-group}相同
  tx-service-group: service-order-group
  service:
    grouplist:
      #Seata 服务器地址
      seata-server: 127.0.0.1:8091
  # Seata 的注册方式为 nacos
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
  # Seata 的配置中心为 nacos
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848

feign:
  sentinel:
    enabled: true #开启OpenFeign功能

management:
  endpoints:
    web:
      exposure:
        include: "*"

mybatis:
  mapperLocations: classpath:mybatis/*.xml

entity、mapper、service、controller、vo

entity

package com.chen.springcloudalibabaseataorder8005.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
    private Long id;
    private Long userId;
    private Long productId;
    private Integer count;
    private BigDecimal money;
    private Integer status;
}

mapper

package com.chen.springcloudalibabaseataorder8005.mapper;

import com.chen.springcloudalibabaseataorder8005.entity.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface OrderMapper {

    /**
     * 创建订单
     */
    int create(Order order);

    /**
     * 修改订单状态,从零改为1
     */
    void update(@Param("userId") Long userId, @Param("status") Integer status);
}

mapper.xml

注意:mapper文件我这里放在当前项目\src\main\resources\mybatis\下面。可以根据根据自己喜好放置,记得一定要做相应设置不然无法映射。我这里在 文件中配置的 mybatis-mapperLocations

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.chen.springcloudalibabaseataorder8005.mapper.OrderMapper">
    <!--定义一个结果集和实体类的映射表-->
    <resultMap id="BaseResultMap" type="com.chen.springcloudalibabaseataorder8005.entity.Order">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="user_id" property="userId" jdbcType="BIGINT"/>
        <result column="product_id" property="productId" jdbcType="BIGINT"/>
        <result column="count" property="count" jdbcType="INTEGER"/>
        <result column="money" property="money" jdbcType="DECIMAL"/>
        <result column="status" property="status" jdbcType="INTEGER"/>
    </resultMap>

    <insert id="create">
        INSERT INTO `order` (`id`, `user_id`, `product_id`, `count`, `money`, `status`)
        VALUES (NULL, #{userId}, #{productId}, #{count}, #{money}, #{status});
    </insert>

    <update id="update">
        UPDATE `order`
        SET status = #{status}
        WHERE user_id = #{userId} AND status = #{status};
    </update>
</mapper>

service

订单服务下面的Service要创建三个。一个是订单自己的Service。 加调用库存和账户两个。

package com.chen.springcloudalibabaseataorder8005.service;

import com.chen.springcloudalibabaseataorder8005.entity.Order;
import com.chen.springcloudalibabaseataorder8005.vo.CommonResult;

public interface OrderService {
    /**
     * 创建订单数据
     */
    CommonResult create(Order order);
}
package com.chen.springcloudalibabaseataorder8005.service;

import com.chen.springcloudalibabaseataorder8005.vo.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

//通过OpenFeign远程调用库存的微服务
@FeignClient(value = "spring-cloud-alibaba-seata-storage-8006")
public interface StorageService {

    /**
     * 扣减库存
     */
    @PostMapping(value = "/storage/decrease")
    CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}
package com.chen.springcloudalibabaseataorder8005.service;

import com.chen.springcloudalibabaseataorder8005.vo.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.math.BigDecimal;

//通过OpenFeign远程调用账号微服务
@FeignClient(value = "spring-cloud-alibaba-seata-account-8007")
public interface AccountService {

    /**
     * 扣减账户余额,需要传入用户ID跟扣除的金额
     */
    @PostMapping("/account/decrease")
    CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}

serviceImpl

package com.chen.springcloudalibabaseataorder8005.service.impl;

import com.chen.springcloudalibabaseataorder8005.entity.Order;
import com.chen.springcloudalibabaseataorder8005.mapper.OrderMapper;
import com.chen.springcloudalibabaseataorder8005.service.AccountService;
import com.chen.springcloudalibabaseataorder8005.service.OrderService;
import com.chen.springcloudalibabaseataorder8005.service.StorageService;
import com.chen.springcloudalibabaseataorder8005.vo.CommonResult;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Resource
    private OrderMapper orderMapper;

    @Resource
    private StorageService storageService;

    @Resource
    private AccountService accountService;

    /**
     * 1.创建订单->2.调用库存服务扣减库存->3.调用账户服务扣减账户余额->4.修改订单状态
     */
    @GlobalTransactional
    @Override
    public CommonResult create(Order order) {
        log.info("------->下单开始");
        //本应用创建订单
        orderMapper.create(order);

        //远程调用库存服务扣减库存
        log.info("------->订单微服务调用库存微服务,扣减库存开始");
        storageService.decrease(order.getProductId(), order.getCount());
        log.info("------->订单微服务调用库存微服务,扣减库存结束");

        //远程调用账户服务扣减余额
        log.info("------->订单微服务调用账户微服务,扣减余额开始");
        accountService.decrease(order.getUserId(), order.getMoney());
        log.info("------->订单微服务调用账户微服务,减余额结束");

        //修改订单状态为已完成
        log.info("------->order-service中修改订单状态开始");
        // 这里的话是不是应该是orderId
        orderMapper.update(order.getUserId(), 0);
        log.info("------->order-service中修改订单状态结束");

       return new CommonResult(200,"订单创建成功");
    }
}

vo

package com.chen.springcloudalibabaseataorder8005.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult implements Serializable {
    private static final long serialVersionUID = 1L;
    private int code;
    private String msg;
    private Object data;

    public CommonResult(int code,String msg){
        this(code,msg,null);
    }
}

controller

package com.chen.springcloudalibabaseataorder8005.controller;

import com.chen.springcloudalibabaseataorder8005.entity.Order;
import com.chen.springcloudalibabaseataorder8005.service.OrderService;
import com.chen.springcloudalibabaseataorder8005.vo.CommonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;

@RestController
public class OrderController {
    @Autowired
    private OrderService orderService;

    @GetMapping("/order/create/{productId}/{count}/{money}")
    public CommonResult create(@PathVariable("productId") Integer productId, @PathVariable("count") Integer count, @PathVariable("money") BigDecimal money) {
        Order order = new Order();
        order.setUserId(1L);
        order.setProductId(Integer.valueOf(productId).longValue());
        order.setCount(count);
        order.setMoney(money);
        order.setStatus(0);
        return orderService.create(order);
    }
}

搭建库存服务

建表

在 MySQL 数据库中,新建一个名为 seata_storage 的数据库实例,并通过以下 SQL 语句创建 2 张表:storage(订单表)和 undo_log(回滚日志表)。

-- 创建库存服务数据库
CREATE DATABASE IF NOT EXISTS seata_storage;
DROP TABLE IF EXISTS `storage`;
CREATE TABLE `storage` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `product_id` bigint DEFAULT NULL COMMENT '产品id',
  `total` int DEFAULT NULL COMMENT '总库存',
  `used` int DEFAULT NULL COMMENT '已用库存',
  `residue` int DEFAULT NULL COMMENT '剩余库存',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `storage` VALUES ('1', '1', '100', '0', '100');

DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `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 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 DEFAULT CHARSET=utf8 COMMENT='AT transaction mode undo table';

创建Module spring-cloud-alibaba-seata-storage-8006

Spring Cloud Alibaba Seata - 图18

添加依赖 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--父工程-->
    <parent>
        <groupId>com.chen</groupId>
        <artifactId>spring-cloud-alibaba-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.chen</groupId>
    <artifactId>spring-cloud-alibaba-seata-storage-8006</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-cloud-alibaba-seata-storage-8006</name>
    <description>spring-cloud-alibaba-seata-storage-8006</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--引入 seata 依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
        <!--引入Nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--配置中心读取seata配置-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--添加 Spring Boot 的监控模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--引入MyBatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <!--引入 OpenFeign 的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

application.yaml

server:
  port: 8006  #端口

spring:
  application:
    name: spring-cloud-alibaba-seata-storage-8006 #服务名
  #数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver #数据库驱动,数据库版本如果是8.0以下的请使用 com.mysql.jdbc.Driver
    name: defaultDataSource
    url: jdbc:mysql://localhost:3306/seata_storage?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC #数据库连接地址
    username: root  #数据库的用户名
    password: cj123456789  #数据库密码
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 #nacos 服务器地址
        namespace: public #nacos 命名空间
        username: nacos
        password: nacos
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080  #Sentinel 控制台地址
        port: 8719
    alibaba:
      seata:
        #自定义事务组名称需要与seata-server中file.conf中配置的事务组ID对应,vgroup_mapping.my_test_tx_group = "my_group"
        tx-service-group: service-storage-group

seata:
  application-id: ${spring.application.name}
  #自定义服务群组,该值必须与 Nacos 配置中的 service.vgroupMapping.{my-service-group}=default 中的 {my-service-group}相同
  tx-service-group: service-storage-group
  service:
    grouplist:
      #Seata 服务器地址
      seata-server: 127.0.0.1:8091
  # Seata 的注册方式为 nacos
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
  # Seata 的配置中心为 nacos
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848

feign:
  sentinel:
    enabled: true #开启OpenFeign功能

management:
  endpoints:
    web:
      exposure:
        include: "*"

mybatis:
  mapperLocations: classpath:mybatis/*.xml

entity、mapper、service、controller、vo

同上方类似,详细代码请访问git,查看获取

spring-cloud-alibaba-seata-storage-8006

搭建账户服务

建表

  1. 在 MySQL 数据库中,新建一个名为 seata_account 的数据库实例,并通过以下 SQL 语句创建 2 张表:account(订单表)和 undo_log(回滚日志表)。
-- 创建账户服务数据库
CREATE DATABASE IF NOT EXISTS seata_account;
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
  `user_id` bigint DEFAULT NULL COMMENT '用户id',
  `total` decimal(10,0) DEFAULT NULL COMMENT '总额度',
  `used` decimal(10,0) DEFAULT NULL COMMENT '已用余额',
  `residue` decimal(10,0) DEFAULT '0' COMMENT '剩余可用额度',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `account` VALUES ('1', '1', '1000', '0', '1000');
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `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 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 DEFAULT CHARSET=utf8;

创建Module spring-cloud-alibaba-seata-account-8007

Spring Cloud Alibaba Seata - 图19

添加依赖 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--父工程-->
    <parent>
        <groupId>com.chen</groupId>
        <artifactId>spring-cloud-alibaba-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.chen</groupId>
    <artifactId>spring-cloud-alibaba-seata-account-8007</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-cloud-alibaba-seata-account-8007</name>
    <description>spring-cloud-alibaba-seata-account-8007</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--引入 seata 依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
        <!--引入Nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--配置中心读取seata配置-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--添加 Spring Boot 的监控模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--引入MyBatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <!--引入 OpenFeign 的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.yaml

server:
  port: 8007  #端口

spring:
  application:
    name: spring-cloud-alibaba-seata-account-8007 #服务名
  #数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver #数据库驱动,数据库版本如果是8.0以下的请使用 com.mysql.jdbc.Driver
    name: defaultDataSource
    url: jdbc:mysql://localhost:3306/seata_account?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC #数据库连接地址
    username: root  #数据库的用户名
    password: cj123456789  #数据库密码
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 #nacos 服务器地址
        namespace: public #nacos 命名空间
        username: nacos
        password: nacos
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080  #Sentinel 控制台地址
        port: 8719
    alibaba:
      seata:
        #自定义事务组名称需要与seata-server中file.conf中配置的事务组ID对应,vgroup_mapping.my_test_tx_group = "my_group"
        tx-service-group: service-account-group

seata:
  application-id: ${spring.application.name}
  #自定义服务群组,该值必须与 Nacos 配置中的 service.vgroupMapping.{my-service-group}=default 中的 {my-service-group}相同
  tx-service-group: service-account-group
  # Seata 的注册方式为 nacos
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
  # Seata 的配置中心为 nacos
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848

feign:
  sentinel:
    enabled: true #开启OpenFeign功能

management:
  endpoints:
    web:
      exposure:
        include: "*"

mybatis:
  mapperLocations: classpath:mybatis/*.xml

entity、mapper、service、controller、vo

同上方类似,详细代码请访问git,查看获取

demo/spring-cloud-alibaba-seata-account-8007

测试

三个服务搭建完毕后,我们进行测试。测试思路如下:

  1. 不适用分布式事务,看下存在的问题

请求 http://localhost:8005/order/create/1/1/10,模拟创建订单操作,我们在本地想要查看的地方打好断点,一步步执行。

Spring Cloud Alibaba Seata - 图20

Spring Cloud Alibaba Seata - 图21

Spring Cloud Alibaba Seata - 图22

我们在创建完订单扣库存的时候,模拟库粗不足异常。此时去查看表中数据如下:

order

Spring Cloud Alibaba Seata - 图23

account

Spring Cloud Alibaba Seata - 图24

storage

Spring Cloud Alibaba Seata - 图25

会发现,订单创建OK的,但是库存数据和账户金额数据并没有发生变化。导致数据差异产生脏数据

  1. 开启分布式事务,查看问题是否解决

在创建订单服务添加 <font style="color:#9e880d;">@GlobalTransactional</font>开启全局事务。

Spring Cloud Alibaba Seata - 图26

主配置类上添加开启 <font style="color:#9e880d;">@EnableAutoDataSourceProxy</font>

Spring Cloud Alibaba Seata - 图27

重复上方测试步骤,当触发异常后,所有服务操作的数据都回退到修改之前的状态。此时全局事务生效控制了数据的ACID特性。

常见错误

  1. truncation: Data too long for column 'application_id' at row 1
io.seata.core.exception.TmTransactionException: TransactionException[begin global request failed. xid=null, msg=Data truncation: Data too long for column 'application_id' at row 1]

此时只需要把 application_id 字段调大即可解决