TCC补偿型

什么是TCC补偿型事务

基于消息实现的事务并不能解决所有的业务场景,例如以下场景:某笔订单完成时,同时扣掉用户的现金。

TCC 是 Try-Confirm-Cancel 的缩写,属于补偿型柔性事务的一种。它通过 “预留资源(Try)→ 确认提交(Confirm)→ 补偿回滚(Cancel)” 的三段式流程,在分布式场景下保证最终一致性,适用于对强一致性要求较高的业务(如金融支付、电商订单)。

这里事务发起方是管理订单库的服务,但对整个事务是否提交并不能只由订单服务决定,因为还要确保用户有足够的钱,才能完成这笔交易,而这个信息在管理现金的服务里。这里我们可以引入基于补偿实现的事务,其流程如下:

  • 创建订单数据,但暂不提交本地事务
  • 订单服务发送远程调用到现金服务,以扣除对应的金额
  • 上述步骤成功后提交订单库的事务

以上这个是正常成功的流程,出现异常流程需要回滚的话,将额外发送远程调用到现金服务以加上之前扣掉的金额。

以上流程比基于消息队列实现的事务的流程要复杂,同时开发的工作量也更多:

  • 编写订单服务里创建订单的逻辑
  • 编写现金服务里扣钱的逻辑
  • 编写现金服务里补偿返还的逻辑

可以看到,该事务流程相对于基于消息实现的分布式事务更为复杂,需要额外开发相关的业务回滚方法,也失去了服务间流量削峰填谷的功能。但其仅仅只比基于消息的事务复杂多一点,若不能使用基于消息队列的最终一致性事务,那么可以优先考虑使用基于补偿的事务形态。

TCC要求每个分支事务实现三个操作:预处理Try、确认Confirm、撤销Cancel。

补偿模式核心就是使用一个额外的协调服务来协调各个需要保证一致性的业务服务,协调服务按顺序调用各个业务微服务,如果某个业务服务调用异常(包括业务异常和技术异常)就取消之前所有已经调用成功的业务服务。

image-20251104144608507

补偿模式大致有TCC,和Saga两种细分的方案

TCC 事务模型

TCC(Try-Confirm-Cancel)的概念来源于 Pat Helland 发表的一篇名为“Life beyond Distributed Transactions:an Apostate’s Opinion”的论文。

TCC 分布式事务模型包括三部分:

  • 主业务服务:主业务服务为整个业务活动的发起方,服务的编排者,负责发起并完成整个业务活动。
  • 从业务服务:从业务服务是整个业务活动的参与方,负责提供 TCC 业务操作,实现初步操作(Try)、确认操作(Confirm)、取消操作(Cancel)三个接口,供主业务服务调用。
  • 业务活动管理器:业务活动管理器管理控制整个业务活动,包括记录维护 TCC 全局事务的事务状态和每个从业务服务的子事务状态,并在业务活动提交时调用所有从业务服务的 Confirm 操作,在业务活动取消时调用所有从业务服务的 Cancel 操作。

TCC 提出了一种新的事务模型,基于业务层面的事务定义,锁粒度完全由业务自己控制,目的是解决复杂业务中,跨表跨库等大颗粒度资源锁定的问题。

TCC 把事务运行过程分成 Try、Confirm / Cancel 两个阶段,每个阶段的逻辑由业务代码控制,避免了长事务,可以获取更高的性能。

TCC 分布式事务模型包括三部分:

image-20251104145514118
  • Try 阶段: 调用 Try 接口,尝试执行业务,完成所有业务检查,预留业务资源。
  • Confirm 或 Cancel 阶段: 两者是互斥的,只能进入其中一个,并且都满足幂等性,允许失败重试。
    • Confirm 操作: 对业务系统做确认提交,确认执行业务操作,不做其他业务检查,只使用 Try 阶段预留的业务资源
    • Cancel 操作: 在业务执行错误,需要回滚的状态下执行业务取消,释放预留资源。

TCC 属于补偿型柔性事务,与刚性事务(如 2PC/3PC)、通知型事务(如 MQ 事务消息)的核心差异如下:、

对比维度 TCC 补偿型 刚性事务(2PC/3PC) 通知型事务(MQ 事务消息)
一致性保障 最终一致性(强) 强一致性 最终一致性(弱)
资源锁定时机 Try 阶段即锁定资源 准备阶段锁定资源 无资源锁定(依赖幂等)
适用场景 金融支付、电商核心订单等 传统单体系统 异步通知、非核心业务
实现复杂度 高(需实现 Try/Confirm/Cancel 三个接口)

在实际工程中,TCC 通常通过分布式事务框架来简化实现,典型代表有:

  • Seata-TCC:阿里开源的分布式事务框架,提供 TCC 模式的注解式编程(@TwoPhaseBusinessAction),支持自动生成 Confirm/Cancel 逻辑。但 Seata 的 TCC 模式对 Spring Cloud 并没有提供支持。

  • LCN-TCC:专注于 TCC 模式的框架,通过代理数据源实现事务的拦截与协调。

TCC的工作流程

TCC(Try-Confirm-Cancel)分布式事务模型相对于 XA 等传统模型,其特征在于它不依赖资源管理器(RM)对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务

TCC 模型认为对于业务系统中一个特定的业务逻辑,其对外提供服务时,必须接受一些不确定性,即对业务逻辑初步操作的调用仅是一个临时性操作,调用它的主业务服务保留了后续的取消权。

如果主业务服务认为全局事务应该回滚,它会要求取消之前的临时性操作,这就对应从业务服务的取消操作。

而当主业务服务认为全局事务应该提交时,它会放弃之前临时性操作的取消权,这对应从业务服务的确认操作。每一个初步操作,最终都会被确认或取消。

因此,针对一个具体的业务服务,TCC 分布式事务模型需要业务系统提供三段业务逻辑:

  • Try操作完成所有业务检查,预留必须的业务资源。
  • Confirm做业务确认操作,是真正执行的业务逻辑,不作任何业务检查,只使用 Try 阶段预留的业务资源。因此,只要 Try 操作成功,Confirm 必须能成功。另外,Confirm 操作需满足幂等性,保证一笔分布式事务有且只能成功一次。
  • Cancel实现一个与Try相反的操作即回滚操作。释放 Try 阶段预留的业务资源。同样的,Cancel 操作也需要满足幂等性。

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

image-20251104144835525
image-20251104145457121

想一下,Try 阶段失败可以 Cancel,如果 Confirm 和 Cancel 阶段失败了怎么办?

TCC 中会添加事务日志,如果 Confirm 或者 Cancel 阶段出错,则会进行重试,所以这两个阶段需要支持幂等;如果重试失败,则需要人工介入进行恢复和处理等。

通常情况下,采用TCC则认为Cancel阶段也是一定成功的。若Cancel阶段真的出错了,需引入重试机制或人工处理。

TCC事务中,TM事务管理器可以实现为独立的服务,也可以让全局事务发起方充当TM的角色,TM独立出来是为了成为公用组件,考虑了系统结构和软件复用。

而 TM 在发起全局事务时则生成全局事务记录,全局事务ID贯穿整个分布式事务调用链条,用来记录事务上下文,追踪和记录状态,由于Confirm 和cancel失败需进行重试,因此需要实现为幂等,幂等性是指同一个操作无论请求多少次,其结果都相同。

