分布式事务
分布式事务是指在分布式系统中,多个服务节点共同参与完成的一个事务。在大规模、高并发服务化系统中,一个功能(事务)往往会被拆分成多个具有单一功能的子功能,一个流程会有多个系统的多个单一功能的服务组合实现。分布式事务的主要目标就是保证这些跨服务系统的操作要么全部成功,要么全部失败回滚,从而维护数据的一致性和完整性。
分布式一致性理论
CAP原理
CAP原理:在一个分布式系统中,一致性(Consistency)、可用性(Availability)和分区容忍性(Partition Tolerance)这三个基本需求中,最多只能同时满足其中的两个。

- 一致性(Consistency) :在分布式系统中的一致性指的是所有节点在同一时间点看到的数据是一样的。换句话说,一旦某个客户端完成了对某数据项的更新操作,后续的所有访问请求都应该能够读取到这个最新值。
- 可用性(Availability) :可用性是指系统在面对任何时刻的请求时都能正常处理并返回结果的能力。即使部分节点出现故障,系统仍然能够响应客户端的请求,并给出成功的响应(非错误或超时)。
- 分区容忍性(Partition Tolerance) :分区容忍性意味着即使在网络分区的情况下(即网络中的某些部分节点无法与其他部分节点进行通信),系统仍然能够继续运作。网络分区可能由于网络中断、延迟等原因造成。
为什么不能同时满足?:
在一个理想的环境中,我们当然希望分布式系统能同时具备这三个特性。然而,由于网络分区是不可避免的现实情况,实际上我们需要在这三个属性之间做出权衡:
-
如果选择一致性和分区容忍性(CP系统),那么在发生网络分区时,为了保证数据的一致性,系统可能会拒绝某些节点上的写入操作,从而影响系统的可用性。
-
如果选择可用性和分区容忍性(AP系统),那么在网络分区期间,系统将继续接受所有节点上的读写操作,但这可能导致数据不一致的情况,直到网络恢复正常并且系统有机会同步数据为止。
-
最后,如果试图构建一个既保持高可用又严格一致性的系统(CA系统),则需要假设不存在网络分区,这在广域网环境下通常是不切实际的。
理解CAP原理有助于我们根据应用场景的需求来设计和优化分布式系统,以达到预期的目标。例如,对于一些金融交易系统来说,可能更倾向于选择CP系统;而对于社交网络等应用,则可能更偏好AP系统。
强一致性:ACID
ACID 原则 要求系统严格保证分布式事务的原子性(Atomicity) 、一致性(Consistency) 、隔离性(Isolation) 、持久性(Durability)。
分布式事务的特点:
-
原子性(Atomicity) :事务中的所有操作要么全部完成,要么全部不执行。
-
一致性(Consistency) :事务执行前后,所有相关数据都必须保持一致的状态。
-
隔离性(Isolation) :确保并发事务相互隔离,不会互相干扰。
-
持久性(Durability):一旦事务提交成功,即使系统出现故障,其结果也不应丢失。
最终一致性:BASE
随着互联网的快速发展,特别是社交媒体、电子商务等领域中用户数量和数据量的爆炸式增长,传统的 ACID 事务模型由于其严格的同步机制导致性能瓶颈。因此,需要一种新的方法来解决大规模分布式系统下的可用性和扩展性问题,BASE 原则便是在这样的背景下提出的。
BASE 原则:
-
基本可用(Basically Available) :系统始终能够提供部分功能的服务,即使在发生故障的情况下也能保证核心服务的基本可用性。虽然可能不是100%的功能都能正常运行,但至少可以提供非错误的响应。
-
软状态(Soft state) :与强调强一致性的系统不同,BASE允许系统的状态可以在一段时间内有所变化,甚至是暂时的不一致。系统可以通过异步的方式达到最终的一致性。
-
最终一致性(Eventual consistency):尽管在某个时间点上系统可能存在数据不一致的情况,但只要没有新的更新发生,经过一定时间后,所有节点上的数据最终都将自动同步并达到一致状态。
BASE 原则满足 CAP 原理,它通过牺牲强一致性 来获得可用性。
酸碱平衡原理
在不同的场景下,可以分别利用 ACID(酸) 和 BASE(碱) 来解决分布式服务化系统的一致性问题:
ACID 适用于那些对数据一致性和准确性有严格要求的应用场景,如金融交易系统;而 BASE 则更适合于需要处理大量并发请求且能接受短暂数据不一致情况的大规模分布式系统,比如社交网络平台。
强一致性方案
两阶段提交(2PC)
分布式事务要求在多个机器之间维持数据的一致性。然而,当一台机器执行其本地事务时,并不了解其他机器上的本地事务执行情况。为此,我们需要一个额外的机制来统筹管理各个分布式节点上的事务执行过程,使每个节点都能知晓其它节点的任务执行状态。通过这种机制,可以利用通知和表决的方式来确定是否应提交(Commit)或回滚(Rollback)事务。这个负责统筹和决策的独立组件被称为协调者(Coordinator)。
两阶段提交(Two-Phase Commit,2PC)通过将事务的提交过程分为两个阶段来实现:
-
准备阶段: 协调者(Coordinator)向所有参与者(Participants)发送准备请求,询问是否可以提交事务。每个参与者执行本地事务操作,但不真正提交,而是等待协调者的最终决定。如果一切正常,参与者会回复"准备成功";如果有任何问题,则回复"中止"。
-
提交阶段: 如果协调者收到所有参与者的"准备成功"响应,则发出提交命令,所有参与者正式提交事务。如果任一参与者返回"中止",或者协调者在等待响应时超时,则发出回滚命令,所有参与者撤销之前的操作。

