分布式事务解决方案-2PC, 3PC

Angelia-Wang / 2023-08-15 / 原文

在分布式系统中,每一个机器节点虽然都能明确的知道自己在事务操作中的结果是成功或失败,但无法直接获取其他节点的操作结果。因此在分布式环境中,为了保持事务的 ACID 特性,就需要增加一个“协调者”来管理其他节点(“参与者”)事务的提交和回滚。基于这个思想,衍生出二阶段提交 2PC 和三阶段提交 3PC 这两种分布式一致性协议

生产环境里 3PC 用的是非常少的,一般都是 2PC。

现在比较流行的分布式事务框架 Seata,数据库的 XA 协议,都是基于 2PC 的。

2PC

二阶段提交 (Two-Phase Commit),是一个非常经典的强一致中心化的原子提交协议。目前,绝大多数关系型数据库都采用二阶段提交协议来完成分布式事务处理(例如mysql的XA协议)。因此二阶段提交协议也被广泛运用到分布式系统中。

顾名思义,算法流程就是分为两个阶段提交某一操作,其分为准备阶段、提交阶段。为了更好描述算法过程,为此定义两种角色:

  • 协调者 (Coordinator):协调者协调整个分布式事务的提交和回滚。
  • 参与者 (Participant):分布式事务中的事务资源拥有者。

阶段一:准备阶段

2pc 准备阶段

准备阶段,又被称为投票阶段 (Vote Request),由协调者向参与者发送 prepare 请求,询问当前事务能否处理成功。参与者则在本地执行数据库事务,记录 undo、redo 信息,但并不会提交事务。根据操作结果,返回给协调者 “yes/no”,表示事务是否可以提交。

  • 事务询问
    协调者向所有参与者发送事务内容,询问是否可以执行事务的提交操作,并等待各参与者的响应
  • 执行事务
    各参与者执行事务操作,准备好事务资源,记录 undo、redo 信息到日志,但并不会提交事务
  • 反馈询问结果
    如果参与者成功执行了事务操作,那么返回给协调者 yes(表示当前事务可以提交),否者返回给协调者 no(表示当前事务不能执行)

阶段二:提交阶段

在准备阶段,由于参与者可以返回 yes/no,则在提交阶段也会出现两种可能,即全局提交事务、全局回滚事务。

全局提交事务

2pc 提交阶段

当准备阶段所有参与者都返回 yes 的响应后,协调者将发起全局提交事务请求。

  • 发送提交请求
    由协调者向所有参与者发送 global_commit 请求,要求提交当前事务
  • 事务提交
    当参与者收到 global_commit 请求后,则执行事务提交操作,并释放整个分布式事务期间占用的事务资源
  • 反馈提交结果
    参与在执行完事务提交后,向协调者返回 ack 消息
  • 完成事务提交
    协调者收到所有参与者反馈的 ack 消息后,给客户端返回结果,完成本次事务

全局回滚事务

2pc 提交阶段

当准备阶段有一个参与者都返回 no 的响应后,实际场景中,协调者还会有等待响应时间,如果超时,则协调者将发起全局回滚事务请求,回滚事务。

  • 发送回滚请求
    由协调者向所有参与者发送 global_rollback 请求,要求回滚当前事务
  • 事务回滚
    当参与者收到 global_rollback 请求后,会利用准备阶段记录的 undo 信息来进行回滚,并释放整个分布式事务期间占用的事务资源
  • 反馈回滚结果
    参与在执行完事务提交后,向协调者返回 ack 消息
  • 回滚事务
    协调者收到所有参与者反馈的 ack 消息后,给客户端返回结果,完成回滚事务

优缺点

