07、分布式事务 实战 - 分布式事务解决方案之 TCC

1. 什么是TCC?

分布式事务中的几个角色

TM:事务管理器,可以理解为分布式事务的发起者

分支事务:事务中的多个参与者,可以理解为一个个独立的事务。

TCC是Try、Confirm、Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作:预处理Try、确认Confirm、撤销Cancel。

Try操作做业务检查及资源预留,Confirm做业务确认操作,Cancel实现一个与Try相反的操作即回滚操作。

TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有分支事务的Cancel操作,若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel操作若执行失败,TM会进行重试。

1.1. 正常流程

try阶段:依次调用参与者的try方法,都返回成功

confirm阶段:依次调用参与者的confirm方法,都返回成功

事务完成。

 

 

1.2. 异常流程

try阶段:依次调用参与者的try方法,前面2个参与者try方法返回yes,而参与者3返回no

cancel阶段:对已经成功的参与者执行cancel操作,注意了:cancel阶段参与者调用的顺序和try阶段参与者的顺序相反,即先调用参与者2的cancel,然后调用参与者1的cancel

 

2. TCC场景案例

2.1. 案例1:跨库转账

举例,场景为 A 转账 100 元给 B,A和B账户在不同的服务。

账户A
try:
    try幂等校验
    检查余额是否够100元
    A账户扣减100元
confirm:
    空
cancel:
    cancel幂等校验
    A账户增加可用余额100元

账户B
try:
    空
confirm:
    confirm幂等校验
    B账户增加100元
cancel:
    空

2.2. 案例2:提现到支付宝

举例,大家玩过抖音,有些朋友抖音上面有收益,可以将收益提现到支付宝,假如提现100到支付宝

抖音(账户表:余额、冻结金额)
try:
    try幂等校验
    检查余额是否够100元
    抖音账户表余额-100,冻结金额+100
confirm:
    confirm幂等校验
    抖音账户冻结金额-100
cancel:
    cancel幂等校验
    抖音账户表余额+100,冻结金额-100

账户B
try:
    空
confirm:
    confirm幂等校验
    调用支付宝打款接口,打款100元(对于商户同一笔订单支付宝接口是支持幂等的)
cancel:
    空

3. TCC常见框架

框架名称 github地址 star数量
tcc-transaction https://github.com/changmingxie/tcc-transaction 4750
hmily https://github.com/Dromara/hmily 2900
ByteTCC https://github.com/liuyangming/ByteTCC 2450
EasyTransaction https://github.com/QNJR-GROUP/EasyTransaction 2100

4. 自研TCC框架设计思路

4.1. 涉及到的角色(事务发起者、事务参与者、TCC服务)

(1)、事务发起者(TM)

  • 发起分布式事务:调用tcc服务注册一个分布式事务订单
  • 调用分支:依次调用每个分支
  • 上报结果:最终将事务所有分支的执行结果汇报给TCC服务
  • 提供补偿接口:给TCC服务使用,tcc服务会调用这个补偿接口对进行补偿操作

(2)、事务参与者

  • 提供3个方法:try、confirm、cancel
  • 确保3个方法的幂等性
  • 3个方法返回的结果状态码只有3种(成功、失败、处理中),处理中相当于状态未知,对于状态未知的,会在补偿的过程中进行重试

(3)、TCC服务

  • 是一个独立的服务
  • 提供分布式事务订单注册接口:给事务发起者使用【事务发起者调用tcc服务生成一个分布式事务订单(订单状态:0:处理中,1:处理成功,2:处理失败),获取一个分布式订单id:TID】
  • 提供分布式事务结果上报接口:给事务发起者使用【事务发起者在事务的执行过程中将事务的执行结果汇报给TCC服务】
  • 提供事务补偿操作:启动一个job轮询tcc订单中状态为1的订单,继续调用事务发起者进行补偿,最终经过多次补偿,这个订单最终的状态应该为1(成功)或者2(失败);否则人工介入进行处理

4.2. 时序图

 

 

4.3. 自研TCC框架技术要点

(1)、框架应该考虑的地方

开发者应该只用关注分支中3个方法的代码,其他的应该全部交由框架去完成。

(2)、tcc服务中的事务订单表设计

  • id:订单id

  • bus_order_id:业务方订单id

  • bus_order_type:业务类型 (bus_order_id & bus_order_type 需唯一)

  • request_data:业务请求数据,json格式存储,包含了玩转的业务方请求数据

  • status:状态,0:处理中,100:处理成功,200:处理失败,初始状态为0,最终必须为100或者200

(3)、关于分支中3个方法幂等的设计

以java中的spring为例,可以通过拦截器来实现,拦截器对分支的3个方法进行拦截,拦截器中实现幂等性的操作。

可以用一张表来实现【分支方法执行记录表:tid、分支、方法(try、confirm、cancel)、状态(0:处理中;100:成功;200:失败)、request_json(请求参数)、response_json(响应参数)】

关于请求参数:这个用来记录整个方法请求的完整参数,内部包含了业务参数,可以采用json格式存储。

响应参数:分支方法的执行结果,以json格式存储。

拦截器中,通过分支 & 方法 这2个条件去查询分支方法执行记录表,如果查询的记录状态为100或者200,那么直接将response_json返回。

(4)、try阶段同步、其他阶段异步

如果try阶段全部成功,那么confirm阶段最终应该一定是成功的,try阶段如果有失败的,那么需要执行cancel,最终所有的cancel应该也是一定可以成功的;所以try阶段完成之后,其实已经知道最终的结果了,所以try阶段完成之后,后面的confirm或者cancel可以采用异步的方式去执行;提升系统整体的性能。

(5)、异步上报事务执行结果

发起方将所有分支每个步骤的执行结果及最终事务的执行结果上报给tcc服务,由tcc服务落库,方便运营人员查看事务执行结果以及排错。

(6)、关于补偿

tcc服务中添加一个补偿job,定时轮询tcc分布式订单表,将状态为处理中的记录撸出来,订单表request_data包含了请求参数,使用request_data去调用事务发起者提供的补偿接口进行补偿操作,直到订单的状态为最终状态(成功或者失败)。

补偿采用衰减的形式,对应同一笔订单采用时间间隔衰减的方式补偿,每次间隔时间:10s、20s、40s、80s、160s、320s。。。

(7)、人工干预

tcc分布式订单如果长期处于处理中,经过了很多次的补偿,也未能到达最终状态,此时可能业务有问题,需要人工进行补偿,对于这对订单记录需要有监控系统进行报警,提醒开发者进行干预处理。

5. 小结

如果拿TCC事务的处理流程与2PC两阶段提交做比较,2PC通常都是在跨库的DB层面,而TCC则在应用层面的处理,是2PC在应用层面的一种实现,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自己定义数据操作的粒度,使得降低锁冲突、提高吞吐量成为可能。
而不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、cancel三个操作,代码量比较大。