【第36章】Spring Cloud之Seata分布式事务

文章目录

  • 前言
  • 一、架构图
    • 1. 介绍
    • 2. 项目结构
    • 3. 功能描述
  • 二、用例
    • 1. 准备
      • 1.1 系统表
      • 1.2 业务表
      • 1.3 初始化数据
    • 2. 项目搭建
      • 2.1 项目结构
      • 2.2 主要依赖
      • 2.3 主要配置
  • 三、主要业务代码
    • 1. 仓储服务
      • 1.1 controller
      • 1.2 service
      • 1.3 dao
    • 2. 订单服务
      • 1.1 controller
      • 1.2 service
      • 1.3 dao
    • 3. 帐户服务
      • 1.1 controller
      • 1.2 service
      • 1.3 dao
    • 4. 商品服务
      • 1.1 controller
      • 1.2 service
      • 1.3 rest
  • 四、业务单元测试
    • 1. 准备
    • 2. 成功测试
      • 2.1 结果
      • 2.2 日志
    • 3. 失败测试
      • 3.1 数据重置
      • 3.2 结果
      • 3.3 日志
      • 3.4 控制台
  • 总结


前言

上一章我们已经搭建好了Seata服务端,这里我们根据官方案例来完成分布式事务。


一、架构图

1. 介绍

在这里插入图片描述
我们以传统电商购物系统作为案例,我们有4个服务分别是

  • Business(商品服务)
  • Storage(仓储服务)
  • Order(订单服务)
  • Account(帐户服务)

2. 项目结构

seata-server负责管理Maven依赖及版本管理,4个子SpringBoot服务,负责各自的职责,共同完成整个下单的流程。

在这里插入图片描述

3. 功能描述

这里我们主要使用openfeign(服务调用)+mybatis(数据存储),使用Seata的AT模式完成分布式事务。

二、用例

1. 准备

1.1 系统表

Seata AT 模式 需要使用到 undo_log 表。

