分布式事务:概述、协议与实现

98 阅读21分钟

引言

在单体应用时代,我们依赖数据库的本地事务(Local Transaction)来保证数据的ACID特性。然而,随着微服务架构的盛行,一个完整的业务逻辑往往需要跨多个服务、多个数据库才能完成。这就引出了一个核心难题:如何保证这一系列分布在不同网络节点上的操作,仍然能像一个整体一样,要么全部成功,要么全部失败?

这就是分布式事务(Distributed Transaction)要解决的复杂而又至关重要的问题。本文将深入浅出,为您梳理分布式事务的理论基础、核心协议与主流实现模式,为您构建起清晰的知识图谱。

一、 分布式事务理论

一个经典的生活事例:银行转账
想象一下,你要从账户A转账100元到账户B。这个操作包含两个步骤:

  1. 从账户A中扣除100元。
  2. 往账户B中增加100元。

这两个步骤必须被视为一个不可分割的整体。绝对不允许发生“账户A的钱扣了,但账户B的钱没加上”或者“账户B的钱加了,但账户A的钱没扣”这种中间状态。看看在各种场景下,是如何实现这种保证的。

1.1 事务

事务是数据库管理系统(DBMS)中的一个逻辑工作单元,它由一个或多个操作序列组成。这些操作要么全部成功执行,要么全部不执行,不允许处于中间状态。

事务的能力通过四个核心特性来保证:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。这四个特性的英文首字母缩写为 ACID。这是事务概念的基石。

特性英文含义在银行转账中的体现
原子性Atomicity事务是一个不可分割的最小工作单元。事务中的所有操作要么全部提交成功,要么全部失败回滚。扣款和加款这两个操作必须同时成功或同时失败,不能只发生一个。
一致性Consistency事务必须使数据库从一个一致性状态转变到另一个一致性状态。一致性状态是指数据满足预定义的规则(约束、触发器、级联等)。转账前后,两个账户的总金额应该保持不变(A+B的总和不变)。不会出现钱变多或变少的“灵异事件”。
隔离性Isolation多个事务并发执行时,一个事务的执行不应影响其他事务的执行。数据库系统提供了不同的隔离级别来控制并发事务之间的可见性。在你转账的过程中(事务未提交),另一个查询事务不会看到你中间状态的“A少了100而B还没加100”的数据,它看到的要么是转账前的状态,要么是转账后的状态。
持久性Durability一旦事务提交,它对数据库所做的修改就是永久性的,即使系统发生故障(如断电、崩溃),数据也不会丢失。转账成功后,系统会保证这个结果被永久保存到硬盘中,即使服务器马上断电,重启后数据依然是转账后的结果。

注意一致性(Consistency)是事务的最终目标,而原子性(A)、隔离性(I)和持久性(D)是手段,是为了保证一致性(C)而存在的。

主流的数据库例如MySQL、PostgreSQL等,都支持ACID事务,其内部会采用MVCC(多版本并发控制)技术,实现高性能、高并发的本地事务。

1.2 分布式理论

分布式系统是指多个独立节点通过网络协同工作,共同完成单个节点无法承担的计算、存储或服务任务的系统。其核心目标是解决 “单点故障”“性能瓶颈” 问题,但也因节点间的网络通信(延迟、丢包、分区)引入了 “一致性”“可用性” 等新挑战。

1.2.1 CAP定理:分布式系统的“不可能三角”与深刻理解

CAP定理由Eric Brewer提出,它指出对于一个分布式计算系统来说,一致性(Consistency)可用性(Availability)  和分区容错性(Partition tolerance)  这三个特性不可能同时满足,最多只能同时满足其中两项。

  • 一致性(Consistency):这里指的是强一致性。所有节点在同一时间看到的数据是完全相同的。换句话说,对某个数据的读写操作都是原子性的,后续的读操作都应该读到最新写入的值。
  • 可用性(Availability):每一个非故障的节点,都必须对每一个请求给出一个非错的响应(但不保证数据是最新的)。这意味着系统不能宕机,必须一直提供服务。
  • 分区容错性(Partition Tolerance):系统在遇到任何网络分区(Network Partition)故障时(即网络中的消息丢失或延迟,导致部分节点无法通信),仍然能够继续对外提供服务。
