三阶段提交算法详解
保留所有版权,请引用而不是转载本文(原文地址 https://yeecode.top/blog/117/ )。
通过本文,我们将对三阶段提交(Three-phase Commit,简称3PC)算法进行详细的介绍。在阅读本文之前,需要大家对两阶段提交(Two-phase Commit,简称2PC)算法十分了解。不了解的同学可以翻看我之前的文章。
我们从两阶段提交算法的缺点说起,然后引出三阶段提交算法,并分析三阶段提交算法如何克服了这些缺点。
两阶段提交算法的缺点
两阶段提交的操作步骤比较简单,但也有几个明显的问题。
首先,两阶段提交存在同步阻塞问题。参与者收到协调者发出的“Prepare”消息后,会开启事务完成消息中的操作。事务的开启意味着参与者的并发处理能力将受到很大的影响。而且,参与者不是一个节点而是一群节点,所以整个系统中的节点都会因为事务的开启而阻塞。这个过程可能很长,要等待协调者收集完参与者的消息并发布进一步的消息后,该过程才能结束。
其次,两阶段提交高度依赖协调者发出的消息,因此存在单点故障。如果协调者在两阶段提交的过程中出现问题,则会导致系统失控。尤其是在准备阶段和提交阶段之间,如果此时协调者宕机,则会导致已经开启了事务的各个参与者既不能收到“Commit”消息,也不能收到“Rollback”消息。这样事务会一直开启,导致系统全局阻塞。
第三,两阶段提交其实设置了一个假设:如果参与者能够收到和正常回应“Prepare”消息,那它应该也能正常收到“Commit”消息或者“Rollback”消息。通常,这个假设是成立的。因为参与者在准备阶段和提交阶段之间宕机的概率很小。但是,再小的概率也有可能发生,尤其是在高并发和多节点的情况下。一旦因为网络抖动导致部分参与者无法收到“Commit”消息,则会出现部分参与者提交了事务,部分参与者未提交事务这种不一致情况的发生。
第四,两阶段提交存在状态丢失问题。如果协调者在发出“Commit”消息或者“Rollback”消息后宕机,部分参与者收到了这条消息后提交或者回滚了事务,部分参与者没有收到这条消息。那即使重新选举出一个新的协调者,新的协调者也无法确定各个参与者到底处在哪个状态。
为了解决两阶段提交的缺陷,出现了三阶段提交。
三阶段提交详解
三阶段提交(Three-phase Commit,简称3PC)是二阶段提交的改进,它修复了二阶段提交的一些缺陷,但也使得整个一致性操作过程多出了一个阶段,更为复杂。此外,三阶段提交还引入了一些超时机制,以便于节点在失去或者部分失去与外界联系时做出一些操作。
三阶段提交一共分为三个阶段:CanCommit阶段、PreCommit阶段、DoCommit阶段。
CanCommit阶段可以细分为以下步骤:
- 协调者向参与者发送“CanCommit”消息,询问参与者能否完成消息中的操作。然后协调者等待各个参与者的响应。
- 参与者接收到“CanCommit”消息后,判断自身是否能够顺利完成操作。如果自身可以,则向协调者回应“Yes”消息;如果自身不可以,则向协调者回应“No”消息。
在CanCommit阶段,协调者和参与者只是就能否完成操作进行了交流,并没有进行实际的工作。接下来进入PreCommit阶段。
PreCommit阶段则根据协调者收到的参与者的反馈不同而不同。
如果协调者收到了所有参与者的“Yes”消息,则进行如下操作:
- 协调者向参与者发送“PreCommit”消息。然后协调者等待各个参与者的响应。
- 参与者收到“PreCommit”消息后,将此次要进行的操作封装成事务执行,但不要提交。
- 如果参与者完成了各项操作,则向协调者回应“ACK”消息。
在这种情况下,一致性操作则会进入DoCommit阶段。
如果协调者在一定时间内没有收齐所有参与者的消息,或者收到的消息中有“No”消息,则进行如下操作:
- 协调者向所有参与者发送“Abort”消息,表明此次一致性操作取消。
- 参与者收到“Abort”后,中止当前的操作。如果参与者在一定时间内没有收到协调者发来的操作消息,也中止当前的操作。
这种情况下一致性操作结束,无需再进入DoCommit阶段,
在DoCommit阶段中,协调者会根据参与者的回应采取不同的操作。
如果协调者收到了所有参与者的“ACK”消息,则进行如下的操作:
- 协调者向参与者发送“Commit”消息。
- 参与者收到“Commit”消息后,提交在准备阶段已经完成但尚未提交的事务。
- 参与者向协调者发送“Done”消息。
- 协调者收到所有参与者的“Done”消息,表明该一致性操作以成功的形式结束。
如果协调者在一定时间内没有收齐参与者的“ACK”消息,则进行如下的操作:
- 协调者向参与者发送“Rollback”消息。
- 参与者收到“Rollback”消息后,回滚在准备阶段已经完成但尚未提交的事务。
- 参与者向协调者发送“Done”消息。
- 协调者收到所有参与者的“Done”消息,表明该一致性操作以失败的形式结束。
图{@fig:251san}展示了三阶段提交的消息流。其中图的左侧部分表示一致性操作成功情况下的消息流;图的右侧部分表示因部分参与者在CanCommit阶段回应“No”消息导致的一致性操作失败情况下的消息流。
在DoCommit阶段中,如果参与者在一定时间内没有收到“Commit”消息或者“Rollback”消息,则参与者会提交事务。这是因为既然能够进入DoCommit阶段,说明所有的参与者在CanCommit都回应了“Yes”。此时,所有的参与者都应该可以正确地提交事务,从而完成该一致性操作。
DoCommit阶段中参与者会在无法收到“Commit”消息或者“Rollback”消息时提交事务,这解决了两个问题。首先,整个一致性操作不会因为协调者的突然宕机而导致参与者开启的事务无法关闭,从而阻塞整个系统。其次,如果协调者在发送“Commit”指令前后宕机,则整个系统的状态是确定的,因为各个参与者都会默认提交事务。
三阶段提交的优劣
当然,这个机制仍然存在漏洞。如果协调者在发送“Rollback”消息后宕机,部分参与者收到了“Rollback”消息回滚了事务;部分参与者因为没有收到“Rollback”消息便会默认提交事务。这将导致系统的一致性被打破。而且即使选举出新的协调者,新协调者也无法判断哪些参与者回滚了事务,哪些参与者提交了事务。
不过,这种漏洞的发生概率极小。“Rollback”消息在DoCommit阶段发出,既然能够进入该阶段,说明所有参与者在CanCommit阶段都回复了“Yes”消息,则大概率会在PreCommit阶段正常回复“ACK”消息。因此,协调者因为无法收起“ACK”消息而发出“Rollback”消息的概率很低,而在发出“Rollback”消息后恰好宕机的概率则更低。
因此,三阶段提交通过加入一个新的阶段和引入超时机制减少了两阶段提交的同步阻塞问题,减弱了对协调者的依赖,降低了系统状态丢失的概率。然而,正如我们上面所分析的,三阶段提交依然存在漏洞,并不完美。
总体而言,两阶段提交和三阶段提交的实现比较简单,且能够实现线性一致性。尤其是三阶段提交,其故障概率很低,在实践中应用十分广泛。
好,本文就介绍到这里。如果想对分布式系统的一致性有更全面系统的认识,可以参考下面的书籍。

书籍对各种一致性算法都进行了详细的介绍。对大家体系化地掌握这些知识很有帮助。
另外这本书对分布式系统相关的理论、实践、工程知识均进行了详细的介绍,层层递进,有助于大家建立完整的分布式系统知识体系。涉及一致性、共识、Paxos、分布式事务、服务治理、微服务、幂等、消息系统、Zookeeper等。并且还发行了繁体版。
再次提醒大家,在学习分布式系统时,因为涉及到的理论、框架很多。所以大家一定要理清知识脉络,否则容易学得越多越混乱。
好了,我是程序员易哥。
别忘了点赞……
这回就说这么多!
可以关注我!等我下回接着说!
可以访问个人知乎阅读更多文章:易哥(https://www.zhihu.com/people/yeecode),欢迎关注。