【Distributed System】分布式事务 - 三阶段提交协议(Three-phase Commit Protocol)

Posted by 西维蜀黍 on 2019-01-09, Last Modified on 2023-03-29

三阶段提交协议(Three-phase Commit Protocol)

In computer networking and databases, the three-phase commit protocol (3PC) is a distributed algorithm which lets all nodes in a distributed system agree to commit a transaction. It is a more failure-resilient refinement of the two-phase commit protocol (2PC).

与两阶段提交不同的是,三阶段提交有两个改动点:

  1. 引入超时机制 - 同时在 coordinator 和 participant 中都引入超时机制。这保证了 participant 的事务锁在指定时间后会被自动释放(release),从而避免了因锁长期被占用导致查询性能下降的情况
  2. 把两阶段提交协议的第一个阶段(Voting Phase)拆分成了两个独立的阶段(CanCommit 和 PreCommit),以保证在最后提交阶段之前,各个 participant 的状态是一致的。

三段提交的核心理念是:在询问的时候并不锁定资源,除非所有人都同意了,才开始锁资源

理论上来说,如果第一阶段所有的结点返回成功,那么有理由相信成功提交的概率很大。这样一来,可以降低参与者Cohorts的状态未知的概率。也就是说,一旦参与者收到了PreCommit,意味他知道大家其实都同意修改了。

所谓的三个阶段分别是:

  • 第一阶段 - 询问:CanCommit
  • 第二阶段 - 锁定资源(预提交事务):PreCommit
  • 第三阶段 - 真正提交:DoCommit

三阶段提交的过程

阶段一 - CanCommit

事务询问

3PC 的 CanCommit 阶段其实和2PC的 Voting Phase 很像,即 coordinator 向 participant 发送 commit 请求,以询问是否可以执行事务提交操作。

但与 2PC 中的 Voting Phase 的区别是, 3PC的 CanCommit 阶段并不真正让 participant 执行“预提交本地事务”(而是检查是否能够正常“预提交本地事务”)。

响应反馈

participant 接到 CanCommit 请求之后,正常情况下,如果其自身认为可以顺利执行“预提交事务”(但不会真正执行预提交),则返回 Yes 响应,并进入预备状态;否则反馈 No。

阶段二 - PreCommit

coordinator 在得到所有 participant 的响应之后,会根据其响应结果执行下面任一一种操作:

  1. 执行事务预提交
  2. 中断事务

如果在超时时间后,仍然没有收到所有 participant 的响应,也会中断全局事务。

情况1 - 执行事务预提交

  1. coordinator 发送预提交请求 - coordinator 向所有 participant 发出 preCommit 的请求,以使得 participant 进入prepared 状态。
  2. participant 预提交事务 - participant 收到 preCommit 请求后,会提交事务,对应 2PC 准备阶段中的 “执行事务”,也会 Undo 和 Redo 信息记录到本地的事务日志中。
  3. 各 participant 响应反馈 - 如果这个 participant 成功执行了事务,就反馈 ACK 响应,同时等待指令:提交(commit) 或终止(abort)。

情况2 - 中断事务

  1. coordinator 向所有 participant 发送 abort 请求或者超时了
  2. participant 中断事务 - participant 如果收到 abort 请求或者超时,就会中断事务。

阶段三 - DoCommit

该阶段 participant 会真正 commit 本地事务,具体分为以下两种情况:

  • commit 本地事务
  • abort 本地事务

情况1 - commit 全局事务

如果 coordinator 发送了 commit 请求,且 commit 请求到达了每一个 participant,或者在达到超时时间后,participant 没有收到 coordinator 的 DoCommit(commit 或者 rollback)

  1. coordinator 发送 commit 请求 - coordinator 在接收到各 participant 发送的 ACK 响应后,他将从预提交状态(PreCommit)进入到提交状态(DoCommit),并向所有 participant 发送 doCommit 请求。
  2. participant commit 本地事务 - participant 接收到 doCommit 请求之后,commit 本地事务。并在完成事务提交之后释放所有事务资源。
  3. participant 响应反馈 - participant commit 本地事务之后, participant 向 coordinator 发送 ACK 响应。
  4. 完成事务 - coordinator 接收到所有 participant 的 ACK 响应之后,完成全局事务。

值得强调的是,在达到超时时间后,participant 没有收到 coordinator 的 DoCommit,3PL 会仍然提交,而不是让 participant 的本地事务继续被阻塞(直到收到 coordinator 发来的 commit)

  • 这适当缓解了 participant 的本地事务继续被阻塞的时长和可能性
  • 但仍然无法完全避免数据的不一致

情况2 - abort 全局事务

如果 coordinator 收到的 participant 的 ACK 不是 Yes,就会中断全局事务:

  1. coordinator 发送 abort请求 - coordinator 向所有 participant 发送 rollback 请求
  2. participant 回滚事务 - participant 接收到 rollback 请求之后,利用其在PreCommit阶段记录的 undo 信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源
  3. participant 反馈结果 - participant 完成事务回滚之后,向 coordinator 发送 ACK 消息。
  4. coordinator abort 全局事务 - coordinator 接收到每一个 participant 反馈的 ACK 消息之后,完成全局事务的中断。

三阶段提交的优缺点

优点

相对于二阶段提交,三阶段提交主要解决的单点故障问题,并减少了阻塞的时间。

  • 具体来说,因为一旦 participant 无法及时收到来自 coordinator 的 DoCommit 信息(commit 或者 rollback),他会默认执行 commit。而不会一直持有事务资源并处于阻塞状态,直到收到DoCommit 信息才结束阻塞。

缺点

三阶段提交也会导致数据一致性问题。由于网络原因, coordinator 发送的 abort 响应没有及时被 participant 接收到,那么 participant 在超时达到之前,都不会执行 rollback 操作:

  • 这里可能出现两种情况,如果participant 在超时达到时,仍没有收到 coordinator 发送的 DoCommit 信息,则执行 commit(这点和 2PL 不同);如果 participant 在超时达到之前,收到了 DoCommit 信息,那当然基于信息中的内容来执行

这样就和其他接到 abort 命令并执行回滚的 participant 之间存在数据不一致的情况。

总结来说,三阶段提交中是否会发生 participant 的本地事务被阻塞,依赖于 participant 到这个 participant 的网络的高可用性。而在分布式系统中,网络往往有可能会出问题。因此这个缺点其实是相对于有多个 node 都去通知这个 participant 的情况而言的,在后者的情况中,我们假设多个 node 到这个 participant 的网络都发生故障的概率是更低的(事实上未必一定是这样),因此本地事务资源并处于阻塞状态的时长会相对更短。

Reference