优点:
-
强一致性 :2PC保证了所有参与者要么全部成功提交事务,要么全部取消,从而维护了数据的一致性。
-
简单直接 :算法逻辑相对直观,易于理解和实现。
-
广泛支持:由于其概念上的简单性和对强一致性的支持,许多数据库管理系统都提供了对2PC的支持(几乎所有商业 OLTP 数据库都支持 XA 协议)。
缺点:
-
重量级 :两阶段提交协议通过在准备阶段锁定资源以保证强一致性,实现起来复杂、成本高、不够灵活。
-
阻塞 :对于任何一次指令都必须收到明确的响应,才会继续进行下一步,否则处于阻塞状态,占用的资源被一直锁定,不会释放。
-
单点故障 :如果协调者宕机,参与者没有协调者指挥,会一直阻塞。尽管可以通过选举新的协调者替代原有协调者,但如果协调者在发送一个提交指令后宕机,而提交指令仅仅被一个参与者接收,并且参与者接收后也宕机,则新上任的协调者无法处理这种情况。
-
脑裂:协调者发送提交指令,有的参与者接收到并执行了事务,有的参与者没有接收到事务就没有执行事务,多个参与者之间是不一致的。
三阶段提交(3PC)
三阶段提交(3PC)是两阶段提交(2PC)的改进版本。
3PC 与 2PC 对比:
-
增加一个询问阶段:可以确保尽可能早地发现无法执行操作而需要中止的行为
-
通过超时机制解决了二阶段提交的阻塞问题:准备阶段以后,协调者和参与者执行的任务中都增加了超时,一旦超时,则协调者和参与者都会继续提交事务,默认为成功。这是根据概率统计超时后默认为成功的正确性最大。
-
其他两阶段提交存在的问题,三阶段提交仍存在(单点故障、脑裂等)。
3PC 的三个阶段:
-
询问阶段(CanCommit) :协调者向所有参与者发送 CanCommit 请求,询问是否可以提交事务。参与者根据自身状态返回"可以"或"不可以"的响应。
-
准备阶段(PreCommit) :如果所有参与者都回应"可以",协调者发送 PreCommit 请求。参与者执行事务操作并写入事务日志(不提交),然后返回"准备就绪"状态。
-
提交阶段(DoCommit):协调者根据响应决定是否发送 DoCommit(提交)或 Rollback(回滚)命令。参与者根据命令执行提交或回滚操作,并释放资源。
如果在某一阶段出现超时或协调者失联,参与者可以根据预设的超时策略进行判断,避免长时间阻塞。

TCC 事务补偿
TCC(Try-Confirm-Cancel)通过将事务的执行分为三个阶段来实现:尝试(Try)、确认(Confirm)和取消(Cancel),从而提供了一种灵活的方式来处理分布式系统中的事务。正常流程会先执行try,如果执行没有问题,则再执行Confirm;如果执行过程中出了问题,则执行操作的逆操作Cancel。
TCC的工作流程:
-
尝试(Try)阶段 :各个参与的服务会检查并预留必要的资源,但不会真正执行业务逻辑(比如在金融交易场景中,try 阶段先冻结账户中的资金而不是实际扣款)。尝试阶段的主要目的是确保后续的提交或回滚操作可以成功执行。如果所有服务都能成功地预留资源,则进入下一步;否则,直接进入取消阶段。
-
确认(Confirm)阶段 :当所有服务都成功完成尝试阶段后,协调者会发送确认指令给每个参与者,要求它们正式提交之前预留的资源。此时参与者执行实际的业务逻辑,比如从一个账户转账到另一个账户。确认操作应该是幂等的,即多次执行相同的操作不会产生不同的结果。
-
取消(Cancel)阶段:如果在尝试阶段有任何服务失败,或者在尝试之后决定不继续进行事务,则需要回滚之前的所有操作,这通常涉及到释放之前预留的资源。取消也应该是幂等的,以防止重复调用导致的数据不一致。

