本文目录导读:

- 目录导读
- 分布式事务的痛点与Seata TCC模式定位
- TCC核心三阶段原理与设计哲学
- 真实业务案例:电商下单+库存+积分联动
- 三大坑:空回滚、幂等控制、悬挂问题
- 性能对比与适用场景:TCC vs AT模式
- 常见问答(FAQ)
- TCC模式的最佳实践路线图
目录导读
- 分布式事务的痛点与Seata TCC模式定位
- TCC核心三阶段原理与设计哲学(Try / Confirm / Cancel)
- 真实业务案例:电商下单+库存+积分联动的TCC实现
- 空回滚、幂等控制、悬挂问题——TCC实战的三大“坑”
- 性能对比与适用场景:TCC vs AT模式取舍
- 常见问答(FAQ)与故障排查清单
- TCC模式的最佳实践路线图
分布式事务的痛点与Seata TCC模式定位
在微服务架构中,一次业务操作往往跨多个数据库(如订单库、库存库、积分库),传统本地事务无法解决跨库原子性,而基于消息的最终一致性又无法满足强一致场景,Seata(Simple Extensible Autonomous Transaction Architecture)作为国产开源分布式事务中间件,提供了AT、TCC、Saga、XA四种模式,其中TCC(Try-Confirm-Cancel)模式因其高性能(无全局锁)、强隔离性,成为金融、电商等核心链路的首选。
TCC核心三阶段原理与设计哲学
TCC将一次业务拆分为三个阶段:
- Try阶段:完成业务检查(一致性)并预留资源(隔离性),比如冻结库存、锁定余额。
- Confirm阶段:真正执行业务,使用Try阶段的预留资源。需保证幂等。
- Cancel阶段:若某分支事务失败,则回滚所有已成功的Try,释放预留资源。
设计哲学:用业务代码换全局锁,用幂等设计换一致性,对比AT模式,TCC不依赖数据库Undo Log,因此对性能无额外开销,但需要业务开发人员手动实现三方法。
真实业务案例:电商下单+库存+积分联动
场景:用户下单(订单服务)、扣减库存(库存服务)、增加用户积分(积分服务),要求三个操作原子成功或失败。
Step 1:定义分支事务接口
@LocalTCC
public interface InventoryAction {
@TwoPhaseBusinessAction(name = "inventoryTcc", commitMethod = "confirm", rollbackMethod = "cancel")
boolean tryDeduct(BusinessActionContext ctx,
@BusinessActionContextParameter(paramName = "goodsId") String goodsId,
@BusinessActionContextParameter(paramName = "num") int num);
boolean confirm(BusinessActionContext ctx);
boolean cancel(BusinessActionContext ctx);
}
Step 2:Try阶段(冻结库存)
UPDATE inventory SET frozen_qty = frozen_qty + #{num} WHERE goods_id = #{goodsId} AND qty >= #{num}
若更新行数为0,抛出异常,触发Cancel。
Step 3:Confirm阶段(实际扣减)
UPDATE inventory SET qty = qty - frozen_qty, frozen_qty = 0 WHERE goods_id = #{goodsId}
Step 4:Cancel阶段(解冻库存)
UPDATE inventory SET frozen_qty = frozen_qty - #{num} WHERE goods_id = #{goodsId}
主业务调用(使用全局事务注解):
@GlobalTransactional(name = "create_order_tx")
public void createOrder(OrderDTO orderDTO) {
orderService.create(orderDTO); // 本地事务
inventoryAction.tryDeduct(...); // TCC分支1
pointAction.tryIncrease(...); // TCC分支2
// 所有分支成功,Seata自动调用confirm;任一失败自动调用cancel
}
三大坑:空回滚、幂等控制、悬挂问题
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 空回滚 | Try未执行(因网络超时),但全局事务回滚时调用了Cancel | Cancel方法中,先查询分支记录是否存在(需有事务状态表),若无记录则直接返回成功 |
| 幂等控制 | Confirm/Cancel被重复调用(网络重发) | 使用唯一事务ID(xid + branchId)做记录,用数据库唯一键或分布式锁保证只执行一次 |
| 悬挂控制 | Try后未执行Confirm,因空回滚导致后续重试的Try被忽略 | 在执行Try前,检查Cancel是否已执行过(事务状态表有cancel标记),若有则阻止本次Try |
事务状态表示例(tbl_tx_record):
CREATE TABLE tbl_tx_record ( xid VARCHAR(64) NOT NULL, branch_id BIGINT NOT NULL, status TINYINT COMMENT '0-try,1-confirm,2-cancel', PRIMARY KEY (xid, branch_id) );
性能对比与适用场景:TCC vs AT模式
| 维度 | AT模式 | TCC模式 |
|---|---|---|
| 侵入性 | 低(只需@GlobalTransactional,自动生成反向SQL) |
高(需手写Try/Confirm/Cancel) |
| 性能 | 有全局锁和Undo Log开销 | 无锁,性能最高 |
| 一致性 | 最终一致,依赖数据库隔离 | 强隔离,业务控制预留资源 |
| 适用场景 | 简单跨库更新,容忍较小性能损耗 | 高并发、敏感资源(库存/余额)、需显式管理资源 |
如果追求极致性能且业务能接受手写三方法,TCC是更优解;若快速开发、非核心链路可用AT模式减少工作量。
常见问答(FAQ)
Q1:TCC的Confirm/Cancel方法必须返回boolean类型吗?
是的,返回false或抛出异常会触发全局回滚(对已成功的其他分支执行Cancel),建议用boolean,避免异常被吞。
Q2:如果Try阶段成功,但Confirm阶段因数据库连接失败,会怎样? Seata会不断重试Confirm(默认间隔10秒),直到成功或达到最大重试次数,因此Confirm方法必须支持幂等,不能因重复执行产生双扣款。
Q3:Seata如何保证全局事务ID一致? 全局事务ID(XID)由TC(事务协调器)生成,通过Dubbo/Restful等方式的隐式传参传递给所有参与分支。
Q4:能否只用TCC处理异步操作? 可以,但需注意:Try和Confirm必须在同一个JVM线程上下文内(需透传XID),如果用MQ异步,需手动传递XID并在消费者中绑定事务上下文。
TCC模式的最佳实践路线图
- 先理清业务边界:只有核心链路(资金、库存)才用TCC,非核心用日志或消息补偿。
- 建立分支事务状态表:负责幂等、空回滚、悬挂判断,这是TCC稳定性的基石。
- Try阶段做“冻结/预留”:拒绝在Try阶段直接扣减,这是隔离的关键。
- Confirm和Cancel必须满足交换律:多次执行结果一致,且可重复调用。
- 做好监控与告警:利用Seata Dashboard观察全局事务成功率,对超时分支及时人工介入。
TCC模式是分布式事务的“银弹”之一,但绝非无脑使用,通过本文的案例与陷阱解析,希望能帮你避开那些隐蔽的“深坑”,如果面临极端高并发下的库存扣减,现在你应该知道——Try冻结、Confirm扣减、Cancel解冻,加上一张状态表,就是最稳定的答案。