大纲 前言 官方资源 实战案例一 1、案例说明 本案例是电商实战项目,使用了 Seata 的 AT 模式,并基于 Seata 分布式交易解决方案(如下图所示)。
1.1、版本说明 在本文的所有案例中,各个组件统一使用以下版本:
组件 版本 说明 MySQL 8.4.2 Nacos Server 2.4.3 Seata Server(TC) 2.2.0 Spring Boot 3.2.9 Spring Cloud 2023.0.1 Spring Cloud Alibaba 2023.0.1.3
若希望降低 SpringBoot 与 Spring Cloud 的版本,可以尝试以下版本组合(仅供参考):
组件 版本组合一 版本组合二 说明 MySQL 8.4.2 8.4.2 Nacos Server 2.2.3 2.2.3 Seata Server(TC) 2.0.0 2.0.0 Spring Boot 3.1.7 3.0.9 Spring Cloud 2022.0.4 2022.0.2 Spring Cloud Alibaba 2022.0.0.0 2022.0.0.0
特别注意
Spring Boot 和 Spring Cloud 以及 Spring Cloud Alibaba 的版本号需要互相匹配,还有 Seata Server(TC)的版本也要匹配,否则可能会存在各种兼容问题,具体可以参考官方的版本说明 。
1.2、案例目标 本案例将创建三个服务,分别是订单服务、库存服务、账户服务,各服务之间的调用流程如下:
1)当用户下单时,调用订单服务创建一个订单,然后通过远程调用(OpenFeign)让库存服务扣减下单商品的库存 2)订单服务再通过远程调用(OpenFeign)让账户服务来扣减用户账户里面的余额 3)最后在订单服务中修改订单状态为已完成 上述操作跨越了三个数据库,有两次远程调用,很明显会有分布式事务的问题,项目的整体结构如下:
1 2 3 4 5 seata-transaction-demo ├── seata-common-api # API模块 ├── seata-account-service # 账户模块,端口:8085 ├── seata-storage-service # 库存模块,端口:8086 └── seata-order-service # 订单模块,端口:8087
1.3、代码下载 由于篇幅有限,本案例只给出各个模块的核心代码和配置,完整的案例代码可以从 这里 下载得到。 2、准备工作 2.1、安装 Seata 特别注意
严格根据上述的教程内容,通过 Docker 安装好 Seata Server(TC)后,可能需要额外执行以下步骤,保证 SpringBoot 应用(即 Seata Client)可以正常连接 Seata Server(TC)。
若 Spring Boot 应用不是部署 Docker 容器内(即与 Seata 容器处于不同的网络),那么在 docker-compose.yml
文件中,需要添加 SEATA_IP
环境变量来指定 Seata Server(TC) 注册到 Nacos 注册中心的 IP 地址,并且这里的 IP 地址必须是 SpringBoot 应用可以正常访问的(比如指定为宿主机的 IP),否则 Docker 容器外的 SpringBoot 应用在启动时会无法连接 Seata Server(TC),导致启动失败。配置示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 version: '3.5' service: seata: image: seataio/seata-server:2.0.0 container_name: spring-cloud-2024-seata-server restart: always ports: - 8091 :8091 - 7091 :7091 environment: - SEATA_IP=127.0.0.1 - SEATA_PORT=8091 - TZ=Asia/Shanghai volumes: - /usr/local/docker/docker-volumes/seata/config/resources:/seata-server/resources - /usr/local/docker/docker-volumes/seata/logs:/var/log/seata depends_on: mysql8: condition: service_healthy nacos: condition: service_healthy networks: - cloud-network
2.2、初始化数据库 这里使用 MySQL 数据库来存储 Seata Server(TC)的全局事务会话信息,因此需要执行 SQL 初始化脚本(完整) 来创建 Seata 所需的数据库,还有业务所需的数据库。由于 Seata 的 AT 模式需要用到 UNDO_LOG
回滚日志表,因此在每个业务数据库里都要单独创建 UNDO_LOG
回滚日志表,最终所有用到的数据库和业务表如下图所示:
提示
针对 Seata 2.x
版本,Seata Server(TC)所需的多个表结构可以从 这里 获取得到。 针对 Seata 2.x
版本,Seata Client 所需的 UNDO_LOG
表结构可以从 这里 获取得到。 2.3、初始化 Nacos 配置 在 Nacos 配置中心里面,添加 Seata Client 所需的配置信息,SpringBoot 应用(即 Seata Client)在启动时会读取相应的配置信息,如下图所示:
命名空间: public
分组名称: SEATA_GROUP
Data ID:seata-client.properties
配置信息:service.vgroupMapping.default_tx_group=default
3、创建 Maven 父工程 创建 Maven 父工程,配置好工程需要的父级依赖,目的是为了更方便管理与简化配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 <modules > <module > seata-order-service</module > <module > seata-storage-service</module > <module > seata-account-service</module > <module > seata-common-api</module > </modules > <properties > <mysql.version > 8.0.11</mysql.version > <druid.version > 1.1.20</druid.version > <mybatis.springboot.version > 3.0.3</mybatis.springboot.version > <fastjson2.version > 2.0.40</fastjson2.version > <hutool.version > 5.8.22</hutool.version > <lombok.version > 1.18.26</lombok.version > <spring.boot.version > 3.2.9</spring.boot.version > <spring.cloud.version > 2023.0.1</spring.cloud.version > <spring.cloud.alibaba.version > 2023.0.1.3</spring.cloud.alibaba.version > <spring.boot.test.version > 3.1.5</spring.boot.test.version > </properties > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > ${spring.boot.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 > <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 > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > ${mysql.version}</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > ${druid.version}</version > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > ${mybatis.springboot.version}</version > </dependency > <dependency > <groupId > com.alibaba.fastjson2</groupId > <artifactId > fastjson2</artifactId > <version > ${fastjson2.version}</version > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > <version > ${hutool.version}</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > ${lombok.version}</version > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <version > ${spring.boot.test.version}</version > <scope > test</scope > </dependency > </dependencies > </dependencyManagement >
4、创建订单工程 4.1、创建 pom.xml 订单工程的 Maven 配置内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 <dependencies > <dependency > <groupId > com.clay</groupId > <artifactId > seata-common-api</artifactId > <version > 1.0-SNAPSHOT</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-config</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-bootstrap</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-seata</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > com.alibaba.fastjson2</groupId > <artifactId > fastjson2</artifactId > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > </dependencies >
4.2、创建 application.yml 提示
Seata Server(TC) 从 1.5.0
版本开始,配置文件改为 application.yml
,同时向下兼容旧的 register.conf
和 file.conf
两个配置文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 server: port: 8085 spring: application: name: seata-order-service cloud: nacos: discovery: server-addr: 127.0 .0 .1 :8848 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/seata_order?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true username: seata password: seata druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 filters: stat,wall,log4j connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 mybatis: mapper-locations: classpath*:mybatis/mapper/*.xml type-aliases-package: com.clay.entities configuration: map-underscore-to-camel-case: true seata: registry: type: nacos nacos: application: seata-server server-addr: 127.0 .0 .1 :8848 namespace: group: SEATA_GROUP cluster: default config: type: nacos nacos: server-addr: 127.0 .0 .1 :8848 namespace: group: SEATA_GROUP data-id: seata-client.properties username: password: tx-service-group: default_tx_group service: vgroup-mapping: default_tx_group: default data-source-proxy-mode: AT application-id: ${spring.application.name} logging: level: io: seata: info
4.3、添加全局事务注解 在订单创建的入口方法上面添加 @GlobalTransactional
来控制分布式事务,这里使用 OpenFeign 去调用库存服务和账户服务的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 @Slf4j @Service public class OrderServiceImpl implements OrderService { @Resource private OrderMapper orderMapper; @Resource private StorageFeignApi storageFeignApi; @Resource private AccountFeignApi accountFeignApi; @Override @GlobalTransactional(name = "seata-create-order", rollbackFor = Exception.class) public ResultData create (Order order) { String xid = RootContext.getXID(); log.info("==================>开始创建订单" + "\t" + "xid: " + xid); order.setStatus(0 ); int result = orderMapper.insert(order); Order orderFromDB = null ; if (result > 0 ) { orderFromDB = orderMapper.selectById(order.getId()); log.info("-------> 创建订单成功,orderFromDB info: " + orderFromDB); System.out.println(); log.info("-------> 订单微服务开始调用Storage库存服务,开始扣减库存" ); ResultData storageResult = storageFeignApi.decrease(orderFromDB.getProductId(), orderFromDB.getCount()); Assert.isTrue(storageResult.isSuccess(), storageResult.getMessage()); log.info("-------> 订单微服务结束调用Storage库存服务,扣减库存完成" ); System.out.println(); log.info("-------> 订单微服务开始调用Account账户服务,开始扣减账户余额" ); ResultData accountResult = accountFeignApi.decrease(orderFromDB.getUserId(), orderFromDB.getMoney()); Assert.isTrue(accountResult.isSuccess(), accountResult.getMessage()); log.info("-------> 订单微服务结束调用Account账户服务,扣减账户余额完成" ); System.out.println(); log.info("-------> 开始修改订单状态" ); int updateResult = orderMapper.updateStatus(orderFromDB.getId(), 1 ); log.info("-------> 修改订单状态完成" + "\t" + "修改结果: " + updateResult); } else { log.info("-------> 订单创建失败" ); } System.out.println(); log.info("==================>结束创建订单" + "\t" + "xid: " + xid); return ResultData.success(orderFromDB); } }
4.4、创建主启动类 1 2 3 4 5 6 7 8 9 10 11 @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @MapperScan("com.clay.mapper") public class SeataOrderApplication { public static void main (String[] args) { SpringApplication.run(SeataOrderApplication.class, args); } }
5. 创建库存工程 5.1、创建 pom.xml 库存工程的 Maven 配置内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 <dependencies > <dependency > <groupId > com.clay</groupId > <artifactId > seata-common-api</artifactId > <version > 1.0-SNAPSHOT</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-config</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-bootstrap</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-seata</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > com.alibaba.fastjson2</groupId > <artifactId > fastjson2</artifactId > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > </dependencies >
5.2、创建 application.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 server: port: 8086 spring: application: name: seata-storage-service cloud: nacos: discovery: server-addr: 127.0 .0 .1 :8848 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/seata_storage?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true username: seata password: seata druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 filters: stat,wall,log4j connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 mybatis: mapper-locations: classpath*:mybatis/mapper/*.xml type-aliases-package: com.clay.entities configuration: map-underscore-to-camel-case: true seata: registry: type: nacos nacos: application: seata-server server-addr: 127.0 .0 .1 :8848 namespace: group: SEATA_GROUP cluster: default config: type: nacos nacos: server-addr: 127.0 .0 .1 :8848 namespace: group: SEATA_GROUP data-id: seata-client.properties username: password: tx-service-group: default_tx_group service: vgroup-mapping: default_tx_group: default data-source-proxy-mode: AT application-id: ${spring.application.name} logging: level: io: seata: info
5.3、创建业务处理类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Service @Slf4j public class StorageServiceImpl implements StorageService { @Resource private StorageMapper storageMapper; @Override public ResultData decrease (Long productId, Integer count) { Storage storage = storageMapper.selectByProductId(productId); Integer residue = storage.getResidue(); if (count == null || count <= 0 ) { return ResultData.fail(ReturnCodeEnum.RC380); } if (count > residue) { return ResultData.fail(ReturnCodeEnum.STORAGE_NOT_ENOUGH); } log.info("------->库存服务中扣减库存开始" ); storageMapper.decrease(productId, count); log.info("------->库存服务中扣减库存结束" ); return ResultData.success(); } }
5.4、创建主启动类 1 2 3 4 5 6 7 8 9 10 11 @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @MapperScan("com.clay.mapper") public class SeataStorageApplication { public static void main (String[] args) { SpringApplication.run(SeataStorageApplication.class, args); } }
6、创建账户工程 6.1、创建 pom.xml 账户工程的 Maven 配置内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 <dependencies > <dependency > <groupId > com.clay</groupId > <artifactId > seata-common-api</artifactId > <version > 1.0-SNAPSHOT</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-config</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-bootstrap</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-seata</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > com.alibaba.fastjson2</groupId > <artifactId > fastjson2</artifactId > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > </dependencies >
6.2、创建 application.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 server: port: 8087 spring: application: name: seata-account-service cloud: nacos: discovery: server-addr: 127.0 .0 .1 :8848 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/seata_account?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true username: seata password: seata druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 filters: stat,wall,log4j connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 mybatis: mapper-locations: classpath*:mybatis/mapper/*.xml type-aliases-package: com.clay.entities configuration: map-underscore-to-camel-case: true seata: registry: type: nacos nacos: application: seata-server server-addr: 127.0 .0 .1 :8848 namespace: group: SEATA_GROUP cluster: default config: type: nacos nacos: server-addr: 127.0 .0 .1 :8848 namespace: group: SEATA_GROUP data-id: seata-client.properties username: password: tx-service-group: default_tx_group service: vgroup-mapping: default_tx_group: default data-source-proxy-mode: AT application-id: ${spring.application.name} logging: level: io: seata: info
6.3、创建业务处理类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 @Service @Slf4j public class AccountServiceImpl implements AccountService { @Resource private AccountMapper accountMapper; @Override public ResultData decrease (Long userId, BigDecimal money) { Account account = accountMapper.selectByUserId(userId); BigDecimal residue = account.getResidue(); if (money == null || money.compareTo(BigDecimal.ZERO) < 1 ) { return ResultData.fail(ReturnCodeEnum.RC380); } if (money.compareTo(residue) == 1 ) { return ResultData.fail(ReturnCodeEnum.ACCOUNT_NOT_ENOUGH); } log.info("------->账户服务中扣减账户余额开始" ); accountMapper.decrease(userId, money); myTimeOut(); log.info("------->账户服务中扣减账户余额结束" ); return ResultData.success(); } private static void myTimeOut () { try { TimeUnit.SECONDS.sleep(65 ); } catch (InterruptedException e) { e.printStackTrace(); } } private static void myCompute () { int a = 10 / 0 ; } }
6.4、创建主启动类 1 2 3 4 5 6 7 8 9 10 11 @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @MapperScan("com.clay.mapper") public class SeataAccountApplication { public static void main (String[] args) { SpringApplication.run(SeataAccountApplication.class, args); } }
7、案例代码测试 1)首先启动 MySQL Server、Nacos Server、Seata Server,并按照上文介绍的准备工作 进行初始化
2)分别启动 seata-order-service
、seata-account-service
、seata-storage-service
服务
3)浏览器访问 http://127.0.0.1:8848/nacos
打开 Nacos 的控制台,各服务成功启动后,在 Nacos 的控制台里可以看到有多个服务已注册(如下图所示)
4)观察不同数据库中的 seata_account.t_account
、seata_storage.t_storage
业务表的数据,如下图所示:
5)使用 Postman 调用订单创建接口 http://127.0.0.1:8085/order/create?userId=1&count=3&money=20&productId=1
,由于账户服务有阻塞等待的耗时操作,此时通过浏览器 http://127.0.0.1:7091
访问 Seata 的控制台页面,可以看到对应的事务信息和全局锁信息,如下图所示:
6)由于订单服务使用 OpenFeign 远程调用账户服务来扣减账户余额时,抛出了请求超时的异常,因此订单创建接口的最终响应结果如下: 1 2 3 4 5 6 { "timestamp" : "2024-11-09T07:15:52.746+00:00" , "status" : 500 , "error" : "Internal Server Error" , "path" : "/order/create" }
1 2 3 4 5 6 7 8 9 ################### seata-order 服务的日志 ##################### java.net.SocketTimeoutException: Read timed out at java.base/sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:283) ~[na:na] at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:309) ~[na:na] at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:350) ~[na:na] at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:803) ~[na:na] at java.base/java.net.Socket$SocketInputStream.read(Socket.java:966) ~[na:na] at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:244) ~[na:na]
1 2 3 4 5 6 7 8 9 10 11 12 ################### seata-storage 服务的日志 ##################### [seata-storage-service] [nio-8086-exec-1] c.clay.service.impl.StorageServiceImpl : ------->库存服务中扣减库存开始 [seata-storage-service] [nio-8086-exec-1] o.a.seata.rm.AbstractResourceManager : branch register success, xid:127.0.0.1:8091:18598824581371085, branchId:18598824581371091, lockKeys:t_storage:1 [seata-storage-service] [nio-8086-exec-1] ServiceLoader$InnerEnhancedServiceLoader : Load [org.apache.seata.rm.datasource.undo.parser.ProtostuffUndoLogParser] class fail: io/protostuff/runtime/IdStrategy [seata-storage-service] [nio-8086-exec-1] ServiceLoader$InnerEnhancedServiceLoader : Load compatible class io.seata.rm.datasource.undo.parser.spi.JacksonSerializer [seata-storage-service] [nio-8086-exec-1] c.clay.service.impl.StorageServiceImpl : ------->库存服务中扣减库存结束 [seata-storage-service] [_RMROLE_1_1_144] o.a.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:BranchRollbackRequest{xid='127.0.0.1:8091:18598824581371085', branchId=18598824581371091, branchType=AT, resourceId='jdbc:mysql://127.0.0.1:3306/seata_storage', applicationData='null'} [seata-storage-service] [_RMROLE_1_1_144] org.apache.seata.rm.AbstractRMHandler : Branch Rollbacking: 127.0.0.1:8091:18598824581371085 18598824581371091 jdbc:mysql://127.0.0.1:3306/seata_storage [seata-storage-service] [_RMROLE_1_1_144] o.a.s.r.d.undo.AbstractUndoLogManager : xid 127.0.0.1:8091:18598824581371085 branch 18598824581371091, undo_log deleted with GlobalFinished [seata-storage-service] [_RMROLE_1_1_144] o.a.s.rm.datasource.DataSourceManager : branch rollback success, xid:127.0.0.1:8091:18598824581371085, branchId:18598824581371091 [seata-storage-service] [_RMROLE_1_1_144] org.apache.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
1 2 3 4 5 6 7 8 9 10 11 12 ################### seata-account 服务的日志 ##################### [seata-account-service] [nio-8087-exec-1] c.clay.service.impl.AccountServiceImpl : ------->账户服务中扣减账户余额开始 [seata-account-service] [nio-8087-exec-1] o.a.seata.rm.AbstractResourceManager : branch register success, xid:127.0.0.1:8091:18598824581371085, branchId:18598824581371094, lockKeys:t_account:1 [seata-account-service] [nio-8087-exec-1] ServiceLoader$InnerEnhancedServiceLoader : Load [org.apache.seata.rm.datasource.undo.parser.ProtostuffUndoLogParser] class fail: io/protostuff/runtime/IdStrategy [seata-account-service] [nio-8087-exec-1] ServiceLoader$InnerEnhancedServiceLoader : Load compatible class io.seata.rm.datasource.undo.parser.spi.JacksonSerializer [seata-account-service] [_RMROLE_1_1_144] o.a.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:BranchRollbackRequest{xid='127.0.0.1:8091:18598824581371085', branchId=18598824581371094, branchType=AT, resourceId='jdbc:mysql://127.0.0.1:3306/seata_account', applicationData='null'} [seata-account-service] [_RMROLE_1_1_144] org.apache.seata.rm.AbstractRMHandler : Branch Rollbacking: 127.0.0.1:8091:18598824581371085 18598824581371094 jdbc:mysql://127.0.0.1:3306/seata_account [seata-account-service] [_RMROLE_1_1_144] o.a.s.r.d.undo.AbstractUndoLogManager : xid 127.0.0.1:8091:18598824581371085 branch 18598824581371094, undo_log deleted with GlobalFinished [seata-account-service] [_RMROLE_1_1_144] o.a.s.rm.datasource.DataSourceManager : branch rollback success, xid:127.0.0.1:8091:18598824581371085, branchId:18598824581371094 [seata-account-service] [_RMROLE_1_1_144] org.apache.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked [seata-account-service] [nio-8087-exec-1] c.clay.service.impl.AccountServiceImpl : ------->账户服务中扣减账户余额结束
参考资料