怎么理解3PC解决了2PC的阻塞问题?

标签: 分布式

保留所有版权,请引用而不是转载本文(原文地址 https://yeecode.top/blog/121/ )。

正如题目所说,三阶段提交(Three-phase Commit,简称3PC)相比于两阶段提交(Two-phase Commit,简称2PC)的一个重大进步就是,前者解决了后者的阻塞问题。(或者说是部分解决,这个我们后面详细分析)

那如何详细解释这一点,还得从两阶段提交算法说起。先分析下两阶段提交哪里存在阻塞。然后,再看三阶段提交算法如何避免了这个阻塞。

好,我们开始介绍。

我尽量写的简单生动些。

而且,你没看错。虽然像是知乎十年前才有的答案,但确实是新回答……

哎……

1 两阶段提交

两阶段提交(Two-phase Commit,简称2PC)是一种比较简单的一致性算法(或者说协议)。它将整个提交过程分成了准备阶段(Prepare)提交阶段(Commit)这两个阶段。

1.1 具体实现

在准备阶段中,可以细分成如下的操作步骤:

  1. 协调者给参与者发送“Prepare”消息,“Prepare”消息中包含了此次更新要进行的所有操作。然后协调者等待各个参与者的响应。
  2. 参与者收到“Prepare”消息后将消息中的操作封装为一个事务并执行,但不要提交。
  3. 参与者在执行事务的过程中如果遇到任何问题导致事务中的操作无法完成,则向协调者回应“No”消息;如果事务中的全部操作能够完成,则向协调者回应“Yes”消息。

经过准备阶段,所有的参与者已经尝试完成了所有的操作,但是没有把这些操作提交。并且,根据将自身能否完成操作的结果回应给了协调者。接下来,便进入了第二个阶段:提交阶段。

在提交阶段中,协调者会根据收集到的各个参与者的回应执行不同的操作。

如果协调者收到了所有参与者的“Yes”消息,则进行如下操作:

  1. 协调者向参与者发送“Commit”消息。
  2. 参与者收到“Commit”消息后,提交在准备阶段已经完成但尚未提交的事务。
  3. 参与者向协调者发送“Done”消息。
  4. 协调者收到所有参与者的“Done”消息,表明该一致性操作以成功的形式结束。

如果协调者在一定时间内没有收齐所有参与者的消息,或者收到的消息中有“No”消息,则进行如下操作:

  1. 协调者向参与者发送“Rollback”消息。
  2. 参与者收到“Rollback”消息后,回滚在准备阶段已经完成但尚未提交的事务。
  3. 参与者向协调者发送“Done”消息。
  4. 协调者收到所有参与者节点的“Done”消息,表明该一致性操作以回滚的形式结束。

可见在提交阶段,无论是决定要“Commit”还是“Rollback”,整个一致性操作都能结束。

下图展示了两阶段提交的消息流。其中图的左侧部分表示一致性操作成功情况下的消息流;图的右侧部分表示因部分参与者不回应导致的一致性操作失败情况下的消息流。

两阶段提交的消息流

上述过程中,协调者可以是一个专门起到协调作用而不参与一致性操作的节点,也可以是一个参与一致性操作的节点。如果它是后者,那它不仅要完成协调者的工作,还要作为参与者完成所有参与者要进行的工作,即它也要将操作封装成事务执行、回应“No”消息或“Yes”消息、提交或回滚事务等。

1.2 阻塞在哪里?

通过了解两阶段提交的工作过程,我们其实已经可以看出明确阻塞点。

参与者收到协调者发出的“Prepare”消息后,会开启事务完成消息中的操作。事务的开启意味着参与者的并发处理能力将受到很大的影响。而且,参与者不是一个节点而是一群节点,所以整个系统中的节点都会因为事务的开启而阻塞。这个过程可能很长,要等待协调者收集完参与者的消息并发布进一步的消息后,该过程才能结束。

那如果协调者在这个阶段宕机,则所有参与者会一直阻塞下去。

这就是两阶段提交存在的同步阻塞问题。显然极大地影响了分布式系统的工作效率。

好,接下来我们看一下三阶段提交,看它如何解决这一阻塞问题。

2 三阶段提交

三阶段提交(Three-phase Commit,简称3PC)是二阶段提交的改进,它修复了二阶段提交的一些缺陷,但也使得整个一致性操作过程多出了一个阶段,更为复杂。此外,三阶段提交还引入了一些超时机制,以便于节点在失去或者部分失去与外界联系时做出一些操作。

三阶段提交一共分为三个阶段:CanCommit阶段PreCommit阶段DoCommit阶段

2.1 具体实现

CanCommit阶段可以细分为以下步骤:

  1. 协调者向参与者发送“CanCommit”消息,询问参与者能否完成消息中的操作。然后协调者等待各个参与者的响应。
  2. 参与者接收到“CanCommit”消息后,判断自身是否能够顺利完成操作。如果自身可以,则向协调者回应“Yes”消息;如果自身不可以,则向协调者回应“No”消息。

在CanCommit阶段,协调者和参与者只是就能否完成操作进行了交流,并没有进行实际的工作。接下来进入PreCommit阶段。

PreCommit阶段则根据协调者收到的参与者的反馈不同而不同。

如果协调者收到了所有参与者的“Yes”消息,则进行如下操作:

  1. 协调者向参与者发送“PreCommit”消息。然后协调者等待各个参与者的响应。
  2. 参与者收到“PreCommit”消息后,将此次要进行的操作封装成事务执行,但不要提交。
  3. 如果参与者完成了各项操作,则向协调者回应“ACK”消息。

在这种情况下,一致性操作则会进入DoCommit阶段。

如果协调者在一定时间内没有收齐所有参与者的消息,或者收到的消息中有“No”消息,则进行如下操作:

  1. 协调者向所有参与者发送“Abort”消息,表明此次一致性操作取消。
  2. 参与者收到“Abort”后,中止当前的操作。如果参与者在一定时间内没有收到协调者发来的操作消息,也中止当前的操作。

这种情况下一致性操作结束,无需再进入DoCommit阶段,

在DoCommit阶段中,协调者会根据参与者的回应采取不同的操作。

如果协调者收到了所有参与者的“ACK”消息,则进行如下的操作:

  1. 协调者向参与者发送“Commit”消息。
  2. 参与者收到“Commit”消息后,提交在准备阶段已经完成但尚未提交的事务。
  3. 参与者向协调者发送“Done”消息。
  4. 协调者收到所有参与者的“Done”消息,表明该一致性操作以成功的形式结束。

如果协调者在一定时间内没有收齐参与者的“ACK”消息,则进行如下的操作:

  1. 协调者向参与者发送“Rollback”消息。
  2. 参与者收到“Rollback”消息后,回滚在准备阶段已经完成但尚未提交的事务。
  3. 参与者向协调者发送“Done”消息。
  4. 协调者收到所有参与者的“Done”消息,表明该一致性操作以失败的形式结束。

下图展示了三阶段提交的消息流。其中图的左侧部分表示一致性操作成功情况下的消息流;图的右侧部分表示因部分参与者在CanCommit阶段回应“No”消息导致的一致性操作失败情况下的消息流。

三阶段提交的消息流

2.2 如何解决了阻塞

在三阶段提交中的DoCommit阶段,如果参与者在一定时间内没有收到“Commit”消息或者“Rollback”消息,则参与者会提交事务。这是因为既然能够进入DoCommit阶段,说明所有的参与者在CanCommit都回应了“Yes”。此时,所有的参与者都应该可以正确地提交事务,从而完成该一致性操作。

DoCommit阶段中参与者会在无法收到“Commit”消息或者“Rollback”消息时提交事务,这解决了两个问题。首先,整个一致性操作不会因为协调者的突然宕机而导致参与者开启的事务无法关闭,从而阻塞整个系统。这就是三阶段提交解决两阶段提交阻塞问题的点。

当然,这里的阻塞只能说是“部分解决”:

这里就看理解了,如果理解的“阻塞”是类似死锁一样的,无解的阻塞,那三阶段提交显然解决了如果理解的“阻塞”是哪怕一瞬间的全局停止相应其他操作,那在三阶段中则还是存在的

但总之,三阶段提交在阻塞这一问题上,相比于两阶段提交,确实是由巨大的进步。

至于为什么“一瞬间的全局停止相应其他操作”不能被解决,这是两阶段提交算法、三阶段提交算法的本质决定的。因为,只有这样,两者才能满足线性一致性。这个展开来就有点长了。如果感兴趣可以阅读书籍:

分布式系统原理与工程实践

《分布式系统原理与工程实践》

书中对两阶段算法、三阶段算法都进行了详细的介绍,还证明了它们为什么满足线性一致性。当然,也介绍了各种一致性的定义等,对大家理解分布式非常有帮助。

书籍部分目录

另外这本书对分布式系统相关的理论、实践、工程知识均进行了详细的介绍,层层递进,有助于大家建立完整的分布式系统知识体系。涉及一致性、共识、Paxos、分布式事务、服务治理、微服务、幂等、消息系统、Zookeeper等。并且还发行了繁体版

再次提醒大家,在学习分布式系统时,因为涉及到的理论、框架很多。所以大家一定要理清知识脉络,否则容易学得越多越混乱。


这是一篇近几千字的长文,希望能帮助到大家!

而且,你没穿越,虽然看上去,像是知乎十年前才会有的答案……

别忘了点赞……

也欢迎关注我!

我是程序员易哥。

可以访问个人知乎阅读更多文章:易哥(https://www.zhihu.com/people/yeecode),欢迎关注。

作者书籍推荐