分布式事务

分布式事务是指事务的参与者、支持事务的服务器、资源服务器及事务管理器分别位于分布式系统的不同节点上。

分布式事务问题也叫分布式数据一致性问题,简单来说就是如何在分布式场景中,保证多个节点数据的一致性。分布式事务产生的核心原因在于存储资源的 分布性,比如多个数据库,或者 MySQL和 Redis 两种不同存储设备的的数据一致性等。

分布式事务问题的理论模型

X/Open 分布式事务模型

X/Open DTP (X/Open Distributed Transaction Processing Reference Model) 是 X/Open 组织定义的一套分布式事务的标准。这个标准提出了使用 两阶段提交(2PC,Two-Phase-Commit)来保证分布式事务的完整性。

X/Open DTP 中包含一下三种角色:

  • AP:Application,表示应用程序。
  • RM:Resource Manager,表示资源管理器,比如数据库。
  • TM:Transaction Manager,表示事务管理器,一般指事务协调者,负责协调和管理事务,提供 AP 编程接口或管理 RM。可以理解为 Spring 中提供的 Transaction Manager。

在这里插入图片描述

上图展示的角色和关系与本地事务的原理基本相同,唯一不同的在于,如果此时 RM 代表数据库,那么 TM 需要能够管理多个数据库的事务,大致实现步骤如下:

  • 配置 TM,把多个 RM 注册到 TM,相当于 TM 注册 RM 作为数据源
  • AP 从 TM 管理的 RM 中获取连接,如果 RM 是数据库则获取 JDBC 连接
  • AP 向 TM 发起一个全局事务,生成全局事务 ID(XID),XID 会通知各个 RM
  • AP 通过第二步获得的连接直接操作 RM 完成数据操作。这时,AP 在每次操作时会把 XID 传递给 RM
  • AP 结束全局事务,TM 会通知各个 RM 全局事务结束
  • 根据各个 RM 的事务执行结果,执行提交或者回滚操作

需要注意的是,TM 和 多个 RM 之间的事务控制,是基于 XA 协议(XA Specification)来完成的。XA 协议是 X/Open 提出的分布式事务处理规范,也是分布式事务处理的工业标准,它定义了 ax_xa_ 系列的函数原型及功能描述、约束等。

在这里插入图片描述

两阶段提交协议

两阶段提交协议的执行流程如下:

  • 准备阶段:事务管理器(TM)通知资源管理器(RM)准备分支事务,记录事务日志,并告知事务管理器的准备结果。
  • 提交/回滚阶段:如果所有的资源管理器(RM)在准备阶段都明确返回成功,则事务管理器(TM)向所有的资源管理器(RM)发起事务提交指令完成数据的变更。反之,如果任何一个资源管理器(RM)明确返回失败,则事务管理器(TM)会向所有的资源管理器(RM) 发送事务回滚指令。

在这里插入图片描述

两阶段提交将一个事务的处理过程分为投票和执行两个阶段,它的优点在于充分考虑了分布式系统的不可靠因素,并且采用非常简单的方式(两阶段提交)就把由于系统不可靠而导致事务提交失败的概率降到最小。当然,它也存在以下缺点:

  • 同步阻塞:所有参与者(RM)都是事务阻塞型的,对于任何一次指令都必须要有明确地响应才能进行下一步,否则会处于阻塞状态,占用的资源一直被锁定。
  • 过于保守:任何一个节点失败都会导致数据回滚。
  • 事务协调者的单点故障:如果协调者在第二阶段出现了故障,那么其他的参与者(RM)会一直处于锁定状态。
  • “脑裂”导致数据不一致问题:在第二阶段,事务协调者向所有参与者(RM)发送 commit 请求后,发生局部网络异常导致只有一部分参与者(RM)收到了 commit 请求,这部分参与者(RM)收到请求后会执行 commit 操作,但未收到 commit 请求的节点由于事务无法提交,导致数据出现不一致问题。
三阶段提交协议

三阶段提交协议是两阶段提交协议的改进版本,它利用 超时机制 解决了 同步阻塞 的问题。