优点:
-
灵活性高 :允许开发者根据具体业务需求自定义尝试、确认和取消的具体实现。
-
支持部分提交 :与传统的两阶段提交相比,TCC模式可以在某些情况下实现部分提交,提高了系统的灵活性。
-
减少锁定时间:因为只有在确认阶段才会真正提交数据,所以在尝试阶段不需要长时间锁定资源。
缺点:
-
复杂度增加 :需要为每个业务操作设计对应的尝试、确认和取消逻辑,增加了开发和维护成本。
-
幂等性的挑战:由于网络故障等原因可能导致重试,因此必须保证尝试、确认和取消操作都是幂等的。
TCC 模式非常适合那些对一致性有较高要求且能够接受一定复杂度的应用场景,如电商系统的订单处理、库存管理和支付结算等。
最终一致性方案
分布式系统事务的强一致性可以使用两阶段提交协议或三阶段提交协议实现,但复杂度大、成本高、性能低。相比来看,TCC协议更加简单和容易实现,但是TCC协议由于每个事务都需要执行Try,再执行Confirm,略显臃肿。
现实系统中,一般采用最终一致性实现数据一致性,而不需要实现专业的、复杂的一致性协议。
采用最终一致性方案实现分布式事务的场景中,如果出现异常,一般会采用正向补偿的方式,即不会像传统事务方式出现异常时依次进行回滚,而是会通过不断重试或人工干预的方式让事务朝前执行,避免事务回滚。
本地消息表方案
本地消息表这个方案最初是 eBay 提出的,此方案的核心是通过本地事务 保证数据业务操作和消息的一致性,然后通过定时任务 将消息发送至消息中间件,待确认消息发送给消费方成功再将消息删除。
工作流程:
-
事务开始 :当一个业务操作需要跨越多个服务时,首先在发起方的服务中启动一个本地事务。
-
记录消息到本地消息表 :在本地事务执行过程中,除了更新业务数据外,还需要将本次事务的相关信息(如:事务ID、状态等)作为一条消息插入到本地消息表中。这条消息标志着该事务的一部分,待确认是否成功提交。
-
提交本地事务 :如果本地事务执行成功,则提交事务。此时,本地消息表中的消息也一同被提交,确保了业务数据和消息的一致性。
-
发送消息至消息队列 :一旦本地事务提交成功,接下来就是从本地消息表中取出对应的消息,并将其发送到消息中间件(如 RabbitMQ 、Kafka 等)。如果消息发送失败,可以通过定时任务或补偿机制重新尝试发送,直到成功为止,然后再更新本地消息表中的消息状态为已发送。
-
接收并处理消息:其他服务作为消费者监听特定的消息队列,接收到消息后根据消息内容执行相应的业务逻辑。完成后,向消息队列发送确认通知,表示已经成功处理该消息。

优点:
-
解耦服务 :各服务间通过消息队列异步通信,减少了直接依赖,提高了系统的灵活性和可扩展性。
-
易于实现:相较于 TCC 等需要独立协调器的复杂模式,本地消息表方案相对简单易行,适合大多数应用场景。
缺点:
- 需维护本地消息表:与具体的业务场景绑定,耦合性强,不可公用。消息数据与业务数据同库,占用业务系统资源,同时消息服务性能也会受到关系型数据库并发性能的局限。
MQ 事务方案(可靠消息事务)
可靠消息事务可以解决本地消息表与业务场景耦合强、方案不可公用的痛点。
使用 MQ 事务方案的前提条件是 MQ 必须支持事务消息,比如 RocketMQ,核心原理是引入 Half 消息和事务状态检查机制。
RocketMQ 事务消息的工作流程:
-
发送 half 消息 :生产者首先向 RocketMQ 发送一条" Half 消息"。这条消息不会立即对消费者可见,而是处于一种等待确认的状态。在生产者明确告知 RocketMQ 是否提交或回滚之前,消费者无法看到这条消息。
-
执行本地事务 :在 Half 消息成功发送之后,生产者会开始执行本地事务逻辑(例如更新数据库)。这个阶段是与业务逻辑紧密相关的,根据具体的业务需求进行相应的处理。
-
提交或回滚事务 :根据本地事务执行的结果,生产者需要向 RocketMQ 提交一个最终决定:如果本地事务成功,则提交 Half 消息使其变为正常消息并对消费者可见;如果本地事务失败,则回滚 Half 消息,RocketMQ 将不再保留该消息。
-
事务状态检查机制 :如果在指定时间内 RocketMQ 没有收到生产者关于是否提交或回滚的消息,它将启动事务状态检查机制。RocketMQ 会回调生产者的事务状态检查接口,询问当前事务的状态,并据此做出相应的处理(提交或回滚)。
-
消费消息:只有当 Half 消息被成功提交后,它才会对消费者变得可见,此时消费者可以按照正常的流程去消费这条消息。