深入解读与实践意义

CAP定理并非要求“三选二”,而是揭示了分布式系统的内在矛盾。分区容错性(P)是分布式系统必须考虑的属性,因为网络问题无法避免。因此当网络分区(P)必然发生时,必须在一致性(C)可用性(A)之间做出抉择。

  • CP系统(选择一致性与分区容错):当网络发生分区时,为了保证一致性,系统必须拒绝来自无法通信节点的写入或读取请求,直到分区恢复、数据同步完成。这期间系统可能表现为不可用或服务降级。例如:ZooKeeperEtcd 等分布式协调系统,为了保证元数据的一致性,会在节点失联时拒绝服务。
  • AP系统(选择可用性与分区容错):当网络发生分区时,系统仍然可用,但可能会返回旧的数据。每个节点都用自己的本地数据提供服务,但节点之间的数据会暂时不一致。例如:Eureka 服务注册中心,节点之间即使失联,也会各自提供可能不是最新的服务实例列表,以保证系统的可用性。

1.2.2 BASE理论

为了解决CAP理论带来的实践难题,eBay的架构师提出了BASE理论,它是Basically Available(基本可用)Soft state(软状态)Eventually consistent(最终一致性)的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其核心思想是:即使无法做到强一致性,但每个系统都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

  • BA - 基本可用 (Basically Available)

    • 系统在出现不可预知故障时,允许损失部分可用性(如响应时间变长、服务降级),但核心功能必须可用。
    • 例如:双十一期间,淘宝可能会将非核心业务(如评价、推荐)降级,确保你能正常下单和支付。
  • S - 软状态 (Soft State)

    • 允许系统中的数据存在中间状态,并且该中间状态不会影响系统整体可用性。即数据副本在不同节点间的同步存在延迟。
  • E - 最终一致性 (Eventual Consistency)

    • 这是BASE的核心。系统保证在经过一段时间的同步后,所有数据副本最终能够达到一致的状态。 “最终”是一个模糊的时间概念,可能是几毫秒,也可能是几分钟。
    • 例如:你发了一条朋友圈,你自己立刻能看到(读自己写),但你的好友可能会延迟几秒钟才看到。

BASE的本质:通过接受数据的短暂不一致,来换取系统的高性能和高可用。它是对CAP定理的实际应用和补充,是现代互联网分布式架构(如电商、社交)的基石。

1.2 分布式事务

分布式事务是指跨越多个独立节点(如不同数据库、微服务)的事务操作,需满足 “要么所有节点操作全成功,要么全失败” 的 ACID 特性(重点是原子性),解决 “跨节点数据一致性” 问题。

例:银行跨行转账业务是一个典型分布式事务场景,假设A需要跨行转账给B,涉及两个银行的数据,用户A账户-100操作和用户B账户+100操作位于不同的节点,无法通过一个数据库的本地事务保证转账的ACID,只能够通过分布式事务来解决。

核心挑战:为何分布式事务比本地事务复杂?

本地事务(如单机数据库)依赖 “共享内存” 和 “锁机制” 即可保证一致性,但分布式事务因 “网络不可靠” 和 “节点独立性” 面临 3 大难题:

  • 网络不确定性:节点间通信可能延迟、丢包(如库存库收到扣减请求但未及时回复,订单库无法判断结果);

  • 节点故障风险:某节点中途宕机(如支付系统发起支付后宕机,订单库无法确认支付状态);

  • 一致性与性能冲突:保证强一致性需阻塞等待所有节点同步,会牺牲性能与可用性(如金融交易需强一致,但电商大促需高可用)。

二、 分布式事务的主流解决方案

2.1 两阶段提交(2PC,Two-Phase Commit)

2PC是分布式事务的经典协议,它引入了一个协调者(Coordinator / Transaction Manager, TM) 的角色来管理多个参与者(Participants / Resource Managers, RM)

两类角色:

  1. 协调者 (TM)

    • 通常是一个独立的进程或中间件(如Seata Server、数据库自身的TM)。
    • 职责:发起和协调整个分布式事务,最终决定事务是提交还是中止。
  2. 参与者 (RM)

    • 实际管理本地数据和执行本地事务的节点(如MySQL、Oracle实例、微服务)。
    • 职责:执行本地事务操作(但不提交),向协调者报告自身状态,并根据协调者的指令执行最终的提交或回滚。