具体描述如下:

  • CanCommit(询问阶段):事务协调者向参与者发送事务执行请求,询问是否可以完成指令,参与者只需要回答是或者不是即可,不需要做真正的事务操作,这个阶段会有 超时中止 机制。
  • PreCommit(准备阶段):事务协调者会根据参与者的反馈结果决定是否继续执行,如果在询问阶段所有参与者都返回可以执行操作,则事务协调者会向所有参与者发送 PreCommit 请求,参与者收到请求后写 redo 和 undo 日志,执行事务操作但不提交事务,然后返回 ACK 响应等待事务协调者的下一步通知。如果询问阶段任意参与者返回不能执行操作的结果或者超时,那么事务协调者会向所有参与者发送 事务中断 请求。
  • DoCommit(提交或回滚阶段):如果每个参与者在 PreCommit 阶段都返回成功,那么事务协调者会向所有参与者发起事务提交指令。反之,如果参与者中的任一参与者返回失败,那么事务协调者就会发起中止指令来回滚事务。

三阶段提交协议的时序图如图:

在这里插入图片描述

与两阶段提交协议的不同点:

  • 增加另一个 CanCommit 阶段,用于询问所有参与者是否可以执行事务操作并且响应,它的好处是,可以尽早发现无法执行的操作而中止后续的行为。
  • DoCommit 阶段,事务协调者和参与者都引入了 超时机制,一旦超时,事务协调者和参与者会继续提交事务,并且任务处于 成功状态,因为在这种情况下事务默认为成功的可能性较大。

实际上,一旦超时,在三阶段提交协议下仍然可能出现数据不一致的情况,当然概率是比较小的。另外,最大的好处就是 基于超时机制来避免资源的永久锁定

CAP 理论和 Base 理论
CAP 定理

CAP 定理,又称为布鲁尔定理。简单来说它是指在分布式系统中不可能同时能满足 一致性(C:Consistency)、可用性(A:Availablity)、分区容错性(P:Partition Tolerance)这三个基本需求,最多同时满足两个。

  • C:数据在多个副本中要保持强一致。
  • A:系统对外提供的服务必须一直处于可用的状态,在任何故障下,客户端都能在合理的时间内获得服务端的非错误响应。
  • P:在分布式系统中遇到任何网络分区故障,系统仍然能够正常对外提供服务。
BASE 理论

BASE 理论由于 CAP 中的一致性和可用性不可兼得而衍生的一种新的思想,BASE 理论的核心思想是通过 牺牲数据的强一致性来获得高可用性。有如下特性

  • Basically Availiable (基本可用): 分布式系统出现故障时,允许损失一部分功能的可用性,保证核心功能的可用。
  • Soft State(软状态):允许系统中的数据存在中间状态,这个状态不影响系统的可用性,也就是允许系统中不同节点的数据副本之间的同步存在延时。
  • Eventually Consistent (最终一致性):中间状态的数据在经过一段时间之后,会达到一个最终的数据一致性。

分布式事务问题的常见解决方案

TCC 补偿型方案

TCC(Try-Confirm-Cancel)是一种比较成熟的分布式数据一致性解决方案,它实际上把一个完整的业务拆分为如下三个步骤。

  • Try:这个阶段主要是对数据的校验或者资源的预留。
  • Confirm:确认真正执行的任务,只操作 Try 阶段预留的资源。
  • Cancel:取消执行,释放 Try 阶段预留的资源。
  • 先是服务调用链路依次执行 Try 逻辑
  • 如果都正常的话,TCC 分布式事务框架推进执行 Confirm 逻辑,完成整个事务
  • 如果某个服务的 Try 逻辑有问题,TCC 分布式事务框架感知到之后就会推进执行各个服务的 Cancel 逻辑,撤销之前执行的各种操作

其实 TCC 是一种两阶段提交的思想,第一阶段通过 Try 进行准备工作,第二阶段 Comfirm/Cancel 表示 Try 阶段操作的确认和回滚。在分布式事务场景中,每个服务实现 TCC 后,就作为其中的一个资源,参与到整个分布式事务中。然后主业务服务在第一阶段中分别调用所有 TCC 服务的 Try 方法。最后根据第一阶段的执行情况来决定对第二阶段的 Confirm 或者 Cancel。执行流程如下:

在这里插入图片描述

在一些特殊情况下,如果 TCC 服务宕机或者出现异常,导致该服务没有收到 TCC 事务协调器的 Cancel 或者 Confirm 请求,TCC 事务框架会记录一些分布式事务的操作日志,保存分布式事务运行的各个阶段和状态。TCC 事务协调器会根据操作日志来进行重试,以达到数据的最终一致性。

需要注意的是,TCC 服务支持接口调用失败发起重试,所以 TCC 暴露的接口都需要满足 幂等性

基于可靠性消息的最终一致性方案

