分布式事务解决方案介绍

前言

专业术语

  • TX 协议

    • 应用或者应用服务器与事务管理器的接口
  • XA 协议

    • 全局事务管理器与资源管理器的接口。
    • XA 是由 X/Open 组织提出的分布式事务规范,该规范主要定义了全局事务管理器和局部资源管理器之间的接口,主流的数据库产品都实现了 XA 接口。XA 接口是一个双向的系统接口,在事务管理器以及多个资源管理器之间作为通信桥梁。
    • 之所以需要 XA 是因为在分布式系统中从理论上讲两台机器是无法达到一致性状态的,因此引入一个单点进行协调。由全局事务管理器管理和协调的事务可以跨越多个资源和进程。全局事务管理器一般使用 XA 二阶段协议与数据库进行交互。

分布式理论

CAP 理论

CAP 定理是由加州大学伯克利分校 Eric Brewer 教授提出来的,他指出 WEB 服务无法同时满足一下三个属性:

  • 一致性 (Consistency):客户端知道一系列的操作都会同时发生 (生效)
  • 可用性 (Availability):每个操作都必须以可预期的响应结束
  • 分区容错性 (Partition tolerance):即使出现单个组件无法可用,操作依然可以完成

具体地讲在分布式系统中,任何数据库设计或者 Web 应用至多只能同时支持上面的两个属性。显然,任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择

BASE 理论

在分布式系统中,往往追求的是可用性,它的重要程序比一致性要高,那么如何实现高可用性呢?前人已经给我们提出来了另外一个理论,就是 BASE 理论,它是用来对 CAP 定理进行进一步扩充的。BASE 理论指的是:

  • Basically Available(基本可用)
  • Soft state(软状态)
  • Eventually consistent(最终一致性)

BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)

酸碱平衡

ACID 能够保证事务的强一致性,即数据是实时一致的,这在本地事务中是没有问题的。在分布式事务中,强一致性会极大影响分布式系统的性能,因此分布式系统中遵循 BASE 理论即可。但分布式系统的不同业务场景对一致性的要求也不同。如交易场景下,就要求强一致性,此时就需要遵循 ACID 理论,而在注册成功后发送短信验证码等场景下,并不需要实时一致,因此遵循 BASE 理论即可。因此要根据具体业务场景,在 ACID 和 BASE 之间寻求平衡。

分布式事务基础

什么是事务

事务指的就是一个操作单元,在这个操作单元中的所有操作最终要保持一致的行为,要么所有操都成功,要么所有的操作都被撤销。简单地说,事务提供一种” 要么什么都不做,要么做全套 “机制。

什么是本地事务

本地事务其实可以认为是数据库提供的事务机制。说到数据库事务就不得不说,数据库事务中的四大特性(ACID):

  • A:原子性(Atomicity),一个事务中的所有操作,要么全部完成,要么全部不完成
  • C:一致性(Consistency),在一个事务执行之前和执行之后数据库都必须处于一致性状态
  • I:隔离性(Isolation),在并发环境中,当不同的事务同时操作相同的数据时,事务之间互不影响
  • D:持久性(Durability),指的是只要事务成功结束,它对数据库所做的更新就必须永久的保存下来

数据库事务在实现时会将一次事务涉及的所有操作全部纳入到一个不可分割的执行单元,该执行单元中的所有操作要么都成功,要么都失败,只要其中任一操作执行失败,都将导致整个事务的回滚。

什么是分布式事务

分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简而言之,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。一句话概括就是,一次业务操作需要跨多个数据源或者需要跨多个系统进行远程调用,就会产生分布式事务问题。

分布式事务的适用场景

  • 单体系统访问多个数据库:一个服务需要调用多个数据库实例完成数据的增删改操作

distributed-transaction-scene-1

  • 多个微服务访问同一个数据库:多个服务需要调用一个数据库实例完成数据的增删改操作

distributed-transaction-scene-2

  • 多个微服务访问多个数据库:多个服务需要调用多个数据库实例完成数据的增删改操作

distributed-transaction-scene-3

分布式事务解决方案

全局事务(DTP)

全局事务是基于 DTP 模型实现的,DTP 是由 X/Open 组织提出的一种分布式事务模型 — X/Open Distributed Transaction Processing Reference Model。它规定了要实现分布式事务,需要三种角色:

  • AP:Application 应用系统(微服务)
  • RM:Resource Manager 资源管理器(数据库)
  • TM:Transaction Manager 事务管理器(全局事务管理)