TCC事务案例

然而基于补偿的事务形态也并非能实现所有的需求,如以下场景:某笔订单完成时,同时扣掉用户的现金,但交易未完成,也未被取消时,不能让客户看到钱变少了。这时我们可以引入TCC

以 “订单扣减现金” 场景为例理解 TCC 的三段式执行流程

假设业务场景是:用户下单时,订单服务需联动现金服务扣减用户余额,两者必须同时成功或同时失败。

  1. Try 阶段(资源预留)

    • 订单服务:创建订单数据,但不提交本地事务,仅记录 “待确认” 状态(如 订单状态=待支付)。

    • 现金服务:检查用户余额是否充足,若充足则冻结对应金额(如用户余额 100 元,订单金额 50 元,则冻结 50 元,可用余额显示为 50 元),同样不提交本地事务。

    • 目标:确保所有参与方都具备执行事务的资源和条件,且资源处于 “预留锁定” 状态,防止并发冲突。

  2. Confirm 阶段(确认提交)

    • 若Try 阶段所有参与方都成功,TCC 协调器(或发起方)会向各参与方发送Confirm指令:
      • 订单服务:提交本地事务,将订单状态更新为 “已支付”。
      • 现金服务:提交本地事务,将 “冻结金额” 转为 “实际扣除”(可用余额最终为 50 元)。
    • 目标:完成事务的最终提交,释放预留资源的锁定。
  3. Cancel 阶段(补偿回滚)

    • 若 Try 阶段任意参与方失败如现金服务发现余额不足),TCC 协调器会向各参与方发送Cancel指令:
      • 订单服务:回滚本地事务,删除 “待确认” 的订单记录。
      • 现金服务:回滚本地事务,解冻之前冻结的金额(用户余额恢复为 100 元)。
    • 目标:补偿已执行的操作,确保系统数据回滚到事务发起前的状态。

TCC实际上是最为复杂的一种情况,其能处理所有的业务场景,但无论出于性能上的考虑,还是开发复杂度上的考虑,都应该尽量避免该类事务。

TCC事务模型的要求:

TCC 对 “操作特性” 的要求

TCC 事务的每个阶段(Try、Confirm、Cancel)都需满足特定的操作特性,以保证分布式场景下的一致性。

可查询操作

  • 定义:每个服务操作需具备全局唯一标识确定的执行时间,确保事务状态可追溯。
  • 作用:当系统出现异常(如网络超时、服务宕机)时,可通过 “唯一标识 + 时间” 查询事务的当前状态,为重试或补偿提供依据。
  • 示例:订单服务的 “创建订单” 操作,需携带全局唯一的 orderId,且在执行时记录明确的时间戳,以便后续查询该订单的事务状态。

幂等操作

  • 定义:重复调用同一操作时,产生的业务结果与调用一次完全相同
  • 实现方式:
    • 业务层幂等:通过唯一业务标识(如 orderId)确保操作不重复执行(如数据库唯一索引)。
    • 缓存层幂等:系统缓存所有请求的处理结果,重复请求直接返回历史结果。
  • 作用:TCC 的 Confirm 和 Cancel 阶段可能因网络重试而被多次调用,幂等性可避免重复执行导致的业务异常(如重复扣款、重复创建订单)。

可补偿操作

  • 定义:存在 “正向执行(Do)” 和 “逆向补偿(Compensate)” 两个操作,补偿操作可抵消正向操作的业务结果,且需满足幂等性。
  • 阶段映射:TCC 中,Confirm 是正向提交的 “Do 阶段”Cancel 是逆向回滚的 “Compensate 阶段”
  • 约束:补偿操作在业务上必须可行(如 “扣款” 的补偿是 “退款”,“扣库存” 的补偿是 “加库存”),且需评估补偿不完整带来的风险(如退款延迟导致用户投诉)。

TCC 对 “三个阶段(Try、Confirm、Cancel)” 的约束

TCC 的核心逻辑围绕这三个阶段展开,每个阶段有明确的职责和技术要求。

  1. Try 阶段:资源检查与预留

    • 职责:
      • 完成业务检查(如用户余额是否充足、库存是否可用)。
      • 实现准隔离性:预留业务资源(如冻结余额、锁定库存),防止并发冲突。
      • 不提交本地事务,仅将业务状态标记为 “待确认”。
    • 技术要求:
      • 资源预留需足够 “轻量”,避免长时间锁定资源导致性能瓶颈。
      • 检查逻辑需覆盖所有业务约束(如余额 >= 订单金额、库存 >= 购买数量)。
  2. Confirm 阶段:最终提交

    • 职责:真正执行业务逻辑,使用 Try 阶段预留的资源完成事务提交。

    • 要求:

      • 不做任何业务检查(因 Try 阶段已确保资源可用),仅执行 “提交” 逻辑(如扣除冻结余额、扣减锁定库存)。
      • 必须满足幂等性(重复调用需保证结果一致,如多次扣减同一笔冻结余额需仅生效一次)。
      • 本地事务需原子提交(如订单状态更新和余额扣除需在同一个本地事务中完成)。
  3. Cancel 阶段:补偿回滚

    • 职责:释放 Try 阶段预留的资源,回滚已执行的操作。

    • 要求:

      • 必须满足幂等性(重复调用需保证资源释放完整,如多次解冻同一笔冻结余额需仅生效一次)。

      • 补偿逻辑需 “完整可逆”(如冻结余额需全额解冻,锁定库存需全额释放)。

      • 需处理 “部分成功” 的极端场景(如 Try 阶段部分资源预留成功,Cancel 阶段需全部释放)。

TCC需要注意的三种异常

空回滚

在没有调用 TCC 资源 Try 方法的情况下,调用了二阶段的 Cancel 方法,Cancel 方法需要识别出这是一个空回滚,然后直接返回成功。

出现原因是当一个分支事务所在服务宕机或网络异常,分支事务调用记录为失败,这个时候其实是没有执行Try阶段,当故障恢复后,分布式事务进行回滚则会调用二阶段的Cancel方法,从而形成空回滚。

解决思路是关键就是要识别出这个空回滚。

思路很简单就是需要知道一阶段是否执行,如果执行了,那就是正常回滚;如果没执行,那就是空回滚。

前面已经说过TM在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链条。再额外增加一张分支事务记录表,其中有全局事务 ID 和分支事务 ID,第一阶段 Try 方法里会插入一条记录,表示一阶段执行了。Cancel 接口里读取该记录,如果该记录存在,则正常回滚;如果该记录不存在,则是空回滚。

幂等

通过前面介绍已经了解到,为了保证TCC二阶段提交重试机制不会引发数据不一致,要求 TCC 的二阶段 Try、Confirm 和 Cancel 接口保证幂等,这样不会重复使用或者释放资源。如果幂等控制没有做好,很有可能导致数据不一致等严重问题。

解决思路在上述“分支事务记录”中增加执行状态,每次执行前都查询该状态。

悬挂

悬挂就是对于一个分布式事务,其二阶段 Cancel 接口比 Try 接口先执行。

