分布式事务实战:Seata AT模式在Spring Boot微服务中的落地指南

微服务架构下分布式事务的必然性

单体应用里一个数据库连接就能搞定事务,微服务拆分后一个业务操作跨3个服务、3个数据库,本地事务管不了跨库的一致性问题。订单服务扣库存、支付服务扣余额、积分服务加积分——三个操作必须全部成功或全部回滚,中间任何一步失败,数据就不一致了。

微服务架构中分布式事务的常见方案有:2PC(两阶段提交)、TCC(Try-Confirm-Cancel)、Saga(长事务编排)、本地消息表+最终一致性。Seata的AT模式本质上是一种增强型2PC,对业务代码侵入最小——只需要加一个@GlobalTransactional注解。

Seata AT模式的核心机制:一阶段业务+回滚日志

AT模式的执行流程比传统2PC简单得多:

一阶段:业务SQL执行 + 生成回滚日志(undo_log)。业务SQL执行后,Seata拦截器自动记录修改前后的数据快照到undo_log表。

二阶段提交:异步清理undo_log,几乎零开销。

二阶段回滚:读取undo_log反向补偿,恢复到修改前状态。

这个机制的关键优势是:一阶段完成后本地事务已提交,不持有数据库锁,其他业务可以正常读写。回滚通过undo_log反向补偿实现,而不是通过长时间持有锁来保证隔离性。

Seata Server部署与配置

Seata Server(TC,事务协调器)是独立部署的中间件,管理全局事务的分支注册和二阶段决策。

# docker-compose部署Seata Server
version: '3'
services:
  seata-server:
    image: seataio/seata-server:2.2.0
    ports:
      - "8091:8091"
      - "7091:7091"
    environment:
      - SEATA_IP=seata-server
    volumes:
      - ./seata-config:/seata-server/resources
    depends_on:
      - seata-db

Seata Server配置application.yml

server:
  port: 7091

seata:
  registry:
    type: nacos
    nacos:
      server-addr: nacos:8848
      namespace: seata
      group: SEATA_GROUP
      application: seata-server
  store:
    mode: db
    db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://seata-db:3306/seata?useSSL=false
      user: seata
      password: seata_pwd
      min-conn: 5
      max-conn: 30
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock

Spring Boot微服务集成Seata AT模式

每个参与分布式事务的微服务需要:

1. 引入Seata依赖
2. 配置数据源代理
3. 每个业务数据库创建undo_log表
4. 在业务入口方法加@GlobalTransactional注解

Maven依赖:

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

<!-- 如果使用Nacos注册中心 -->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  <version>2023.0.1.0</version>
</dependency>

数据源代理配置——Seata需要代理数据源来拦截SQL生成undo_log:

@Configuration
public class DataSourceProxyConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }
}

每个业务数据库的undo_log建表语句:

CREATE TABLE IF NOT EXISTS `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          NOT NULL COMMENT '状态',
  `log_created`   DATETIME     NOT NULL COMMENT '创建时间',
  `log_modified`  DATETIME     NOT NULL COMMENT '修改时间',
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

业务代码实现:订单-库存-积分三服务事务

订单服务作为事务发起方:

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private StockClient stockClient;

    @Autowired
    private PointsClient pointsClient;

    @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
    public OrderDTO createOrder(OrderRequest request) {
        // 1. 创建订单(本地事务)
        Order order = new Order();
        order.setUserId(request.getUserId());
        order.setProductId(request.getProductId());
        order.setQuantity(request.getQuantity());
        order.setAmount(request.getAmount());
        order.setStatus("CREATED");
        orderMapper.insert(order);

        // 2. 扣减库存(远程调用)
        stockClient.deduct(request.getProductId(), request.getQuantity());

        // 3. 增加积分(远程调用)
        pointsClient.addPoints(request.getUserId(), request.getAmount().intValue());

        return OrderDTO.fromEntity(order);
    }
}

库存服务和积分服务不需要加@GlobalTransactional,只需要被Seata代理数据源即可。Seata通过全局事务ID(XID)的传播来自动关联分支事务。XID通过RPC框架(Feign/Dubbo)的拦截器自动传递,不需要手工编码。

Feign拦截器配置XID传播:

@Configuration
public class SeataFeignConfig {

    @Bean
    public RequestInterceptor seataFeignInterceptor() {
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate template) {
                String xid = RootContext.getXID();
                if (StringUtils.isNotBlank(xid)) {
                    template.header(RootContext.KEY_XID, xid);
                }
            }
        };
    }
}

分布式事务线上运维与故障处理

Seata AT模式线上最常见的故障是undo_log未清理导致表膨胀和全局事务悬挂。

全局事务悬挂:一阶段已经执行但TC未收到分支注册,导致undo_log残留。Seata Server有定时任务清理超时的全局事务(默认60秒),但业务侧也需要定期清理undo_log:

-- 定期清理超过24小时的undo_log
DELETE FROM undo_log
WHERE log_created < DATE_SUB(NOW(), INTERVAL 24 HOUR)
  AND log_status = 1;  -- 1表示已提交

回滚失败处理:二阶段回滚读undo_log发现数据已被修改(脏写),Seata默认策略是打印日志并重试。如果持续失败需要人工介入,在branch_table中查看悬挂的事务分支,手动确认后强制回滚或提交。

Seata控制台的事务列表可以查看所有未完成的全局事务,是线上排查的第一入口。核心指标监控:正在运行的全局事务数量、回滚率、平均事务耗时。回滚率持续上升通常意味着下游服务不稳定,需要从根因解决而不是依赖事务补偿。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-shi-wu-shi-zhan-seataat-mo-shi-zai-springboot/

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

相关推荐