如何设计实现一个通用的分布式事务框架?

一个TCC事务框架需要解决的当然是分布式事务的管理 。关于TCC事务机制的介绍,可以参考TCC事务机制简介 。
TCC事务模型虽然说起来简单,然而要基于TCC实现一个通用的分布式事务框架,却比它看上去要复杂的多,不只是简单的调用一下Confirm/Cancel业务就可以了的 。
本文将以Spring容器为例,试图分析一下,实现一个通用的TCC分布式事务框架需要注意的一些问题 。
一、TCC全局事务必须基于RM本地事务来实现
TCC服务是由Try/Confirm/Cancel业务构成的,其Try/Confirm/Cancel业务在执行时,会访问资源管理器(Resource Manager,下文简称RM)来存取数据 。
这些存取操作,必须要参与RM本地事务,以使其更改的数据要么都commit,要么都rollback 。
这一点不难理解,考虑一下如下场景:
 

如何设计实现一个通用的分布式事务框架?

文章插图
假设图中的服务B没有基于RM本地事务(以RDBS为例,可通过设置auto-commit为true来模拟),那么一旦[B:Try]操作中途执行失败,TCC事务框架后续决定回滚全局事务时,该[B:Cancel]则需要判断[B:Try]中哪些操作已经写到DB、哪些操作还没有写到DB.
假设[B:Try]业务有5个写库操作,[B:Cancel]业务则需要逐个判断这5个操作是否生效,并将生效的操作执行反向操作 。
不幸的是,由于[B:Cancel]业务也有n(0<=n<=5)个反向的写库操作,此时一旦[B:Cancel]也中途出错,则后续的[B:Cancel]执行任务更加繁重 。
因为相比第一次[B:Cancel]操作,后续的[B:Cancel]操作还需要判断先前的[B:Cancel]操作的n(0<=n<=5)个写库中哪几个已经执行、哪几个还没有执行.
这就涉及到了幂等性问题,而对幂等性的保障,又很可能还需要涉及额外的写库操作,该写库操作又会因为没有RM本地事务的支持而存在类似问题 。。。
可想而知,如果不基于RM本地事务,TCC事务框架是无法有效的管理TCC全局事务的 。
反之,基于RM本地事务的TCC事务,这种情况则会很容易处理 。
[B:Try]操作中途执行失败,TCC事务框架将其参与RM本地事务直接rollback即可 。后续TCC事务框架决定回滚全局事务时,在知道“[B:Try]操作涉及的RM本地事务已经rollback”的情况下,根本无需执行[B:Cancel]操作 。
换句话说,基于RM本地事务实现TCC事务框架时,一个TCC型服务的cancel业务要么执行,要么不执行,不需要考虑部分执行的情况 。
二、TCC事务框架应该接管Spring容器的TransactionManager
基于RM本地事务的TCC事务框架,可以将各Try/Confirm/Cancel业务看成一个原子服务:一个RM本地事务提交,参与该RM本地事务的所有Try/Confirm/Cancel业务操作都生效;反之,则都不生效 。
掌握每个RM本地事务的状态以及它们与Try/Confirm/Cancel业务方法之间的对应关系,以此为基础,TCC事务框架才能有效的构建TCC全局事务 。
TCC服务的Try/Confirm/Cancel业务方法在RM上的数据存取操作,其RM本地事务是由Spring容器的PlatformTransactionManager来commit/rollback的,TCC事务框架想要了解RM本地事务的状态,只能通过接管Spring的事务管理器功能 。
2.1. 为什么TCC事务框架需要掌握RM本地事务的状态?
首先,根据TCC机制的定义,TCC事务是通过执行Cancel业务来达到回滚效果的 。仔细分析一下,这里暗含一个事实:只有生效的Try业务操作才需要执行对应的Cancel业务操作 。
换句话说,只有Try业务操作所参与的RM本地事务被commit了,后续TCC全局事务回滚时才需要执行其对应的Cancel业务操作
【如何设计实现一个通用的分布式事务框架?】否则,如果Try业务操作所参与的RM本地事务被rollback了,后续TCC全局事务回滚时就不能执行其Cancel业务,此时若盲目执行Cancel业务反而会导致数据不一致 。
其次,Confirm/Cancel业务操作必须保证生效 。Confirm/Cancel业务操作也会涉及RM数据存取操作,其参与的RM本地事务也必须被commit 。
TCC事务框架需要在确切的知道所有Confirm/Cancel业务操作参与的RM本地事务都被成功commit后,才能将标记该TCC全局事务为完成 。
如果TCC事务框架误判了Confirm/Cancel业务参与RM本地事务的状态,就会造成全局事务不一致 。
最后,未完成的TCC全局,TCC事务框架必须重新尝试提交/回滚操作 。重试时会再次调用各TCC服务的Confirm/Cancel业务操作 。
如果某个服务的Confirm/Cancel业务之前已经生效(其参与的RM本地事务已经提交),重试时就不应该再次被调用 。否则,其Confirm/Cancel业务被多次调用,就会有“服务幂等性”的问题 。


推荐阅读