当前位置: 首页 > news >正文

Seata 分布式事务落地——AT 模式与 TCC 模式的生产选型指南

Seata 分布式事务落地——AT 模式与 TCC 模式的生产选型指南

一、分布式事务的本质问题

在微服务架构中,一个业务操作往往需要跨多个服务、多个数据库完成。以电商下单为例,流程涉及订单服务(创建订单)、库存服务(扣减库存)、账户服务(扣减余额)。这三个操作要么全部成功,要么全部回滚——这正是分布式事务需要解决的问题。

Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的分布式事务解决方案,在 Java 生态中应用最为广泛。它提供了 AT、TCC、Saga、XA 四种模式,其中 AT 模式和 TCC 模式是生产环境使用最多的两种。本文将从原理、代码、生产经验三个维度对比这两种模式。

flowchart TD subgraph "Seata 核心组件" TC["TC - 事务协调器<br/>Transaction Coordinator<br/>(Seata Server)"] TM["TM - 事务管理器<br/>Transaction Manager<br/>(@GlobalTransactional 标注方)"] RM["RM - 资源管理器<br/>Resource Manager<br/>(各微服务的数据库连接)"] end subgraph "下单业务流程" A["订单服务<br/>OrderService<br/>(TM角色)"] -->|"分支事务1"| B["库存服务<br/>InventoryService<br/>(RM角色)"] A -->|"分支事务2"| C["账户服务<br/>AccountService<br/>(RM角色)"] end TM -->|"注册/提交/回滚全局事务"| TC B -->|"注册/报告分支事务状态"| TC C -->|"注册/报告分支事务状态"| TC subgraph "AT模式两阶段" D1["一阶段:执行业务SQL + 记录undo_log"] --> D2["二阶段:异步删除undo_log(提交)"] D1 --> D3["二阶段:根据undo_log回滚数据(回滚)"] end subgraph "TCC模式两阶段" E1["Try:资源预留/检查"] --> E2["Confirm:提交业务操作"] E1 --> E3["Cancel:释放预留资源"] end

二、AT 模式:自动补偿的无侵入方案

2.1 原理简介

AT 模式的核心思路是基于数据库的自动补偿。它不需要业务代码做任何改造,只需在数据库中增加一张undo_log表。执行流程分为两阶段:

  • 一阶段:执行业务 SQL,同时将修改前后的数据(前镜像/后镜像)记录到undo_log表,然后提交本地事务。
  • 二阶段:如果全局事务提交,异步删除undo_log;如果全局事务回滚,根据undo_log中的前镜像生成反向 SQL 进行补偿。

2.2 工程配置

# application.yml seata: tx-service-group: my_test_tx_group # 事务分组名称 service: vgroup-mapping: my_test_tx_group: default # 映射到 Seata Server 集群 grouplist: default: 127.0.0.1:8091 # Seata Server 地址 config: type: nacos nacos: server-addr: 127.0.0.1:8848 group: SEATA_GROUP registry: type: nacos nacos: application: seata-server server-addr: 127.0.0.1:8848

2.3 AT 模式代码示例

