转载自 https://www.cnblogs.com/monkeyblog/p/10449363.html

介绍完分布式系统的一致性相关理论,下面基于不同的一致性模型介绍分布式事务的常见解决方案,后面会再介绍各个方案的使用场景。

分布式事务的实现有许多种,其中较经典是由 Tuxedo 提出的 XA 分布式事务协议,XA 协议包含二阶段提交(2PC)和三阶段提交(3PC)两种实现

本文介绍的偏向于原理,业界已经有不少开源的或者收费的解决方案,篇幅所限,就不再展开介绍。

实际运用理论时进行架构设计时,许多人容易犯“手里有了锤子,看什么都觉得像钉子”的错误,设计方案时考虑的问题场景过多,各种重试,各种补偿机制引入系统,导致系统过于复杂,落地遥遥无期。

世界上解决一个计算机问题最简单的方法:“恰好”不需要解决它!—— 阿里中间件技术专家沈询

有些问题,看起来很重要,但实际上我们可以通过合理的设计或者将问题分解来规避。

设计分布式事务系统也不是需要考虑所有异常情况,不必过度设计各种回滚,补偿机制。

如果硬要把时间花在解决问题本身,实际上不仅效率低下,而且也是一种浪费。

如果系统要实现回滚流程的话,有可能系统复杂度将大大提升,且很容易出现 Bug,估计出现 Bug 的概率会比需要事务回滚的概率大很多。

在设计系统时,我们需要衡量是否值得花这么大的代价来解决这样一个出现概率非常小的问题,可以考虑当出现这个概率很小的问题,能否采用人工解决的方式,这也是大家在解决疑难问题时需要多多思考的地方。

1、二阶段提交方案:强一致性

事务的发起者称协调者,事务的执行者称参与者。

方案简介

二阶段提交协议(Two-phase Commit,即 2PC)是常用的分布式事务解决方案,即将事务的提交过程分为两个阶段来进行处理:准备阶段和提交阶段。事务的发起者称协调者,事务的执行者称参与者。

在分布式系统里,每个节点都可以知晓自己操作的成功或者失败,却无法知道其他节点操作的成功或失败。

当一个事务跨多个节点时,为了保持事务的原子性与一致性,而引入一个协调者来统一掌控所有参与者的操作结果,并指示它们是否要把操作结果进行真正的提交或者回滚(rollback)。

二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。

核心思想就是对每一个事务都采用先尝试后提交的处理方式,处理后所有的读操作都要能获得最新的数据,因此也可以将二阶段提交看作是一个强一致性算法。

处理流程:

阶段 1:准备阶段

准备阶段有如下三个步骤:

  • 协调者向所有参与者发送事务内容,询问是否可以提交事务,并等待所有参与者答复。
  • 各参与者执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)。
  • 如参与者执行成功,给协调者反馈 yes,即可以提交;如执行失败,给协调者反馈 no,即不可提交。

阶段 2:提交阶段

如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(rollback)消息;否则,发送提交(commit)消息。

参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源) 接下来分两种情况分别讨论提交阶段的过程

image.png

情况 1,当所有参与者均反馈 yes,提交事务,如上图:

  • 协调者向所有参与者发出正式提交事务的请求(即 commit 请求)。
  • 参与者执行 commit 请求,并释放整个事务期间占用的资源。
  • 各参与者向协调者反馈 ack(应答)完成的消息。
  • 协调者收到所有参与者反馈的 ack 消息后,即完成事务提交。

分布式事务解决方案(一) - 图2

情况 2,当任何阶段 1 一个参与者反馈 no,中断事务,如上图:

  • 协调者向所有参与者发出回滚请求(即 rollback 请求)。
  • 参与者使用阶段 1 中的 undo 信息执行回滚操作,并释放整个事务期间占用的资源。
  • 各参与者向协调者反馈 ack 完成的消息。
  • 协调者收到所有参与者反馈的 ack 消息后,即完成事务中断。

简单一点理解,可以把协调者节点比喻为带头大哥,参与者理解比喻为跟班小弟,带头大哥统一协调跟班小弟的任务执行。

方案总结

2PC 方案实现起来简单,实际项目中使用比较少,主要因为以下问题:

  • 性能问题:所有参与者在事务提交阶段处于同步阻塞状态,占用系统资源,容易导致性能瓶颈。
  • 可靠性问题:如果协调者存在单点故障问题,如果协调者出现故障,参与者将一直处于锁定状态。
  • 数据一致性问题:在阶段 2 中,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。

2、三阶段提交

方案简介

三阶段提交是在二阶段提交上的改进版本,主要是加入了超时机制。同时在协调者和参与者中都引入超时机制。

三阶段将二阶段的准备阶段拆分为2个阶段,插入了一个preCommit阶段,以此来处理原先二阶段,参与者准备后,参与者发生崩溃或错误,导致参与者无法知晓是否提交或回滚的不确定状态所引起的延时问题。

处理流程

阶段 1:canCommit

协调者向参与者发送 commit 请求,参与者如果可以提交就返回 yes 响应(参与者不执行事务操作),否则返回 no 响应:

  • 协调者向所有参与者发出包含事务内容的 canCommit 请求,询问是否可以提交事务,并等待所有参与者答复。
  • 参与者收到 canCommit 请求后,如果认为可以执行事务操作,则反馈 yes 并进入预备状态,否则反馈 no。

阶段 2:preCommit

协调者根据阶段 1 canCommit 参与者的反应情况来决定是否可以进行基于事务的 preCommit 操作。根据响应情况,有以下两种可能