CREATE TABLE IF NOT EXISTS `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(11)      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 AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);

1.2 业务表

CREATE TABLE IF NOT EXISTS  `storage_tbl` (`id` int(11) NOT NULL AUTO_INCREMENT,`commodity_code` varchar(255) DEFAULT NULL,`count` int(11) DEFAULT 0,PRIMARY KEY (`id`),UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;CREATE TABLE IF NOT EXISTS  `order_tbl` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_id` varchar(255) DEFAULT NULL,`commodity_code` varchar(255) DEFAULT NULL,`count` int(11) DEFAULT 0,`money` int(11) DEFAULT 0,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;CREATE TABLE IF NOT EXISTS  `account_tbl` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_id` varchar(255) DEFAULT NULL,`money` int(11) DEFAULT 0,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1  DEFAULT CHARSET=utf8;

1.3 初始化数据

delete from account_tbl;
delete from order_tbl;
delete from storage_tbl;
insert into account_tbl(user_id,money) values('U100001','10000');
insert into storage_tbl(commodity_code,count) values('C00321','100');

2. 项目搭建

这里我们主要以账户服务为主,内容过多其他内容不便演示

2.1 项目结构

在这里插入图片描述

2.2 主要依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</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-seata</artifactId>
</dependency>
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</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>

2.3 主要配置

应用配置(application.yml)

base:config:mdb:hostname: 127.0.0.1 #your mysql server ip addressdbname: seata #your database name for testport: 3306 #your mysql server listening portusername: seata #your mysql server usernamepassword: seata #your mysql server password
server:port: 18084
spring:application:name: account-servicemain:allow-bean-definition-overriding: truecloud:nacos:discovery:server-addr: ${NACOS_SERVER_ADDR}namespace: ${NACOS_NAMESPACE}username: ${NACOS_USERNAME}password: ${NACOS_PASSWORD}group: SEATA_GROUPdatasource:name: storageDataSource#    druid don't support GraalVM now because of there is CGlib proxy#    type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://${base.config.mdb.hostname}:${base.config.mdb.port}/${base.config.mdb.dbname}?useSSL=false&serverTimezone=UTCusername: ${base.config.mdb.username}password: ${base.config.mdb.password}
#    druid:
#      max-active: 20
#      min-idle: 2
#      initial-size: 2
seata:enabled: trueapplication-id: ${spring.application.name}
#  高可用:应用级别的控制,可动态切换
#  tx-service-group: ${spring.application.name}-tx-grouptx-service-group: default_tx_groupconfig:type: nacosnacos:dataId: "seata-client.yaml"server-addr: ${NACOS_SERVER_ADDR}namespace: ${NACOS_NAMESPACE}username: ${NACOS_USERNAME}password: ${NACOS_PASSWORD}registry:type: nacosnacos:server-addr: ${NACOS_SERVER_ADDR}namespace: ${NACOS_NAMESPACE}username: ${NACOS_USERNAME}password: ${NACOS_PASSWORD}group: SEATA_GROUP
mybatis:mapper-locations: classpath:mapper/*.xmlconfiguration:map-underscore-to-camel-case: true

客户端配置(seata-client.yaml)

seata:enabled: trueapplication-id: applicationNametx-service-group: default_tx_groupenable-auto-data-source-proxy: truedata-source-proxy-mode: ATuse-jdk-proxy: falsescan-packages: firstPackage,secondPackageexcludes-for-scanning: firstBeanNameForExclude,secondBeanNameForExcludeexcludes-for-auto-proxying: firstClassNameForExclude,secondClassNameForExcludeclient:rm:async-commit-buffer-limit: 10000report-retry-count: 5table-meta-check-enable: falsereport-success-enable: falsesaga-branch-register-enable: falsesaga-json-parser: fastjsonsaga-retry-persist-mode-update: falsesaga-compensate-persist-mode-update: falsetcc-action-interceptor-order: -2147482648 #Ordered.HIGHEST_PRECEDENCE + 1000sql-parser-type: druidlock:retry-interval: 10retry-times: 30retry-policy-branch-rollback-on-conflict: truetm:commit-retry-count: 5rollback-retry-count: 5default-global-transaction-timeout: 60000degrade-check: falsedegrade-check-period: 2000degrade-check-allow-times: 10interceptor-order: -2147482648 #Ordered.HIGHEST_PRECEDENCE + 1000undo:data-validation: truelog-serialization: jacksonlog-table: undo_logonly-care-update-columns: truecompress:enable: truetype: zipthreshold: 64kload-balance:type: XIDvirtual-nodes: 10service:vgroup-mapping:default_tx_group: defaultgrouplist:default: 192.168.145.128:8091enable-degrade: falsedisable-global-transaction: false      transport:shutdown:wait: 3thread-factory:boss-thread-prefix: NettyBossworker-thread-prefix: NettyServerNIOWorkerserver-executor-thread-prefix: NettyServerBizHandlershare-boss-worker: falseclient-selector-thread-prefix: NettyClientSelectorclient-selector-thread-size: 1client-worker-thread-prefix: NettyClientWorkerThreadworker-thread-size: defaultboss-thread-size: 1type: TCPserver: NIOheartbeat: trueserialization: seatacompressor: noneenable-tm-client-batch-send-request: falseenable-rm-client-batch-send-request: truerpc-rm-request-timeout: 15000rpc-tm-request-timeout: 30000log:exception-rate: 100tcc:fence:log-table-name: tcc_fence_logclean-period: 1hsaga:enabled: falsestate-machine:table-prefix: seata_enable-async: falseasync-thread-pool:core-pool-size: 1max-pool-size: 20keep-alive-time: 60trans-operation-timeout: 1800000service-invoke-timeout: 300000auto-register-resources: trueresources:- classpath*:seata/saga/statelang/**/*.jsondefault-tenant-id: 000001charset: UTF-8          

事务分组配置(service.vgroupMapping.default_tx_group)

default

更多配置请参考官方脚本

三、主要业务代码

1. 仓储服务

1.1 controller

package org.example.storage.controller;import io.seata.core.context.RootContext;
import org.example.storage.service.StorageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/storage")
public class StorageController {private static final Logger LOGGER = LoggerFactory.getLogger(StorageController.class);private static final String SUCCESS = "SUCCESS";private static final String FAIL = "FAIL";@Autowiredprivate StorageService storageService;@GetMapping(value = "/deduct/{commodityCode}/{count}", produces = "application/json")public String deduct(@PathVariable("commodityCode") String commodityCode, @PathVariable("count") int count) {LOGGER.info("Storage Service Begin ... xid: " + RootContext.getXID());int result = storageService.deduct(commodityCode, count);LOGGER.info("Storage Service End ... ");if (result == 1) {return SUCCESS;}return FAIL;}}

1.2 service

package org.example.storage.service;public interface StorageService {/*** 扣减库存** @param commodityCode 商品编号* @param count         扣减数量* @return result   执行结果*/int deduct(String commodityCode, int count);
}
package org.example.storage.service.impl;import io.seata.core.context.RootContext;
import org.example.storage.dao.StorageMapper;
import org.example.storage.model.Storage;
import org.example.storage.service.StorageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;@Service
public class StorageServiceImpl implements StorageService {private static final Logger LOGGER = LoggerFactory.getLogger(StorageService.class);@Resourceprivate StorageMapper storageMapper;@Overridepublic int deduct(String commodityCode, int count) {LOGGER.info("Stock Service Begin ... xid: " + RootContext.getXID());LOGGER.info("Deducting inventory SQL: update stock_tbl set count = count - {} where commodity_code = {}", count,commodityCode);Storage stock = storageMapper.findByCommodityCode(commodityCode);stock.setCount(stock.getCount() - count);int result = storageMapper.updateById(stock);LOGGER.info("Stock Service End ... ");return result;}}

1.3 dao

package org.example.storage.dao;import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.example.storage.model.Storage;
import org.springframework.stereotype.Repository;import java.util.List;@Mapper
@Repository
public interface StorageMapper {Storage selectById(@Param("id") Integer id);Storage findByCommodityCode(@Param("commodityCode") String commodityCode);int updateById(Storage record);void insert(Storage record);void insertBatch(List<Storage> records);int updateBatch(@Param("list") List<Long> ids, @Param("commodityCode") String commodityCode);
}

2. 订单服务

1.1 controller

package org.example.order.controller;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Random;import io.seata.core.context.RootContext;
import org.example.order.service.OrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;@RestController
@RequestMapping("/order")
public class OrderController {private static final Logger LOGGER = LoggerFactory.getLogger(OrderController.class);private static final String SUCCESS = "SUCCESS";private static final String FAIL = "FAIL";@Autowiredprivate OrderService orderService;@PostMapping(value = "/create", produces = "application/json")public String create(@RequestParam("userId") String userId,@RequestParam("commodityCode") String commodityCode,@RequestParam("orderCount") int orderCount) {LOGGER.info("Order Service Begin ... xid: " + RootContext.getXID());int result = orderService.create(userId, commodityCode, orderCount);LOGGER.info("Order Service End ... Created " + result);if (result == 2) {return SUCCESS;}return FAIL;}}

1.2 service

package org.example.order.service;public interface OrderService {/*** 创建订单** @param userId        用户ID* @param commodityCode 商品编号* @param orderCount    订购数量* @return result   执行结果*/int create(String userId, String commodityCode, int orderCount);
}
package org.example.order.service.impl;import org.example.order.dao.OrderMapper;
import org.example.order.feign.AccountService;
import org.example.order.model.Order;
import org.example.order.service.OrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;@Service
public class OrderServiceImpl implements OrderService {private static final Logger LOGGER = LoggerFactory.getLogger(OrderService.class);private static final String SUCCESS = "SUCCESS";@Resourceprivate AccountService accountService;@Resourceprivate OrderMapper orderMapper;@Overridepublic int create(String userId, String commodityCode, int count) {int result=0;Order order = new Order();order.setUserId(userId);order.setCommodityCode(commodityCode);order.setCount(count);int orderMoney = calculate(count);order.setMoney(orderMoney);//保存订单result+=orderMapper.insert(order);//扣减余额if(SUCCESS.equalsIgnoreCase(accountService.debit(userId, orderMoney))){result+=1;}return result;}private int calculate(int count) {return 200 * count;}}

1.3 dao

package org.example.order.dao;import org.apache.ibatis.annotations.Mapper;
import org.example.order.model.Order;
import org.springframework.stereotype.Repository;@Mapper
@Repository
public interface OrderMapper {int insert(Order record);}

3. 帐户服务

1.1 controller

package org.example.account.controller;import io.seata.core.context.RootContext;
import org.example.account.service.AccountService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/account")
public class AccountController {private static final Logger LOGGER = LoggerFactory.getLogger(AccountController.class);private static final String SUCCESS = "SUCCESS";private static final String FAIL = "FAIL";@Autowiredprivate AccountService accountService;@PostMapping(value = "/debit", produces = "application/json")public String debit(@RequestParam("userId") String userId,@RequestParam("money") int money) {LOGGER.info("Account Service ... xid: " + RootContext.getXID());int result = accountService.debit(userId, money);LOGGER.info("Account Service End ... ");if (result == 1) {return SUCCESS;}return FAIL;}}

1.2 service

package org.example.account.service;public interface AccountService {/*** 余额扣款** @param userId 用户ID* @param money  扣款金额* @return result 处理结果*/int debit(String userId, int money);
}
package org.example.account.service.impl;import org.example.account.dao.AccountMapper;
import org.example.account.model.Account;
import org.example.account.service.AccountService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;@Service
public class AccountServiceImpl implements AccountService {private static final Logger LOGGER = LoggerFactory.getLogger(AccountService.class);@Resourceprivate AccountMapper accountMapper;@Overridepublic int debit(String userId, int money) {LOGGER.info("Deducting balance SQL: update account_tbl set money = money - {} where user_id = {}", money,userId);Account account = accountMapper.selectByUserId(userId);account.setMoney(money);return accountMapper.updateById(account);}
}

1.3 dao

package org.example.account.dao;import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.example.account.model.Account;
import org.springframework.stereotype.Repository;@Mapper
@Repository
public interface AccountMapper {Account selectByUserId(@Param("userId") String userId);int updateById(Account record);}

4. 商品服务

商品服务包含了rest和feign两种请求方式

1.1 controller

package org.example.business.controller;import io.seata.spring.annotation.GlobalTransactional;
import org.example.business.feign.OrderService;
import org.example.business.feign.StorageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;@RestController
public class BusinessController {private static final Logger LOGGER = LoggerFactory.getLogger(BusinessController.class);private static final String SUCCESS = "SUCCESS";private static final String FAIL = "FAIL";private static final String USER_ID = "U100001";private static final String COMMODITY_CODE = "C00321";private static final int ORDER_COUNT = 2;@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate OrderService orderService;@Autowiredprivate StorageService storageService;@GlobalTransactional(timeoutMills = 300000, name = "spring-cloud-demo-tx")@GetMapping(value = "/seata/rest", produces = "application/json")public String rest() {String result = restTemplate.getForObject("http://storage-service/storage/deduct/" + COMMODITY_CODE + "/" + ORDER_COUNT,String.class);if (!SUCCESS.equals(result)) {throw new RuntimeException();}String url = "http://order-service/order/create";HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();map.add("userId", USER_ID);map.add("commodityCode", COMMODITY_CODE);map.add("orderCount", ORDER_COUNT + "");HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);ResponseEntity<String> response;try {response = restTemplate.postForEntity(url, request, String.class);}catch (Exception exx) {throw new RuntimeException("mock error");}result = response.getBody();if (!SUCCESS.equals(result)) {throw new RuntimeException();}return SUCCESS;}@GlobalTransactional(timeoutMills = 300000, name = "spring-cloud-demo-tx")@GetMapping(value = "/seata/feign", produces = "application/json")public String feign() {String result = storageService.deduct(COMMODITY_CODE, ORDER_COUNT);if (!SUCCESS.equals(result)) {throw new RuntimeException();}result = orderService.create(USER_ID, COMMODITY_CODE, ORDER_COUNT);if (!SUCCESS.equals(result)) {throw new RuntimeException();}return SUCCESS;}}

1.2 service

package org.example.business.feign;import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;@FeignClient(name = "storage-service")
public interface StorageService {/*** 扣减库存** @param commodityCode 商品编号* @param count         扣减数量* @return result   执行结果*/@GetMapping(value = "/storage/deduct/{commodityCode}/{count}")String deduct(@PathVariable("commodityCode") String commodityCode, @PathVariable("count") int count);
}
package org.example.business.feign;import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;@FeignClient(name = "order-service")
public interface OrderService {/*** 创建订单** @param userId        用户ID* @param commodityCode 商品编号* @param orderCount    订购数量* @return result   执行结果*/@PostMapping(value = "/order/create")String create(@RequestParam("userId") String userId,@RequestParam("commodityCode") String commodityCode,@RequestParam("orderCount") int orderCount);
}

1.3 rest

package org.example.business.config;import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;/*** Create by zjg on 2024/9/15*/
@Configuration
public class BusinessConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();}
}

四、业务单元测试

1. 准备

首先启动我们的服务,并保证服务正常注册到Nacos

在这里插入图片描述
在这里插入图片描述

2. 成功测试

2.1 结果

localhost:18081/seata/feign
在这里插入图片描述
在这里插入图片描述

2.2 日志

我们每个服务都能从上下文中获取到xid

2024-09-15T21:37:32.765+08:00  INFO 10488 --- [business-service] [io-18081-exec-9] i.seata.tm.api.DefaultGlobalTransaction  : Begin new global transaction [192.168.145.128:8091:2162292867793093336]
2024-09-15T21:37:32.977+08:00  INFO 10488 --- [business-service] [io-18081-exec-9] i.seata.tm.api.DefaultGlobalTransaction  : transaction 192.168.145.128:8091:2162292867793093336 will be commit
2024-09-15T21:37:32.984+08:00  INFO 10488 --- [business-service] [io-18081-exec-9] i.seata.tm.api.DefaultGlobalTransaction  : transaction end, xid = 192.168.145.128:8091:2162292867793093336
2024-09-15T21:37:32.984+08:00  INFO 10488 --- [business-service] [io-18081-exec-9] i.seata.tm.api.DefaultGlobalTransaction  : [192.168.145.128:8091:2162292867793093336] commit status: Committed2024-09-15T21:37:32.768+08:00  INFO 4560 --- [storage-service] [io-18082-exec-5] o.e.s.controller.StorageController       : Storage Service Begin ... xid: 192.168.145.128:8091:2162292867793093336
2024-09-15T21:37:32.769+08:00  INFO 4560 --- [storage-service] [io-18082-exec-5] o.e.storage.service.StorageService       : Stock Service Begin ... xid: 192.168.145.128:8091:2162292867793093336
2024-09-15T21:37:32.769+08:00  INFO 4560 --- [storage-service] [io-18082-exec-5] o.e.storage.service.StorageService       : Deducting inventory SQL: update stock_tbl set count = count - 2 where commodity_code = C00321
2024-09-15T21:37:32.777+08:00  INFO 4560 --- [storage-service] [io-18082-exec-5] io.seata.rm.AbstractResourceManager      : branch register success, xid:192.168.145.128:8091:2162292867793093336, branchId:2162292867793093338, lockKeys:storage_tbl:1
2024-09-15T21:37:32.781+08:00  INFO 4560 --- [storage-service] [io-18082-exec-5] o.e.storage.service.StorageService       : Stock Service End ... 
2024-09-15T21:37:32.781+08:00  INFO 4560 --- [storage-service] [io-18082-exec-5] o.e.s.controller.StorageController       : Storage Service End ... 
2024-09-15T21:37:33.600+08:00  INFO 4560 --- [storage-service] [h_RMROLE_1_7_16] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:BranchCommitRequest{xid='192.168.145.128:8091:2162292867793093336', branchId=2162292867793093338, branchType=AT, resourceId='jdbc:mysql://192.168.145.128:3306/seata', applicationData='null'}
2024-09-15T21:37:33.601+08:00  INFO 4560 --- [storage-service] [h_RMROLE_1_7_16] io.seata.rm.AbstractRMHandler            : Branch committing: 192.168.145.128:8091:2162292867793093336 2162292867793093338 jdbc:mysql://192.168.145.128:3306/seata null
2024-09-15T21:37:33.601+08:00  INFO 4560 --- [storage-service] [h_RMROLE_1_7_16] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed2024-09-15T21:37:32.785+08:00  INFO 11676 --- [order-service] [io-18083-exec-9] o.e.order.controller.OrderController     : Order Service Begin ... xid: 192.168.145.128:8091:2162292867793093336
2024-09-15T21:37:32.796+08:00  INFO 11676 --- [order-service] [io-18083-exec-9] io.seata.rm.AbstractResourceManager      : branch register success, xid:192.168.145.128:8091:2162292867793093336, branchId:2162292867793093340, lockKeys:order_tbl:2
2024-09-15T21:37:32.975+08:00  INFO 11676 --- [order-service] [io-18083-exec-9] o.e.order.controller.OrderController     : Order Service End ... Created 2
2024-09-15T21:37:33.695+08:00  INFO 11676 --- [order-service] [h_RMROLE_1_7_16] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:BranchCommitRequest{xid='192.168.145.128:8091:2162292867793093336', branchId=2162292867793093340, branchType=AT, resourceId='jdbc:mysql://192.168.145.128:3306/seata', applicationData='null'}
2024-09-15T21:37:33.695+08:00  INFO 11676 --- [order-service] [h_RMROLE_1_7_16] io.seata.rm.AbstractRMHandler            : Branch committing: 192.168.145.128:8091:2162292867793093336 2162292867793093340 jdbc:mysql://192.168.145.128:3306/seata null
2024-09-15T21:37:33.695+08:00  INFO 11676 --- [order-service] [h_RMROLE_1_7_16] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed2024-09-15T21:37:32.803+08:00  INFO 12564 --- [account-service] [io-18084-exec-5] o.e.a.controller.AccountController       : Account Service ... xid: 192.168.145.128:8091:2162292867793093336
2024-09-15T21:37:32.803+08:00  INFO 12564 --- [account-service] [io-18084-exec-5] o.e.account.service.AccountService       : Deducting balance SQL: update account_tbl set money = money - 400 where user_id = U100001
2024-09-15T21:37:32.970+08:00  INFO 12564 --- [account-service] [io-18084-exec-5] io.seata.rm.AbstractResourceManager      : branch register success, xid:192.168.145.128:8091:2162292867793093336, branchId:2162292867793093342, lockKeys:account_tbl:1
2024-09-15T21:37:32.973+08:00  INFO 12564 --- [account-service] [io-18084-exec-5] o.e.a.controller.AccountController       : Account Service End ... 
2024-09-15T21:37:33.697+08:00  INFO 12564 --- [account-service] [h_RMROLE_1_7_16] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:BranchCommitRequest{xid='192.168.145.128:8091:2162292867793093336', branchId=2162292867793093342, branchType=AT, resourceId='jdbc:mysql://192.168.145.128:3306/seata', applicationData='null'}
2024-09-15T21:37:33.698+08:00  INFO 12564 --- [account-service] [h_RMROLE_1_7_16] io.seata.rm.AbstractRMHandler            : Branch committing: 192.168.145.128:8091:2162292867793093336 2162292867793093342 jdbc:mysql://192.168.145.128:3306/seata null
2024-09-15T21:37:33.698+08:00  INFO 12564 --- [account-service] [h_RMROLE_1_7_16] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed

3. 失败测试

失败的案例怎么做呢?简单,帐户服务作为作为购物生命周期的最后一环,我们在账户服务产生异常即可

3.1 数据重置

truncate table account_tbl;
truncate table order_tbl;
truncate table storage_tbl;
insert into account_tbl(user_id,money) values('U100001','10000');
insert into storage_tbl(commodity_code,count) values('C00321','100');

3.2 结果

localhost:18081/seata/rest
在这里插入图片描述
在这里插入图片描述

可以看到我们的数据是没有发生变化的

3.3 日志

此处日志过多,仅剪辑关键部分

2024-09-15T21:58:20.193+08:00  INFO 10488 --- [business-service] [io-18081-exec-6] i.seata.tm.api.DefaultGlobalTransaction  : Begin new global transaction [192.168.145.128:8091:2162292867793093649]
2024-09-15T21:58:20.331+08:00  INFO 10488 --- [business-service] [io-18081-exec-6] i.seata.tm.api.DefaultGlobalTransaction  : transaction 192.168.145.128:8091:2162292867793093649 will be rollback
2024-09-15T21:58:20.381+08:00  INFO 10488 --- [business-service] [io-18081-exec-6] i.seata.tm.api.DefaultGlobalTransaction  : transaction end, xid = 192.168.145.128:8091:2162292867793093649
2024-09-15T21:58:20.381+08:00  INFO 10488 --- [business-service] [io-18081-exec-6] i.seata.tm.api.DefaultGlobalTransaction  : [192.168.145.128:8091:2162292867793093649] rollback status: Rollbacked
2024-09-15T21:58:20.381+08:00 ERROR 10488 --- [business-service] [io-18081-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.RuntimeException: try to proceed invocation error] with root cause
java.lang.RuntimeException: mock errorat org.example.business.controller.BusinessController.rest(BusinessController.java:59) ~[classes/:na]2024-09-15T21:58:20.195+08:00  INFO 4560 --- [storage-service] [io-18082-exec-8] o.e.s.controller.StorageController       : Storage Service Begin ... xid: 192.168.145.128:8091:2162292867793093649
2024-09-15T21:58:20.195+08:00  INFO 4560 --- [storage-service] [io-18082-exec-8] o.e.storage.service.StorageService       : Stock Service Begin ... xid: 192.168.145.128:8091:2162292867793093649
2024-09-15T21:58:20.195+08:00  INFO 4560 --- [storage-service] [io-18082-exec-8] o.e.storage.service.StorageService       : Deducting inventory SQL: update stock_tbl set count = count - 2 where commodity_code = C00321
2024-09-15T21:58:20.204+08:00  INFO 4560 --- [storage-service] [io-18082-exec-8] io.seata.rm.AbstractResourceManager      : branch register success, xid:192.168.145.128:8091:2162292867793093649, branchId:2162292867793093651, lockKeys:storage_tbl:1
2024-09-15T21:58:20.207+08:00  INFO 4560 --- [storage-service] [io-18082-exec-8] o.e.storage.service.StorageService       : Stock Service End ... 
2024-09-15T21:58:20.207+08:00  INFO 4560 --- [storage-service] [io-18082-exec-8] o.e.s.controller.StorageController       : Storage Service End ... 
2024-09-15T21:58:20.337+08:00  INFO 4560 --- [storage-service] [_RMROLE_1_14_16] i.s.c.r.p.c.RmBranchRollbackProcessor    : rm handle branch rollback process:BranchRollbackRequest{xid='192.168.145.128:8091:2162292867793093649', branchId=2162292867793093651, branchType=AT, resourceId='jdbc:mysql://192.168.145.128:3306/seata', applicationData='null'}
2024-09-15T21:58:20.337+08:00  INFO 4560 --- [storage-service] [_RMROLE_1_14_16] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 192.168.145.128:8091:2162292867793093649 2162292867793093651 jdbc:mysql://192.168.145.128:3306/seata
2024-09-15T21:58:20.344+08:00  INFO 4560 --- [storage-service] [_RMROLE_1_14_16] i.s.r.d.undo.AbstractUndoLogManager      : xid 192.168.145.128:8091:2162292867793093649 branch 2162292867793093651, undo_log deleted with GlobalFinished
2024-09-15T21:58:20.344+08:00  INFO 4560 --- [storage-service] [_RMROLE_1_14_16] i.seata.rm.datasource.DataSourceManager  : branch rollback success, xid:192.168.145.128:8091:2162292867793093649, branchId:2162292867793093651
2024-09-15T21:58:20.344+08:00  INFO 4560 --- [storage-service] [_RMROLE_1_14_16] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked2024-09-15T21:58:20.210+08:00  INFO 11676 --- [order-service] [io-18083-exec-7] o.e.order.controller.OrderController     : Order Service Begin ... xid: 192.168.145.128:8091:2162292867793093649
2024-09-15T21:58:20.221+08:00  INFO 11676 --- [order-service] [io-18083-exec-7] io.seata.rm.AbstractResourceManager      : branch register success, xid:192.168.145.128:8091:2162292867793093649, branchId:2162292867793093653, lockKeys:order_tbl:2
2024-09-15T21:58:20.330+08:00 ERROR 11676 --- [order-service] [io-18083-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: feign.FeignException$InternalServerError: [500] during [POST] to [http://account-service/account/debit?userId=U100001&money=400] [AccountService#debit(String,int)]: [{"timestamp":"2024-09-15T13:58:20.329+00:00","status":500,"error":"Internal Server Error","path":"/account/debit"}]] with root cause
feign.FeignException$InternalServerError: [500] during [POST] to [http://account-service/account/debit?userId=U100001&money=400] [AccountService#debit(String,int)]: [{"timestamp":"2024-09-15T13:58:20.329+00:00","status":500,"error":"Internal Server Error","path":"/account/debit"}]
2024-09-15T21:58:20.348+08:00  INFO 11676 --- [order-service] [_RMROLE_1_14_16] i.s.c.r.p.c.RmBranchRollbackProcessor    : rm handle branch rollback process:BranchRollbackRequest{xid='192.168.145.128:8091:2162292867793093649', branchId=2162292867793093653, branchType=AT, resourceId='jdbc:mysql://192.168.145.128:3306/seata', applicationData='null'}
2024-09-15T21:58:20.349+08:00  INFO 11676 --- [order-service] [_RMROLE_1_14_16] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 192.168.145.128:8091:2162292867793093649 2162292867793093653 jdbc:mysql://192.168.145.128:3306/seata
2024-09-15T21:58:20.376+08:00  INFO 11676 --- [order-service] [_RMROLE_1_14_16] i.s.r.d.undo.AbstractUndoLogManager      : xid 192.168.145.128:8091:2162292867793093649 branch 2162292867793093653, undo_log deleted with GlobalFinished
2024-09-15T21:58:20.377+08:00  INFO 11676 --- [order-service] [_RMROLE_1_14_16] i.seata.rm.datasource.DataSourceManager  : branch rollback success, xid:192.168.145.128:8091:2162292867793093649, branchId:2162292867793093653
2024-09-15T21:58:20.377+08:00  INFO 11676 --- [order-service] [_RMROLE_1_14_16] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked2024-09-15T21:58:20.328+08:00  INFO 12564 --- [account-service] [io-18084-exec-3] o.e.a.controller.AccountController       : Account Service ... xid: 192.168.145.128:8091:2162292867793093649
2024-09-15T21:58:20.328+08:00 ERROR 12564 --- [account-service] [io-18084-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.RuntimeException: seata fail test] with root cause
java.lang.RuntimeException: seata fail testat org.example.account.controller.AccountController.debit(AccountController.java:29) ~[classes/:na]

从日志中可以看出,仓储服务和订单服务已经执行,发生异常后进行了回退的动作,并且在这个过程中之前创建的undo_log表,也就是AT需要借助数据库完成分布式事务的功能

3.4 控制台

从控制台也能看到我们的事务执行信息,但是这个过程很短,事务执行完成几秒后可能就删除了

在这里插入图片描述


总结

回到顶部

分布式事务到这里就要告一段落了,希望大家都能学到东西吧,这部分的源码已经上传附件,over!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/425094.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mac清理其他文件:释放存储空间的高效指南

每个Mac用户都可能遇到存储空间不足的问题&#xff0c;尤其是当“其他”文件积累到一定体积时。在Mac上&#xff0c;“其他”文件通常包括各种系统文件、缓存、文档以及不被归类为应用程序、照片、电影或音乐的其他类型的文件。这些文件往往不易被注意&#xff0c;但逐渐占用了…

【笔记】CCF直播:《如何在国际会议上有效交流》(2024-9-15)

目录 一、提问的勇气二、提问什么三、其他主题的报告为什么听四、会议前怎么读大量论文&#xff1f;五、workshop为什么参加&#xff1f;Poster环节&#xff1f;六、提问环节七、其他 今天听了《如何在国际会议上有效交流》的直播讲座&#xff0c;记录一些笔记。 一、提问的勇…

我又做了一个国标GB28181设备模拟器的Windows版本,让国标28181开发更简单,不用再费劲弄个摄像机来调试国标GB28181开发了

之前我搞过一个《EasyGBD国标GB28181设备端模拟器帮助测试国标GB28181平台&#xff08;EasyGBD-&#xff1e;EasyGBS&#xff09;》&#xff0c;当时&#xff0c;主要是在安卓手机上&#xff0c;用摄像机的本地摄像头来做为视频源、用摄像机的麦克风做为音频源&#xff0c;对外…

微服务网关终极进化:设计模式驱动的性能与可用性优化(四)

时间&#xff1a;2024年09月12日 作者&#xff1a;小蒋聊技术 邮箱&#xff1a;wei_wei10163.com 微信&#xff1a;wei_wei10 希望大家帮个忙&#xff01;如果大家有工作机会&#xff0c;希望帮小蒋推荐一下&#xff0c;小蒋希望遇到一个认真做事的团队&#xff0c;一起努力…

CefSharp_Vue交互(Element UI)_WinFormWeb应用(3)---通过页面锁屏和关机(含示例代码)

一、预览 实现功能:通过vue标题栏按钮锁屏和关机 1.1 预览 1.2 代码 锁屏代码csharp LockWorkStation() 关机代码chsharp 注意vue代码参数和此参数一致(0/1/2) 方法ExitWindowsEx()

python 读取excel

一、安装依赖&#xff1a; pandas 二、新建excel 示例数据&#xff1a;students.xlsx 三、定义类&#xff1a;student.py Student class Student:def __init__(self, name, sex):self.name nameself.sex sexdef show(self):print(f姓名&#xff1a;{self.name} 性别&#…

828华为云征文 | 使用Flexus云服务器X实例部署GLPI资产管理系统

828华为云征文 | 使用Flexus云服务器X实例部署GLPI资产管理系统 1. 部署环境说明2. 部署基础环境2.1. 操作系统基本配置2.2. 部署Nginx2.3. 部署MySQL2.4. 部署PHP 3. 部署GLPI资产管理系统 1. 部署环境说明 本次环境选择使用华为云Flexus云服务器X实例&#xff0c;因为其具有高…

初学Linux(学习笔记)

初学Linux&#xff08;学习笔记&#xff09; 前言 本文跳过了Linux前期的环境准备&#xff0c;直接从知识点和指令开始。 知识点&#xff1a; 1.目录文件夹&#xff08;Windows&#xff09; 2.文件内容属性 3.在Windows当中区分文件类型是通过后缀&#xff0c;而Linux是通过…

【Linux下的cpp】编译调试(gcc、g++、gdb)

【Linux下的cpp】编译调试&#xff08;gcc、g、gdb&#xff09; 文章目录 【Linux下的cpp】编译调试&#xff08;gcc、g、gdb&#xff09;简述gcc、g、gdb编译过程g 编译参数命令行编译演练1、直接编译2、生成库文件并编译链接静态库并生成可执行文件链接动态库生成可执行文件 …

Mistral AI再创新高,Pixtral 12B多模态模型强势来袭

前沿科技速递&#x1f680; 近日&#xff0c;Mistral AI 发布了其首款多模态大模型——Pixtral 12B。作为一款具有语言与视觉处理能力的模型&#xff0c;Pixtral 12B 支持高达10241024像素的图像&#xff0c;具备强大的文本生成、图像理解与生成能力&#xff0c;能够处理复杂的…

Dubbo从入门到实战

Dubbo从入门到实战 1、为什么需要dubbo 很多时候&#xff0c;其实我们使用这个技术的时候&#xff0c;可能都是因为项目需要&#xff0c;所以&#xff0c;我们就用了&#xff0c;但是&#xff0c;至于为什么我们需 要用到这个技术&#xff0c;可能自身并不是很了解的&#x…

【C++STL简介】——我与C++的不解之缘(八)

前言 学过了C的模版&#xff0c;接下来学习C中的STL&#xff08;标准模版库&#xff09;&#xff0c;先来了解一下STL是啥 一、什么是STL STL&#xff08;standard template libaray 标准模版库&#xff09;&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的…

Zookeeper工作机制、特点、数据结构、应用场景、配置参数解读

ZK工作机制 从涉及模式角度来理解&#xff1a;是一个基于观察者模式设计的分布式服务管理框架&#xff0c;负责存储和管理大家都关心的数据&#xff0c;然后接受观察者的注册&#xff0c;一旦这些数据的状态发生变化&#xff0c;zk就负责通知已在zk上注册的那些观察者做出相应…

【FATFS】FATFS简介及下载

1、FATFS简介 FatFs 是一个针对嵌入式系统开发的通用文件系统模块&#xff0c;主要用于支持 FAT 文件系统。它最初由 ChaN 开发&#xff0c;并被广泛应用于嵌入式设备上。FatFs 以其轻量级、可配置和设备无关的特性著称&#xff0c;支持 FAT12、FAT16、FAT32 以及 exFAT 文件系…

Linux:进程状态和优先级

一、进程状态 1.1 操作系统学科&#xff08;运行、阻塞、挂起&#xff09; 为了弄明白正在运行的进程是什么意思&#xff0c;我们需要知道进程的不同状态 大多数操作系统都遵循以下原则 1.1.1 运行状态 因为有一个调度器需要确保CPU的资源被合理使用&#xff0c;所以需要维护…

【AI大模型】ChatGPT模型原理介绍(下)

目录 &#x1f354; GPT-3介绍 1.1 GPT-3模型架构 1.2 GPT-3训练核心思想 1.3 GPT-3数据集 1.4 GPT-3模型的特点 1.5 GPT-3模型总结 &#x1f354; ChatGPT介绍 2.1 ChatGPT原理 2.2 什么是强化学习 2.3 ChatGPT强化学习步骤 2.4 监督调优模型 2.5 训练奖励模型 2.…

【H2O2|全栈】关于CSS(1)CSS基础(一)

目录 CSS基础知识 前言 准备工作 啥是CSS&#xff1f; 如何引用CSS&#xff1f; 选择器 通配符选择器 类名&#xff08;class&#xff09;选择器 id选择器 CSS解析顺序&#xff08;优先级&#xff09; 常见CSS标签&#xff08;一&#xff09; 字体属性 font-style…

SQL Server开启网络访问

目前工作中很少用到SQL Server了&#xff0c;最近需要测试几个表&#xff0c;需要搭建一个SQL Server数据库服务&#xff0c;这里做个总结吧。 安装这里就不做详细介绍了&#xff0c;本文只介绍如何开启SQL Server网络访问。 1、云服务器安全组设置 如果是搭建在云服务器上&a…

[数据集][目标检测]智慧交通铁路异物入侵检测数据集VOC+YOLO格式802张7类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;802 标注数量(xml文件个数)&#xff1a;802 标注数量(txt文件个数)&#xff1a;802 标注类别…

虚幻引擎 | (类恐鬼症)玩家和NPC语音聊天(下)

上下文Conversation Array 要让GPT记住上下文&#xff0c;实现GPT4里的连续对话功能&#xff0c;需要把以下内容存入conversation array中去。 NPC background storyuser input promptNPC anwser open AI API的JsonObject JSONObject是一种数据结构&#xff0c;可以理解为JSO…