Spring Boot分布式事务实战:Seata AT模式配置到压测调优

Spring Boot分布式事务选Seata还是自己造

微服务架构下,跨服务的数据一致性是后端开发无法绕开的问题。订单服务扣减库存、支付服务冻结资金、积分服务预增积分——这些操作要么全部成功,要么全部回滚。本地事务管不了跨库操作,分布式事务的选型直接决定了系统的可靠性和吞吐上限。本文聚焦Seata AT模式的实战配置,从零搭建到压测调优。

分布式事务方案对比:为什么AT模式适合多数业务

分布式事务的常见方案有四种:2PC、TCC、Saga、本地消息表。Seata AT模式本质是2PC的自动化版本——框架替你写补偿逻辑。

AT模式的工作机制:一阶段执行业务SQL并拦截,生成回滚日志(undo_log)存入数据库;二阶段提交时删除undo_log,回滚时根据undo_log反向补偿。对业务代码侵入最小,只需要加一个@GlobalTransactional注解。

不适用AT模式的场景:高并发短事务(锁冲突严重)、非关系型数据库操作、长事务。这些场景考虑TCC或Saga。

Seata Server部署与配置

Seata Server是事务协调器(TC),所有微服务的事务分支在此注册和协调。

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

  seata-store:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: Seata@2026
      MYSQL_DATABASE: seata
    volumes:
      - ./sql/seata-server.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "3307:3306"

Seata Server配置文件(application.yml):

server:
  port: 7091

seata:
  config:
    type: nacos
    nacos:
      server-addr: 10.0.1.50:8848
      namespace: seata
      group: SEATA_GROUP
  registry:
    type: nacos
    nacos:
      server-addr: 10.0.1.50: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-store:3306/seata?rewriteBatchedStatements=true
      user: root
      password: Seata@2026
      min-conn: 10
      max-conn: 100
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock

微服务接入Seata AT模式

以订单服务为例,每个参与分布式事务的微服务需要完成三件事:引入依赖、配置代理数据源、添加undo_log表。

<!-- pom.xml 核心依赖 -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>seata-spring-cloud-starter</artifactId>
    <version>2023.0.3.0</version>
</dependency>

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

-- 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(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事务回滚日志表';

业务代码:@GlobalTransactional实战

订单创建涉及三个服务:订单服务创建订单、库存服务扣减库存、账户服务扣减余额。

// 订单服务 - 事务发起方
@Service
public class OrderService {

    @DubboReference
    private InventoryService inventoryService;

    @DubboReference
    private AccountService accountService;

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

        // 2. 扣减库存(远程调用,Seata自动注册分支事务)
        inventoryService.deduct(request.getProductId(), request.getQuantity());

        // 3. 扣减余额(远程调用)
        accountService.debit(request.getUserId(), request.getAmount());

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

        return OrderDTO.fromEntity(order);
    }
}

// 库存服务 - 事务参与方
@Service
public class InventoryServiceImpl implements InventoryService {

    @Autowired
    private InventoryMapper inventoryMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deduct(Long productId, Integer quantity) {
        Inventory inv = inventoryMapper.selectByProductId(productId);
        if (inv == null) {
            throw new BusinessException("商品不存在: " + productId);
        }
        if (inv.getStock() < quantity) {
            throw new BusinessException("库存不足,当前: " + inv.getStock());
        }
        inv.setStock(inv.getStock() - quantity);
        inventoryMapper.updateById(inv);
    }
}

关键点:事务发起方加@GlobalTransactional,参与方只需要@Transactional。Seata通过拦截SQL自动生成undo_log,远程调用时xid通过Dubbo Filter透传。

XID透传:跨服务事务上下文传递

分布式事务的xid需要从发起方传递到每个参与方。Spring Cloud通过HTTP Header传递,Dubbo通过Attachment传递。

// Dubbo XID透传Filter(如果seata-dubbo集成未自动生效)
@Activate(group = {Constants.PROVIDER, Constants.CONSUMER})
public class SeataDubboFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String xid = RootContext.getXID();
        if (xid != null) {
            RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid);
        }
        try {
            Result result = invoker.invoke(invocation);
            return result;
        } finally {
            String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID);
            if (rpcXid != null) {
                RootContext.bind(rpcXid);
            }
        }
    }
}

Spring Cloud用户配置seata-spring-cloud-starter后,xid通过SeataTransactionPropagationInterceptor自动在HTTP Header中传递,无需手动处理。

压测与调优:并发下的锁冲突处理

AT模式在高并发下最大的瓶颈是全局锁。当两个全局事务同时修改同一行数据,后获取锁的事务会等待甚至超时回滚。

# Seata客户端调优配置(application.yml)
seata:
  client:
    tm:
      commit-retry-count: 3       # 提交失败重试次数
      rollback-retry-count: 3     # 回滚失败重试次数
    rm:
      async-commit-buffer-limit: 10000  # 异步提交缓冲区
      report-retry-count: 5       # 报告重试次数
      table-meta-checker-enabled: true
      report-success-enable: false
      saga-branch-register-enable: false
      saga-json-parser: jackson
      lock:
        retry-interval: 10        # 全局锁重试间隔(ms)
        retry-times: 30           # 全局锁重试次数
        retry-policy-branch-rollback-on-conflict: true

压测数据(订单创建场景,10并发,1000次请求):

  • 单服务本地事务:QPS 850,0失败
  • Seata AT模式(3服务):QPS 320,约5%锁冲突回滚
  • Seata AT模式(优化后,lock retry=50):QPS 280,约2%锁冲突回滚

全局锁重试次数调高会降低锁冲突失败率,但会增加事务持有时间,QPS反而下降。实际调优需要在失败率和吞吐之间取平衡。对于极高并发场景,建议将热点数据操作拆分为TCC模式,AT模式保留给低冲突的普通业务。

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

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

相关推荐