今天分享seata分布式事务TCC模式的原理和使用:
一、seata分布式事务的原理
一个分布式的全局事务,整体是
两阶段提交
的模型。全局事务是由若干分支事
务组成的,分支事务要满足
两阶段提交
的模型要求,即需要每个分支事务都具备自己的:
1
、一阶段
prepare
行为
2
、二阶段
commit
或
rollback
行为
![](https://i-blog.csdnimg.cn/blog_migrate/5e3c36208994f9a49eca5c5753b09239.png)
TCC 模式,不依赖于底层数据资源的事务支持:
- 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
- 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
- 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。
二、使用流程:
1、注解和配置
TCC
模式的
TM
跟
AT
模式的使用类似,只要在方法上面加上
@GlobalTransactional
注解就就可以了, file.conf 和
registry.conf
配置都跟
AT
模式一样。
2、TCC 的 RM 的使用配置:
RM 的接口上面必须要有@LocalTCC 注解,且必须在接口上面,如图核心接口代码:
@LocalTCC//核心注解
@RequestMapping("/product/mall/service/manage/IKillService")
public interface IKillService {
//核心方法配置
@TwoPhaseBusinessAction(name = "updateByIdTcc", commitMethod = "updateIdTccCommit",
rollbackMethod = "updateTccRollback")
@RequestMapping(value = "/updateByIdTcc",
method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_VALUE)
boolean updateByIdTcc(@RequestBody BusinessActionContext businessActionContext,
@BusinessActionContextParameter(paramName = "killGoodsPriceStr")
@RequestParam("killGoodsPriceStr") String killGoodsPriceStr);
//提交方法
@RequestMapping(value = "/updateIdTccCommit", method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_VALUE)
boolean updateIdTccCommit(@RequestBody BusinessActionContext businessActionContext);
//回滚方法
@RequestMapping(value = "/updateTccRollback", method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_VALUE)
boolean updateTccRollback(@RequestBody BusinessActionContext businessActionContext);
}
三、TCC 使用过程中的几个典型问题及解决方案
1、空回滚
首先是空回滚。什么是空回滚?空回滚就是对于一个分布式事务,在没有调用
TCC
资源
Try 方法的情况下,调用了二阶段的 Cancel
方法,
Cancel
方法需要识别出这是一个空回滚,然 后直接返回成功。
什么样的情形会造成空回滚呢?如果调用 try
逻辑有问题,比如调用方机器宕机、网络异常, 都会造成 RPC
调用失败,即未执行
Try
方法。但是分布式事务已经开启了,需要推进到 终态,因此,TC
会回调参与者二阶段
Cancel
接口,从而形成空回滚。
解决方案:
需要一张额外的事务控制表,其中有分布式事务
ID
和分支事务
ID
,第一阶段
Try
方法里 会插入一条记录,表示一阶段执行了。Cancel
接口里读取该记录,如果该记录存在,则正
常回滚;如果该记录不存在,则是空回滚。
2、悬挂
悬挂就是对于一个分布式事务,其二阶段
Cancel
接口比
Try
接口先执行。因为允许空回滚 的原因,Cancel
接口认为
Try
接口没执行,空回滚直接返回成功,对于
Seata
框架来说,
认为分布式事务的二阶段接口已经执行成功,整个分布式事务就结束了。
但是这之后
Try
方 法才真正开始执行,预留业务资源,前面提到事务并发控制的业务加锁,对于一个 Try
方
法预留的业务资源,只有该分布式事务才能使用,然而
Seata
框架认为该分布式事务已经 结束,也就是说,当出现这种情况时,该分布式事务第一阶段预留的业务资源就再也没有人
能够处理了,对于这种情况,我们就称为悬挂,即业务资源预留后没法继续处理。
解决方案:
可以在二阶段执行时插入一条事务控制记录,状态为已回滚,这样当一阶段执行时,先读取 该记录,如果记录存在,就认为二阶段已经执行;否则二阶段没执行。
3、幂等
幂等就是对于同一个分布式事务的同一个分支事务,重复去调用该分支事务的第二阶段接 口,因此,要求 TCC
的二阶段
Confirm
和
Cancel
接口保证幂等,不会重复使用或者释
放资源。如果幂等控制没有做好,很有可能导致资损等严重问题。 什么样的情形会造成重复提交或回滚?从图中可以看到,提交或回滚是一次 TC
到参与者
的网络调用。因此,网络故障、参与者宕机等都有可能造成参与者
TCC
资源实际执行了二 阶段防范,但是 TC
没有收到返回结果的情况,这时,
TC
就会重复调用,直至调用成功,
整个分布式事务结束。
解决方案:
一个简单的思路就是记录每个分支事务的执行状态。在执行前状态,如果已执行,那就不再 执行;否则,正常执行。前面在讲空回滚的时候,已经有一张事务控制表了,事务控制表的 每条记录关联一个分支事务,那我们完全可以在这张事务控制表上加一个状态字段,用来记 录每个分支事务的执行状态。除此之外我们还应该要有业务幂等,比如查询支付状态,如果 是已经支付就不让再次支付了,直接结束二阶段。
到此seata分布式事务TCC模式原理和使用分享完成,下篇我们分析其源码执行流程。