本文基於SpringBoot+Dubbo+Nacos+Seata+MyBatis-Plus搭建分佈式事務案例,模擬電商下單場景,包含扣減庫存、賬户餘額和創建訂單三個關鍵步驟。案例完整展示了分佈式系統中如何保證多個服務操作的原子性,為處理跨服務事務提供實踐參考。
1、案例搭建
1.1、業務説明
本案例演示的是下單的過程,具體流程包括:
- 扣減庫存
- 扣減賬户餘額
- 創建訂單
我們知道這是一個典型的分佈式場景,這三個環節要麼同時成功,要麼一起失敗回滾
1.2、項目搭建
我的項目結構大致如下圖所示,需要注意的是在這之上還有一個,所以要注意pom文件要注意一下
1.2.1、seta-demo
seata-demo作為演示代碼的父工程,下面包含狂起來的幾個模塊,pom文件如下:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.15</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.2.6</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
<version>3.2.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.27</version>
</dependency>
</dependencies>
</dependencyManagement>
1.2.2、seata-mall
1.2.2.1、pom文件版本依賴
<dependencies>
<dependency>
<groupId>com.study</groupId>
<artifactId>mall-order-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.study</groupId>
<artifactId>mall-user-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.study</groupId>
<artifactId>mall-product-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
</dependency>
</dependencies>
1.2.2.2、配置文件
application.properties
server.port=8080
spring.application.name=seata-mall
dubbo.application.name=seata-mall
dubbo.registry.address=nacos://${NACOS_ADDRESS}
dubbo.registry.parameters.username=${NACOS_USERNAME}
dubbo.registry.parameters.password=${NACOS_PASSWORD}
dubbo.consumer.check=false
dubbo.application.qos-port=22220
dubbo.protocol.name=tri
dubbo.protocol.port=50050
1.2.2.3、啓動類
SeataMallBootstrap.java
@EnableDubbo
@SpringBootApplication
public class SeataMallBootstrap {
public static void main(String[] args) {
SpringApplication.run(SeataMallBootstrap.class, args);
}
}
1.2.2.4、業務接口和實現類
IBusinessService.java
public interface IBusinessService {
/**
* 下單
* @param userId 用户ID
* @param productId 商品ID
* @param number 數量
* @return
*/
String order(String userId, String productId, Integer number);
}
BusinessServiceImpl.java
@Slf4j
@Service
public class BusinessServiceImpl implements IBusinessService {
@DubboReference
private IUserService userService;
@DubboReference
private IProductService productService;
@DubboReference
private IOrderService orderService;
@Override
public String order(String userId, String productId, Integer number) {
//獲取單價
Long price = this.productService.getPrice(productId);
//扣減庫存
this.productService.detectStock(productId, number);
Long amount = price * number;
//扣減用户餘額
this.userService.deductMoney(userId, amount);
//創建訂單
this.orderService.createOrder(userId, productId, number, amount);
return "訂單創建成功";
}
}
1.2.2.5、接口請求對象
UserOrderRequest.java
@Data
public class UserOrderRequest implements Serializable {
private String userId;
private String productId;
private Integer number;
private Integer price;
}
1.2.2.6、接口
OrderController.java
@RestController
public class OrderController {
@Resource
private IBusinessService businessService;
@PostMapping("/order")
public String order(@RequestBody UserOrderRequest orderRequest){
return this.businessService.order(
orderRequest.getUserId(),
orderRequest.getProductId(),
orderRequest.getNumber()
);
}
}
1.2.3、seata-order
1.2.3.1、mall-order-api
api比較簡單,只有一個簡單的接口定義IOrderService.java
public interface IOrderService {
/**
* 創建訂單
* @param userId 用户id
* @param productId 商品id
* @param number 數量
* @param price 金額
*/
void createOrder(String userId, String productId, Integer number, Long price);
}
1.2.3.2、mall-order-provider
1.2.3.2.1、定義訂單實體和對應的mapper
Order.java
@Data
@TableName("mall_order")
public class Order implements java.io.Serializable {
@TableId(type = IdType.AUTO)
private Integer orderId;
private String userId;
private String productId;
private Integer num;
private Long amount;
}
OrderMapper.java
public interface OrderMapper extends BaseMapper<Order> {
}
1.2.3.2.2、接口實現類
OrderServiceImpl.java
@Slf4j
@DubboService
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
@Override
public void createOrder(String userId, String productId, Integer number, Long amount) {
log.info("createOrder: userId = {}, productId = {}, number = {}, amount = {}", userId , productId , number, amount);
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setNum(number);
order.setAmount(amount);
this.baseMapper.insert(order);
}
}
1.2.3.2.3、啓動類
SeataMallOrderBootstrap.java
@EnableDubbo
@SpringBootApplication
@MapperScan(basePackages = "com.study.mall.order")
public class SeataMallOrderBootstrap {
public static void main(String[] args) {
SpringApplication.run(SeataMallOrderBootstrap.class, args);
}
}
1.2.3.2.4、配置文件
application.properties
server.port=8081
spring.application.name=mall-order-provider
spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.max-active=50
spring.datasource.druid.max-wait=60000
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
spring.datasource.druid.filters=stat,wall
spring.datasource.druid.url=jdbc:mysql://${MYSQL_ADDRESS}/seata_mall_order?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.druid.username=${MYSQL_USERNAME}
spring.datasource.druid.password=${MYSQL_PASSWORD}
dubbo.application.name=mall-order-provider
dubbo.registry.address=nacos://${NACOS_ADDRESS}
dubbo.registry.register-mode=instance
dubbo.registry.parameters.username=${NACOS_USERNAME}
dubbo.registry.parameters.password=${NACOS_PASSWORD}
dubbo.application.qos-port=22221
dubbo.protocol.name=tri
dubbo.protocol.port=50051
1.2.3.2.5、pom版本依賴
<dependencies>
<dependency>
<groupId>com.study</groupId>
<artifactId>mall-order-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
1.2.4、seata-product
1.2.4.1、mall-product-api
api也是簡單的一個接口定義IProductService.java
public interface IProductService {
/**
* 扣減庫存
* @param productId 商品id
* @param number 扣減數量
*/
void detectStock(String productId, Integer number);
/**
* 獲取商品價格
* @param productId 商品id
* @return 商品價格
*/
Long getPrice(String productId);
}
1.2.4.1、mall-product-provider
1.2.4.2.1、定義訂單實體和對應的mapper
Product.java
@Data
@TableName("mall_product")
public class Product implements java.io.Serializable {
@TableId(type = IdType.INPUT)
private String productId;
private String product_name;
private Long price;
private Integer stock;
}
ProductMapper.java
public interface ProductMapper extends BaseMapper<Product> {
}
1.2.4.2.2、接口實現類
ProductServiceImpl.java
@Slf4j
@DubboService
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {
@Override
public void detectStock(String productId, Integer number) {
log.info("detectStock: productId = {}, number = {}", productId, number);
Product product = this.baseMapper.selectById(productId);
if (Objects.isNull(product)) {
throw new RuntimeException("商品不存在");
}
if (product.getStock() < number) {
throw new RuntimeException("庫存不足");
}
product.setStock(product.getStock() - number);
this.baseMapper.updateById(product);
}
@Override
public Long getPrice(String productId) {
Product product = this.baseMapper.selectById(productId);
if (Objects.isNull(product)) {
throw new RuntimeException("商品不存在");
}
return product.getPrice();
}
}
1.2.4.2.3、啓動類
@EnableDubbo
@SpringBootApplication
@MapperScan(basePackages = "com.study.mall.product")
public class SeataMallProductBootstrap {
public static void main(String[] args) {
SpringApplication.run(SeataMallProductBootstrap.class, args);
}
}
1.2.4.2.4、配置文件
server.port=8082
spring.application.name=mall-product-provider
spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.max-active=50
spring.datasource.druid.max-wait=60000
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
spring.datasource.druid.filters=stat,wall
spring.datasource.druid.url=jdbc:mysql://${MYSQL_ADDRESS}/seata_mall_product?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.druid.username=${MYSQL_USERNAME}
spring.datasource.druid.password=${MYSQL_PASSWORD}
dubbo.application.name=mall-product-provider
dubbo.registry.address=nacos://${NACOS_ADDRESS}
dubbo.registry.register-mode=instance
dubbo.registry.parameters.username=${NACOS_USERNAME}
dubbo.registry.parameters.password=${NACOS_PASSWORD}
dubbo.application.qos-port=22222
dubbo.protocol.name=tri
dubbo.protocol.port=50052
1.2.4.2.5、pom版本依賴
<dependencies>
<dependency>
<groupId>com.study</groupId>
<artifactId>mall-product-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
1.2.5、seata-user
1.2.5.1、mall-user-api
api同樣也只是定義一個接口
public interface IUserService {
/**
* 扣錢
* @param userId
* @param money
*/
void deductMoney(String userId, Long money);
}
1.2.5.2、mall-user-provider
1.2.5.2.1、定義訂單實體和對應的mapper
User.java
@Data
@TableName("mall_user")
public class User implements java.io.Serializable {
@TableId(type = IdType.INPUT)
private String userId;
private String name;
private Long money;
}
UserMapper.java
public interface UserMapper extends BaseMapper<User> {
}
1.2.5.2.2、接口實現類
UserServiceImpl.java
@Slf4j
@DubboService
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public void deductMoney(String userId, Long money) {
log.info("deductMoney userId: {}, money: {}", userId , money);
User user = this.baseMapper.selectById(userId);
if (Objects.isNull(user)) {
throw new RuntimeException("用户不存在");
}
if (user.getMoney() < money) {
throw new RuntimeException("餘額不足");
}
user.setMoney(user.getMoney() - money);
this.baseMapper.updateById(user);
}
}
1.2.5.2.3、啓動類
@EnableDubbo
@SpringBootApplication
@MapperScan(basePackages = "com.study.mall.user")
public class SeataMallUserBootstrap {
public static void main(String[] args) {
SpringApplication.run(SeataMallUserBootstrap.class, args);
}
}
1.2.5.2.4、配置文件
server.port=8083
spring.application.name=mall-user-provider
spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.max-active=50
spring.datasource.druid.max-wait=60000
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
spring.datasource.druid.filters=stat,wall
spring.datasource.druid.url=jdbc:mysql://${MYSQL_ADDRESS}/seata_mall_user?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.druid.username=${MYSQL_USERNAME}
spring.datasource.druid.password=${MYSQL_PASSWORD}
dubbo.application.name=mall-user-provider
dubbo.registry.address=nacos://${NACOS_ADDRESS}
dubbo.registry.register-mode=instance
dubbo.registry.parameters.username=${NACOS_USERNAME}
dubbo.registry.parameters.password=${NACOS_PASSWORD}
dubbo.application.qos-port=22223
dubbo.protocol.name=tri
dubbo.protocol.port=50053
1.2.5.2.5、pom版本依賴
<dependencies>
<dependency>
<groupId>com.study</groupId>
<artifactId>mall-user-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
1.3、數據庫腳本
-- 創建數據庫
CREATE DATABASE IF NOT EXISTS `seata_mall_user` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
CREATE DATABASE IF NOT EXISTS `seata_mall_product` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
CREATE DATABASE IF NOT EXISTS `seata_mall_order` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
-- 創建表結構
CREATE TABLE IF NOT EXISTS `seata_mall_user`.`mall_user` (
`user_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户ID',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户名',
`money` bigint DEFAULT '0' COMMENT '賬户餘額',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='賬户表';
CREATE TABLE IF NOT EXISTS `seata_mall_product`.`mall_product` (
`product_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`product_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`price` bigint NOT NULL DEFAULT '0',
`stock` int NOT NULL DEFAULT '0',
PRIMARY KEY (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='商品表';
CREATE TABLE IF NOT EXISTS `seata_mall_order`.`mall_order` (
`order_id` int NOT NULL AUTO_INCREMENT,
`user_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '賬户ID',
`product_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '商品ID',
`num` int NOT NULL COMMENT '購買商品數量',
`amount` bigint NOT NULL COMMENT '訂單總金額',
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='訂單表';
-- 初始化
truncate table `seata_mall_user`.`mall_user` ;
INSERT INTO `seata_mall_user`.`mall_user` (`user_id`, `name`, `money`) VALUES ('10010', '張三', 100000);
truncate table `seata_mall_product`.`mall_product` ;
INSERT INTO `seata_mall_product`.`mall_product` (`product_id`, `product_name`, `price`, `stock`) VALUES ('A10021', 'iPhone 16 Pro Max', 9800, 100);
truncate table `seata_mall_order`.`mall_order` ;
-- 創建undo_log表
CREATE TABLE IF NOT EXISTS `seata_mall_user`. `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
truncate table `seata_mall_user`. `undo_log`;
CREATE TABLE IF NOT EXISTS `seata_mall_product`. `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
truncate table `seata_mall_product`. `undo_log`;
CREATE TABLE IF NOT EXISTS `seata_mall_order`. `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
truncate table `seata_mall_order`. `undo_log`;
1.4、正常執行
將mall-order-provider、mall-user-provider、mall-product-provider、seata-mall三個模塊啓動起來,登錄到nacos控制枱可以看到三個服務已經正常發佈了
數據庫中的數據如下:
接下來可以用postman模擬一下請求:
可以看到請求成功,數據庫中的數據也變成了這樣:
1.5、異常
接下來模擬一個異常,我們在OrderServiceImpl中模擬拋出一個異常後重啓這幾個服務,然後重置數據庫中的數據後再用postman再次調用
@Slf4j
@DubboService
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
@Override
public void createOrder(String userId, String productId, Integer number, Long amount) {
log.info("createOrder: userId = {}, productId = {}, number = {}, amount = {}", userId , productId , number, amount);
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setNum(number);
order.setAmount(amount);
this.baseMapper.insert(order);
throw new RuntimeException("測試創建訂單異常");
}
}
結果發現商品表裏的庫存被扣減了,賬户的餘額也被扣減了,但是訂單表中並沒有生成訂單數據
很明顯,這就是一個很嚴重的問題,如何解決呢?接下來就是Seata出場了
2、Seata接入(AT模式)
2.1、啓動seata服務
部署方案參考
服務啓動成功後登錄控制枱:默認賬號和密碼都是seata
我這裏什麼都沒修改,都是默認的配置直接啓動的
2.2、應用接入
2.2.1、引入相關依賴
所有的分佈式事務參與方都需要
<dependency>
<groupId>org.apache.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
2.2.2、添加seata相關配置
這裏以mall-product-provider為例,其它幾個應用相同
# seata配置
seata.application-id=mall-product-provider
seata.tx-service-group=my_test_tx_group
seata.enabled=true
seata.service.vgroup-mapping.my_test_tx_group=default
seata.service.grouplist.default=${seata.address:192.168.0.110}:8091
seata.registry.type=file
seata.config.type=file
- seata.application-id:應用標識
- seata.tx-service-group:服務分組,應用會先找到
seata.service.vgroup-mapping.服務分組名稱對應的值,然後再從配置文件中找到seata.service.grouplist.這個值對應的內容找到seata集羣地址- seata.registry.type:註冊中心類型(後續會介紹)
- seata.config.type:配置中心類型(後續會介紹)
2.2.3、通過註解聲明全局分佈式事務
在需要開啓全局分佈式事務的業務方法中打@GlobalTransactional,表示這裏會開啓分佈式事務
@Slf4j
@Service
public class BusinessServiceImpl implements IBusinessService {
@DubboReference
private IUserService userService;
@DubboReference
private IProductService productService;
@DubboReference
private IOrderService orderService;
@Override
@GlobalTransactional
public String order(String userId, String productId, Integer number) {
//獲取單價
Long price = this.productService.getPrice(productId);
//扣減庫存
this.productService.detectStock(productId, number);
Long amount = price * number;
//扣減用户餘額
this.userService.deductMoney(userId, amount);
//創建訂單
this.orderService.createOrder(userId, productId, number, amount);
return "訂單創建成功";
}
}
2.2.4、再次執行
上述配置完成後,再次執行下單方法,可以看到數據庫中賬號表裏邊的餘額沒有變化、商品表的庫存數量也沒有變化、訂單表也沒有生成訂單,完全符合預期,查看業務日誌可以看到事務的執行情況
- seata-mall
2025-12-28 15:15:46.183 INFO 20760 --- [nio-8080-exec-4] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-12-28 15:15:46.183 INFO 20760 --- [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2025-12-28 15:15:46.185 INFO 20760 --- [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
2025-12-28 15:15:46.219 INFO 20760 --- [nio-8080-exec-4] ServiceLoader$InnerEnhancedServiceLoader : Load compatible class io.seata.core.context.ContextCore
2025-12-28 15:15:46.278 INFO 20760 --- [nio-8080-exec-4] o.a.seata.tm.TransactionManagerHolder : TransactionManager Singleton org.apache.seata.tm.DefaultTransactionManager@68fc95bf
2025-12-28 15:15:46.290 INFO 20760 --- [nio-8080-exec-4] o.a.s.tm.api.DefaultGlobalTransaction : Begin new global transaction [172.26.0.1:8091:370025418433409035]
2025-12-28 15:15:50.993 INFO 20760 --- [nio-8080-exec-4] o.a.s.tm.api.DefaultGlobalTransaction : transaction 172.26.0.1:8091:370025418433409035 will be rollback
2025-12-28 15:15:51.367 INFO 20760 --- [nio-8080-exec-4] o.a.s.tm.api.DefaultGlobalTransaction : transaction end, xid = 172.26.0.1:8091:370025418433409035
2025-12-28 15:15:51.367 INFO 20760 --- [nio-8080-exec-4] o.a.s.tm.api.DefaultGlobalTransaction : [172.26.0.1:8091:370025418433409035] rollback status: Rollbacked
2025-12-28 15:15:51.391 ERROR 20760 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: 測試創建訂單異常] with root cause
java.lang.RuntimeException: 測試創建訂單異常
at com.study.mall.order.service.OrderServiceImpl.createOrder(OrderServiceImpl.java:25) ~[na:na]
at com.study.mall.order.service.OrderServiceImpl$$FastClassBySpringCGLIB$$41f97068.invoke(<generated>) ~[na:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.29.jar:5.3.29]
at org.springframework.aop.framework.CglibAopProxy.invokeMethod(CglibAopProxy.java:386) ~[spring-aop-5.3.29.jar:5.3.29]
at org.springframework.aop.framework.CglibAopProxy.access$000(CglibAopProxy.java:85) ~[spring-aop-5.3.29.jar:5.3.29]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:704) ~[spring-aop-5.3.29.jar:5.3.29]
at com.study.mall.order.service.OrderServiceImpl$$EnhancerBySpringCGLIB$$11a70ef9.createOrder(<generated>) ~[na:na]
at com.study.mall.order.IOrderServiceDubboWrap0.invokeMethod(IOrderServiceDubboWrap0.java) ~[na:na]
at org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:73) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:100) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker.invoke(DelegateProviderMetaDataInvoker.java:55) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.ClassLoaderCallbackFilter.invoke(ClassLoaderCallbackFilter.java:38) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at com.alibaba.dubbo.rpc.Invoker$CompatibleInvoker.invoke(Invoker.java:76) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.seata.integration.dubbo.alibaba.AlibabaDubboTransactionProviderFilter.invoke(AlibabaDubboTransactionProviderFilter.java:45) ~[seata-all-2.3.0.jar:2.3.0]
at com.alibaba.dubbo.rpc.Filter.invoke(Filter.java:34) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:80) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:45) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.seata.SeataTransactionPropagationProviderFilter.invoke(SeataTransactionPropagationProviderFilter.java:66) ~[dubbo-filter-seata-1.0.2.jar:1.0.2]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:108) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:55) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.AccessLogFilter.invoke(AccessLogFilter.java:119) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:206) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:54) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:41) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.metrics.filter.MetricsFilter.invoke(MetricsFilter.java:73) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.metrics.filter.MetricsProviderFilter.invoke(MetricsProviderFilter.java:36) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.ProfilerServerFilter.invoke(ProfilerServerFilter.java:64) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:144) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CallbackRegistrationInvoker.invoke(FilterChainBuilder.java:196) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.call.AbstractServerCallListener.invoke(AbstractServerCallListener.java:65) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.call.UnaryServerCallListener.onComplete(UnaryServerCallListener.java:81) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.call.AbstractServerCall.onComplete(AbstractServerCall.java:206) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.stream.TripleServerStream$ServerDecoderListener.close(TripleServerStream.java:480) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.frame.TriDecoder.deliver(TriDecoder.java:101) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.frame.TriDecoder.close(TriDecoder.java:67) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.stream.TripleServerStream$ServerTransportObserver.doOnData(TripleServerStream.java:443) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.stream.TripleServerStream$ServerTransportObserver.lambda$onData$2(TripleServerStream.java:434) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.common.threadpool.serial.SerializingExecutor.run(SerializingExecutor.java:102) ~[dubbo-3.2.6.jar:3.2.6]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_421]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_421]
at org.apache.dubbo.common.threadlocal.InternalRunnable.run(InternalRunnable.java:41) ~[dubbo-3.2.6.jar:3.2.6]
at java.lang.Thread.run(Thread.java:750) [na:1.8.0_421]
2025-12-28 15:15:55.178 INFO 20760 --- [eoutChecker_2_1] o.a.s.c.r.n.NettyClientChannelManager : will connect to 192.168.0.110:8091
2025-12-28 15:15:55.179 INFO 20760 --- [eoutChecker_2_1] o.a.s.c.rpc.netty.NettyPoolableFactory : NettyPool create channel to transactionRole:RMROLE,address:192.168.0.110:8091,msg:< RegisterRMRequest{resourceIds='null', version='2.3.0', applicationId='seata-mall', transactionServiceGroup='my_test_tx_group', extraData='null'} >
2025-12-28 15:15:55.191 INFO 20760 --- [eoutChecker_2_1] o.a.s.c.rpc.netty.RmNettyRemotingClient : register RM success. client version:2.3.0, server version:2.0.0,channel:[id: 0x9ed2077b, L:/192.168.0.102:58663 - R:/192.168.0.110:8091]
2025-12-28 15:15:55.191 INFO 20760 --- [eoutChecker_2_1] o.a.s.c.rpc.netty.NettyPoolableFactory : register success, cost 7 ms, version:2.0.0,role:RMROLE,channel:[id: 0x9ed2077b, L:/192.168.0.102:58663 - R:/192.168.0.110:8091]
- mall-order-provider
2025-12-28 15:15:49.705 INFO 24868 --- [:50051-thread-2] c.s.mall.order.service.OrderServiceImpl : createOrder: userId = 10010, productId = A10021, number = 2, amount = 19600
2025-12-28 15:15:50.544 INFO 24868 --- [:50051-thread-2] ServiceLoader$InnerEnhancedServiceLoader : Load compatible class io.seata.rm.datasource.exec.InsertExecutor
2025-12-28 15:15:50.854 INFO 24868 --- [:50051-thread-2] o.a.seata.rm.AbstractResourceManager : branch register success, xid:172.26.0.1:8091:370025418433409035, branchId:370025418433409038, lockKeys:mall_order:1
2025-12-28 15:15:50.868 WARN 24868 --- [:50051-thread-2] ServiceLoader$InnerEnhancedServiceLoader : Load [org.apache.seata.rm.datasource.undo.parser.ProtostuffUndoLogParser] class fail: io/protostuff/runtime/IdStrategy
2025-12-28 15:15:50.873 INFO 24868 --- [:50051-thread-2] ServiceLoader$InnerEnhancedServiceLoader : Load compatible class io.seata.rm.datasource.undo.parser.spi.JacksonSerializer
2025-12-28 15:15:50.940 ERROR 24868 --- [:50051-thread-2] o.a.dubbo.rpc.filter.ExceptionFilter : [DUBBO] Got unchecked and undeclared exception which called by 192.168.0.102. service: com.study.mall.order.IOrderService, method: createOrder, exception: java.lang.RuntimeException: 測試創建訂單異常, dubbo version: 3.2.6, current host: 192.168.0.102, error code: 5-36. This may be caused by , go to https://dubbo.apache.org/faq/5/36 to find instructions.
java.lang.RuntimeException: 測試創建訂單異常
at com.study.mall.order.service.OrderServiceImpl.createOrder(OrderServiceImpl.java:25) ~[classes/:na]
at com.study.mall.order.service.OrderServiceImpl$$FastClassBySpringCGLIB$$41f97068.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.29.jar:5.3.29]
at org.springframework.aop.framework.CglibAopProxy.invokeMethod(CglibAopProxy.java:386) ~[spring-aop-5.3.29.jar:5.3.29]
at org.springframework.aop.framework.CglibAopProxy.access$000(CglibAopProxy.java:85) ~[spring-aop-5.3.29.jar:5.3.29]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:704) ~[spring-aop-5.3.29.jar:5.3.29]
at com.study.mall.order.service.OrderServiceImpl$$EnhancerBySpringCGLIB$$11a70ef9.createOrder(<generated>) ~[classes/:na]
at com.study.mall.order.IOrderServiceDubboWrap0.invokeMethod(IOrderServiceDubboWrap0.java) ~[dubbo-3.2.6.jar:na]
at org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:73) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:100) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker.invoke(DelegateProviderMetaDataInvoker.java:55) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.ClassLoaderCallbackFilter.invoke(ClassLoaderCallbackFilter.java:38) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at com.alibaba.dubbo.rpc.Invoker$CompatibleInvoker.invoke(Invoker.java:76) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.seata.integration.dubbo.alibaba.AlibabaDubboTransactionProviderFilter.invoke(AlibabaDubboTransactionProviderFilter.java:45) ~[seata-all-2.3.0.jar:2.3.0]
at com.alibaba.dubbo.rpc.Filter.invoke(Filter.java:34) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:80) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:45) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.seata.SeataTransactionPropagationProviderFilter.invoke(SeataTransactionPropagationProviderFilter.java:66) ~[dubbo-filter-seata-1.0.2.jar:1.0.2]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:108) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:55) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.AccessLogFilter.invoke(AccessLogFilter.java:119) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:206) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:54) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:41) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.metrics.filter.MetricsFilter.invoke(MetricsFilter.java:73) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.metrics.filter.MetricsProviderFilter.invoke(MetricsProviderFilter.java:36) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.ProfilerServerFilter.invoke(ProfilerServerFilter.java:64) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:144) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:334) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CallbackRegistrationInvoker.invoke(FilterChainBuilder.java:196) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.call.AbstractServerCallListener.invoke(AbstractServerCallListener.java:65) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.call.UnaryServerCallListener.onComplete(UnaryServerCallListener.java:81) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.call.AbstractServerCall.onComplete(AbstractServerCall.java:206) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.stream.TripleServerStream$ServerDecoderListener.close(TripleServerStream.java:480) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.frame.TriDecoder.deliver(TriDecoder.java:101) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.frame.TriDecoder.close(TriDecoder.java:67) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.stream.TripleServerStream$ServerTransportObserver.doOnData(TripleServerStream.java:443) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.rpc.protocol.tri.stream.TripleServerStream$ServerTransportObserver.lambda$onData$2(TripleServerStream.java:434) ~[dubbo-3.2.6.jar:3.2.6]
at org.apache.dubbo.common.threadpool.serial.SerializingExecutor.run(SerializingExecutor.java:102) ~[dubbo-3.2.6.jar:3.2.6]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_421]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_421]
at org.apache.dubbo.common.threadlocal.InternalRunnable.run(InternalRunnable.java:41) ~[dubbo-3.2.6.jar:3.2.6]
at java.lang.Thread.run(Thread.java:750) ~[na:1.8.0_421]
2025-12-28 15:15:51.246 INFO 24868 --- [h_RMROLE_1_1_16] o.a.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:BranchRollbackRequest{xid='172.26.0.1:8091:370025418433409035', branchId=370025418433409038, branchType=AT, resourceId='jdbc:mysql://192.168.0.110:3306/seata_mall_order', applicationData='null'}
2025-12-28 15:15:51.249 INFO 24868 --- [h_RMROLE_1_1_16] org.apache.seata.rm.AbstractRMHandler : Branch Rollbacking: 172.26.0.1:8091:370025418433409035 370025418433409038 jdbc:mysql://192.168.0.110:3306/seata_mall_order
2025-12-28 15:15:51.356 INFO 24868 --- [h_RMROLE_1_1_16] o.a.s.r.d.undo.AbstractUndoLogManager : xid 172.26.0.1:8091:370025418433409035 branch 370025418433409038, undo_log deleted with GlobalFinished
2025-12-28 15:15:51.357 INFO 24868 --- [h_RMROLE_1_1_16] o.a.s.rm.datasource.DataSourceManager : branch rollback success, xid:172.26.0.1:8091:370025418433409035, branchId:370025418433409038
2025-12-28 15:15:51.358 INFO 24868 --- [h_RMROLE_1_1_16] org.apache.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
- mall-user-provider
2025-12-28 15:15:48.192 INFO 29260 --- [:50053-thread-2] c.s.mall.user.service.UserServiceImpl : deductMoney userId: 10010, money: 19600
2025-12-28 15:15:49.095 INFO 29260 --- [:50053-thread-2] o.a.seata.rm.AbstractResourceManager : branch register success, xid:172.26.0.1:8091:370025418433409035, branchId:370025418433409037, lockKeys:mall_user:10010
2025-12-28 15:15:49.109 WARN 29260 --- [:50053-thread-2] ServiceLoader$InnerEnhancedServiceLoader : Load [org.apache.seata.rm.datasource.undo.parser.ProtostuffUndoLogParser] class fail: io/protostuff/runtime/IdStrategy
2025-12-28 15:15:49.115 INFO 29260 --- [:50053-thread-2] ServiceLoader$InnerEnhancedServiceLoader : Load compatible class io.seata.rm.datasource.undo.parser.spi.JacksonSerializer
2025-12-28 15:15:51.124 INFO 29260 --- [h_RMROLE_1_1_16] o.a.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:BranchRollbackRequest{xid='172.26.0.1:8091:370025418433409035', branchId=370025418433409037, branchType=AT, resourceId='jdbc:mysql://192.168.0.110:3306/seata_mall_user', applicationData='null'}
2025-12-28 15:15:51.126 INFO 29260 --- [h_RMROLE_1_1_16] org.apache.seata.rm.AbstractRMHandler : Branch Rollbacking: 172.26.0.1:8091:370025418433409035 370025418433409037 jdbc:mysql://192.168.0.110:3306/seata_mall_user
2025-12-28 15:15:51.235 INFO 29260 --- [h_RMROLE_1_1_16] o.a.s.r.d.undo.AbstractUndoLogManager : xid 172.26.0.1:8091:370025418433409035 branch 370025418433409037, undo_log deleted with GlobalFinished
2025-12-28 15:15:51.236 INFO 29260 --- [h_RMROLE_1_1_16] o.a.s.rm.datasource.DataSourceManager : branch rollback success, xid:172.26.0.1:8091:370025418433409035, branchId:370025418433409037
2025-12-28 15:15:51.237 INFO 29260 --- [h_RMROLE_1_1_16] org.apache.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
- mall-product_provider
2025-12-28 15:15:47.227 INFO 33244 --- [:50052-thread-3] c.s.m.p.service.ProductServiceImpl : detectStock: productId = A10021, number = 2
2025-12-28 15:15:47.668 INFO 33244 --- [:50052-thread-3] o.a.seata.rm.AbstractResourceManager : branch register success, xid:172.26.0.1:8091:370025418433409035, branchId:370025418433409036, lockKeys:mall_product:A10021
2025-12-28 15:15:47.682 WARN 33244 --- [:50052-thread-3] ServiceLoader$InnerEnhancedServiceLoader : Load [org.apache.seata.rm.datasource.undo.parser.ProtostuffUndoLogParser] class fail: io/protostuff/runtime/IdStrategy
2025-12-28 15:15:47.687 INFO 33244 --- [:50052-thread-3] ServiceLoader$InnerEnhancedServiceLoader : Load compatible class io.seata.rm.datasource.undo.parser.spi.JacksonSerializer
2025-12-28 15:15:51.004 INFO 33244 --- [h_RMROLE_1_1_16] o.a.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:BranchRollbackRequest{xid='172.26.0.1:8091:370025418433409035', branchId=370025418433409036, branchType=AT, resourceId='jdbc:mysql://192.168.0.110:3306/seata_mall_product', applicationData='null'}
2025-12-28 15:15:51.005 INFO 33244 --- [h_RMROLE_1_1_16] org.apache.seata.rm.AbstractRMHandler : Branch Rollbacking: 172.26.0.1:8091:370025418433409035 370025418433409036 jdbc:mysql://192.168.0.110:3306/seata_mall_product
2025-12-28 15:15:51.111 INFO 33244 --- [h_RMROLE_1_1_16] o.a.s.r.d.undo.AbstractUndoLogManager : xid 172.26.0.1:8091:370025418433409035 branch 370025418433409036, undo_log deleted with GlobalFinished
2025-12-28 15:15:51.112 INFO 33244 --- [h_RMROLE_1_1_16] o.a.s.rm.datasource.DataSourceManager : branch rollback success, xid:172.26.0.1:8091:370025418433409035, branchId:370025418433409036
2025-12-28 15:15:51.114 INFO 33244 --- [h_RMROLE_1_1_16] org.apache.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
總結:
seata作為阿里巴巴開源的一個分佈式事務的解決方案,提供了四種事務模式,AT模式(默認)、TCC模式、Saga模式以及XA模式。而且AT模式是代碼無侵入的,只需要通過簡單的配置和一個事務註解即可完成,使用起來非常簡單。至於裏邊的原理後續文章再介紹