整个事务分成两个阶段:

  • 阶段一:表决阶段(投票阶段),所有参与者都将本事务执行预提交,并将能否成功的信息反馈发给协调者
  • 阶段二:执行阶段(提交阶段),协调者根据所有参与者的反馈,通知所有参与者,步调一致地执行提交或者回滚

distributed-transaction-dtp-1


distributed-transaction-dtp-2

优点:

  • 提高了数据一致性的概率,实现成本较低

缺点:

  • 单点问题:事务协调者宕机
  • 同步阻塞:延迟了提交时间,加长了资源阻塞时间
  • 数据不一致:在提交的第二阶段,依然存在 Commit 结果未知的情况,有可能导致数据不一致(即两阶段提交无法解决的问题)

两阶段提交(2PC)

分布式系统的一个难点是如何保证架构下多个节点在进行事务性操作的时候保持一致性。为实现这个目的,两阶段提交算法的成立基于以下假设:

  • 该分布式系统中,存在一个节点作为协调者(Coordinator),其他节点作为参与者(Cohorts),且节点之间可以进行网络通信
  • 所有节点都采用预写式日志,且日志被写入后即被保持在可靠的存储设备上,即使节点损坏不会导致日志数据的消失
  • 所有节点不会永久性损坏,即使损坏后仍然可以恢复

第一阶段(投票阶段):

  • 协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应
  • 参与者节点执行询问发起为止的所有事务操作,并将 Undo 信息和 Redo 信息写入日志(注意:如果成功,这里其实每个参与者已经执行了事务操作)
  • 各参与者节点响应协调者节点发起的询问,如果参与者节点的事务操作实际执行成功,则它返回一个” 同意” 消息;如果参与者节点的事务操作实际执行失败,则它返回一个” 中止” 消息

第二阶段(提交阶段):

当协调者节点从所有参与者节点获得的相应消息都为” 同意” 时:

  • 协调者节点向所有参与者节点发出” 正式提交(Commit)” 的请求
  • 参与者节点正式完成操作,并释放在整个事务期间内占用的资源
  • 参与者节点向协调者节点发送” 完成” 消息
  • 协调者节点受到所有参与者节点反馈的” 完成” 消息后,完成事务

中断事务的发生:

如果任一参与者节点在第一阶段返回的响应消息为” 中止”,或者协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:

  • 协调者节点向所有参与者节点发出” 回滚操作(Rollback)” 的请求
  • 参与者节点利用之前写入的 Undo 信息执行回滚,并释放在整个事务期间内占用的资源
  • 参与者节点向协调者节点发送” 回滚完成” 消息
  • 协调者节点受到所有参与者节点反馈的” 回滚完成” 消息后,取消事务
  • 特别注意:不管最后结果如何,第二阶段都会结束当前事务

两阶段提交的缺点:

  • 资源阻塞:执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态
  • 参与者发生故障:协调者需要给每个参与者额外指定超时机制,超时后整个事务失败(没有多少容错机制)
  • 协调者发生故障:参与者会一直阻塞下去,需要额外的备机进行容错(这个可以依赖 Paxos 协议实现 HA)
  • 两阶段提交无法解决的问题:协调者在发出 Commit 消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否已经被提交成功,这就有可能导致数据不一致

两阶段提交(2PC)方案与 XA 方案的关系

  • (1) 两阶段提交(2PC)方案和 XA 方案在实际意义上指的是同一个分布式事务解决方案。两阶段提交(2PC)是概念和算法名称,XA 是 2PC 的一个具体实现标准,由 X/Open 定义,用于协调多个数据库或资源管理器的事务。所以在技术文档中,会看到 2PC 和 XA,大多数情况下可以认为指的是同一类分布式事务方案,只是 XA 更强调标准化实现。
  • (2) 在现代微服务架构中,每个服务通常只能操作自己独立的数据库,不允许跨服务直接访问其他服务的数据库,否则会破坏服务边界,导致系统治理困难、数据混乱且难以维护。如果需要获取其他服务的数据,必须通过调用其 API 接口,而不是跨数据库访问。由于 XA 的典型使用场景是单体应用或者单个服务需要同时操作多个数据库,这也是 XA 方案在微服务场景中几乎不被采用的主要原因。不过,只要数据库支持 XA(如 MySQL、Oracle),结合 Spring + JTA 就可以实现 2PC/XA 分布式事务解决方案。

三阶段提交(3PC)

与两阶段提交不同的是,三阶段提交有两个改动点:

  • 引入超时机制。同时在协调者和参与者中都引入超时机制
  • 在第一阶段和第二阶段中插入一个准备阶段,保证了在最后提交阶段之前各参与节点的状态是一致的

