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

📅 2026/7/11 4:11:12
Seata 分布式事务落地——AT 模式与 TCC 模式的生产选型指南
Seata 分布式事务落地——AT 模式与 TCC 模式的生产选型指南一、分布式事务的本质问题在微服务架构中一个业务操作往往需要跨多个服务、多个数据库完成。以电商下单为例流程涉及订单服务创建订单、库存服务扣减库存、账户服务扣减余额。这三个操作要么全部成功要么全部回滚——这正是分布式事务需要解决的问题。SeataSimple Extensible Autonomous Transaction Architecture是阿里巴巴开源的分布式事务解决方案在 Java 生态中应用最为广泛。它提供了 AT、TCC、Saga、XA 四种模式其中 AT 模式和 TCC 模式是生产环境使用最多的两种。本文将从原理、代码、生产经验三个维度对比这两种模式。flowchart TD subgraph Seata 核心组件 TC[TC - 事务协调器br/Transaction Coordinatorbr/Seata Server] TM[TM - 事务管理器br/Transaction Managerbr/GlobalTransactional 标注方] RM[RM - 资源管理器br/Resource Managerbr/各微服务的数据库连接] end subgraph 下单业务流程 A[订单服务br/OrderServicebr/TM角色] --|分支事务1| B[库存服务br/InventoryServicebr/RM角色] A --|分支事务2| C[账户服务br/AccountServicebr/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:88482.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 不适用。性能开销每次写操作都需要额外记录前后镜像数据。脏写风险默认隔离级别为读未提交高并发下可能出现脏读。可通过GlobalLock或SELECT FOR UPDATE缓解。三、TCC 模式业务侵入式的资源预留方案3.1 原理简介TCCTry-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 以降低开发成本。无论选择哪种模式以下三条生产铁律不可忽视超时机制全局事务必须设置timeoutMills超时后自动触发回滚。生产环境建议设置在 30-60 秒之间——过短可能误杀正常执行的长事务过长则降低故障切换速度。幂等设计TCC 模式的 Confirm/Cancel 必须幂等AT 模式虽由框架保证但建议在业务代码中也加入防重逻辑如基于 XID 的事务去重表。监控告警监控undo_log堆积量、TCC 事务超时率、全局事务平均耗时等关键指标。undo_log 堆积超过 10000 行或超过 24 小时未被清理意味着大量历史数据占用存储需排查 Seata Server 的二阶段调度是否正常。作者程序员鸭梨李然Java 架构师专注微服务架构与分布式事务实践。欢迎留言交流。