Spring Boot集成Seata分布式事务实战:AT模式从原理到生产部署

Spring Boot集成Seata分布式事务实战指南

微服务架构下,跨服务的数据一致性是后端开发无法回避的核心问题。订单创建需要同时操作订单服务、库存服务和账户服务,任何一个环节失败都需要全部回滚。Seata作为阿里开源的分布式事务框架,提供AT、TCC、Saga、XA四种事务模式,其中AT模式对业务代码侵入最小,是Spring Boot微服务架构的首选方案。

Seata AT模式运行原理

AT模式的核心是两阶段提交加自动SQL解析:

  1. 一阶段:拦截业务SQL,生成前镜像(before image)→执行SQL→生成后镜像(after image)→生成回滚日志(undo log)→提交本地事务
  2. 二阶段提交:异步删除undo log,事务完成
  3. 二阶段回滚:根据undo log的前镜像反向补偿SQL,恢复数据

AT模式对业务SQL有要求:必须支持本地ACID事务,且SQL对应的表必须有主键或唯一索引——Seata依赖主键生成前镜像的WHERE条件。

Seata Server部署与配置

Seata Server(TC事务协调器)支持三种存储模式:file、db、redis。生产环境必须使用db或redis模式保证事务日志持久化。

MySQL存储模式配置:

# seata-server application.yml
server:
  port: 7091

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

Seata Server依赖的数据库表结构需要提前创建,SQL脚本位于seata-server的script/server/db目录下。初始化后启动Seata Server:

# Docker部署
docker run -d --name seata-server \
  -p 7091:7091 \
  -p 8091:8091 \
  -v /opt/seata/config:/seata-server/resources \
  seataio/seata-server:2.0.0

Spring Boot微服务集成Seata客户端

每个参与分布式事务的微服务都需要集成Seata客户端。以订单服务为例:

Maven依赖:

<dependency>
  <groupId>io.seata</groupId>
  <artifactId>seata-spring-boot-starter</artifactId>
  <version>2.0.0</version>
</dependency>
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  <version>2023.0.1.0</version>
</dependency>

客户端配置:

# application.yml
seata:
  enabled: true
  application-id: order-service
  tx-service-group: order-tx-group
  service:
    vgroup-mapping:
      order-tx-group: default
      stock-tx-group: default
      account-tx-group: default
  registry:
    type: nacos
    nacos:
      server-addr: nacos:8848
      namespace: seata
      group: SEATA_GROUP
  config:
    type: nacos
    nacos:
      server-addr: nacos:8848
      namespace: seata
      group: SEATA_GROUP

# 数据源代理配置(AT模式必须使用Seata代理数据源)
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://mysql:3306/order_db
    username: root
    password: root_pwd

关键点:Seata AT模式通过代理数据源拦截SQL,自动生成undo log。Spring Boot自动配置会检测Seata依赖并创建DataSourceProxy,但手动配置更可控:

@Configuration
public class SeataDataSourceConfig {

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

    @Primary
    @Bean("dataSource")
    public DataSourceProxy dataSourceProxy(DruidDataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }
}

分布式事务代码实现

订单服务作为事务发起方,使用@GlobalTransactional注解标注事务入口:

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private StockFeignClient stockClient;
    @Autowired
    private AccountFeignClient accountClient;

    @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
    @Transactional(rollbackFor = Exception.class)
    public OrderCreateResult createOrder(OrderCreateRequest request) {
        // 1. 创建订单
        Order order = new Order();
        order.setUserId(request.getUserId());
        order.setProductId(request.getProductId());
        order.setQuantity(request.getQuantity());
        order.setTotalAmount(request.getAmount());
        order.setStatus("CREATED");
        orderMapper.insert(order);

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

        // 3. 扣减账户余额(远程调用账户服务)
        accountClient.debit(request.getUserId(), request.getAmount());

        return new OrderCreateResult(order.getId(), "SUCCESS");
    }
}

库存服务作为事务参与方,不需要添加全局事务注解,Seata通过XID传播自动加入全局事务:

@Service
public class StockService {

    @Autowired
    private StockMapper stockMapper;

    @Transactional(rollbackFor = Exception.class)
    public void deduct(Long productId, Integer quantity) {
        Stock stock = stockMapper.selectByProductId(productId);
        if (stock == null) {
            throw new BusinessException("商品不存在: " + productId);
        }
        if (stock.getAvailable() < quantity) {
            throw new BusinessException("库存不足: 可用" + stock.getAvailable() + ", 需要" + quantity);
        }
        stockMapper.deduct(productId, quantity);
    }
}

XID传播通过Feign拦截器自动完成:

@Configuration
public class SeataFeignConfig {

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

异常场景与回滚验证

分布式事务最难处理的是异常场景的一致性保障。以下三个场景需要逐一验证:

场景一:库存服务抛出异常,订单和账户数据应自动回滚。

@Test
public void testStockInsufficient_Rollback() {
    // 准备: 库存仅剩5件
    stockService.initStock(productId, 5);

    // 执行: 下单10件
    assertThrows(BusinessException.class, () -> {
        orderService.createOrder(new OrderCreateRequest(userId, productId, 10, amount));
    });

    // 验证: 订单表无记录,库存不变
    assertEquals(0, orderMapper.selectByUserId(userId).size());
    assertEquals(5, stockMapper.selectByProductId(productId).getAvailable());
}

场景二:账户余额不足,订单和库存数据应自动回滚。

场景三:网络超时导致远程调用失败。Feign默认超时时间需要合理配置:

# application.yml
feign:
  client:
    config:
      default:
        connect-timeout: 5000
        read-timeout: 10000
  circuitbreaker:
    enabled: true

resilience4j:
  timelimiter:
    configs:
      default:
        timeout-duration: 15s

服务治理与性能优化

Seata AT模式在性能上的主要瓶颈是一阶段写入undo log带来的IO开销。优化策略:

  • undo log表索引优化:为branch_table和lock_table添加合理的复合索引,减少锁冲突
  • 批量操作优化:单事务内避免循环调用远程服务,改为批量接口减少RPC次数
  • 全局事务超时配置:默认60秒,长事务场景需适当延长
  • 读写隔离策略:AT模式默认全局锁读已提交,对一致性要求不高的场景可降级为读未提交

全局事务监控也是服务治理的重要环节。Seata提供全局事务查询API,配合Prometheus指标采集可实现事务成功率、平均耗时等核心指标的监控:

@Component
public class SeataMetrics {

    private final Counter txTotal = Counter.build()
        .name("seata_global_tx_total")
        .help("Total global transactions")
        .labelNames("status")
        .register();

    @EventListener
    public void onGlobalTransactionEvent(GlobalTransactionEvent event) {
        String status = event.getStatus().name();
        txTotal.labels(status).inc();
    }
}

分布式事务不是银弹。业务设计时应优先考虑通过事件驱动、最终一致性方案(如消息表+定时补偿)规避分布式事务。仅在强一致性场景(如资金扣减、库存扣减)下才引入Seata,这是微服务架构中服务治理的基本原则。

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

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

相关推荐