Spring Boot 3.x集成Seata分布式事务AT模式踩坑实录

Spring Boot微服务为什么需要分布式事务

单体应用拆分为微服务后,原本一个本地事务就能搞定的业务操作,现在跨了多个服务的数据库。比如电商下单场景:订单服务创建订单、库存服务扣减库存、账户服务扣减余额——三个操作必须全部成功或全部回滚。用本地事务做不到,因为每个服务连的是不同的数据库实例。

Seata是蚂蚁金服开源的分布式事务中间件,AT模式是其核心方案,对业务代码侵入最小:只需在方法上加@GlobalTransactional注解,Seata通过拦截SQL自动生成回滚日志(undo log),在任意分支事务失败时自动补偿。和TCC模式需要手动写Try/Confirm/Cancel三个方法不同,AT模式的改造成本低得多。

Seata AT模式的工作原理

AT模式的核心流程分两阶段:

第一阶段(业务执行+生成undo log):Seata拦截业务SQL,在执行前查询数据旧值,执行后查询数据新值,将旧值+新值组合生成undo log记录写入seata_undo_log表,然后执行业务SQL并提交本地事务。此时本地事务已提交,其他服务可以看到中间状态。

第二阶段(提交或回滚):如果全局事务决议为提交,异步删除undo log(一阶段已提交,不需要再做任何事);如果决议为回滚,读取undo log中的旧值,生成反向SQL(把新值改回旧值)执行补偿,然后删除undo log。

关键设计点:一阶段就提交了本地事务,释放了数据库锁,因此不会长时间持有锁导致性能问题。代价是数据有短暂的中间状态可见窗口,但通过全局锁机制保证不会出现脏写——其他全局事务在修改同一行数据时必须先获取全局锁,获取失败则等待或回滚。

Spring Boot 3.x集成Seata实战步骤

第一步:部署Seata Server

Seata Server是事务协调器(TC),所有微服务作为事务参与者(RM/TM)向TC注册。使用Docker快速部署:

# docker-compose.seata.yml
version: "3"
services:
  seata-server:
    image: seataio/seata-server:2.2.0
    ports:
      - "8091:8091"
      - "7091:7091"
    environment:
      - SEATA_IP=seata-server
      - STORE_MODE=db
    volumes:
      - ./seata-config:/seata-server/config

Seata Server的存储模式建议用db(数据库存储事务日志),生产环境不推荐file模式。配置文件application.yml

seata:
  config:
    type: nacos
    nacos:
      server-addr: nacos:8848
      namespace: seata
      group: SEATA_GROUP
  store:
    mode: db
    db:
      datasource: druid
      db-type: mysql
      url: jdbc:mysql://mysql:3306/seata?rewriteBatchedStatements=true
      user: seata
      password: seata_pwd
      min-conn: 5
      max-conn: 30
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table

第二步:微服务引入Seata依赖

每个参与分布式事务的Spring Boot服务都需要添加依赖和配置:

<!-- pom.xml -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

<!-- Spring Boot 3.x 需要额外引入 -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-autoconfigure-client</artifactId>
    <version>2.2.0</version>
</dependency>

服务端配置application.yml

seata:
  enabled: true
  application-id: order-service
  tx-service-group: my-tx-group
  service:
    vgroup-mapping:
      my-tx-group: default
  registry:
    type: nacos
    nacos:
      server-addr: nacos:8848
      namespace: seata
      group: SEATA_GROUP

第三步:每个微服务的数据库建undo_log表

