二阶段提交协议和三阶段提交协议

2019/04/02 ZooKeeper

二阶段提交和三阶段提交 (2 phase commit 和3 phase commit)

分布式系统中,每一个节点虽然都能够知道自身事务操作的结果,却无法获取其他节点的操作结果。

当事务跨越多个分布式节点时,为了保持分布式事务处理的原子性和一致性,2PC和3PC算法引入了一个称为协调者(Coordinator)的组件统一调度所有分布式节点,被调度的节点称为参与者(Participant)

二阶段提交 (2 phase commit)

  1. 提交事务请求 (commit-request)
  • 协调者向所有的参与者发送事务执行请求,并等待反馈事务执行结果。
  • 参与者收到请求之后,执行事务但不提交,并记录事务日志。
  • 参与者将事务执行情况反馈给协调者,同时阻塞等待协调者的后续指令。
  1. 执行事务提交 (commit)

如果协调者收到所有参与者执行成功:

  • 协调者节点向所有参与者节点发出”正式提交(commit)”的请求。
  • 参与者节点正式完成操作,并释放在整个事务期间内占用的资源
  • 参与者节点向协调者节点发送”完成”消息。
  • 协调者节点受到所有参与者节点反馈的”完成”消息后,完成事务。

如果协调者收到有参与者执行失败或者超时:

  • 协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。
  • 参与者节点执行回滚,并释放在整个事务期间内占用的资源
  • 参与者节点向协调者节点发送”回滚完成”消息。
  • 协调者节点受到所有参与者节点反馈的”回滚完成”消息后,取消事务。

二阶段提交分析

  1. 单点问题,如果协调者出问题,则整个流程无法执行。
  2. 同步阻塞,所有参与者在阶段1结束后阻塞等待协调者,无法进行其他任务。
  3. 数据不一致,第二阶段如果由于网络问题或协调者宕机,只有部分节点接收到commit指令,其余节点依然在阻塞等待,造成数据不一致。

二阶段提交无法解决的问题

当协调者与参与者同时出错时,两阶段无法保证事务执行的完整性。

当协调者最终发出COMMIT消息后宕机,而唯一接收到该消息的参与者也宕机。
即使选举产生了新的协调者,该事务的状态也是不确定的。

三阶段提交 (3 phase commit)

三阶段提交是二阶段提交的改进,将二阶段提交的第一步 commit-request 分解为两步 CanCommit和PreCommit。三阶段提交由三步组成:CanCommit, PreCommit, DoCommit。

相对于二阶段提交的改进:
1. 在第一阶段和第二阶段中插入一个准备阶段,保证了在最后提交阶段之前各参与节点的状态是一致的。
2. 同时在协调者和参与者中都引入超时机制。
  1. CanCommit
  • 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
  • 参与者接到CanCommit请求之后,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态,否则反馈No。

CanCommit并不执行事务,只是做一些执行前的检查,测试网络状态,做好事务执行前的准备。

  1. PreCommit

如果协调者收到所有参与者CanCommit为Yes:

  • 发送预提交请求。协调者向参与者发送PreCommit请求,并进入Prepared阶段。
  • 事务预提交。参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
  • 响应反馈。如果参与者成功的执行了事务操作,则返回YES,否则返回NO。

如果协调者收到有参与者CanCommit为No,或响应超时:

  • 发送中断请求,协调者向所有参与者发送abort请求。
  • 中断事务,参与者收到来自协调者的abort请求之后,或等待协调者命令超时,执行事务的中断。
  1. DoCommit

如果协调者收到了所有参与者PreCommit为YES:

  • 向所有参与者发送doCommit请求。
  • 参与者接收到doCommit请求之后,或等待协调者命令超时,执行正式的事务提交。并在完成事务提交之后释放所有事务资源
  • 事务提交完之后,向协调者发送Ack响应。
  • 协调者接收到所有参与者的ack响应之后,完成事务。

如果协调者收到有参与者PreCommit为No,或响应超时:

  • 协调者向所有参与者发送abort请求。
  • 参与者接收到abort请求之后,执行事务的回滚操作,并在完成回滚之后释放所有的事务资源
  • 参与者完成事务回滚之后,向协调者发送ACK消息
  • 协调者接收到参与者反馈的ACK消息之后,执行事务的中断。
DoCommit阶段,如果参与者等待协调者命令超时,依然会执行commit操作。
因为在CanCommit阶段,已经测试过所有参与者能够执行事务,因此,最终事务被执行的概率较大,所以算法实现选择执行该commit。
三阶段提交是“非阻塞”协议。
三阶段提交在两阶段提交的第一阶段与第二阶段之间插入了一个准备阶段,
使得原先在两阶段提交中,参与者在投票之后,由于协调者发生崩溃或错误,
而导致参与者处于无法知晓是否提交或者中止的“不确定状态”所产生的可能相当长的延时的问题得以解决。 举例来说,假设有一个决策小组由一个主持人负责与多位组员以电话联络方式协调是否通过一个提案,以两阶段提交来说,主持人收到一个提案请求,打电话跟每个组员询问是否通过并统计回复,然后将最后决定打电话通知各组员。
要是主持人在跟第一位组员通完电话后失忆,而第一位组员在得知结果并执行后老人痴呆,那么即使重新选出主持人,也没人知道最后的提案决定是什么,也许是通过,也许是驳回,不管大家选择哪一种决定,都有可能与第一位组员已执行过的真实决定不一致,老板就会不开心认为决策小组沟通有问题而解雇。
三阶段提交即是引入了另一个步骤,主持人打电话跟组员通知请准备通过提案,以避免没人知道真实决定而造成决定不一致的失业危机。
为什么能够解决二阶段提交的问题呢?
回到刚刚提到的状况,在主持人通知完第一位组员请准备通过后两人意外失忆,即使没人知道全体在第一阶段的决定为何,全体决策组员仍可以重新协调过程或直接否决,不会有不一致决定而失业。
那么当主持人通知完全体组员请准备通过并得到大家的再次确定后进入第三阶段,
当主持人通知第一位组员请通过提案后两人意外失忆,这时候其他组员再重新选出主持人后,
仍可以知道目前至少是处于准备通过提案阶段,表示第一阶段大家都已经决定要通过了,此时便可以直接通过。

即不同于2PC,在3PC中,第二阶段和第三阶段即使命令超时或协调者宕机,由于第一阶段大家已经同意能够COMMIT,因此新选举出来的协调者仍然会向参与者发出COMMIT指令。

三阶段提交分析

  1. 解决二阶段提交单点故障和同步阻塞问题

三阶段提交为参与者同样引入了超时,在第二阶段超时则终止commit,在第三阶段超时则执行commit。

  1. 无法解决数据不一致的问题

三阶段提交通过将commit-request分为CanCommit和PreCommit两部分,增大了DoCommit阶段参与者等待超时协调者发送的是执行事务命令的概率。
但是如果事实上协调者发送的是rollback,则依然会存在参与者之间数据不一致的情况。

REFS

  • https://www.hollischuang.com/archives/681
本文地址:https://cheng-dp.github.io/2019/04/02/2pc-3pc/

Search

    Table of Contents