出现原因是在 RPC 调用分支事务 try 时,先注册分支事务,再执行RPC调用,如果此时 RPC 调用的网络发生拥堵,通常 RPC 调用是有超时时间的,RPC 超时以后,TM 就会通知 RM 回滚该分布式事务,可能回滚完成后,RPC 请求才到达参与者真正执行,而一个 Try 方法预留的业务资源,只有该分布式事务才能使用,该分布式事务第一阶段预留的业务资源就再也没有人能够处理了,对于这种情况,我们就称为悬挂,即业务资源预留后没法继续处理。

解决思路是如果二阶段执行完成,那一阶段就不能再继续执行。在执行一阶段事务时,判断在该全局事务下的“分支事务记录”表中是否已经有二阶段事务记录,如果有则不执行Try。

TCC与2PC对比

TCC其实本质和2PC是差不多的:

  • T就是Try,两个C分别是Confirm和Cancel。
  • Try就是尝试,请求链路中每个参与者依次执行Try逻辑,如果都成功,就再执行Confirm逻辑,如果有失败,就执行Cancel逻辑

TCC与XA两阶段提交有着异曲同工之妙,下图列出了二者之间的对比

image-20251104153635320
  1. 在阶段1:

    • ·在XA中,各个RM准备提交各自的事务分支,事实上就是准备提交资源的更新操作(insert、delete、update等);

    • 而在TCC中,是主业务活动请求(try)各个从业务服务预留资源。

  2. 在阶段2中

    • XA根据第一阶段每个RM是否都prepare成功,判断是要提交还是回滚。如果都prepare成功,那么就commit每个事务分支,反之则rollback每个事务分支。

    • TCC中,如果在第一阶段所有业务资源都预留成功,那么confirm各个从业务服务,否则取消(cancel)所有从业务服务的资源预留请求。

TCC和2PC不同的是:

  • XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。基于数据库锁实现,需要数据库支持XA协议,由于在执行事务的全程都需要对相关数据加锁,一般高并发性能会比较差

  • TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁,性能较好。

    但是对微服务的侵入性强,微服务的每个事务都必须实现try、confirm、cancel等3个方法,开发成本高,今后维护改造的成本也高为了达到事务的一致性要求,try、confirm、cancel接口必须实现幂等性操作由于事务管理器要记录事务日志,必定会损耗一定的性能,并使得整个TCC事务时间拉长

TCC它会弱化每个步骤中对于资源的锁定,以达到一个能承受高并发的目的(基于最终一致性)。

  • 而且 TCC 中的两阶段提交并没有对开发者完全屏蔽,也就是说从代码层面,开发者是可以感受到两阶段提交的存在。try、confirm/cancel在执行过程中,一般都会开启各自的本地事务,来保证方法内部业务逻辑的ACID特性。其中:

    1. try过程的本地事务,是保证资源预留的业务逻辑的正确性。

    2. confirm/cancel 执行的本地事务逻辑确认/取消预留资源,以保证最终一致性,也就是所谓的补偿型事务(Compensation-Based Transactions)。由于是多个独立的本地事务,因此不会对资源一直加锁。

补偿是一个独立的支持ACID特性的本地事务,用于在逻辑上取消服务提供者上一个ACID事务造成的影响,对于一个长事务(long-running transaction),与其实现一个巨大的分布式ACID事务,不如使用基于补偿性的方案,把每一次服务调用当做一个较短的本地ACID事务来处理,执行完就立即提交

什么情况使用TCC

TCC是可以解决部分场景下的分布式事务的,但是,它的一个问题在于,需要每个参与者都分别实现Try,Confirm和Cancel接口及逻辑,这对于业务的侵入性是巨大的。

TCC 方案严重依赖回滚和补偿代码,最终的结果是:回滚代码逻辑复杂,业务代码很难维护。所以,TCC 方案的使用场景较少,但是也有使用的场景。

比如说跟钱打交道的,支付、交易相关的场景,大家会用 TCC方案,严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性,保证在资金上不会出现问题。

image-20251104154921833

SAGA长事务模型

SAGA可以看做一个异步的、利用队列实现的补偿事务。

Saga相关概念

什么是Saga

Saga 是针对长活事务(long lived transaction,LLT)设计的事务模型,1987 年由普林斯顿大学的 Hector Garcia-Molina 和 Kenneth Salem 在论文《Sagas》中提出,核心是解决长事务的处理问题。论文地址:sagas

Saga 是将一个长活事务(LLT)分解为多个可交错运行的子事务集合,每个子事务都是能保持数据库一致性的本地真实事务。

简单来说,它把分布式事务拆分成多个本地事务,每个本地事务配套执行模块和补偿模块(类似 TCC 中的 Confirm 和 Cancel)。当任意一个本地事务出错时,通过调用补偿方法恢复之前的事务,最终实现事务的最终一致性。

这样的SAGA事务模型,是牺牲了一定的隔离性和一致性的,但是提高了long-running事务的可用性。

Saga 模型其组成

Saga 模型由三部分组成:

  • LLT(Long Live Transaction,长活事务):由一系列本地事务组成的事务链,即 LLT = T1 + T2 + T3 + … + Ti(Ti 为本地事务)。
  • 本地事务:事务链中的子事务,是构成 LLT 的基本单元,每个都能独立保证自身操作的原子性(依托本地数据库的事务能力)。
  • 补偿(Ci):每个本地事务 Ti 对应一个补偿操作 Ci,用于在 Ti 执行出错或整个 Saga 事务需要回滚时,撤销 Ti 已产生的影响。

Saga 有两种典型执行顺序

Saga 有两种典型执行顺序:

  • 正常执行:按子事务顺序依次执行,即 T1, T2, T3, …, Tn(所有子事务均成功)。
  • 异常回滚执行:当某个子事务 Tj(0 < j < n)失败时,执行已完成子事务的补偿操作,顺序为 T1, T2, …, Tj(失败), Cj, …, C2, C1。

Saga 的恢复策略

Saga 的恢复策略

  • 向后恢复(Backward Recovery):若任意本地子事务失败,通过补偿已完成的所有子事务来撤销影响,最终回到事务执行前的状态。例如,T1、T2 成功,T3 失败时,执行顺序为 T1, T2, T3(失败), C2, C1。

    这样的SAGA事务模型,是牺牲了一定的隔离性和一致性的,但是提高了long-running事务的可用性。

  • 向前恢复(Forward Recovery):对失败的子事务进行重试,直至成功,适用于业务必须完成的场景(无需补偿操作)。例如,T2 失败时,执行顺序为 T1, T2(失败), T2(重试), T3, …, Tn。

所以说,对于一个SAGA事务,如果执行过程中遭遇失败,那么接下来有两种选择,一种是进行回滚,另一种是重试继续。

回滚的机制相对简单一些,只需要在进行下一步之前,把下一步的操作记录到保存点就可以了。一旦出现问题,那么从保存点处开始回滚,反向执行所有的补偿操作即可。

而往前重试的支持,需要把全局事务的所有子事务事先编排好并保存,然后在失败时,重新读取未完成的进度,并重试继续执行。

显然,向前恢复没有必要提供补偿事务,如果你的业务中,子事务(最终)总会成功,或补偿事务难以定义或不可能,向前恢复更符合任务必须成功的需求。