主要利用消息中间件(Kafka、RocketMQ 或 RabbitMQ)的可靠性机制来实现数据一致性的投递。

例如:电商支付场景,大部分电商平台基于营销策略,在支付后,会给用户账户增加一定的积分奖励。所以,当系统接收到第三方返回的支付结果时,需要更新支付服务的支付状态,以及更新账户服务的积分余额,这里就涉及到两个服务的数据一致性问题。这里的数据一致性并不要求实时性,所以可采用基于可靠性消息的最终一致性方案来保证支付服务和账户服务的数据一致性。如下图:支付服务在收到支付结果通知后,先更新支付订单的状态,再发送一条消息到分布式消息队列中,账户服务会监听到指定队列的消息并做响应的处理,完成数据的同步。

在这里插入图片描述

在上图的解决方案中,有一个支付服务本地事务与发送消息这个操作的原子性问题,

  • 先发送消息,再执行数据库事务,可能会出现发送消息成功但是本地事务更新失败的情况,导致数据不一致。
  • 先执行数据库事务操作,再发送消息,可能会出现 MQ 响应超时导致异常,从而将本地事务回滚,但是消息可能发送成功了,导致数据不一致。

以上问题也有成熟的解决方案,以 RocketMQ 为例,它提供了事务消息模型,如下描述:

  • 生产者发送一条事务消息到消息队列上,消息队列只记录这条消息的数据,此时消费者无法消费这条消息。
  • 生产者执行具体的业务逻辑,完成本地事务的操作。
  • 接着生产者根据本地事务的执行结果发送一条确认消息给消息队列服务器,如果本地事务执行成功,则发送一个 Commit 消息,表示第一步中发送的消息可以被消费,否则,消息队列服务器会把第一步存储的消息删除。
  • 如果生产者在执行本地事务的过程中因为某些情况一直未给消息队列服务器发送确认,那么消息队列服务器会定时 主动回查 生产者获取本地事务的执行结果,然后根据回查结果来决定这条消息是否需要投递给消费者。(事务回查
  • 消息队列上存储的消息被生产者确认后,消费者就可以消费这条消息,消息消费完成之后发送一个确认标识给消息队列服务器,表示该消息投递成功。

在这里插入图片描述

最大努力通知型

最大努力通知型和基于消息可靠性消息的最终一致性方案的实现是类似的,也比较适用于对 数据一致性要求不高 的场景,最典型的使用场景就是支付宝支付结果的通知,实现流程如下图:

站在商户的角度分析最大努力通知型的处理过程。

  • 商户先创建一个支付订单,然后调用支付宝发起支付请求。
  • 支付宝唤醒支付页面完成支付操作,支付宝同样会针对该商户创建一个支付交易,并且根据用户的支付结果记录支付状态。
  • 支付完成后触发一个回调通知给商户,商户收到该通知后,根据结果修改本地支付订单的状态,并且返回一个处理状态给支付宝。
  • 针对这个订单,在理想状态下支付宝的交易状态和商户的交易状态会在通知完成后达到最终一致。但是由于网络的不确定性,支付结果通知可能会失败或者丢失,导致商户端的支付订单的状态时未知的。所以最大努力通知型的作用就体现了。如果商户端在收到支付结果通知后没有返回一个 “SUCCESS” 状态码,那么这个支付结果回调请求会以 衰减重试机制(逐步拉大通知的间隔) 继续触发,比如 1 min、5min、10min…,直到达到最大通知数。如果达到指定次数之后商户还没有返回确认状态,怎么处理?
  • 支付宝提供了一个交易结果查询接口,可以根据这个支付宝订单号去支付宝查询支付状态,然后根据这个结果来更新商户的支付订单状态,这个过程可以通过定时器来触发,也可通过人工对账来触发。

从上述分析发现,最大努力通知,就是在商户端如果没有返回一个消息确认时,支付宝会不断地进行重试,直到收到一个消息确认或达到最大重试次数。

在这里插入图片描述

分布式事务框架 Seata

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务,它提供了 AT、TCC、Saga 和 XA 事务模式。

AT 模式

AT 模式是 Seata 最主推的分布式事务解决方案,它是基于 XA 演进而来的一种分布式事务模式,它同样有三大模块,分别是 TM、RM 和 TC,其中 TM 和 RM 作为 Seata 的客户端与业务系统集成,TC 作为 Seata 的服务器独立部署。TM 表示事务管理器(Transaction Manager),它负责向 TC(Transaction Coordinator,事务协调器) 注册一个全局事务,并生成全局唯一的 XID。在 AT 模式下,每个数据库资源被当作一个 RM(Resource Manager),在业务层面通过 JDBC 标准的接口访问 RM 时,Seata 会对所有的请求进行拦截。每个本地事务进行提交时,RM 都会向 TC 注册一个分支事务。AT 模式 和 XA 模式一样,也是一种两阶段提交事务模型。

在这里插入图片描述

具体执行流程如下:

  • TM 向 TC 注册全局事务,并生成全局唯一的 XID。
  • RM 向 TC 注册分支事务,并将其纳入该 XID 对应的全局事务范围。
  • RM 向 TC 汇报资源的准备状态。
  • TC 汇总所有事务参与者的执行状态,决定分布式事务是全部回滚还是提交。
  • TC 通知所有 RM 提交/回滚事务。
Saga 模式

Saga 模式又称为长事务解决方案,主要描述是在没有两阶段提交的情况下如何解决分布式事务问题。其核心思想是:把一个业务流程中的长事务拆分为多个本地短事务,业务流程中的每个参与者都提交 真实的提交 给本地短事务,当其中一个参与者事务执行失败,则通过补偿机制补偿前面已经成功的参与者。

如下图,Saga 由一系列的 sub-transaction Ti 组成,每个 Ti 都有对应的补偿动作 Ci ,补偿动作用于撤销 Ti 造成的数据变更结果。它和 TCC 相比,少了 Try 这个预留动作,每一个 Ti 操作都真实地影响到数据库

在这里插入图片描述

另外,Saga 提供了以下两种补偿恢复方式。

  • 向后恢复,如果任一子事务执行失败,则把之前执行的结果逐一撤销。
  • 向前恢复,也就是不进行补偿,而是对失败的事务进行重试,这种方式比较适合于事务必须执行成功的场景。

不管是向前恢复还是向后恢复,都可能会出现失败的情况,在最坏的情况下只能人工干预处理。

优劣势
  • 优点:一阶段直接提交本地事务;没有锁等待,性能较高;在事件驱动的模式下,短事务可以异步执行;补偿机制的实现比较简单。
  • 缺点:不提供原子性和隔离性支持,隔离性的影响比较大,比如用户购买一个商品后赠送一张优惠券,如果用户使用了优惠券,那么事务如果出现异常回滚时就会出问题 。
Saga 的实现方式

以电商品台下单场景为例,一般会设计订单的创建、商品库存的扣减、钱包支付、积分赠送等操作,整个时序图如下:

在这里插入图片描述

电商平台下单的流程是一个典型的长事务场景,根据 Saga 模式的定义,先将长事务拆分成多个本地短事务,每个服务的本地事务按照执行顺序逐一提交,一旦其中一个服务的事务出现异常,则采用补偿的方式逐一撤回。这一过程会涉及 Saga 的协调模式,它有两种常用的协调模式。

  • 事件/编排式:把 Saga 的决策和执行顺序逻辑分布在 Saga 的每一个参与者中,它们通过交换事件的方式来进行沟通。
  • 命令/协同式:把 Saga 的决策和执行顺序逻辑集中在一个 Saga 控制类中,它以 命令/回复 的方式与每项服务进行通信,告诉它们应该执行哪些操作。
事件/编排式

在基于事件的编排模式中,第一个服务执行完一个本地事务之后,发送一个事件。这个事件会被一个或者多个服务监听,监听到事件的服务再执行本地事务并发布新的事件,此后一直延续这种事件触发模式,直到业务流程中最后一个服务的本地事务执行结束。

在这里插入图片描述

命令/协同式

命令/协同需要定义一个 Saga 协调器,负责告诉每一个参与者该做什么,Saga 协调器以命令/回复的方式与每项服务进行通信。

在这里插入图片描述

需要注意的是,订单 Saga 协调器必须提前知道 ”创建订单事务“ 的所有流程(Seata 是通过基于 JSON 的状态机引擎来实现的),并且在整个流程中任何一个环节执行失败,它都需要向每个参与者发送命令撤销之前的事务操作

参考资料

[1] 分布式理论之CAP定理(布鲁尔定理) - SegmentFault 思否

[2] 《Spring Cloud Alibaba 微服务原理与实战》

[3] 什么是TCC?_vincent_wen0766的博客-CSDN博客_tcc是什么

[4] MQ消息最终一致性解决方案 - 简书 (jianshu.com)

  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值