import io.seata.spring.annotation.GlobalTransactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * 订单服务——分布式事务发起方(TM 角色)。 * 使用 @GlobalTransactional 注解开启全局事务。 */ @Service public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private InventoryFeignClient inventoryClient; @Autowired private AccountFeignClient accountClient; /** * 创建订单——全局事务入口。 * AT 模式只需要在业务入口方法加 @GlobalTransactional, * 各子服务的本地事务由 Seata 代理自动管理。 * * @param userId 用户ID * @param productId 商品ID * @param quantity 购买数量 * @return 订单ID * @throws OrderCreateException 如果下单失败 */ @GlobalTransactional(name = "create-order", timeoutMills = 300000, rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class) public Long createOrder(Long userId, Long productId, Integer quantity) throws OrderCreateException { // 参数校验 if (userId == null || productId == null || quantity == null || quantity <= 0) { throw new IllegalArgumentException("订单参数不合法: userId=" + userId + ", productId=" + productId + ", quantity=" + quantity); } // 1. 创建订单(本地事务,被 Seata 代理管理) Order order = new Order(); order.setUserId(userId); order.setProductId(productId); order.setQuantity(quantity); order.setStatus(OrderStatus.CREATED); orderMapper.insert(order); // 2. 扣减库存(远程调用,自动加入全局事务分支) try { inventoryClient.deduct(productId, quantity); } catch (Exception e) { throw new OrderCreateException("扣减库存失败: " + e.getMessage(), e); } // 3. 扣减余额(远程调用,自动加入全局事务分支) try { accountClient.deduct(userId, order.getTotalAmount()); } catch (Exception e) { throw new OrderCreateException("扣减余额失败: " + e.getMessage(), e); } // 4. 更新订单状态 order.setStatus(OrderStatus.PAID); orderMapper.updateById(order); return order.getId(); } }
import io.seata.core.context.RootContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; /** * 库存服务——分布式事务参与者(RM 角色)。 * AT 模式下,本地 @Transactional 的事务由 Seata 代理接管。 */ @RestController @RequestMapping("/inventory") public class InventoryController { @Autowired private InventoryMapper inventoryMapper; /** * 扣减库存接口。 * Seata AT 代理会自动在本地事务提交前记录 undo_log。 * * @param productId 商品ID * @param quantity 扣减数量 * @throws InventoryException 如果库存不足 */ @PostMapping("/deduct") @Transactional(rollbackFor = Exception.class) public void deduct(Long productId, Integer quantity) throws InventoryException { // 打印全局事务 XID,用于日志追踪 System.out.println("全局事务 XID = " + RootContext.getXID()); // 查询当前库存 Inventory inventory = inventoryMapper.selectForUpdate(productId); if (inventory == null) { throw new InventoryException("商品不存在: productId=" + productId); } if (inventory.getStock() < quantity) { throw new InventoryException("库存不足: 当前库存=" + inventory.getStock() + ", 需要=" + quantity); } // 扣减库存(Seata 会自动记录前镜像和后镜像到 undo_log) int affected = inventoryMapper.deductStock(productId, quantity); if (affected != 1) { throw new InventoryException("库存扣减失败,影响行数异常: " + affected); } } }

2.4 AT 模式的局限性

  • 仅支持关系型数据库undo_log依赖数据库事务,Redis/MongoDB 等 NoSQL 不适用。
  • 性能开销:每次写操作都需要额外记录前后镜像数据。
  • 脏写风险:默认隔离级别为"读未提交",高并发下可能出现脏读。可通过@GlobalLockSELECT FOR UPDATE缓解。

三、TCC 模式:业务侵入式的资源预留方案

3.1 原理简介

TCC(Try-Confirm-Cancel)模式要求开发者自行实现三个方法:

  • Try:资源检查和预留(如冻结库存、预占额度)。
  • Confirm:提交业务操作(如真正扣减冻结的库存)。
  • Cancel:释放预留的资源(如解冻库存)。

TCC 模式对代码有侵入性,但换来的是更好的性能(无全局锁)和更广的适用范围(Redis、消息队列等均可参与)。

3.2 TCC 模式代码示例

import io.seata.rm.tcc.api.BusinessActionContext; import io.seata.rm.tcc.api.BusinessActionContextParameter; import io.seata.rm.tcc.api.LocalTCC; import io.seata.rm.tcc.api.TwoPhaseBusinessAction; /** * 库存服务的 TCC 接口定义。 * 使用 @LocalTCC 注解标记为 TCC 服务。 */ @LocalTCC public interface InventoryTccAction { /** * Try 阶段:预扣库存(将库存从 available 转移到 frozen)。 * * @param actionContext 事务上下文(Seata 自动注入) * @param productId 商品ID * @param quantity 预扣数量 * @return true 表示预留成功 */ @TwoPhaseBusinessAction(name = "inventoryTcc", commitMethod = "commit", rollbackMethod = "rollback") boolean prepareDeduct(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "productId") Long productId, @BusinessActionContextParameter(paramName = "quantity") Integer quantity); /** * Confirm 阶段:确认扣减(将库存从 frozen 中真正扣除)。 * * @param actionContext 事务上下文 * @return true 表示确认成功 */ boolean commit(BusinessActionContext actionContext); /** * Cancel 阶段:回滚(将库存从 frozen 恢复到 available)。 * * @param actionContext 事务上下文 * @return true 表示回滚成功 */ boolean rollback(BusinessActionContext actionContext); }
import io.seata.rm.tcc.api.BusinessActionContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * 库存 TCC 接口实现。 * * 关键设计原则: * - Try 阶段做资源预留,不真正扣减 * - Confirm 阶段必须幂等 * - Cancel 阶段必须幂等且允许空回滚 */ @Service public class InventoryTccActionImpl implements InventoryTccAction { @Autowired private InventoryMapper inventoryMapper; @Override @Transactional(rollbackFor = Exception.class) public boolean prepareDeduct(BusinessActionContext actionContext, Long productId, Integer quantity) { // 空回滚检测:如果这条记录之前已经被 Cancel,则不再执行 Try String xid = actionContext.getXid(); if (inventoryMapper.existsTccLog(xid)) { System.out.println("检测到空回滚,跳过 Try: xid=" + xid); return true; } // 扣减可用库存,增加冻结库存(Seata 代理管理本地事务) int affected = inventoryMapper.freezeStock(productId, quantity); if (affected != 1) { throw new RuntimeException("冻结库存失败: productId=" + productId + ", quantity=" + quantity); } // 记录 TCC 操作日志(用于幂等性判断和故障恢复) inventoryMapper.insertTccLog(xid, "INVENTORY_DEDUCT", productId.toString(), quantity.toString(), "TRY"); return true; } @Override @Transactional(rollbackFor = Exception.class) public boolean commit(BusinessActionContext actionContext) { String xid = actionContext.getXid(); // 幂等性检查:如果已 Confirm,则直接返回成功 if (inventoryMapper.isTccPhaseCompleted(xid, "CONFIRM")) { return true; } Long productId = Long.valueOf(actionContext.getActionContext("productId").toString()); Integer quantity = Integer.valueOf(actionContext.getActionContext("quantity").toString()); // 真正扣减冻结库存 inventoryMapper.confirmDeduct(productId, quantity); // 更新 TCC 日志状态 inventoryMapper.updateTccLogStatus(xid, "CONFIRM"); return true; } @Override @Transactional(rollbackFor = Exception.class) public boolean rollback(BusinessActionContext actionContext) { String xid = actionContext.getXid(); // 幂等性检查:如果已 Cancel,则直接返回成功 if (inventoryMapper.isTccPhaseCompleted(xid, "CANCEL")) { return true; } Long productId = Long.valueOf(actionContext.getActionContext("productId").toString()); Integer quantity = Integer.valueOf(actionContext.getActionContext("quantity").toString()); // 解冻库存(恢复可用库存) inventoryMapper.unfreezeStock(productId, quantity); // 记录回滚日志(防止空回滚后的 Try 再次执行) inventoryMapper.insertTccLog(xid, "INVENTORY_DEDUCT", productId.toString(), quantity.toString(), "CANCEL"); return true; } }

四、AT vs TCC:生产选型决策矩阵

维度AT 模式TCC 模式
代码侵入性无侵入,只需配置高侵入,需实现 Try/Confirm/Cancel
性能中等(需记录 undo_log)较好(无全局锁)
适用场景关系型数据库场景任何资源类型(DB/Redis/MQ)
幂等性框架自动保证开发者自行保证
空回滚/悬挂框架自动处理开发者自行处理
隔离性默认读未提交可自定义
运维复杂度中(需管理业务补偿表)
开发效率高(1-2天集成)中(3-5天开发)

选型建议

  • 纯关系型数据库 + 改造时间紧 → 优先选AT 模式
  • 涉及 Redis/MQ 等非数据库资源 → 必须选TCC 模式
  • 高并发库存扣减场景 → 推荐TCC 模式(避免热点行锁竞争)。
  • 简单业务 + 低并发 →AT 模式性价比最高。

五、总结

Seata 的 AT 模式和 TCC 模式各有所长,没有绝对的优劣之分。在实际项目中,我们通常在一个应用中混用两种模式:核心交易链路(订单/支付)使用 TCC 以保证性能和资源灵活性,非核心链路(日志记录/通知)使用 AT 以降低开发成本。

无论选择哪种模式,以下三条生产铁律不可忽视:

  1. 超时机制:全局事务必须设置timeoutMills,超时后自动触发回滚。生产环境建议设置在 30-60 秒之间——过短可能误杀正常执行的长事务,过长则降低故障切换速度。
  2. 幂等设计:TCC 模式的 Confirm/Cancel 必须幂等;AT 模式虽由框架保证,但建议在业务代码中也加入防重逻辑(如基于 XID 的事务去重表)。
  3. 监控告警:监控undo_log堆积量、TCC 事务超时率、全局事务平均耗时等关键指标。undo_log 堆积(超过 10000 行或超过 24 小时未被清理)意味着大量历史数据占用存储,需排查 Seata Server 的二阶段调度是否正常。

作者:程序员鸭梨(李然),Java 架构师,专注微服务架构与分布式事务实践。欢迎留言交流。

http://www.cnnetsun.cn/news/3277669.html

相关文章:

  • UEFI开发实战:用DeepSeek-V4-Pro在裸金属环境运行坦克大战
  • AI角色控制器性能优化指南:从硬件选型到软件调优实战
  • DeepSeek-R1国产芯片全栈适配实战指南
  • 阿里转型AI公司:投入巨大成果初显,利润表何时跟上步伐?
  • 12864液晶(ST7920)驱动优化:C代码内存占用从1KB降至32B的缓存策略
  • 2026 企业 GEO 优化选型指南
  • 美国AI情感产品市场爆发:用户需求多样,出海品牌机遇几何?
  • 腾讯开源MoE大模型Hy3:295B参数高效推理实战指南
  • Qoder Cloud Agents:全托管AI Agent平台在Loop Engineering中的实践
  • 工业信号干扰解决方案:FOD4216与PIC18LF47K40实战应用
  • netsh advfirewall 防火墙规则管理:5条命令实现端口开放与程序例外
  • VOFA+串口示波器实战:编码电机PID波形调试与4类异常分析
  • 合肥晶合集成“A+H”上市,募资近70亿港元,全球晶圆代工排名第九
  • 工业级智能功率驱动器TPD2015FN与PIC18F45K22的电机控制方案
  • HITEK A1052100N-01 以太网端口模块
  • 零壹教育:解决数据错乱问题,Pandas数据合并规范操作指南
  • AI Agent在生产环境中的GEO优化实践与工程化落地
  • 还在手动写CRUD?Claude Code自动化开发实战(含Docker+FastAPI+PostgreSQL端到端Demo)——限时开放调试日志原始数据
  • prompts.chat:04-role-based-prompting
  • 2026年度专科毕业论文软件大横评:学范文工具一站式创作功能实测与避坑攻略
  • 【MySQL】主从复制故障排查与修复总结
  • 工业级ADC信号采集系统设计与优化实践
  • 从AGI到ASI:Transformer与多智能体技术路径解析
  • 基于STM32单片机水质监测PH值电导率浑浊度等蓝牙无线APP/WiFi无线APP/摄像头视频监控设计DIY173
  • 先出租算力砸盘、后建数据中心加仓,Meta如何破解算力错配与基因困境?
  • Waterfox 6.6.16.1发布:搜索功能紧急修复,6.7.0-beta.1测试版亮点多!
  • NSK W1401FA精密滚珠丝杠技术详解
  • 2026/7/10学习agent ReAct自动循环agent
  • Codex:Rust 编写的开发者智能代理运行时
  • AI超大规模计算资源分析:从架构选择到中小团队实践指南