整个过程分为两个阶段:

阶段一:提交请求/投票阶段

  1. 协调者TM向所有参与者RM发送prepare请求,并携带事务内容。
  2. 参与者RM执行事务,写入undo/redo日志,并锁定资源。
  3. 参与者RM回复协调者TM自己的投票结果:Yes(准备成功)或No(准备失败)。

阶段二:提交/回滚执行阶段

  1. Case 1:如果所有参与者都回复Yes,协调者向所有参与者发送commit命令。
  2. Case 2:如果任何参与者回复No或超时,协调者向所有参与者发送rollback命令。
  3. 参与者在收到命令后执行操作(提交或回滚),并释放锁资源。
  4. 参与者向协调者发送ack确认消息。
  5. 协调者在收到所有ack后,完成整个事务。

优点

  1. 强一致性:保证了分布式系统内数据的强一致性,是所有参与者状态的原子性约定。
  2. 概念简单:协议流程清晰,易于理解。
  3. 原生支持:许多关系型数据库(如MySQL InnoDB、Oracle)都原生支持XA协议(2PC的工业标准实现)。

缺点 (非常突出)

2PC的缺点在很大程度上限制了它在高并发场景下的应用:

  1. 同步阻塞 (Synchronous Blocking)

    • 性能瓶颈:从第一阶段的prepare到第二阶段的commit/rollback,所有参与者的事务资源(如数据库行锁)都处于锁定状态。在此期间,其他事务无法访问这些被锁定的资源。
    • 影响:这会导致系统吞吐量下降,响应时间变长,在高并发场景下这是致命的。
  2. 单点问题 (Single Point of Failure, SPOF)

    • 协调者单点故障:整个协议的核心是协调者。如果协调者在发送prepare之后、commit之前宕机,所有参与者都将被阻塞,它们的事务资源会一直处于锁定状态,无法继续提供服务,只能等待协调者恢复。
    • 参与者故障:如果参与者在发送Yes后、收到commit之前宕机,当它恢复后,必须查询协调者或其他参与者才能知道事务的最终结果,否则它的数据将处于不确定状态。
  3. 数据不一致 (Data Inconsistency) - 极端情况

    • 在第二阶段,协调者发送了部分commit命令后突然宕机,并且唯一收到commit命令的那个参与者也宕机了
    • 此时,存活的参与者因为没有收到指令(或收到指令但未执行)而无法做出决策,可能导致部分节点提交了事务,而另一部分节点未提交,从而引发数据不一致。

2.2 三阶段提交(3PC,Three-Phase Commit)

3PC是针对2PC缺点的改进,它将2PC的“提交请求阶段”一分为二,并引入了超时机制来解决阻塞问题。三个阶段分别是:CanCommit, PreCommit, DoCommit

第一阶段:CanCommit(询问阶段)

这个阶段是一个预检查阶段,目的是试探一下各个参与者是否具备成功执行事务的条件,并不锁定资源

  1. 事务询问:协调者向所有参与者发送CanCommit?请求,询问是否可以执行事务。

  2. 参与者反馈

    • 如果参与者认为自己能够成功执行事务(例如,网络正常、数据库可访问、业务检查通过),则返回 Yes 响应,并进入预备状态
    • 否则,返回 No

与2PC的区别:此阶段参与者不会执行本地事务,更不会锁定资源。这只是一个“摸底”操作。

第二阶段:PreCommit(预提交阶段)

协调者根据第一阶段的投票结果做出判断:

情况A:所有参与者都返回 Yes

协调者决定继续执行事务。

  1. 发送预提交请求:协调者向所有参与者发送PreCommit请求。
  2. 执行事务:参与者收到PreCommit后,开始执行本地事务操作(写Undo/Redo日志),并锁定资源
  3. 反馈结果:如果参与者成功执行,则返回 Ack 响应,并进入就绪状态,等待最终指令。
情况B:任何一个参与者返回 No,或协调者等待超时

协调者决定中止事务。

  1. 发送中止请求:协调者向所有参与者发送Abort请求。
  2. 参与者中止:参与者收到Abort后(或超时后),中断事务。

第三阶段:DoCommit(执行提交阶段)