理论上补偿事务永不失败,然而,在分布式世界中,服务器可能会宕机,网络可能会失败,甚至数据中心也可能会停电。在这种情况下我们能做些什么? 最后的手段是提供回退措施,比如人工干预。

Saga的使用条件

但是 Saga 并非适用于所有长事务场景,使用存在以下限制:

  • 仅允许两层嵌套:顶级 Saga 和简单子事务,不支持更深层次的嵌套。
  • 不满足全原子性:其他 Saga 可能会看到当前 Saga 的部分执行结果(隔离性较弱)。
  • 子事务需独立:每个子事务必须是独立的原子行为(如航班预订、酒店预订等自然独立的业务操作),且能通过本地数据库保证原子性。

补偿也有需考虑的事项:补偿事务从理论上来说,需要撤消事务Ti的行为,但未必能将数据库返回到执行Ti时的状态。例如你已经把钱打到对面的账户里了,或者你已经消耗了某种一次性的资源

但这对我们的技术层次来说不是问题,因为这是业务设计角度的问题。其实难以撤消的行为也有可能被补偿。

对于ACID的保证

Saga对于ACID的保证和TCC一样:

  • 原子性(Atomicity):正常情况下保证。
  • 一致性(Consistency),在某个时间点,会出现A库和B库的数据违反一致性要求的情况,但是最终是一致的。
  • 隔离性(Isolation),在某个时间点,A事务能够读到B事务部分提交的结果。
  • 持久性(Durability),和本地事务一样,只要commit则数据被持久。

Saga不提供ACID保证,因为原子性和隔离性不能得到满足。原论文描述如下:

1
full atomicity is not provided. That is, sagas may view the partial results of other sagas

通过saga log,saga可以保证一致性和持久性。

SAGA模型的解决方案

SAGA模型的核心思想是,通过某种方案,将分布式事务转化为本地事务,从而降低问题的复杂性。

比如以DB和MQ的场景为例,业务逻辑如下:

  1. 向DB中插入一条数据。
  2. 向MQ中发送一条消息。

由于上述逻辑中,对应了两种存储端,即DB和MQ,所以,简单的通过本地事务是无法解决的。那么,依照SAGA模型,他还是可以拥有像其他柔性事务的解决方案

半消息模式 和 本地表模式

而且目前看到市面上已经有很多的saga实现,他们都具备saga的基本功能。

这些实现,可以大致可以分为两类

  • 状态机实现

    这一类的典型实现有 Seata 的 saga,他引入了一个DSL语言定义的状态机,允许用户做以下操作:

    • 在某一个子事务结束后,根据这个子事务的结果,决定下一步做什么

    • 能够把子事务执行的结果保存到状态机,并在后续的子事务中作为输入

    • 允许没有依赖的子事务之间并发执行

    这种方式的优点是:

    • 功能强大,事务可以灵活自定义

    缺点是:

    • 状态机的使用门槛非常高,需要了解相关DSL,可读性差,出问题难调试。官方例子是一个包含两个子事务的全局事务,Json格式的状态机定义大约有95行,较难入门。

    • 接口入侵强,只能使用特定的输入输出接口参数类型,在云原生时代,对强类型的gRPC不友好

  • 非状态机实现

    这一类的实现有 eventuate的saga,dtm的saga。在这一类的实现中,没有引入新的DSL来实现状态机,而是采用函数接口的方式,定义全局事务下的各个分支事务:

    优点:

    • 简单易上手,易维护

    缺点:

    • 难以做到状态机的事务灵活自定义

PS:eventuate的作者将基于事件订阅协作的模式,也称为saga,因为他的影响力大,因此许多文章在介绍saga模式的时候都会提这个。但事实上这个模式与原先的saga论文相关不大,也与各家实现的saga模式相关不大,所以这里没有专门去论述这种模式

这些分类,注意不是四种实现方式,非状态机实现、状态机实现的 Saga,与半消息模式、本地表模式并非并列关系,前两者是 Saga 事务的整体实现框架分类,后两者是 Saga 中解决 “本地事务与跨存储交互”(如 DB+MQ)的具体技术方案

  • 状态机实现 / 非状态机实现:是 Saga 事务的整体架构设计分类,决定了 “如何组织多个子事务的执行流程、如何处理分支逻辑与并发”。

    比如 Seata Saga 用状态机定义子事务的依赖和流向,dtm Saga 用函数接口直接串联子事务,核心区别在 “流程控制方式”。

  • 半消息模式 / 本地表模式:是 Saga 中单个子事务的具体实现技术,解决的是 “本地事务(如 DB 操作)与跨存储操作(如 MQ 发消息)如何保证一致性” 的问题。

    比如执行 “DB 插入 + MQ 发消息” 这个子事务时,可用半消息模式确保 MQ 消息在 DB 提交后才真正发送,也可用本地表记录消息状态确保不丢消息。

简单说:一个 Saga 事务(无论状态机还是非状态机实现),其内部的每个子事务,都可能用到半消息或本地表模式来保证自身的一致性。两者是 “框架” 与 “组件” 的关系,而非替代关系。

Saga分布式事务如何协调

首先,Saga 中的每个事务存在多个子事务,每个子事务都有一个补偿事务,其在事务回滚的时候使用。由于子事务对应的操作在分布式的系统架构中会部署在不同的服务中,这些子事务为了完成共同的事务需要进行协同。

实际上在启动一个Saga事务时,协调逻辑会告诉第一个Saga参与者,也就是子事务,去执行本地事务。事务完成之后Saga的会按照执行顺序调用Saga的下一个参与的子事务。这个过程会一直持续到Saga事务执行完毕。

如果在执行子事务的过程中遇到子事务对应的本地事务失败,则 Saga 会按照相反的顺序执行补偿事务。通常来说我们把这种 Saga 执行事务的顺序称为个 Saga 的协调逻辑。

这种协调逻辑有两种模式,编排(Choreography)和控制(Orchestration)分别如下:

编排(Choreography)

核心特点:去中心化,子事务(服务)之间通过事件消息交互,自主决定后续流程。

执行流程(以 “下单业务:订单→支付→库存→发货” 为例)

  • 正常执行

    1. 订单服务执行 “创建订单”,并发送「创建订单消息」到消息队列。
    2. 支付服务监听该消息,执行 “支付订单”,并发送「支付消息」。
    3. 库存服务监听「支付消息」,执行 “扣减库存”,并发送「扣减库存消息」。
    4. 发货服务监听「扣减库存消息」,执行 “发货”,并发送「发货消息」。
    5. 订单服务监听「发货消息」,标记订单事务完成。
    image-20251104162651700
  • 异常回滚

    假设 “发货” 失败:

    1. 发货服务发送「发货失败消息」。
    2. 库存服务监听该消息,执行 “回滚库存”(将扣减的库存加回),并发送「扣减失败消息」。
    3. 支付服务监听「扣减失败消息」,执行 “回滚支付”(退款),并发送「支付失败消息」。
    4. 订单服务监听「支付失败消息」,标记订单事务失败。
    image-20251104162645145

