news 2026/7/11 4:11:03

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 架构师,专注微服务架构与分布式事务实践。欢迎留言交流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/11 4:09:46

UEFI开发实战:用DeepSeek-V4-Pro在裸金属环境运行坦克大战

1. 这不是又一场“参数发布会”&#xff0c;而是一次UEFI开发者的实战压力测试我盯着QEMU窗口里那辆金色履带缓缓转动的坦克&#xff0c;帧率稳定在30.2&#xff0c;粒子拖尾在480p屏幕上划出清晰的橙色轨迹——这行代码不是跑在Linux终端里&#xff0c;不是编译成Python脚本&a…

作者头像 李华
网站建设 2026/7/11 4:09:33

AI角色控制器性能优化指南:从硬件选型到软件调优实战

1. 项目概述&#xff1a;为什么你的AI角色控制器跑不“顺”&#xff1f;最近在游戏开发圈和独立动画师群体里&#xff0c;AI4Animation这个开源项目火得不行。简单来说&#xff0c;它就像给角色动画装上了一颗“AI大脑”&#xff0c;能让角色根据物理环境、玩家输入或者预设目标…

作者头像 李华
网站建设 2026/7/11 4:06:44

DeepSeek-R1国产芯片全栈适配实战指南

1. 项目概述&#xff1a;国产芯片跑通DeepSeek-R1&#xff0c;不是“能用”&#xff0c;而是“好用”最近在几个AI基础设施交流群里&#xff0c;大家聊得最多的一句话是&#xff1a;“DeepSeek-R1模型跑起来了&#xff0c;但卡在国产芯片上——不是跑不动&#xff0c;是跑不稳、…

作者头像 李华
网站建设 2026/7/11 4:06:32

阿里转型AI公司:投入巨大成果初显,利润表何时跟上步伐?

核心判断&#xff1a;阿里的双重身份 从资本开支、技术栈和新增收入看&#xff0c;阿里已是纯血AI公司&#xff1b;但从利润来源、自由现金流和用户入口看&#xff0c;它仍是由电商供血的转型公司。阿里完成了战略身份切换&#xff0c;财务身份尚在途中。 长鑫投资&#xff1a;…

作者头像 李华
网站建设 2026/7/11 4:05:35

12864液晶(ST7920)驱动优化:C代码内存占用从1KB降至32B的缓存策略

12864液晶&#xff08;ST7920&#xff09;驱动优化&#xff1a;C代码内存占用从1KB降至32B的缓存策略在嵌入式开发中&#xff0c;资源受限的环境常常迫使开发者对每一字节的内存都精打细算。12864液晶屏作为常见的显示设备&#xff0c;其驱动代码的内存占用问题尤为突出。本文将…

作者头像 李华
网站建设 2026/7/11 4:03:27

2026 企业 GEO 优化选型指南

AI 时代&#xff0c;企业为什么一定要重视 GEO 优化 如今消费者获取品牌信息的渠道已经发生明显转变&#xff0c;不再只依靠传统网页搜索&#xff0c;豆包、文心一言、ChatGPT 等各类大模型问答&#xff0c;成为采购决策前的核心信息来源。艾瑞咨询 2026 年行业调研数据显示&a…

作者头像 李华