也就是说,除了引入超时机制之外,3PC 把 2PC 的投票阶段再次一分为二,这样三阶段提交就有 CanCommit、PreCommit、DoCommit 三个阶段。

CanCommit 阶段:

3PC 的 CanCommit 阶段其实和 2PC 的投票阶段很像,协调者向参与者发送 Commit 请求,参与者如果可以提交就返回 Yes 响应,否则返回 No 响应:

  • 事务询问:协调者向参与者发送 CanCommit 请求,询问是否可以执行事务提交操作,然后开始等待参与者的响应
  • 响应反馈:参与者接到 CanCommit 请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回 Yes 响应,并进入预备状态,否则返回 No 响应

PreCommit 阶段:

协调者根据参与者的响应情况来决定是否可以执行事务的 PreCommit 操作。根据响应情况,有以下两种可能:

  • 假如协调者从所有的参与者获得的反馈都是 Yes 响应,那么就会执行事务的预执行

    • 发送预提交请求:协调者向参与者发送 PreCommit 请求后,并进入 Prepared 阶段
    • 事务预提交:参与者接收到 PreCommit 请求后,会执行事务操作,并将 Undo 和 Redo 信息记录到事务日志中
    • 响应反馈:如果参与者成功的执行了事务操作,则返回 ACK 响应,同时开始等待最终指令
  • 假如有任何一个参与者向协调者发送了 No 响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断

    • 发送中断请求:协调者向所有参与者发送 Abort 请求
    • 中断事务:参与者收到来自协调者的 Abort 请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断

Undo 和 Redo 日志

  • (1) Undo 和 Redo 日志是用于事务回滚和数据恢复的重要机制。
  • (2) Undo 日志:记录在事务操作前的数据状态,以便在需要回滚事务时,可以撤销已执行的操作,恢复到事务开始前的状态。举例来说,如果事务在某个步骤中修改了数据库表中的数据,Undo 日志就会在执行修改之前记录原始数据值。这样如果事务最终没有提交成功,系统可以通过这些日志信息将数据还原到修改前的状态。
  • (3) Redo 日志:记录事务操作后的数据状态,以便在事务失败需要恢复时重新应用这些操作。当事务最终准备提交时,Redo 日志会确保所有的更改都能被重复应用,以达到一致的提交效果。举例来说,如果事务已经提交,但系统在写入数据到持久性存储时发生了崩溃,那么通过 Redo 日志,就可以在系统恢复后重新执行已提交的操作,确保数据一致性。值得一提的是,Redo 是单词 do again 的含义,表示 重做 或者 再次执行 的意思。

DoCommit 阶段

该阶段进行真正的事务提交,也可以分为以下两种情况:

  • 执行提交:

    • 发送提交请求:协调接收到参与者发送的 ACK 响应,那么它将从预提交状态进入到提交状态,并向所有参与者发送 DoCommit 请求
    • 事务提交:参与者接收到 DoCommit 请求之后,执行正式的事务提交,并在完成事务提交之后释放所有事务资源
    • 响应反馈:事务提交完之后,向协调者发送 ACK 响应
    • 完成事务:协调者接收到所有参与者的 ACK 响应之后,完成事务
  • 中断事务

    • 发送中断请求:协调者向所有参与者发送 Abort 请求
    • 事务回滚:参与者接收到 Abort 请求之后,利用其在阶段二记录的 Undo 信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源
    • 反馈结果:参与者完成事务回滚之后,向协调者发送 ACK 消息
    • 中断事务:协调者接收到参与者反馈的 ACK 消息之后,执行事务的中断

这里协调者如果没有接收到参与者发送的 ACK 响应(可能是接受者发送的不是 ACK 响应,也可能响应超时),那么就会执行中断事务。

TCC(两阶段型、补偿型)

TCC(Try Confirm Cancel)属于补偿型分布式事务(又被称为补偿事务),类似 2PC 的柔性分布式解决方案,属于 2PC 的改良版。TCC 实现分布式事务一共有三个步骤:

  • Try(尝试待执行的业务):这个过程并未执行业务,只是完成所有业务的一致性检查,并预留好执行所需的全部资源

  • Confirm(确认执行业务):确认执行业务操作,不做任何业务检查,只使用 Try 阶段预留的业务资源。通常情况下,采用 TCC 则认为 Confirm 阶段是不会出错的。即只要 Try 成功,Confirm 就一定成功。若 Confirm 阶段真的出错了,需引入重试机制或人工处理

  • Cancel(取消待执行的业务):取消 Try 阶段预留的业务资源。通常情况下,采用 TCC 则认为 Cancel 阶段也是一定成功的。若 Cancel 阶段真的出错了,需引入重试机制或人工处理

