大白话 Seata 分布式事务浅析,详解TCC模式
大家好,我是此林。
说到分布式事务,第一时间想到 Seata,它支持多种事务模型,比如:XA模式、AT模式、TCC模式、Saga模式(长事务)。
其中 TCC 模式是高性能分布式事务解决方案,适用于核心系统等对 性能有很高要求的场景。
不过,相对于 XA 和 AT 无业务入侵的即插即用模式,TCC 是一种 侵入式 的分布式事务解决方案,需要业务系统自行实现 Try,Confirm,Cancel 三个操作,对业务系统有着非常大的入侵性,设计相对复杂。
一、分布式事务
这里贴一张官网的图,其实也就是 Seata 分布式事务的 领域模型,无论是哪种模式(XA、AT、TCC、Saga),都遵循这个模型。
从图中可以看到,三个角色的覆盖级别是:TC > TM > RM
对于Seata 中的
- TC(Transaction Coordinator)
- TM(Transaction Manager)
- RM(Resource Manager)
三者的关系,比难以理解,我们用大白话+代码演示的形式展示。
1. TC,事务协调者,我们如果想使用 Seata,是不是需要独立不是 Seata 服务?那这个 Seata 可以简单理解为事务协调中心,即 TC。
2. TM,事务管理器,可以简单理解为就是 @GlobalTransactional 注解,即全局事务。
3. RM,资源管理器,这个层级最低,可以理解为标注了@GlobalTransactional 注解的方法下的远程调用 RPC/Feigin 服务分支事务。
我们创建一个电商业务,用户下单时:
-
扣减库存(库存服务);
-
创建订单(订单服务);
-
资金支付(账户服务)。
@Service
public class OrderService {@Autowiredprivate InventoryService inventoryService;@Autowiredprivate AccountService accountService;@Autowiredprivate OrderMapper orderMapper;// 这里就是 TM@GlobalTransactional(name = "create_order_tx", rollbackFor = Exception.class)public void createOrder(String userId, String productId, int amount) {// 调用 RM#1 扣减库存inventoryService.deduct(productId, amount);// 调用 RM#2 扣减账户余额accountService.debit(userId, amount * 10);// 插入订单记录(当前服务自己就是 RM)orderMapper.insert(new Order(userId, productId, amount));}
}
可以看到,TM(事务发起方)下,会调用 RM#1 扣减库存,调用 RM#2 扣减账户余额,插入订单记录(当前服务自己就是 RM)。
这样一看是不是关系理清很多了?
二、什么是 TCC 模式?
TCC(Try-Confirm-Cancel)是 Seata 支持的一种 侵入式的分布式事务解决方案,最早由蚂蚁金服提出。
一个分布式的全局事务,整体是 两阶段提交 的模型。全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:
- 一阶段 prepare 行为
- 二阶段 commit 或 rollback 行为
AT 模式基于 支持本地 ACID 事务 的 关系型数据库:
- 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
- 二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。
- 二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。
相应的,TCC 模式,不依赖于底层数据资源的事务支持:
- 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
- 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
- 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。
所以为什么说 TCC 侵入性高?
TCC 服务化的资源管理方式,不依赖于数据库事务,而是由业务系统显式地控制这三个阶段。
XA 和 AT 模式直接使用数据源代理来屏蔽分布式事务细节。
TCC下,业务方需要自行定义 TCC 资源的“准备”、“提交”和“回滚” 。
TCC 三个阶段的含义
阶段 | 说明 |
---|---|
Try | 尝试执行业务,预留必要资源(如扣减库存、锁定额度) |
Confirm | 确认执行业务,真正提交资源(如减库存、扣款) |
Cancel | 回滚业务,释放预留资源,保证幂等 |
三、TCC 基本使用
我们以一个下单(订单 + 库存)为例子,用 TCC 模式 简化演示。
1. TM 发起全局事务(@GlobalTransactional)
@Service
public class OrderService {@Autowiredprivate InventoryTccAction inventoryTcc;@Autowiredprivate PaymentTccAction paymentTcc;// TM 在这里@GlobalTransactionalpublic void placeOrder(String productId, int count) {inventoryTcc.prepare(null, productId, count); // RM 分支事务1paymentTcc.prepare(null, "userA", 100); // RM 分支事务2}
}
其中 InventoryTccAction 是库存服务,PaymentTccAction 是支付服务。
2. RM1:库存服务实现 TCC 接口
public interface InventoryTccAction {@TwoPhaseBusinessAction(name = "inventoryTcc", commitMethod = "commit", rollbackMethod = "rollback")boolean prepare(BusinessActionContext ctx, @BusinessActionContextParameter(paramName = "productId") String productId, int count);boolean commit(BusinessActionContext ctx);boolean rollback(BusinessActionContext ctx);
}
3. RM 2:支付服务实现 TCC 接口
public interface PaymentTccAction {@TwoPhaseBusinessAction(name = "paymentTcc", commitMethod = "commit", rollbackMethod = "rollback")boolean prepare(BusinessActionContext ctx, @BusinessActionContextParameter(paramName = "userId") String userId, int amount);boolean commit(BusinessActionContext ctx);boolean rollback(BusinessActionContext ctx);
}
在 RM 业务接口中核心的注解是 @TwoPhaseBusinessAction,表示当前方法使用 TCC 模式管理事务提交,并标明了 Try,Confirm,Cancel 三个阶段。name属性,给当前事务注册了一个全局唯一的的 TCC bean name。
注意,如果 TCC 参与者是本地 bean(非远程RPC服务),本地 TCC bean 还需要在接口定义中添加 @LocalTCC 注解,比如支付服务是本地的:
@LocalTCC public interface PaymentTccAction {@TwoPhaseBusinessAction(name = "paymentTcc", commitMethod = "commit", rollbackMethod = "rollback")boolean prepare(BusinessActionContext ctx, @BusinessActionContextParameter(paramName = "userId") String userId, int amount);boolean commit(BusinessActionContext ctx);boolean rollback(BusinessActionContext ctx); }
4. TM、RM、TC 工作流程图
5. 注意事项
在旧版Seata,比如小于1.3,为了让 Dubbo/Sofa 的远程调用对象支持 TCC,框架引入了一个类
具体位置:src/main/java/org/apache/seata/spring/tcc/TccAnnotationProcessor.java
@Deprecated
public class TccAnnotationProcessor implements BeanPostProcessor
这个类目前已经被标记为废弃。
那时候框架并不会自动识别是否是 TCC Bean,必须依赖 TccAnnotationProcessor
这个类来扫描注解字段并手动生成代理。
不过用法上相比于现在,只不过需要在TM 发起全局事务加上@Reference
注解而已,比如:
@Reference
private PaymentTccAction paymentTcc;
TccAnnotationProcessor
主要功能:
-
在 Spring 启动时扫描 Bean 字段上的
@Reference
(Dubbo)或@SofaReference
注解。 -
判断其字段类型的方法是否带有
@TwoPhaseBusinessAction
。 -
如果带有,则通过反射为该字段注入代理对象。
-
将该代理对象包装成 TCC RM 支持的形式,注册为资源参与者。
在新版 Seata 里,调用方不需要处理代理,Tcc advice 逻辑移动到了 RM 侧,内置支持 RM 自动识别。
今天的分享就到这里了,
我是此林,关注我吧,带你看不一样的世界!