SAGA 长事务
SAGA 是一种用于在微服务架构中管理长时间运行的分布式事务的架构模式。
与 2PC 等依赖传统单体事务(需要跨服务锁定数据库)的方式不同,SAGA 的核心思想是将一个大的事务分解为一系列较小的、可以独立提交的本地事务(也称为步骤或子事务),每个步骤都是原子性的,并且可以被补偿操作所撤销。补偿操作是指当 SAGA 中的某一步骤失败时,为了保持系统状态的一致性,需要对之前已经成功执行的步骤进行撤销或调整的操作。每个步骤都应该定义好对应的补偿操作逻辑,确保即使发生故障也能恢复到一致的状态。
SAGA 工作原理:
-
事务拆分 :将一个大的事务拆分为多个更小、独立的子事务(步骤),每个步骤由不同的服务处理。例如:用户下单、用户支付、库存扣减等操作。
-
独立执行 :每个步骤独立运行,不需要等待其他步骤完成。如果某个步骤执行成功,系统将继续执行下一个步骤。
-
SAGA 执行协调器 :由一个中心组件(SAGA 执行协调器)来管理和协调这些步骤的流程,它按顺序触发每一个步骤,并监控执行状态。
-
补偿操作 :如果某个步骤失败,系统不会回滚整个事务,而是执行补偿操作 ,撤销之前成功步骤的影响。例如:库存恢复、退款、取消订单等操作。
-
SAGA 日志(SAGA Log):用于记录和跟踪长时间运行的分布式事务的状态,确保所有步骤要么全部成功完成,要么在发生故障时通过补偿操作恢复一致性。
SAGA有两种主要的实现模式:
- 控制 - Orchestration(中心化管理):这种方式使用一个中心化的 SAGA 协调器来管理和指导整个流程。协调器负责决定哪个服务应该在何时执行,并处理所有可能发生的错误情况。当某个步骤失败时,协调器会按照预定的策略调用相应的补偿操作来回滚先前已完成的步骤。


- 编排 - Choreography(消息驱动):在这种方式下,没有中心化的控制组件来协调各个步骤的执行顺序。相反,每一个服务在完成其部分工作后,通过发布事件通知下一个服务继续执行。这种方式依赖于消息传递机制来驱动流程的进展。

优点:
-
支持长时间运行的事务 :非常适合处理涉及多个服务的复杂业务流程。
-
无阻塞: 与 2PC 等传统分布式事务不同,2PC 会阻塞参与者直到事务完成,而 SAGA 允许每个步骤独立执行,避免了长时间阻塞影响应用性能。
-
优雅的故障处理:不同于 2PC 在发生故障时回滚整个事务,SAGA 使用补偿动作来撤销成功完成的步骤,确保系统保持一致性,可以根据具体需求灵活设计补偿策略。
缺点:
-
设计复杂度增加 :需要仔细设计每一步骤及其补偿逻辑,增加了系统的复杂度。
-
调试难度大:由于涉及多个服务间的交互,出现问题时定位和解决问题相对困难。
定期校对与补偿模式
定期校对与补偿模式的核心思想是通过定期检查系统状态,并根据预设规则执行相应的补偿操作来修复不一致的状态。
实现定期校对与补偿模式的关键组件:
-
状态记录 :需要一种机制来记录每个事务的状态和执行结果,这可以通过数据库表、日志文件等形式实现。
-
定时任务 :设置一个或多个定时任务,定期扫描状态记录,识别出未完成或异常结束的事务。
-
补偿逻辑:为每种可能的异常情况定义对应的补偿逻辑,当检测到问题时,自动触发这些逻辑进行修复。若无法自动化处理,则需要触发告警人工介入进行手工修复。
定期校对模式多用于金融系统中:金融系统由于涉及资金安全,需要严格保证准确性,所以需要多重的一致性保证机制,包括商户交易对账、现金对账、账户对账、手续费对账、系统间的一致性对账等。
其他方案
-
超时重试模式 :注意防重和幂等设计
-
补偿模式:通知运营补偿、技术自动化补偿(记录log然后回放补偿等)
总结
所有跨VM(跨节点)的一致性问题,通用的解决方案思路是:
-
强一致性 :分布式事务,但落地太难且成本太高。
-
最终一致性 :主要是用"记录 "和"正向补偿 "的方式。
-
在做所有的不确定的事情之前,先把事情记录下来,然后去做不确定的事情。
-
结果可能是:成功、失败或是不确定。"不确定"(例如超时等)可以等价为失败。
-
成功就可以把记录的东西清理掉了;对于失败和不确定,可以依靠定时任务等方式把所有失败的事情重新搞一遍,直到成功为止。