这是真正提交的阶段,协调者根据第二阶段的结果做出最终决定。注意:进入此阶段后,正常情况下协调者默认所有参与者都会同意提交。

情况A:协调者正常工作
  1. 发送提交请求:协调者收到所有参与者在PreCommit阶段的Ack后,向所有参与者发送DoCommit请求。
  2. 参与者提交:参与者收到DoCommit后,正式提交本地事务,释放资源,完成后返回HaveCommitted Ack。
  3. 协调者完成:协调者收到所有Ack后,完成事务。
情况B:协调者未正常工作 / 参与者超时

这是3PC最核心的改进点!

在PreCommit阶段后,参与者如果处于就绪状态(即已发送Ack),但长时间没有收到协调者的DoCommitAbort指令(协调者宕机或网络分区),它不会像2PC那样无限期等待,而是会在超时后自动提交事务

为什么它敢这么做?
因为根据协议设计,所有参与者都在PreCommit阶段返回了Ack,这意味着所有参与者都已成功执行事务并做好了提交准备。既然大家都同意了,那么大概率协调者是想发送DoCommit,只是在发送前宕机了。因此,参与者可以安全地自动提交。

同理:如果参与者在第三阶段收到的是Abort请求,或者在其他阶段超时,它也会执行回滚。

3PC如何解决2PC的问题?

  1. 降低了同步阻塞范围
  • 3PC将资源锁定的时间点推迟到了PreCommit阶段之后,而不是2PC的整个第一阶段之后。这意味着资源被锁定的时间窗口更短了。
  • 更重要的是,在就绪状态下,如果协调者故障,参与者不会永久阻塞,而是可以通过超时机制自动继续执行,极大提高了系统的可用性。
  1. 缓解了单点故障问题
  • 引入了超时机制。无论是协调者还是参与者,在超时后都会采取默认行动(提交或中止),而不是无限期等待,从而避免了因协调者宕机而导致的整个系统“卡死”问题。

3PC的缺点与局限性

尽管3PC是一个理论上更优秀的协议,但它并未成为工业标准,原因如下:

  1. 网络假设过于理想
  • 3PC的核心——超时后自动提交——依赖于一个关键假设:网络分区和节点故障可以被可靠地检测到(即需要完美的故障检测器)。在真实的异步网络中,我们无法准确区分是“节点宕机”还是“网络延迟”,这可能导致数据不一致
  1. 数据不一致风险
  • 考虑一种极端情况:协调者发送了Abort请求,但由于网络分区,只有部分参与者收到了并执行了回滚。另一部分参与者因为超时而自动提交了事务。这就导致了系统状态的不一致:部分节点已回滚,部分节点已提交。
  1. 实现复杂,性能开销
  • 三个阶段需要更多的网络通信(3轮RPC),状态管理也更加复杂。其性能开销通常比2PC更大。
  • 由于存在数据不一致的风险,很多系统宁愿选择更简单、更“安全”的2PC,或者直接转向最终一致性方案。

三、 实现模式:从理论到实践

基于上述协议,业界衍生出了多种成熟的分布式事务实现模式。

3.1 XA模式:基于数据库的2PC

详细内容见:分布式事务XA模式:基于数据库的2PC

XA是由X/Open组织定义的分布式事务处理(DTP)规范。它准确地描述了2PC协议中事务管理器(TM)  与资源管理器(RM,如数据库)  之间的接口。

  • 工作原理:应用程序(AP)通过TM通知多个支持XA协议的数据库(RM),开始一个全局事务。TM负责协调所有RM,执行2PC流程。

  • 特点

    • 强一致性
    • 对业务侵入小,开发者像编写本地事务一样编程。
    • 性能低,存在同步阻塞和长时间锁表的问题。
    • 高度依赖数据库对XA协议的支持。

3.2 AT模式:自动化的补偿事务

