一 什么是TCC
TCC将每个分支事务都分成三个部分(Try、Confirm、Cancel):
- Try:业务检查及资源预留。
- Confirm:真正执行业务,不做任何业务检查。使用Try阶段预留的资源。
- Cancel:实现回滚操作,释放资源。
二 TCC实现分布式事务的流程
1、 第一阶段:全局事务管理器分别调用所有分支事务,所有分支事务进行Try操作,当所有分支事务的Try操作都成功,或者某部分分支事务的Try操作失败,都进入第二阶段;
2、 第二阶段:如果第一阶段所有分支事务Try都成功执行,则全局事务管理器通知所有分支事务进行Confirm操作,否则,通知所有分支事务进行Cancel操作;
成功图例:
失败图例:
三 举例说明TCC流程
场景:A账户给B账户转30块,AB在不同的服务。
方案1:
A服务:
Try{
检查A账户余额是否大于30元。
A账户扣减30元。
}
Confirm{
空。
}
Cancle{
A账户增加30元。
}
B服务:
Try{
B账户增加30元。
}
Confirm{
空。
}
Cancle{
B账户扣减30元。
}
方案1存在的问题:
1、 由于网络原因,A服务的Try没有执行,分支超时,则全局事务通知所有分支事务进行Cancel,那A账户就多了30元;
2、 Try、Confirm、Cancel都是有单独的线程去执行,且会出现重复调用,不支持幂等性;
3、 B服务执行Try后账户B增加了30元,其他服务将这30元使用了,后因为某种原因AB分支事务需要执行Cancel,B账户就不够30元了;
4、 与1类型,B服务的Try没有执行,分支超时,则全局事务通知所有分支事务进行Cancel,那B账户就少了30元;
问题解决:
1、 A服务执行Cancel前判断A服务是否执行了Try;
2、 AB服务增加幂等性;
3、 B服务在Confirm中实现增加30元;
4、 B服务执行Cancel前判断B服务是否执行了Try;
优化后的方案2:
A服务:
Try{
增加幂等性。
判断是否已经执行了Cancel。若Cancle已执行,则不执行Try,反之则执行Try。
检查A账户余额是否大于30元。
A账户扣减30元。
}
Confirm{
空。
}
Cancle{
增加幂等性。
判断是否已经执行了Try。若Try未执行,则不执行Cancel,反之则执行Cancel。
A账户增加30元。
}
B服务:
Try{
空。
}
Confirm{
增加幂等性。
B账户增加30元。
}
Cancle{
空。
}
由此我们可以发现使用TCC需要注意一些问题。
四 TCC需要注意的三种异常:
1、 空回滚;
执行Cancel时需要判断当前分支事务是否已经执行Try。 2、 悬挂;
执行Try时需要判断当前分支事务是否已经执行Cancel。
3、 幂等性;
由于Confirm和cancel失败需进行重试,因此需要实现幂等性。
五 TCC与2PC区别
TCC本质上也是二阶段提交协议,但他们又有很大不同:
- TCC作用与服务层,2PC作用于资源层。(TCC开发人员通过业务代码实现数据提交与回滚,2PC基于数据库厂商原生协议,由数据库层面实现数据提交与回滚。)
- TCC三个接口逻辑由开发人员编写,2PC由数据库厂商或第三方编写。
- 由于以上两原因,TCC可以自由控制资源锁定的粒度。
- TCC侵入业务逻辑过强,每个分支事务都需要实现Try、Confirm、Cancel三个接口,改造成本高。
六 TCC缺点
- 代码侵入性太强,每个分支事务都得实现Try、Confirm、Cancel。(这一点我可难了,代码量庞大,耦合性高..手动狗头)
- 幂等性难以控制。
七 TCC的实现框架
ByteTCC,TCC-transaction,Hmily。