Seata AT模式:百万订单下的跨服务一致性实战指南

385 阅读3分钟

分布式事务就像一场没有司仪的集体婚礼——新郎(订单服务)说"我愿意"时,新娘(库存服务)可能正在后台修改逃跑路线。别慌,Seata AT就是你的婚礼拯救大师!

AT模式核心三幕剧

graph LR
A[TM 开启全局事务] --> B[RM 注册分支事务]
B --> C[业务SQL + Undo Log]
C --> D[RM 报告状态]
D --> E{全局成功?}
E -->|Yes| F[异步删除Undo Log]
E -->|No| G[基于Undo Log回滚]

百万级订单的生存法则

  1. 全局锁优化:用Redis替代DB锁,TP99降低40ms
  2. TC分片路由:按xid哈希路由到不同TC集群
  3. 异步化提交:二阶段提交转异步队列(慎用!)
  4. Saga模式降级:超时订单转补偿流程

手撕代码时刻(Spring Boot + Seata 1.8.0)

订单服务:

@RestController
public class OrderController {
    
    @GlobalTransactional(name = "create-order-tx", timeoutMills = 60000)
    @PostMapping("/order")
    public String createOrder(@RequestBody OrderDTO dto) {
        // 1. 扣减库存(跨服务调用)
        inventoryFeignClient.deduct(dto.getSkuId(), dto.getCount());
        
        // 2. 本地创建订单
        orderService.create(dto); // 伪代码:insert into orders...
        
        // 测试回滚:手动触发异常
        if(dto.getCount() > 1000) throw new RuntimeException("✨ 表演一个事务回滚 ✨");
        
        return "Order-2025" + System.currentTimeMillis();
    }
}

库存服务:

@Service
public class InventoryServiceImpl {

    @Transactional
    public void deduct(String skuId, int count) {
        // 关键操作:Seata自动代理数据源
        jdbcTemplate.update(
            "UPDATE inventory SET stock = stock - ? WHERE sku_id = ? AND stock >= ?",
            count, skuId, count
        );
        
        // Seata自动记录undo_log:
        // {"before":{"stock":100},"after":{"stock":90}}
    }
}

性能压测数据(双机房部署)

并发量传统XASeata AT提升
1万32秒11秒3x
10万超时失败98秒
100万-22分钟-

📌实测技巧:server.undo.logSaveDays=3将undo保留时间从默认7天改为3天,磁盘空间节省40%

避坑了避坑了 AT不是银弹

  1. 隔离级别:  默认是读未提交(Read Uncommitted)。高并发扣库存时,可能出现脏读(看到未提交的库存减少)。解决方案:

    • 业务上使用SELECT ... FOR UPDATE(牺牲性能)。
    • 改用TCC/SAGA模式(业务侵入性强)。
    • 设计幂等接口,结合状态机处理中间状态。
  2. 全局锁冲突:

    • 避免长时间持有全局锁(分支事务内不宜处理耗时远程调用)。
    • 热点数据(如秒杀商品)考虑分片键设计,将库存分散到不同记录(如10条记录各存100件)。
  3. 非SQL操作:  AT模式强依赖SQL解析。对Redis、MQ等非SQL操作无效!需结合TCC或消息事务。

终极方案:混合事务模式

// 在AT事务中嵌套Saga
@GlobalTransactional
public void hybridTransaction() {
    atTransaction(); // AT操作
    
    // 调用Saga参与者
    StartSagaBuilder.newBuilder()
        .withService(serviceA)
        .withCompensation(serviceACompensation);
}

📣 技术负责人原话:"Seata AT就像分布式系统的安全带——平时感觉不到存在,出事时能救命!"

Demo验证技巧

# 强制触发回滚(测试用)
curl -X POST http://order-service/order \
  -H "Content-Type: application/json" \
  -d '{"skuId":"G-777", "count":2000}' 

# 查看undo_log(Seata的时光机)
mysql> select * from undo_log \G
*************************** 1. row ***************************
    branch_id: 725883267
    xid: 10.16.xx.xx:8091:725883266
    rollback_info: {"@class":"io.seata.rm.datasource.undo...."}

输出回滚日志即证明事务生效!

🚨 最后三条保命建议

  1. 生产环境必须开TC集群(单节点是作死行为)
  2. 更新语句一定加WHERE条件限制范围
  3. 重要业务搭配Saga模式做补偿事务(双重保险)