优缺点

  • 优点:

    • 简单:子事务只需发布 / 监听事件,逻辑内聚。

    • 松耦合:服务间通过消息订阅交互,组合灵活。

  • 缺点:

    • 理解困难:需通读所有服务的消息逻辑才能理清整体流程。
    • 循环依赖风险:服务间可能因消息交互形成循环调用(如 A→B→A)。
    • 紧耦合风险:每个服务需精确订阅上游消息,否则流程中断。

可以看到这种就是上面说的最基本的向后恢复

控制(Orchestration)

核心特点:中心化,通过 ** 控制类(Saga 协调器)** 统一调度子事务的执行与回滚。

执行流程(以 “下单业务:订单→支付→库存→发货” 为例)

  • 正常执行

    1. 订单服务向 Saga 协调器发起事务请求。
    2. 协调器调用 “支付服务 - 支付订单”,收到「支付完成」反馈。
    3. 协调器调用 “库存服务 - 扣减库存”,收到「扣减完成」反馈。
    4. 协调器调用 “发货服务 - 发货”,收到「发货完成」反馈。
    5. 协调器通知订单服务,事务完成。
    image-20251104162705577
  • 异常回滚

    假设 “发货” 失败:

    1. 发货服务向协调器反馈「发货失败」。
    2. 协调器调用 “库存服务 - 回滚库存”(恢复扣减的库存)。
    3. 协调器调用 “支付服务 - 回滚支付”(执行退款)。
    4. 协调器通知订单服务,事务失败。
    image-20251104162711417

优缺点

  • 优点:
    • 避免循环依赖:流程由协调器统一控制,服务间无直接消息依赖。
    • 降低复杂性:服务只需实现自身的 “执行 / 补偿” 逻辑,协调逻辑由协调器集中管理。
    • 易测试、易扩展:测试只需关注协调器,新增步骤仅需修改协调器逻辑。
  • 缺点:
    • 依赖协调器:协调器成为单点,若故障则整个事务瘫痪;且协调器逻辑可能过度集中。
    • 管理难度增加:需额外维护协调器服务,架构复杂度上升。

Saga的状态机实现—— Seata Saga 的模式

Seata Saga 是 “控制模式” 的典型实现,通过状态机(DSL 定义) 扮演 “协调器” 角色,具体执行逻辑如下:

1. 核心组件

  • Saga 状态机:用 DSL(如 JSON)定义子事务的执行顺序、分支逻辑、补偿策略。例如,一个简单的状态机定义会包含 “执行支付→执行扣库存→执行发货” 的流程,以及每个步骤的补偿操作(如 “回滚支付→回滚库存”)。
  • 子事务参与者:各业务服务(如订单、支付、库存服务),需实现 “执行方法” 和 “补偿方法”。

2. 执行流程

  • 正常执行

    1. 发起方(如订单服务)触发 Saga 事务,调用 Seata Saga 状态机。
    2. 状态机按 DSL 定义的顺序,依次调用各子事务的 “执行方法”(如调用支付服务的 “支付订单”)。
    3. 每个子事务执行成功后,向状态机返回确认,状态机继续执行下一个子事务。
    4. 所有子事务执行完成,Saga 事务提交。
  • 异常回滚

    若某子事务执行失败(如 “发货” 失败):

    1. 状态机捕获失败后,按反向顺序调用已完成子事务的 “补偿方法”(如先调用 “回滚库存”,再调用 “回滚支付”)。
    2. 所有补偿操作执行完成,Saga 事务回滚。

3. Seata Saga 的优势与设计

  • 灵活性:通过状态机 DSL 支持复杂分支逻辑(如 “支付成功则走库存,失败则直接回滚”)。
  • 兼容性:支持 HTTP、Dubbo、Spring Cloud 等多种服务调用方式。
  • 可靠性:通过日志记录事务状态,确保故障恢复时能继续执行或回滚。

Saga的非状态机实现—dtm的saga设计

dtm 支持 TCC 和saga模式,这两个模式有不同的特点,各自适应不同的业务场景,相互补充。

image-20251104162926078

dtm 的 Saga 属于非状态机实现的典型代表,其设计目标是降低使用门槛的同时,满足长事务、异步场景的核心需求(如子事务并发、异步结果处理)。

dtm 不通过 DSL 定义状态机,而是以函数接口 / 代码逻辑直接串联子事务,同时支持子事务并发执行自定义执行顺序,兼顾了灵活性与易用性。

而 TCC 的定位是一致性要求较高的短事务。一致性要求较高的事务一般都是短事务(一个事务长时间未完成,在用户看来一致性是比较差的,一般没有必要采用TCC这种高一致性的设计),因此TCC的事务分支编排放在了AP端(即程序代码里),由用户灵活调用。这样用户可以根据每个分支的结果,做灵活的判断与执行。

SAGA的定位是一致性要求较低的长事务/短事务。对于类似订机票这种这样的场景,持续时间长,可能持续几分钟到一两天,就需要把整个事务的编排保存到服务器,避免发起全局事务的APP因为升级、故障等原因,导致事务编排信息丢失。

dtm Saga 针对于 长业务 长事务 的痛点,针对性地解决了这些问题:

  • dtm支持子事务并发执行,无需等待前一个子事务完成再执行下一个,降低了延迟

    • 旅游订票需同时订 “去程机票” 和 “返程机票”,若串行执行,可能因去程耗时过长导致返程票售罄。dtm 可同时发起 “去程机票预订” 和 “返程机票预订” 两个子事务,并行调用第三方接口,大幅缩短整体事务耗时。
  • dtm 针对无法当时就给出结果的子事务,可按照指定时间间隔自动重试该子事务。而子事务可返回 “进行中” 状态

    • 订票后第三方需一段时间才返回结果(如几分钟到几小时),需持续查询状态直至成功 / 失败,逻辑可设计为:
      • 若未下单:执行 “下单逻辑”,返回 “进行中”。
      • 若已下单(重试请求):查询第三方订票结果,返回 “成功”“失败” 或继续 “进行中”。
  • dtm 支持自定义子事务的执行顺序,可指定 “不可回滚操作” 在所有可回滚子事务成功后执行,保证一旦执行则最终成功。也就是说,可以让不可回滚操作确保在可回滚的子事务完成后执行。

Saga的半消息模式

RocketMQ新版本中,就支持了这种模式。

前面我们通知型事务的时候就说了,什么是半消息。简单来说,就是在消息上加了一个状态,支持了事务

半消息模式是 Saga 中解决 “数据库操作与消息队列发送” 一致性的技术方案,核心是通过“待确认消息” 机制 ,将 DB 操作与 MQ 消息的发送绑定为一个逻辑上的本地事务。

半消息的完整事务逻辑如下:

  1. 向MQ发送半消息。
  2. 向DB插入数据。
  3. 向MQ发送确认消息。

我们发现,通过半消息的形式,将DB的操作夹在了两个MQ操作的中间。假设,第2步失败了,那么,MQ中的消息就会一直是半消息状态,也就不会被消费者消费。

那么,半消息就一直存在于MQ中吗?或者是说如果第3步失败了呢?

为了解决上面的问题,MQ引入了一个扫描的机制。即MQ会每隔一段时间,对所有的半消息进行扫描,并就扫描到的存在时间过长的半消息,向发送者进行询问,询问如果得到确认回复,则将消息改为确认状态,如得到失败回复,则将消息删除。