分布式事务解决方案(一) - 图3

情况 1:阶段 1 所有参与者均反馈 yes,参与者预执行事务,如上图:

  • 协调者向所有参与者发出 preCommit 请求,进入准备阶段。
  • 参与者收到 preCommit 请求后,执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)。
  • 各参与者向协调者反馈 ack 响应或 no 响应,并等待最终指令。

分布式事务解决方案(一) - 图4

情况 2:阶段 1 任何一个参与者反馈 no,或者等待超时后协调者尚无法收到所有参与者的反馈,即中断事务,如上图:

  • 协调者向所有参与者发出 abort 请求。
  • 无论收到协调者发出的 abort 请求,或者在等待协调者请求过程中出现超时,参与者均会中断事务。

阶段 3:do Commit

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

分布式事务解决方案(一) - 图5

情况 1:阶段 2 所有参与者均反馈 ack 响应,执行真正的事务提交,如上图:

  • 如果协调者处于工作状态,则向所有参与者发出 do Commit 请求。
  • 参与者收到 do Commit 请求后,会正式执行事务提交,并释放整个事务期间占用的资源。
  • 各参与者向协调者反馈 ack 完成的消息。
  • 协调者收到所有参与者反馈的 ack 消息后,即完成事务提交。

分布式事务解决方案(一) - 图6

情况 2:阶段 2 任何一个参与者反馈 no,或者等待超时后协调者尚无法收到所有参与者的反馈,即中断事务,如上图:

  • 如果协调者处于工作状态,向所有参与者发出 abort 请求。
  • 参与者使用阶段 1 中的 undo 信息执行回滚操作,并释放整个事务期间占用的资源。
  • 各参与者向协调者反馈 ack 完成的消息。
  • 协调者收到所有参与者反馈的 ack 消息后,即完成事务中断。

注意:进入阶段 3 后,无论协调者出现问题,或者协调者与参与者网络出现问题,都会导致参与者无法接收到协调者发出的 do Commit 请求或 abort 请求。此时,参与者都会在等待超时之后,继续执行事务提交。

方案总结

优点:相比二阶段提交,三阶段提交降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题,阶段 3 中协调者出现问题时,参与者会继续提交事务。

缺点:数据不一致问题依然存在,当在参与者收到 preCommit 请求后等待 do commite 指令时,此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。

3. TCC 事务:最终一致性

方案简介

TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。

TCC 是服务化的二阶段编程模型其 Try、Confirm、Cancel 3 个方法均由业务编码实现

  • Try 操作作为一阶段,负责资源的检查和预留。
  • Confirm 操作作为二阶段提交操作,执行真正的业务。
  • Cancel 是预留资源的取消。

TCC 事务的 Try、Confirm、Cancel 可以理解为 SQL 事务中的 Lock、Commit、Rollback。

处理流程

为了方便理解,下面以电商下单为例进行方案解析,这里把整个过程简单分为扣减库存,订单创建 2 个步骤,库存服务和订单服务分别在不同的服务器节点上。

①Try 阶段

从执行阶段来看,与传统事务机制中业务逻辑相同。但从业务角度来看,却不一样。

TCC 机制中的 Try 仅是一个初步操作,它和后续的确认一起才能真正构成一个完整的业务逻辑,这个阶段主要完成:

  • 完成所有业务检查( 一致性 ) 。
  • 预留必须业务资源( 准隔离性 ) 。
  • Try 尝试执行业务。

TCC 事务机制以初步操作(Try)为中心的,确认操作(Confirm)和取消操作(Cancel)都是围绕初步操作(Try)而展开。

因此,Try 阶段中的操作,其保障性是最好的,即使失败,仍然有取消操作(Cancel)可以将其执行结果撤销。

分布式事务解决方案(一) - 图7

假设商品库存为 100,购买数量为 2,这里检查和更新库存的同时,冻结用户购买数量的库存,同时创建订单,订单状态为待确认。

②Confirm / Cancel 阶段

根据 Try 阶段服务是否全部正常执行,继续执行确认操作(Confirm)或取消操作(Cancel)。

Confirm 和 Cancel 操作满足幂等性,如果 Confirm 或 Cancel 操作执行失败,将会不断重试直到执行完成。

Confirm:当 Try 阶段服务全部正常执行, 执行确认业务逻辑操作

分布式事务解决方案(一) - 图8

这里使用的资源一定是 Try 阶段预留的业务资源。在 TCC 事务机制中认为,如果在 Try 阶段能正常的预留资源,那 Confirm 一定能完整正确的提交。

Confirm 阶段也可以看成是对 Try 阶段的一个补充,Try+Confirm 一起组成了一个完整的业务逻辑。

Cancel:当 Try 阶段存在服务执行失败, 进入 Cancel 阶段

分布式事务解决方案(一) - 图9

Cancel 取消执行,释放 Try 阶段预留的业务资源,上面的例子中,Cancel 操作会把冻结的库存释放,并更新订单状态为取消。

方案总结

TCC 事务机制相对于传统事务机制(X/Open XA),TCC 事务机制相比于上面介绍的 XA 事务机制,有以下优点:

  • 性能提升:具体业务来实现控制资源锁的粒度变小,不会锁定整个资源。
  • 数据最终一致性:基于 Confirm 和 Cancel 的幂等性,保证事务最终完成确认或者取消,保证数据的一致性。
  • 可靠性:解决了 XA 协议的协调者单点故障问题,由主业务方发起并控制整个业务活动,业务活动管理器也变成多点,引入集群。

缺点: TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。