用开奶茶店的方式,理解Seata的TCC模式
大家好,我是Asthenia,今天咱们用开奶茶店的例子,聊聊分布式事务中的TCC模式。想象一下,你开了一家网红奶茶店,顾客下单后要同时扣减库存、生成订单、发放优惠券。这三个操作就像三个微服务,如何保证它们要么全部成功,要么全部回滚?这就是TCC要解决的难题。
一、TCC vs AT:自动挡和手动挡的区别
先说说AT模式,它像自动挡汽车:你只管踩油门(执行业务SQL),系统自动记录操作前后的数据镜像(就像行车记录仪),回滚时自动恢复。但遇到复杂业务(比如需要调用第三方接口),AT就力不从心了。
TCC模式则是手动挡:需要你亲自编写三个操作:
- Try:准备原料(预留资源),比如冻结库存10杯奶茶
- Confirm:正式制作(提交操作),实际扣减10杯
- Cancel:退回原料(回滚操作),解冻10杯
就像顾客下单后,你先准备好奶茶原料(Try),等支付成功再制作(Confirm),如果取消订单就把原料放回冰柜(Cancel)。
二、设计TCC的三板斧
设计TCC服务就像设计奶茶制作流程:
1. Try阶段设计原则
- 幂等性:重复点单不影响,用订单号做唯一标识
- 空回滚防护:遇到未执行Try却收到Cancel时,要记录"空回滚标记"
- 防悬挂:先执行Cancel再收到Try请求时,要拒绝执行
2. 典型代码结构
java
@TwoPhaseBusinessAction(name = "reduceStock", commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext context,
@BusinessActionContextParameter(paramName = "sku") String sku,
@BusinessActionContextParameter(paramName = "count") int count) {
// 冻结库存
inventoryService.freeze(sku, count);
return true;
}
public boolean commit(BusinessActionContext context) {
// 实际扣减库存
String sku = (String) context.getActionContext("sku");
int count = (Integer) context.getActionContext("count");
inventoryService.reduce(sku, count);
return true;
}
public boolean rollback(BusinessActionContext context) {
// 解冻库存
String sku = (String) context.getActionContext("sku");
int count = (Integer) context.getActionContext("count");
inventoryService.unfreeze(sku, count);
return true;
}
三、TCC实现原理拆解
我们通过奶茶订单的生命周期,看看TCC如何运转:
A. 注解驱动 - @TwoPhaseBusinessAction 就像奶茶配方标签:
java
@TwoPhaseBusinessAction(name = "orderAction",
commitMethod = "confirmOrder",
rollbackMethod = "cancelOrder")
public void prepareOrder(...) {...}
- name:动作名称(珍珠奶茶配方)
- commitMethod:确认方法(制作奶茶)
- rollbackMethod:回滚方法(退回原料)
B. 资源注册 - 备料登记 当应用启动时,GlobalTransactionScanner就像店长,扫描所有带@TwoPhaseBusinessAction的方法,把它们注册到TransactionManager(中央厨房管理系统):
java
// 自动注册TCC资源
TCCResourceRegistry.getInstance()
.registerResourceManager(new TCCResourceManager());
C. 事务发起 - 订单流水线
- 顾客下单:@GlobalTransactional标记的方法开始全局事务
java
@GlobalTransactional
public void createOrder(OrderDTO order) {
// 调用各个TCC服务
}
- 准备阶段:依次调用各服务的Try方法,就像准备奶茶原料
- 事务协调器(TC)记录事务状态,类似订单跟踪系统
- 成功时触发Confirm(制作奶茶),失败时触发Cancel(退回原料)
四、全局视角看TCC
整个过程就像奶茶店的全流程管理:
- 店长(TM)发起订单(全局事务)
- 各工位(RM)登记自己的任务(分支事务注册)
- 准备间完成备料(Try阶段)
- 中央厨房(TC)协调制作或取消
关键点在于事务上下文传递,就像订单小票在各部门流转。全局事务ID(XID)就是订单号,保证所有操作可追踪。
五、什么时候该用TCC?
- 需要与外部系统交互(比如调用支付接口)
- 业务补偿逻辑复杂(比如退单要恢复积分)
- 对性能要求高(Try阶段只是资源预留)
但就像手动挡需要更多操作,TCC需要多写约3倍代码。对于简单场景,AT模式可能更合适。
总结一下,TCC模式就像精心设计的奶茶制作流程,通过明确的三个阶段,在分布式系统中实现了可靠的事务控制。理解它需要把握"预留-确认/取消"的核心思想,就像经营好一家奶茶店需要清晰的工序和应急方案。
六、TM/RM/TC的协作流程(厨房版)
角色定义:
- 顾客(TM):发起订单(全局事务)
- 厨师(RM):处理具体菜品(分支事务)
- 餐厅调度系统(TC):协调整个订单
文字时序图:
ini
1. [顾客] 下单酸菜鱼 -- TM发起@GlobalTransactional
│
2. [调度系统] 生成订单号(XID) -- TC创建全局事务
│
3. [顾客] 通知后厨准备食材 -- TM调用RM的Try接口
│ ├──> [厨师A] 备料酸菜鱼(Try)
│ └──> [厨师B] 煮米饭(Try)
│
4. [调度系统] 记录各菜品准备状态 -- TC注册分支事务
│
5. 所有备料完成? -- TC检查Try结果
│
6. 是 → [调度系统] 通知开始烹饪 -- TC触发Confirm
│ ├──> [厨师A] 制作酸菜鱼(Confirm)
│ └──> [厨师B] 蒸煮米饭(Confirm)
│
否 → [调度系统] 通知退回食材 -- TC触发Cancel
├──> [厨师A] 退还鱼肉(Cancel)
└──> [厨师B] 倒掉生米(Cancel)
七、关键通信过程解析
1. 事务发起阶段(顾客点单)
java
// TM端代码示例
@GlobalTransactional
public void placeOrder() {
// 调用RM服务(会携带XID)
dishService.prepare();
riceService.prepare();
}
- TM与TC通信:注册全局事务,获取XID(类似餐厅系统生成订单号)
- 网络协议:TM通过Netty长连接与TC保持心跳
2. 分支注册阶段(厨师接单)
java
// RM资源注册核心逻辑
public class DishTCCImpl implements TCCService {
@Override
@TwoPhaseBusinessAction(...)
public boolean prepare() {
// 自动向TC注册分支事务
TransactionContextManager.getContext().addBranch();
}
}
- RM与TC通信:通过RPC上报分支状态(类似厨师报告备料进度)
- 数据存储:TC将事务状态持久化到数据库(如MySQL)
3. 决议阶段(后厨协作)
- 一阶段提交:TC异步批量处理Confirm/Cancel请求(类似调度系统批量通知厨师)
- 异常处理:采用指数退避重试策略(如网络抖动时自动重试3次)
八、设计精妙之处
- 异步化设计:像餐厅的非阻塞式操作,TC处理请求不阻塞主流程
- 最终一致性:允许短暂状态不一致(就像允许备料和烹饪存在时间差)
- 故障隔离:单个厨师(RM)故障不影响整体(其他菜品仍可继续制作)
九、实际开发注意事项
-
网络超时 :设置合理的超时时间(就像规定厨师10分钟内必须响应)
properties# Seata客户端配置 seata.tm.degrade-check-period=2000 seata.tm.rollback-retry-timeout=120000
-
幂等设计 :给每个操作加唯一标识(类似给每道菜打上订单号)
javapublic boolean confirm(BusinessActionContext context) { String orderNo = context.getXid(); // 全局唯一标识 if(processedOrders.contains(orderNo)) return true; }
-
监控指标 :需要监控的关键指标(类似餐厅的看板系统)
- TC端:全局事务成功率、平均处理时长
- RM端:分支事务重试次数、资源冻结时间
通过这个视角,TCC就像一套精密的餐厅协作系统,每个组件各司其职,通过标准化的协议保证最终一致性。这种设计既保证了灵活性(允许各厨师自主操作),又通过中央调度系统(TC)确保了整体流程可控。