image-20251104164335602

半消息机制的一个问题是:要求业务方提供查询消息状态接口,对业务方依然有较大的侵入性。

一般情况下用在,需要将 “DB 操作” 与 “MQ 消息发送” 绑定,也就是对最终一致性要求较高的场景

假设业务逻辑是 “向 DB 插入订单数据 → 向 MQ 发送订单消息”,半消息模式的执行步骤如下:

  1. 发送半消息:业务系统先向 MQ 发送一条 “半消息”(状态为 “待确认”),此时该消息不会被消费者消费。
  2. 执行 DB 操作:在同一个本地事务中,向 DB 插入订单数据。若此步骤失败,整个本地事务回滚,半消息不会被确认。
  3. 确认半消息:若 DB 操作成功,业务系统向 MQ 发送 “确认指令”,将半消息标记为 “可消费”,消费者开始处理该消息。
  4. 异常处理(MQ 主动扫描):若步骤 3 因网络故障等原因未执行,MQ 会定时扫描 “待确认时间过长” 的半消息,主动询问业务系统 “该消息是否需要确认”。若业务系统返回 “失败”,则删除该半消息;若返回 “成功”,则标记为可消费。

按照上面的分析,本地事务的原子性是这样的,步骤 2 和步骤 3 并非严格的 “同一事务”,但通过 “半消息 + 确认机制”,逻辑上保证了 “DB 操作成功则消息必发,DB 操作失败则消息不发”。

一般情况下,需要业务系统的 “确认接口” 和 “MQ 扫描询问接口” 需支持幂等(即重复调用结果一致),避免因重试导致消息重复发送。

Saga的本地消息表

本地消息表模式是 Saga 中解决 “数据库操作与消息队列发送” 一致性的技术方案,核心是通过在业务数据库中创建 “消息表”,将 “业务操作” 与 “消息发送” 绑定为一个本地事务,再通过异步线程扫描消息表完成消息的最终发送。

在DB中,新增一个消息表,用于存放消息。如下:

  1. 在DB业务表中插入数据。
  2. 在DB消息表中插入数据。
  3. 异步将消息表中的消息发送到MQ,收到ack后,删除消息表中的消息。
image-20251104164640356

假设业务逻辑是 “向 DB 插入订单数据 → 向 MQ 发送订单消息”,本地消息表模式的执行步骤如下:

  1. 创建本地消息表:在业务数据库中新增一张order_message表,包含字段:id(主键)、order_id(关联订单)、message_content(消息内容)、status(状态:待发送、已发送、发送失败)、retry_count(重试次数)、create_time/update_time等。
  2. 执行本地事务
    • 开启数据库事务,同时执行两个操作:
      • order表插入订单数据;
      • order_message表插入一条 ** 状态为 “待发送”** 的消息记录。
    • 若其中任一操作失败,整个事务回滚,确保 “业务操作” 和 “消息记录” 的原子性。
  3. 异步发送消息
    • 业务系统启动异步线程 / 定时任务,扫描order_message表中 “待发送” 的消息。
    • 针对每条 “待发送” 消息,尝试向 MQ 发送;发送成功后,更新消息状态为 “已发送”;发送失败则更新 “重试次数”,并根据策略决定是否继续重试(如重试 3 次后标记为 “发送失败”,人工介入)。

其中,“业务操作” 和 “插入消息记录” 在同一个数据库事务中执行,确保两者要么都成功,要么都失败。

而且,这种方式的消息可靠性:通过 “状态标记 + 重试机制” 保障消息最终发送成功

  • 状态标记:status字段区分消息的不同阶段,避免重复发送或漏发。
  • 重试机制:对发送失败的消息,按一定时间间隔(如指数退避)重试,直到成功或达到最大重试次数。

这种情况,MQ 消费者需支持幂等(即重复消费同一条消息时结果一致),避免因消息重试导致业务逻辑重复执行。

这种一般用在需要将 “DB 操作” 与 “消息发送” 强一致绑定,且 MQ 不支持半消息模式(如 RabbitMQ)的场景;或业务方希望自定义消息发送逻辑的场景。

核心优势:

  • 兼容性强:不依赖 MQ 原生特性,任何 MQ 都可适配。
  • 灵活性高:可自定义消息发送时机、重试策略、失败处理逻辑(如人工介入)。
  • 业务侵入性低:消息表与业务表在同一数据库,事务管理由数据库原生支持,代码逻辑清晰。

Saga 和 TCC 的区别

最大的区别是核心的流程

TCC(Try-Confirm-Cancel)

  • 三阶段流程:
    • Try(预留):对资源进行预留 / 锁定,例如 “保存邮件草稿”“冻结账户余额”。
    • Confirm(提交):确认执行操作,例如 “发送邮件”“扣除冻结的余额”。
    • Cancel(取消):释放 Try 阶段的预留资源,例如 “删除邮件草稿”“解冻余额”。
  • 典型案例:发邮件场景中,TCC 先 “保存草稿(Try)”,再 “发送邮件(Confirm)”;若需撤销,直接 “删除草稿(Cancel)” 即可。

Saga

  • 两阶段流程:
    • Ti(直接提交):直接执行业务操作(无预留阶段),例如 “直接发送邮件”。
    • Ci(补偿):通过逆向操作撤销 Ti 的影响,例如 “再发送一封邮件说明撤销”。
  • 典型案例:发邮件场景中,Saga 直接 “发送邮件(Ti)”;若需撤销,需 “发送补偿邮件(Ci)”,实现逻辑更复杂。

业务侵入性也有区别

TCC 的侵入性

  • 改造原有业务逻辑,拆分出 Try、Confirm、Cancel 三个接口。例如,原本 “发送邮件” 的逻辑需拆分为 “保存草稿(Try)”“发送邮件(Confirm)”“删除草稿(Cancel)”。
  • 若第三方服务无 Try 接口(如某些支付网关仅提供 “直接扣款” 接口),TCC 实现会非常困难(需 “tricky” 的适配逻辑)。

Saga 的侵入性

  • 只需为每个子事务添加补偿动作(Ci),无需拆分原有业务逻辑。例如,原有 “发送邮件” 逻辑保留,新增 “发送补偿邮件” 作为 Ci 即可。
  • 对第三方服务友好:即使服务只有 “执行” 接口(无预留能力),Saga 仍可通过补偿动作适配。

隔离性与性能对比

隔离性

  • TCC:Try 阶段通过锁定资源保证隔离性(如冻结余额后,其他事务无法操作该余额),但会引入锁竞争。
  • Saga无隔离性保证,事务执行过程中可能出现 “脏读”(如 A 事务读到 B 事务的部分执行结果),仅保证最终一致性。

性能与吞吐量

  • TCC:最少需要 2n 次通信(n 为子事务数量,Try + Confirm/Cancel),且 Try 阶段的锁会降低并发性能。
  • Saga:仅需 n 次通信(Ti + 若失败则 Ci),且一阶段直接提交本地事务(无锁),高性能、高吞吐;支持事件驱动的异步执行(如子事务通过 MQ 异步触发)。

适用场景对比