2PC 协议明显的优点就是:原理简单、容易实现。但是它的缺点更加明显:

  • 同步阻塞
    每个参与者都需要等待协调者的消息,才能继续下一阶段。当协调者在发送第二阶段的消息之前宕机,那么所有参与者将一直锁定准备阶段的事务资源,事务推进不下去了,造成事务阻塞。只有等到协调者恢复,事务才能继续进行。
    协调者要等待所有参与者的响应或超时才能继续下一阶段。
  • 数据不一致
    在第二阶段,如果出现网络异常导致一部分参与者收到了commit 请求,一部分参与者没有收到 commit 请求,结果会是一部分参与者提交了事务,一部分参与者无法进行事务提交。
  • 单点问题、脑裂
    协调者在 2PC 中,太过重要,当协调者宕机,整个集群将不可用。脑裂是指因为网络原因,出现多个协调者。

宕机(故障)恢复

2PC 宕机故障,分为多种情况。部分参与者宕机、协调者宕机、部分参与者和协调者都宕机。

  • 部分参与者宕机
    参与者恢复后询问协调者宕机期间处理情况便可完成一致性

  • 协调者宕机
    新选一个协调者替换,询问所有参与者最后那条事务执行情况,然后决定进行 commit/rollback,或者不处理。

  • 部分参与者和协调者都宕机

    新竞选的协调者询问所有参与者最后那条事务执行情况。则又分为几种情况:

    • 如果有参与者第一阶段返回 no,或者第二阶段执行了 rollback。则协调者命令所有参与者执行 rollback;
    • 如果所有参与者第一阶段返回 yes,第二阶段有参与者执行了 commit,则协调者命令所有参与者执行 commit;
    • 如果所有参与者第一阶段返回 yes,第二阶段没有参与者执行任何操作,此时协调者则不知道宕机的参与者执行了 commit/rollback。即不能明确的恢复过来。

3PC

上面小节讲解了二阶段提交协议的原理,也指出了它所存在的问题,三阶段提交协议则是为了解决 2PC 的同步阻塞、数据不一致的问题而诞生。

在 2PC 的基础上,3PC 将准备阶段一分为二,形成由 CanCommit、PreCommit、DoCommit 三个阶段组成的事务提交协议。并且引入参与者也增加超时机制(2PC只有协调者拥有超时机制)。

image-20230815083148540.png

阶段一:CanCommit

  • 事务询问
    由协调者向所有参与者发送一个包含事务内容的 canCommit 请求,询问是否可以执行事务提交操作,并等待参与者响应
  • 反馈询问响应
    参与者收到 canCommit 请求后,根据自身逻辑判断是否可以顺利执行事务,那么反馈 yes,否则反馈 no。说白了就是检查下自身状态的健康性,看有没有能力进行事务操作。

阶段二:PreCommit

  • 在阶段一结果中,如果所有参与者都返回 yes,则执行事务预提交,协调者发起 PreCommit 请求。
  • 如果有任何一个参与者节点返回的结果是 no,或者协调者在等待参与者节点反馈的过程中超时,整个分布式事务就会回滚,协调者就会向所有的参与者发送 “abort” 请求。

注意:上面所说的参与者超时机制,在该阶段就可以派上用场了。在该阶段开始之前,如果协调者宕机,参与者在等待超时时间后,各自选择回滚事务,最终达成一致。但是协调者在发出一部分 preCommit 请求后宕机,有一部分没有收到 preCommit 请求,那情况则是:一部分参与者执行了 preCommit 操作,一部分参与者没有执行 preCommit 操作,最终导致各节点之间数据不一致。

执行事务预提交

  • 发送 preCommit 请求
    由协调者向所有参与者发送 preCommit 请求,并等待参与者响应
  • 事务预提交
    参与者收到 preCommit请求后,会执行事务操作,将 undo 和 redo 信息写入事务日志中,但不会提交事务
  • 反馈执行响应
    参与者将执行结果反馈给协调者,同时等待最终指令提交(commit)或终止(abort)

回滚事务

该阶段回滚事务相比阶段三回滚事务要简单,只需要由协调者向所有参与者发送 abort 请求,通知回滚事务。期间参与者在等待协调者的 preCommit 请求超时后也会主动回滚本地事务。

