本文基於SpringBoot+Dubbo+Nacos+Seata+MyBatis-Plus搭建分佈式事務案例,模擬電商下單場景,包含扣減庫存、賬户餘額和創建訂單三個關鍵步驟。案例完整展示了分佈式系統中如何保證多個服務操作的原子性,為處理跨服務事務提供實踐參考。

1、案例搭建

1.1、業務説明

本案例演示的是下單的過程,具體流程包括:

  • 扣減庫存
  • 扣減賬户餘額
  • 創建訂單

我們知道這是一個典型的分佈式場景,這三個環節要麼同時成功,要麼一起失敗回滾

1.2、項目搭建

我的項目結構大致如下圖所示,需要注意的是在這之上還有一個,所以要注意pom文件要注意一下

【分佈式事務】3、分佈式事務解決方案Seata初體驗_分佈式事務

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

【分佈式事務】3、分佈式事務解決方案Seata初體驗_分佈式事務_02

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

【分佈式事務】3、分佈式事務解決方案Seata初體驗_seata_03

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控制枱可以看到三個服務已經正常發佈了

【分佈式事務】3、分佈式事務解決方案Seata初體驗_分佈式事務_04

數據庫中的數據如下:

【分佈式事務】3、分佈式事務解決方案Seata初體驗_分佈式事務_05

【分佈式事務】3、分佈式事務解決方案Seata初體驗_seata_06

【分佈式事務】3、分佈式事務解決方案Seata初體驗_分佈式事務_07

接下來可以用postman模擬一下請求:

【分佈式事務】3、分佈式事務解決方案Seata初體驗_分佈式事務_08

可以看到請求成功,數據庫中的數據也變成了這樣:

【分佈式事務】3、分佈式事務解決方案Seata初體驗_分佈式事務_09

【分佈式事務】3、分佈式事務解決方案Seata初體驗_分佈式事務_10

【分佈式事務】3、分佈式事務解決方案Seata初體驗_分佈式事務_11

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

【分佈式事務】3、分佈式事務解決方案Seata初體驗_seata_12

我這裏什麼都沒修改,都是默認的配置直接啓動的

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模式是代碼無侵入的,只需要通過簡單的配置和一個事務註解即可完成,使用起來非常簡單。至於裏邊的原理後續文章再介紹