Spring Boot集成Seata分布式事务实战:AT模式配置与高并发场景调优

分布式事务是微服务架构绕不过去的坎

单体应用里数据库事务靠ACID保证一致性,拆成微服务后,一个业务操作横跨多个数据库,本地事务管不了跨库一致性。订单服务扣库存、支付服务扣余额、积分服务加积分,三步要么全成功要么全回滚。Seata的AT模式是侵入性最小的分布式事务方案——不需要改业务SQL,只需加注解。

Seata AT模式的工作原理

AT模式的核心是两阶段提交,但对业务代码完全透明:

一阶段:拦截业务SQL,在执行前保存数据快照(before image),执行后保存变更后的快照(after image),生成回滚日志(undo_log)。业务SQL和undo_log在同一个本地事务中提交,保证原子性。

二阶段提交:TC(事务协调器)通知各分支提交,因为一阶段已经提交了,只需要异步清理undo_log即可,性能极高。

二阶段回滚:TC通知各分支回滚,读取undo_log中的before image,生成反向补偿SQL执行回滚。如果发现after image与当前数据不一致(被脏写),需要人工介入。

Seata Server部署与Spring Boot集成

Seata Server部署

# docker-compose.yml
version: '3'
services:
  seata-server:
    image: seataio/seata-server:2.2.0
    ports:
      - "8091:8091"
      - "7091:7091"
    environment:
      - SEATA_IP=0.0.0.0
    volumes:
      - ./seata-config:/seata-server/resources
    depends_on:
      - seata-store

  seata-store:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: seata
    volumes:
      - ./sql:/docker-entrypoint-initdb.d

application.yml配置(每个微服务)

# 各微服务的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: 127.0.0.1:8848
      namespace: seata
      group: SEATA_GROUP
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: seata
      group: SEATA_GROUP

每个微服务数据库建undo_log表

-- 每个参与分布式事务的数据库都要建这张表
CREATE TABLE IF NOT EXISTS `undo_log` (
  `branch_id`     BIGINT       NOT NULL,
  `xid`           VARCHAR(128) NOT NULL,
  `context`       VARCHAR(128) NOT NULL,
  `rollback_info` LONGBLOB     NOT NULL,
  `log_status`    INT(11)      NOT NULL,
  `log_created`   DATETIME(6)  NOT NULL,
  `log_modified`  DATETIME(6)  NOT NULL,
  UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB;

业务代码:一个注解搞定分布式事务

集成完成后,业务代码只需在入口方法上加@GlobalTransactional注解:

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private InventoryClient inventoryClient;
    
    @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.setProductId(dto.getProductId());
        order.setAmount(dto.getAmount());
        order.setStatus("CREATED");
        orderMapper.insert(order);
        
        // 2. 扣减库存(远程调用)
        inventoryClient.deduct(dto.getProductId(), dto.getQuantity());
        
        // 3. 扣减账户余额(远程调用)
        accountClient.debit(dto.getUserId(), dto.getAmount());
        
        order.setStatus("PAID");
        orderMapper.updateById(order);
        return order;
    }
}

远程服务只需要引入seata依赖和配置undo_log,不需要加@GlobalTransactional。Seata通过数据源代理自动拦截SQL,记录undo_log。

高并发场景下的Seata调优

AT模式在一阶段就提交了本地事务,性能瓶颈主要在undo_log的写入和全局锁的争用上。

全局锁优化

默认情况下,AT模式会在全局事务提交前持有全局锁,防止脏写。高并发写入场景下全局锁成为瓶颈。优化方案:

# Nacos配置中心 - seata配置
# 全局锁超时时间(毫秒),默认30秒太长
client.lock.lock-retry-interval = 10
client.lock.lock-retry-times = 30

# 对于读多写少场景,可以关闭全局锁(需自行保证不脏写)
# 注意:关闭全局锁后需要业务层保证幂等性
service.disable-global-transaction = false

undo_log表优化

-- undo_log表加上索引,加速清理
ALTER TABLE `undo_log` ADD INDEX `idx_log_created` (`log_created`);
-- 定期清理已提交的undo_log
DELETE FROM `undo_log` WHERE `log_status` = 1 
  AND `log_modified` < DATE_SUB(NOW(), INTERVAL 7 DAY);

Seata Server JVM调优

# seata-server启动参数
JAVA_OPT="-Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 
  -XX:+HeapDumpOnOutOfMemoryError 
  -Dio.netty.leakDetection.level=disabled"

G1收集器适合Seata的事务日志写入模式,200ms的GC停顿在大多数业务场景下可接受。Netty的内存泄漏检测关闭后吞吐提升约15%。

异常场景处理与最佳实践

场景1:全局事务超时

默认超时60秒。如果业务中有耗时较长的远程调用,需要调大超时时间:

@GlobalTransactional(timeoutMills = 300000) // 5分钟超时
public Order createOrder(OrderDTO dto) {
    // ...
}

场景2:脏写检测触发

回滚时发现after image与当前数据不一致,说明在分布式事务期间有其他事务修改了数据。Seata默认会记录异常日志并人工介入。处理方式:

# 在Nacos配置中开启脏写自动处理
# 注意:仅在业务能容忍覆盖回滚时开启
client.undo.log-serialization = jackson
client.undo.only-care-update-columns = true

场景3:幂等性保证

分布式事务可能重试,远程调用接口必须幂等:

@PostMapping("/deduct")
public Result deduct(@RequestParam String productId, 
                     @RequestParam Integer quantity,
                     @RequestParam String txId) {
    // 用txId做幂等校验
    DeductRecord exist = deductRecordMapper.selectByTxId(txId);
    if (exist != null) {
        return Result.success("already deducted");
    }
    // 正常执行扣减逻辑
    inventoryService.deduct(productId, quantity);
    // 记录事务ID
    deductRecordMapper.insert(txId, productId, quantity);
    return Result.success();
}

场景4:避免大事务

分布式事务越短越好。不要在@GlobalTransactional方法中做RPC调用之外的耗时操作(如大文件上传、复杂计算)。把不必要的操作移到事务外,只在事务中保留必须保证一致性的核心操作。

Seata AT模式在微服务架构中的定位是"低成本的一致性保障"。牺牲了一点点性能(undo_log写入和全局锁),换来了业务代码几乎零侵入。对于大多数电商、支付、SaaS场景,AT模式的性价比远高于TCC和Saga模式。

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

(0)
小编小编
上一篇 2026年7月30日
下一篇 2026年7月30日

相关推荐

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)
小编小编
上一篇 2026年7月27日
下一篇 2026年7月27日

相关推荐