TCC 适用场景

  • 隔离性要求高的短事务,例如金融支付(需确保扣款过程中余额不被重复使用)、库存扣减(需锁定库存防止超卖)。
  • 业务逻辑可清晰拆分为 “预留 - 提交 / 取消” 三阶段的场景。

Saga 适用场景

  • 长事务 / 异步场景:如跨系统的订单流程(下单→支付→物流→通知)、订票业务(需长时间等待第三方确认)。
  • 业务逻辑简单,或第三方服务无预留能力的场景。
  • 对最终一致性要求高,但可接受过程中隔离性缺失的场景。

也就是说

  • 选 TCC:业务需强隔离、资源需预留(如支付、库存),且能接受较高的业务改造成本。
  • 选 Saga:业务是长事务 / 异步流程、对侵入性要求低、第三方服务无预留接口,且可接受最终一致性(无隔离性)。

五大方案总体对比

2PC 方案

对比维度 详情
定义 两阶段提交(Two-Phase Commit,2PC)是一种分布式事务协议,用于保证分布式系统中多个节点上的事务要么全部提交成功,要么全部回滚,以确保数据的一致性。
原理与阶段流程 准备阶段(Prepare Phase):协调者向所有参与者发送 prepare 请求,参与者执行本地事务操作,但不提交事务,记录 undo/redo 日志,然后向协调者反馈响应,成功返回 “同意”,失败返回 “中止”。提交阶段(Commit Phase):若所有参与者都返回 “同意”,协调者向所有参与者发送 commit 请求,参与者完成本地事务提交,并向协调者发送 ack 响应,协调者收到所有 ack 后完成事务;若任一参与者返回 “中止” 或超时,协调者向所有参与者发送 rollback 请求,参与者利用 undo 日志回滚本地事务,并向协调者发送 ack 响应,协调者收到所有 ack 后中断事务。
一致性 具有强一致性,能严格保证所有节点的事务原子性,即要么所有节点都提交事务,要么所有节点都回滚事务,不会出现部分提交的情况。
性能 性能开销较大。在准备阶段和提交阶段,参与者的资源(如数据库连接、锁等)会被持续占用,直到全局提交 / 回滚。而且协调者需要等待所有参与者反馈后才进入第二阶段,参与者也要等协调者指令才能提交 / 回滚,存在同步阻塞问题,一旦某个节点响应慢,就会造成全局阻塞,拖慢系统性能。
可靠性 存在单点故障问题,协调者是整个事务流程的核心,一旦协调者宕机,整个事务流程可能会 “卡壳” 。如果在提交阶段协调者发生故障,部分参与者可能已经提交事务,而部分参与者未收到指令无法操作,导致数据不一致。虽然可以通过一些机制如给协调者增加超时机制、设置备份节点等,但实现复杂度高,且不能完全解决问题。
适用场景 适合对数据强一致性要求高,但对性能、可用性容忍度稍高的场景,例如金融核心交易场景(如转账、清算),这类场景更注重数据的准确性,即使牺牲一定性能也要保证数据不出错。
业务侵入性 对业务代码侵入性较小,主要是在事务的提交和回滚逻辑上进行处理,不需要在业务代码中大量编写额外的事务控制逻辑,但依赖数据库对 XA 事务的支持。

TCC 方案

对比维度 详情
定义 TCC(Try-Confirm-Cancel)是一种基于业务补偿的分布式事务解决方案,将分布式事务的执行过程拆分为三个阶段,通过业务逻辑的定制化设计来解决跨服务调用时的数据一致性问题 。
原理与阶段流程 Try(尝试阶段):检查业务所需资源是否可用,对业务所需资源进行锁定或预留,但不实际执行业务操作。例如在电商下单场景中,订单服务创建 “待支付” 状态订单,库存服务检查并锁定库存,支付服务检查并预留支付金额。Confirm(确认阶段):当所有参与方的 Try 阶段都成功时,正式执行业务操作,将 Try 阶段预留的资源进行实际消耗,完成最终的数据修改。如订单服务将 “待支付” 订单更新为 “已支付”,库存服务实际扣减库存,支付服务实际扣除预留金额。Cancel(取消阶段):只要有一个参与方的 Try 阶段失败,就对所有参与方执行 Cancel 操作,撤销 Try 阶段的预备操作,释放预留资源,恢复到事务执行前状态。如订单服务将 “待支付” 订单改为 “已取消”,库存服务释放锁定库存,支付服务释放预留金额 。
一致性 实现最终一致性,通过 Confirm 和 Cancel 操作来保证在整个事务流程中,最终所有参与方的数据状态达成一致。
性能 性能相对较好,资源仅在 Try 阶段短暂锁定,减少了锁竞争和资源占用时间,在高并发场景下,能有效提升系统吞吐量和响应速度,降低了事务处理过程中的阻塞情况。
可靠性 不存在中心化协调器的单点故障问题,各服务节点相对独立,某一节点故障时,可通过重试机制和事务日志进行恢复。但如果出现网络分区等极端情况,可能导致部分节点的事务状态不一致,需要额外的处理机制来解决 。
适用场景 适用于高并发、短事务场景,以及对性能要求较高,业务逻辑可拆分为三个阶段且具有补偿机制的场景,如电商的订单支付、金融领域的资金转账等 。
业务侵入性 业务侵入性高,需要对业务代码进行大量改造,每个参与事务的服务都需要实现 Try、Confirm 和 Cancel 三个接口方法,并且要保证这些接口的幂等性、空回滚处理、悬挂处理等复杂逻辑 。

Saga 方案

对比维度 详情
定义 Saga 是一种基于本地事务和异步消息实现最终一致性的分布式事务解决方案,将一个长事务拆分成多个可交错执行的子事务集合,每个子事务都是一个保持数据库一致性的真实事务 。
原理与阶段流程 Saga 事务由一系列子事务 Ti 组成,每个子事务都有对应的补偿动作 Ci。正常执行时,依次执行 T1、T2、…、Tn 。当某个子事务 Tj 执行失败时,按照相反顺序执行已成功子事务的补偿事务 Cj-1、…、C2、C1 ,以此来撤销之前的操作,恢复到事务执行前状态 。例如在电商订单处理中,先执行创建订单事务 T1,再执行扣减库存事务 T2,若 T2 失败,则执行创建订单的补偿事务 C1,取消订单。
一致性 实现最终一致性,在事务执行过程中,可能存在某一时间点各服务数据不一致情况,但经过一系列子事务和补偿事务的处理,最终能保证所有相关服务的数据达成一致 。
性能 性能较好,因为各个子事务都是本地事务,执行过程中没有全局锁,各子事务可异步执行,支持高并发,能有效提升系统吞吐量 。
可靠性 相对可靠,即使某个子事务所在节点出现故障,也可以通过重试机制和事务日志进行恢复,且由于是异步消息驱动,部分节点故障不会影响整个事务流程。不过消息队列的可靠性会影响整个方案可靠性,如果消息丢失或重复消费,可能导致事务状态不一致 。
适用场景 适用于长事务场景以及业务流程复杂、涉及多个服务调用的场景,如电商平台的订单创建、库存扣减、物流安排、支付处理等多个步骤的业务流程 。
业务侵入性 业务侵入性较高,需要为每个子事务设计对应的补偿事务逻辑,并且要处理消息的发送、接收、幂等性等问题,开发和维护成本较高 。