tcc-transaction

TCC 两阶段提交与 XA 两阶段提交的区别:

  • XA 是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,会一直持有资源的锁
  • TCC 是业务层面的分布式事务,最终一致性,不会一直持有资源的锁

TCC 事务的优缺点:

  • 优点:把数据库层的两阶段提交上提到了应用层来实现,规避了数据库层的 2PC 性能低下的问题
  • 缺点:TCC 的 Try、Confirm 和 Cancel 操作功能需业务提供,开发成本高

最大努力通知(定期校对)

最大努力通知也被称为定期校对,其实是对第二种解决方案的进一步优化。它引入了本地消息表来记录错误消息,然后加入失败消息的定期校对功能,来进一步保证消息会被下游系统消费。

best-effort-notice

第一步:消息由系统 A 投递到消息中间件

  • 1)处理业务的同一事务中,向本地消息表中写入一条记录
  • 2)准备专门的消息发送者不断地发送本地消息表中的消息到消息中间件,如果发送失败则重试

第二步:消息由中间件投递到系统 B

  • 1)消息中间件收到消息后负责将该消息同步投递给相应的下游系统,并触发下游系统的任务执行
  • 2)当下游系统处理成功后,向消息中间件反馈确认应答,消息中间件便可以将该条消息删除,从而该事务完成
  • 3)对于投递失败的消息,利用重试机制进行重试,对于重试失败的,写入错误消息表
  • 4)消息中间件需要提供失败消息的查询接口,下游系统会定期查询失败消息,并将其消费

优缺点:

  • 优点: 一种非常经典的实现,实现了最终一致性
  • 缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理,并且在业界并没有成熟的方案来解决

可靠消息最终一致性

基于可靠消息实现最终一致性的方案是通过消息中间件(如 Kafka、RocketMQ)保证上、下游应用数据操作的最终一致性。假设有 A 和 B 两个系统,分别可以处理任务 A 和任务 B。此时存在一个业务流程,需要将任务 A 和任务 B 在同一个事务中处理,此时就可以使用消息中间件来实现这种分布式事务。

reliable-message-service

第一步:消息由系统 A 投递到消息中间件

  • 1)在系统 A 处理任务 A 前,首先向消息中间件发送一条消息
  • 2)消息中间件收到后将该条消息持久化,但并不投递。持久化成功后,向 A 回复一个确认应答
  • 3)系统 A 收到确认应答后,则可以开始处理任务 A
  • 4)任务 A 处理完成后,向消息中间件发送 Commit 或者 Rollback 请求。该请求发送完成后,对系统 A 而言,该事务的处理过程就结束了
  • 5)如果消息中间件收到 Commit,则向 B 系统投递消息;如果收到 Rollback,则直接丢弃消息。但是如果消息中间件收不到 Commit 和 Rollback 指令,那么就要依靠” 超时询问机制”

超时询问机制

系统 A 除了实现正常的业务流程外,还需提供一个事务询问的接口,供消息中间件调用。当消息中间件收到发布消息便开始计时,如果到了超时没收到确认指令,就会主动调用系统 A 提供的事务询问接口询问该系统目前的状态。该接口会返回三种结果,中间件根据三种结果做出不同反应:

  • 提交:将该消息投递给系统 B
  • 回滚:直接将消息丢弃
  • 处理中:继续等待

第二步:消息由中间件投递到系统 B

消息中间件向下游系统投递完消息后便进入阻塞等待状态,下游系统便立即进行任务的处理,任务处理完成后便向消息中间件返回应答。

  • 如果消息中间件收到确认应答后便认为该事务处理完毕
  • 如果消息中间件在等待确认应答超时之后就会重新投递,直到下游消费者返回消费成功响应为止。一般消息中间件可以设置消息重试的次数和时间间隔,如果最终还是不能成功投递,则需要手工干预。这里之所以使用人工干预,而不是使用让 A 系统回滚,主要是考虑到整个系统设计的复杂度问题

基于可靠消息服务的分布式事务,前半部分使用异步,注重性能;后半部分使用同步,注重开发成本。

Seata 支持的四种事务模式

阿里巴巴的 Seata 支持四种事务模式:AT(自动提交)、XA(两阶段提交)、TCC(Try-Confirm-Cancel)、SAGA(补偿事务)。

