本文参考https://zhuanlan.zhihu.com/p/648556608,在小徐的基础上做了个人的笔记。
TCC(Try-Confirm-Cancel)是一种分布式事务处理模式,旨在保证分布式系统中的事务一致性。它的核心思想是将一个分布式事务分解为多个子事务,每个子事务都有三个关键操作:
Try:在Try阶段,子事务尝试执行业务检查、资源预留和其他必要的前置操作,以准备好执行实际的业务。如果Try操作成功,表示可以继续执行后续的Confirm操作。
Confirm:在Confirm阶段,子事务执行实际的业务确认操作,将之前预留的资源实际分配,完成业务处理。只有当所有分支事务的Try操作都成功,并且TM发起Confirm操作时,才会真正确认事务的执行。
Cancel:在Cancel阶段,子事务执行与Try操作相反的操作,即回滚操作,用于撤销之前的资源预留和业务处理。如果Try操作失败,TM将发起所有分支事务的Cancel操作。
**TCC的分布式事务管理器(TM)**首先发起所有分支事务的Try操作,如果有任何一个Try操作失败,TM将会发起所有分支事务的Cancel操作,以确保分布式事务的一致性。只有当所有分支事务的Try操作都成功时,TM才会发起所有分支事务的Confirm操作,最终完成整个事务的确认。
TCC模式通过将分布式事务拆解成多个子事务并明确Try、Confirm和Cancel操作,提供了更细粒度的事务控制,能够适应不同业务场景的需求。然而,实现TCC模式需要开发者谨慎处理异常、重试机制和分布式事务管理,因此具有一定的复杂性
下面是 TCC 分布式事务实现方案的整体架构,大家可以先整体浏览一下存个印象,下面我们会逐一展开介绍:
在 TCC 分布式事务架构中,包含三类角色:
下面我们引入一个具体的分布式事务场景问题,并通过 TCC 架构加以实现,帮助大家进一步提高对 TCC 分布式事务方案的感性认识.
现在假设我们需要维护一个电商后台系统,需要处理来自用户的支付请求. 每当有一笔支付请求到达,我们需要执行下述三步操作,并要求其前后状态保持一致性:
上面这三步操作分别需要对接订单、账户、库存三个不同的子模块,底层的状态数据是基于不同的数据库和存储组件实现的,并且我们这套后台系统是基于当前流行的微服务架构实现的,这三子个模块本身对应的就是三个相互独立的微服务,因此如何实现在一笔支付请求处理流程中,使得这三笔操作对应的状态数据始终保持高度一致性,就成了一个非常具有技术挑战性的问题.
首先,我们基于 TCC 的设计理念,将订单模块、账户模块、库存模块分别改造成三个 TCC Component,每个 Component 对应需要暴露出 Try、Confirm、Cancel 三个 API,对应于冻结资源、确认更新资源、回滚解冻资源三个行为.
同时,为了能够简化后续 TX Manager 和 Application 之间的交互协议,每个 TCC Component 会以插件的形式提前注册到 TX Manager 维护的组件市场 Component Market 中,并提前声明好一个全局唯一键与之进行映射关联.
由于每个 TCC Component 需要支持 Try 接口的锁定操作,因此其中维护的数据需要在明细记录中拆出一个用于标识 “冻结” 状态的标签,或者在状态机中拆出一个 “冻结” 状态.
最终在第二阶段的 Confirm 或者 Cancel 请求到达时,再把 ”冻结“ 状态调整为 ”成功“ 或者 ”失败“ 的终态.
下面描述一下,基于 TCC 架构实现后,对应于一次支付请求的分布式事务处理流程:
在上述流程中,有一个很重要的环节需要补充说明,首先,TCC 本质上是一个两阶段提交(Two Phase Commitment Protocol,2PC)的实现方案,分为 Try 和 Confirm/Cancel 的两个阶段:
针对于这个场景,TCC 架构中采用的解决方案是:在第二阶段中,TX Manager 轮询重试 + TCC Component 幂等去重. 通过这两套动作形成的组合拳,保证 Confirm/ Cancel 操作至少会被 TCC Component 执行一次.
首先,针对于 TX Manager 而言:
需要注意,在 TX Manager 轮询重试的流程中,针对下游 TCC Component 的 Confirm 和 Cancel 请求只能保证 at least once 的语义,换句话说,这部分请求是可能出现重复的.
因此,在下游 TCC Component 中,需要在接收到 Confirm/Cancel 请求时,执行幂等去重操作. 幂等去重操作需要有一个唯一键作为去重的标识,这个标识键就是 TX Manager 在开启事务时为其分配的全局唯一的 Transaction ID,它既要作为这项事务在事务日志表中的唯一键,同时在 TX Manager 每次向 TCC Component 发起请求时,都要携带上这笔 Transaction ID.
首先针对于事务协调器 TX Manager,其核心要点包括:
注册TCC Component接口:该接口用于向分布式事务框架注册TCC组件(Try、Confirm、Cancel组件),以便管理和执行事务。不同的应用程序可能有不同的TCC组件需求,因此需要提供接口来注册和管理这些组件。
启动分布式事务接口:作为与应用程序交互的唯一入口,该接口用于启动分布式事务。应用程序通过调用此接口来触发分布式事务的执行,并根据事务的执行结果进行反馈。
全局唯一的Transaction ID:每个分布式事务需要一个全局唯一的事务标识,通常称为Transaction ID,用于跟踪和管理事务。同时,需要一个事务日志表来记录每项分布式事务的执行进展明细,以便进行事务状态的管理和恢复。
串联Try-Confirm/Cancel两阶段流程:TCC模式的核心是将事务分为Try、Confirm和Cancel三个阶段,需要实现这三个阶段的串联执行流程。根据Try的结果,推进执行Confirm或Cancel流程,以确保事务的一致性。
持续运行轮询检查任务:为了确保分布式事务的最终一致性,需要持续运行轮询检查任务,监测每个处于中间态的分布式事务,并将其推进到终态(已确认或已取消)。这有助于处理各种异常情况和故障恢复。
对于 TCC Component 而言,其需要关心和处理的工作包括:
下面针对最后一点提到的空回滚操作,进一步加以说明:
这个空回滚机制本质上是为了解决 TCC 流程中出现的悬挂问题,下面我们举个具体例子加以说明:
具体来说,如果TX Manager在向Component A发起Try请求时发生了超时,并且TX Manager已经批量执行了Cancel操作,然后之前由于网络问题而阻塞的Try请求到达了Component A,这可能导致请求到达的次序颠倒。在这种情况下,Component A需要确保只要接收到了对应的Cancel请求,之后到来的Try请求需要被忽略,以维护事务的一致性。
支持空回滚操作是TCC组件的一项关键功能。通过空回滚操作,即使Try请求到达的次序发生颠倒,Component A仍然可以正确处理事务。当Component A收到Cancel请求时,它可以标记相应的事务为已取消状态,并且在之后收到的Try请求到达时,可以忽略它们,因为事务已经被取消。
这种机制确保了TCC事务模式的鲁棒性,即使在不稳定的网络环境下,也能够维护事务的一致性。空回滚操作是一种有效的方式来处理请求到达顺序的问题,确保分布式事务的可靠性。
最后我们针对 TCC 分布式事务实现方案的优劣势进行分析:
优势:
劣势:
事实上,上面提到的第二点劣势也并非是 TCC 方案的缺陷,而是所有分布式事务都存在的问题,由于网络请求以及第三方系统的不稳定性,分布式事务永远无法达到 100% 的原子性.
Transaction Message:能够支持狭义的分布式事务. 基于消息队列组件中半事务消息以及轮询检查机制,保证了本地事务和消息生产两个动作的原子性,但不具备事务的逆向回滚能力
TCC Transaction:能够支持广义的分布式事务. 架构中每个模块需要改造成实现 Try/Confirm/Cancel 能力的 TCC 组件,通过事务协调器进行全局 Try——Confirm/Cancel 两阶段流程的串联,保证数据的最终一致性趋近于 100%