AT(Automatic Transaction)模式是由Seata框架首创的一种无侵入的分布式事务解决方案。它是对2PC协议的一种改进,实现了对业务代码的“零侵入”。

  • 工作原理
    • 阶段一
      1. 开发者只需编写普通的业务SQL(如 UPDATE stock SET count = count - 1 WHERE id = 1)。
      2. Seata的数据源代理会拦截SQL,在执行前查询数据的前置镜像(Before Image),在执行后查询数据的后置镜像(After Image)。
      3. 将这些镜像数据作为回滚日志(undo_log),与业务SQL在同一个本地事务中提交到数据库。
      4. 此时,本地事务已提交,资源锁被释放,极大地提升了吞吐量。Seata通过全局锁来保证隔离性。
    • 阶段二
      • 提交:非常高效,协调者只需异步删除各个节点的undo_log即可。
      • 回滚:协调者根据阶段一记录的undo_log中的前置镜像,生成反向的回滚SQL并执行,将数据恢复到事务前的状态,然后删除undo_log。

特点

  • 最终一致性
  • 对业务代码几乎无侵入,开发体验类似本地事务,是其主要优势。
  • 性能较好,因为一阶段就提交了本地事务,释放了数据库锁。
  • 补偿操作自动化,无需开发者手动编写。
  • 可能存在脏回滚(如全局锁被意外绕过)的风险,需要默认遵循“重试成功优先”的原则。

3.3 TCC模式:业务层面的2PC

TCC(Try-Confirm-Cancel)是服务层面的2PC,它将2PC的“准备”和“提交”阶段映射为需要开发者手动实现的三个接口。

  • 工作原理
    • Try:尝试执行业务。完成所有业务检查,并预留必须的资源(如:冻结库存、预扣优惠券)。
    • Confirm:确认执行业务。真正使用Try阶段预留的资源,要求操作幂等
    • Cancel:取消执行业务。释放Try阶段预留的资源,要求操作幂等
  • 工作流程:主业务服务依次调用所有依赖服务的Try接口。若全部成功,则协调者调用所有Confirm接口完成确认;若任何Try失败,则协调者调用所有已成功Try的Cancel接口进行补偿。

特点

  • 最终一致性
  • 高性能:资源在Try阶段即被预留而非锁定,Confirm/Cancel执行很快。
  • 高业务侵入性:需要为每个操作设计并实现三个接口,开发复杂度高。
  • 需解决空回滚(未执行Try却调了Cancel)、幂等悬挂(Try延迟到达,在Cancel之后才执行)等问题。

3.4 Saga模式:长事务解决方案

Saga模式将一个长事务拆分为多个连续的本地短事务T1, T2, T3, ...,每个事务Ti都有一个对应的补偿操作Ci(Compensating Transaction)。

  • 执行顺序
    • 成功T1 -> T2 -> T3 -> ... -> Tn
    • 失败:如果在执行T3时失败,则执行补偿流程:C3 -> C2 -> C1
  • 特点
    • 最终一致性
    • 性能好,避免了长事务锁资源。
    • 补偿逻辑的设计有挑战,且不保证隔离性(可能发生脏写)。

3.5 基于消息的最终一致性

这是互联网公司最常用的模式之一,其核心是通过消息队列的可靠性来异步地保证数据最终一致。

  • 经典本地消息表法
    1. 业务执行与消息落地在同一个本地事务中完成(如在订单库中同时创建订单和一条消息记录)。
    2. 有一个后台任务轮询消息表,将“待发送”的消息投递到消息队列(MQ)。
    3. 下游服务消费消息,执行本地操作。执行成功后,确认消息(ACK)。
    4. 如果下游服务执行失败,消息队列会重投消息,直至成功(要求消费操作幂等)。

特点

  • 最终一致性
  • 吞吐量高,业务侵入性相对较低。
  • 实现了服务的彻底解耦。

四、 总结与对比

没有一种模式是完美的银弹,每种模式都是在一致性、性能、复杂度之间做出的权衡。下表总结了它们的核心差异:

模式一致性性能业务侵入性复杂度典型适用场景
XA强一致数据库支持好,短事务,强一致场景(如传统金融)
AT最终一致中高极低Java技术栈,希望无侵入,高并发互联网应用
TCC最终一致对性能和要求高,资金、交易等核心系统
Saga最终一致长流程业务,如订单、旅行预订
消息事务最终一致非常高高并发互联网业务,如扣库存、发通知

结语

分布式事务是一个复杂而广阔的领域,本文介绍的只是其核心的协议与模式。在实践中,选择哪种方案往往取决于你的业务场景、技术架构和对一致性级别的容忍度。