阶段三:DoCommit

同样,根据上一阶段的结果,该阶段也会存在两种情况。阶段二所有参与者成功反馈,则执行提交事务请求,否则回滚事务。

注意参与者超时机制,在该阶段也有所体现。在该阶段,无论是协调者宕机了,还是网络原因导致参与者没有收到该阶段的(提交/回滚)请求,参与者都会执行本地事务提交。倘若在该阶段,协调者需要发送的是提交事务请求,参与者自动提交本地事务,最后能达到一致性。倘若在该阶段,协调者需要发送的是回滚事务请求,如果参与者没有收到该请求,则参与者也会自动提交本地事务,最终导致各节点之间数据不一致。

提交事务

  • 发送提交请求
    协调者就会从“预提交状态”变为“提交状态”。然后向所有的参与者节点发送 ”global_commit” 请求
  • 事务提交
    参与者收到 global_commit 请求后,将执行本地事务提交操作,并释放整个事务执行期间占用的事务资源
  • 反馈提交结果
    参与者向协调者反馈 ack 提交结果
  • 完成事务
    协调者收到所有参与者的 ack 消息后,完成事务

回滚事务

  • 发送回滚请求
    协调者向所有参与者发送 abort 请求
  • 事务回滚
    参与者根据阶段二中记录的 undo 信息,来执行回滚操作,并释放占用事务资源
  • 反馈回滚结果
    参与者向协调者反馈 ack 回滚结果
  • 回滚事务
    协调者收到所有参与者的 ack 消息后,回滚事务

优缺点

3PC 的优点:

  • 降低了同步阻塞时间。引入参与者超时机制,在参与者超时后,会自动进行本地 commit/abort 从而进行释放资源,那么其他全局事务就不会因为获取不到该资源而阻塞了。避免了参与者长时间无法与协调者节点通讯(协调者宕机)的情况下,无法释放资源的问题。
  • 降低了事务资源的锁定范围。增加了 CanCommit 阶段,不像 2PC 一样从一开始就锁定所有事务资源,3PC 在通过 CanCommit 阶段排除掉个别不具备处理事务能力的参与者的前提下,再进入第二阶段锁定事务资源。

3PC 的缺点:

  • 在某些情况下,因为自动提交/回滚本地事务的约定,必然会造成数据的不一致性。
  • 增加一轮消息,增加了复杂度和协商效率。
  • 单点问题、脑裂问题仍存在。

宕机(故障)恢复

直接分析 2PC 中的协调者和参与者都宕机情况下,新晋升的协调者怎么恢复一致性的。

这种情况下,新晋升的协调者同样会询问所有的参与者事务处理情况。这看上去和 2PC 一样啊?但是深入分析下就会发现不一样。

我们假设第三阶段,协调者和参与者宕机了,并且参与者执行了第三阶段的操作,没人知道它执行了什么,也就存在两种可能:

  • 宕机的参与者没执行第三阶段任何操作或者执行了第三阶段的 commit 操作。那么协调者在询问所有的参与者事务执行情况时,得到的 ack 响应一定是,所有参与者处于第二阶段成功完成的状态或者第三阶段执行了commit 操作的状态。据此,协调者如果收到这样的回复,那一定可以断定宕机的参与者执行了 commit 操作或者没执行第三阶段的操作,那么协调者可以大胆的发送 DoCommit 消息。
  • 宕机的参与者执行第三阶段的 abort 操作。那么协调者在询问所有的参与者事务执行情况时,得到的ACK响应一定是,一定存在一个参与者的第二阶段的响应是 no,否则第三阶段不会发起 abort 操作。据此,协调者如果收到存在一个参与者处于第二阶段的 abort 状态时,则可以大胆的发送 abort 操作给所有的参与者。

参考链接

分布式一致性协议 - 2PC, 3PC

分布式一致性协议之 2PC 和 3PC