AT(Automatic Transaction - 自动提交)

  • 概述:
    • 基于数据库的 Undo/Redo 日志机制实现分布式事务,应用层无需修改业务逻辑。
    • 在全局事务中,Seata 自动拦截 SQL,通过 Undo/Redo 回滚事务。
  • 优点:
    • 无需改造业务代码,使用简单;
    • 性能相对较高,适合大部分分布式事务场景;
    • 自动处理事务回滚。
  • 缺点:
    • 依赖数据库,跨库或复杂 SQL 场景可能受限;
    • 对 SQL 语句支持有限,不支持存储过程等复杂逻辑。
  • 适用场景:
    • 微服务架构下的常规分布式事务;
    • 以数据库操作为主,SQL 简单,要求性能和一致性兼顾的业务。

XA(Two-Phase Commit - 两阶段提交)

  • 概述:
    • 通过事务管理器协调多个数据库,先询问各数据库是否准备好提交(Prepare 阶段),全部准备好则提交,否则回滚。
    • 严格保证全局一致性。
  • 优点:
    • 强一致性保证;
    • 模型清晰,数据库支持良好。
  • 缺点:
    • 性能低,锁资源时间长,容易阻塞;
    • 扩展性差,高并发场景不适用。
  • 适用场景:
    • 单体应用或低并发分布式事务;
    • 强一致性要求高、性能要求低的后台管理系统或内部工具。

TCC(Try-Confirm-Cancel)

  • 概述:
    • 通过三个阶段实现事务一致性:Try(预留资源)→ Confirm(提交操作)→ Cancel(失败回滚);
    • 业务层需实现补偿逻辑。
  • 优点:
    • 可严格保证强一致性;
    • 适合资金类或核心业务场景。
  • 缺点:
    • 业务代码复杂,需要手动编写补偿逻辑;
    • 开发和维护成本高。
  • 适用场景:
    • 核心业务、资金交易、支付、订单扣减库存等对一致性要求极高的场景;
    • 各步骤执行时间短的场景。

SAGA(补偿事务)

  • 概述:
    • 将全局事务拆分成一系列局部事务,每个局部事务成功则继续,失败则执行补偿事务回滚前面已完成的事务。
    • 典型实现是通过异步消息或事件驱动完成。
  • 优点:
    • 性能高,不锁资源;
    • 易扩展,适合高并发微服务系统。
  • 缺点:
    • 一致性为最终一致性,不保证实时强一致性;
    • 需要实现补偿逻辑,增加开发复杂度。
  • 适用场景:
    • 高并发、跨多个服务的微服务场景;
    • 可容忍短暂不一致,要求最终一致性的业务,如订单、物流、库存等。

分布式事务问题

避免滥用分布式事务

在微服务架构下,系统通常由几十甚至上百个服务组成。理论上,如果每个跨服务调用都要保证强一致性,就需要在系统中大量使用分布式事务。但实际经验表明,这种做法的成本极高。

首先,任何一种分布式事务方案(比如 XA、TCC、可靠消息最终一致性等),都会显著增加系统复杂度:

  • 代码量与逻辑复杂度至少增加数倍甚至十倍;
  • 开发周期明显延长;
  • 系统性能与吞吐量显著下降;
  • 架构更加脆弱,反而可能引入新的 Bug。

在大型系统中,真正需要分布式事务的场景往往非常少。以团队实践为例,一个拥有几百个服务的大型系统中,分布式事务的使用场景可能只有寥寥几个。绝大多数跨服务调用,如果出现异常,通常只需做到:

  • 完整的异常日志记录;
  • 实时告警与监控(邮件、短信等);
  • 事后快速排查与修复。

从实际运行情况看,大多数问题是功能性或体验性缺陷,真正涉及数据不一致的 Bug,每月也就几例。对于这类问题,通常通过人工修复(编写临时脚本补偿数据、修复字段值等)即可,成本远低于大规模使用分布式事务。因此,在分布式事务的使用上,需要权衡:

  • 必须保证数据绝对正确的关键业务(约占 0.01% ~ 1% 的场景,如资金、交易、订单),才应该使用分布式事务方案来确保一致性。
  • 对于非关键业务(如会员积分、优惠券、商品信息等),通常只需依赖监控与人工修复机制,而无需强制引入分布式事务。

总结来说:分布式事务是保证强一致性的利器,但它的代价高昂,应该谨慎使用,只在最核心的业务场景落地。

参考资料