-- 每个参与分布式事务的数据库都要建这张表
CREATE TABLE IF NOT EXISTS `seata_undo_log` (
  `branch_id`     BIGINT       NOT NULL COMMENT '分支事务ID',
  `xid`           VARCHAR(128) NOT NULL COMMENT '全局事务ID',
  `context`       VARCHAR(128) NOT NULL COMMENT '上下文',
  `rollback_info` LONGBLOB     NOT NULL COMMENT '回滚信息',
  `log_status`    INT(11)      NOT NULL COMMENT '日志状态',
  `log_created`   DATETIME(6)  NOT NULL COMMENT '创建时间',
  `log_modified`  DATETIME(6)  NOT NULL COMMENT '修改时间',
  UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB COMMENT ='AT undo log表';

第四步:在业务方法上加注解

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private StockClient stockClient;
    @Autowired
    private AccountClient accountClient;

    @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
    public Order createOrder(OrderDTO dto) {
        // 1. 创建订单(本地事务)
        Order order = new Order();
        order.setUserId(dto.getUserId());
        order.setCommodityCode(dto.getCommodityCode());
        order.setCount(dto.getCount());
        order.setMoney(dto.getMoney());
        order.setStatus("CREATING");
        orderMapper.insert(order);

        // 2. 远程调用扣减库存
        stockClient.deduct(dto.getCommodityCode(), dto.getCount());

        // 3. 远程调用扣减余额
        accountClient.debit(dto.getUserId(), dto.getMoney());

        // 4. 更新订单状态
        order.setStatus("COMPLETED");
        orderMapper.updateById(order);

        return order;
    }
}

只要任意一步抛异常,Seata TC会通知所有参与的RM读取undo log执行回滚。

AT模式的踩坑与应对方案

坑1:undo_log表没建导致静默失败

如果某个微服务的数据库没有建undo_log表,Seata不会报错阻止启动,而是在运行时写undo失败,回滚时找不到日志,全局事务部分回滚部分未回滚,数据不一致。部署检查清单中必须包含undo_log表的确认。

坑2:全局锁超时

两个全局事务同时修改同一行数据时,后到的事务需要等待前一个事务释放全局锁。默认等待时间30秒,超时后抛异常。高并发场景下这个超时值需要调大,同时优化业务逻辑减少跨事务的热点行争用:

seata:
  client:
    rm:
      lock:
        retry-interval: 50      # 获取全局锁重试间隔50ms
        retry-times: 120        # 重试120次(6秒)
      async-commit-buffer-limit: 10000

坑3:SQL兼容性问题

Seata AT模式通过拦截SQL生成undo log,但不是所有SQL语法都支持。不支持的操作包括:INSERT时不带列名(INSERT INTO t VALUES(...)),多表UPDATE,使用数据库函数作为列值(如NOW()),LIMIT子句等。写业务SQL时要注意这些限制:

// 不支持的写法
INSERT INTO order_table VALUES(1, 'test', NOW());

// 改为
INSERT INTO order_table (id, name, created_at) VALUES(1, 'test', '2026-07-28 10:00:00');

坑4:Spring Boot 3.x的Bean初始化顺序

Spring Boot 3.x使用Spring Framework 6,Bean的初始化顺序和2.x有差异。如果Seata的数据源代理没有在MyBatis/JPA之前生效,AT模式拦截不到SQL。确保数据源代理配置正确:

@Bean
@Primary
public DataSource dataSource(DataSource originalDataSource) {
    // Seata代理原始数据源
    return new DataSourceProxy(originalDataSource);
}

确认代理生效的方法:启动日志中搜索DataSourceProxy相关输出,或通过断点确认注入的DataSource类型。

AT模式与TCC/Saga的选型决策

AT模式适合90%的分布式事务场景,但有边界:如果业务操作不是SQL(比如调用第三方API、发送消息等),AT无法自动生成undo log,这时需要用TCC模式手动写补偿逻辑。如果业务流程很长(涉及5个以上服务),AT模式的全局锁持有时间长会影响并发,Saga模式更适合长流程——每个步骤只做正向操作,失败时反向执行补偿链。

选型建议:SQL操作为主、流程3-5步以内用AT;涉及非SQL操作用TCC;超长流程(如旅行预订涉及航班+酒店+租车+保险)用Saga。不要在一个项目中混用多种模式,增加维护复杂度。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot3x-ji-cheng-seata-fen-bu-shi-shi-wu-at-mo-shi-cai/

(0)
小编小编
上一篇 1小时前
下一篇 1小时前

相关推荐