异步确保型方案

对比维度 详情
定义 异步确保型方案是一种基于消息队列实现事务最终一致性的分布式事务解决方案,将原本同步执行的事务操作通过消息队列转化为异步操作 。
原理与阶段流程 事务发起方在执行本地事务时,将事务相关信息(如订单创建、支付成功等事件)封装成消息发送到消息队列。消息队列负责存储和转发消息,事务参与方从消息队列中消费消息,并执行相应的业务逻辑。如果消费失败,消息队列会根据重试机制进行重试,直到消息被成功处理 。例如电商下单后,订单服务发送消息到消息队列,库存服务从队列消费消息进行库存扣减。
一致性 实现最终一致性,在事务执行过程中,各服务之间的数据状态可能存在短暂不一致,但随着消息的可靠传输与消费,最终能保证所有服务的数据达成一致 。
性能 性能较好,由于是异步操作,事务发起方无需等待事务参与方的处理结果,可立即返回,提高了系统的响应速度和吞吐量,适合处理高并发场景下的事务 。
可靠性 依赖消息队列的可靠性。消息队列需要具备消息持久化、重试机制、消息确认(ACK)等功能,以确保消息不丢失、不重复。若消息队列出现故障,可能导致消息丢失或处理异常,影响事务的一致性 。不过可通过引入高可用的消息队列集群、配置备份节点等方式提高可靠性 。
适用场景 适用于对响应时间要求较高、实时性要求不高、事务执行周期较长的场景,如电商系统中的订单创建与后续的物流通知、积分发放等业务,以及支付系统中的异步对账等场景 。
业务侵入性 业务侵入性相对较低,主要是在业务逻辑中增加消息的发送和接收逻辑,无需对业务代码进行大规模改造,但需要处理消息的幂等性问题,防止重复消费导致数据不一致 。

尽最大努力方案

对比维度 详情
定义 尽最大努力通知型是一种分布式事务解决方案,对一致性要求最低,适用于最终一致性时间敏感度低的业务场景 。发起通知方通过一定机制,最大程度地将业务处理结果通知到接收方,但允许消息存在一定丢失情况 。
原理与阶段流程 业务活动主动方在完成业务处理后,向业务活动被动方发送消息。若消息发送失败或接收方未成功接收,主动方会进行多次通知,通常采用间隔递增的方式,如第 1 次失败后隔 10 秒进行第 2 次通知,第 2 次失败后隔 30 秒进行第 3 次通知 。当达到一定通知次数(如 N 次)后不再通知。此外,被动方会根据定时策略,主动向主动方查询(主动方需提供查询接口),以恢复可能丢失的业务消息 。例如支付宝在用户支付成功后,会多次向商户异步通知支付结果,若商户未收到,可主动查询订单支付状态 。
一致性 实现最终一致性,不过在一致性保障程度上相对较弱,允许事务执行过程中出现较长时间的数据不一致情况 。
性能 性能开销较小,因为没有复杂的事务协调机制和长时间的资源锁定,对系统性能影响较小,能适应高并发场景 。
可靠性 可靠性相对较低,依赖消息发送和接收的重试机制以及被动方的主动查询机制。若消息发送失败且查询机制未有效触发,可能导致事务处理结果不一致 。
适用场景 适用于对事务一致性要求不高、实时性要求较低的场景,如银行通知、商户通知、一些对数据准确性要求不高的日志记录场景等 。
业务侵入性 业务侵入性较低,主要是在业务逻辑中增加消息发送、接收以及查询接口的实现逻辑,对原有业务代码结构改动较小 。

五大方案对比总结

对比维度 2PC 方案 TCC 方案 Saga 方案 异步确保型方案 尽最大努力方案
一致性 强一致性,严格保证所有节点事务原子性,要么全提交,要么全回滚 最终一致性,通过 Confirm 和 Cancel 操作保证所有参与方数据状态最终一致 最终一致性,在事务执行过程中可能存在数据不一致,但经子事务和补偿事务处理后最终达成一致 最终一致性,事务执行中各服务数据可能短暂不一致,随消息传输与消费最终达成一致 最终一致性,一致性保障较弱,允许事务执行过程中出现较长时间数据不一致
性能 性能开销大,准备和提交阶段参与者资源被持续占用,存在同步阻塞问题,某节点响应慢会拖慢全局性能 性能相对较好,资源仅在 Try 阶段短暂锁定,减少锁竞争和资源占用时间,高并发场景下能提升吞吐量和响应速度 性能较好,各子事务是本地事务且可异步执行,无全局锁,支持高并发,能提升系统吞吐量 性能较好,异步操作,事务发起方无需等待参与方处理结果,可立即返回,适合高并发场景 性能开销小,无复杂事务协调机制和长时间资源锁定,对系统性能影响小,适应高并发场景
可靠性 存在单点故障问题,协调者宕机可能导致事务流程 “卡壳”,出现数据不一致,虽可通过增加超时机制、设置备份节点等方式解决,但实现复杂度高 不存在中心化协调器单点故障问题,各服务节点相对独立,某节点故障可通过重试机制和事务日志恢复,但极端网络分区情况可能导致事务状态不一致 相对可靠,某子事务节点故障可通过重试机制和事务日志恢复,异步消息驱动使部分节点故障不影响整体事务流程,但消息队列可靠性影响方案可靠性 依赖消息队列可靠性,消息队列需具备消息持久化、重试、消息确认等功能,可通过引入高可用消息队列集群、配置备份节点等方式提高可靠性 可靠性相对较低,依赖消息发送和接收重试机制以及被动方主动查询机制,若消息发送失败且查询机制未有效触发,可能导致事务处理结果不一致
适用场景 适合对数据强一致性要求高,但对性能、可用性容忍度稍高的场景,如金融核心交易场景(转账、清算) 适用于高并发、短事务场景,以及对性能要求较高,业务逻辑可拆分为三个阶段且具有补偿机制的场景,如电商订单支付、金融领域资金转账 适用于长事务场景以及业务流程复杂、涉及多个服务调用的场景,如电商平台订单创建、库存扣减、物流安排、支付处理等业务流程 适用于对响应时间要求较高、实时性要求不高、事务执行周期较长的场景,如电商系统订单创建与后续物流通知、积分发放,支付系统异步对账等场景 适用于对事务一致性要求不高、实时性要求较低的场景,如银行通知、商户通知、对数据准确性要求不高的日志记录场景等
业务侵入性 对业务代码侵入性较小,主要在事务提交和回滚逻辑上处理,依赖数据库对 XA 事务的支持 业务侵入性高,需对业务代码大量改造,每个参与事务服务都要实现 Try、Confirm 和 Cancel 三个接口方法,并保证接口幂等性、空回滚处理、悬挂处理等复杂逻辑 业务侵入性较高,需为每个子事务设计对应的补偿事务逻辑,处理消息发送、接收、幂等性等问题,开发和维护成本较高 业务侵入性相对较低,主要在业务逻辑中增加消息发送和接收逻辑,需处理消息幂等性问题,防止重复消费导致数据不一致 业务侵入性较低,主要在业务逻辑中增加消息发送、接收以及查询接口实现逻辑,对原有业务代码结构改动较小