分布式架构之「 两阶段提交协议」

855 阅读11分钟

两阶段提交协议是一种经典的强一致性中心化副本控制协议。虽然在工程中该协议有较多的问题,但研究该协议能很好的理解分布式系统的几个典型问题。


问题背景

两阶段提交协议是一种历史悠久的分布式控制协议。最早用于在分布式数据库中,实现分布式事务。这里有必要首先简单介绍一下两阶段提交的最初问题背景,从而更好的理解该协议。


在经典的分布式数据库模型中,同一个数据库的各个副本运行在不同的节点上,每个副本的数据要求完全一致。数据库中的操作都是事务,一个事务是一系列读、写操作,事务满足ACID。每个事务的最终状态要么是提交,要么是失败。一旦一个事务成功提交,那么这个事务中所有的写操作都成功,否则所有的写操作都失败。在单机上,事务靠日志技术或者MVCC等技术实现。在分布式数据库中,需要有一种控制协议。使得事务要么在所有的副本上都提交,要么在所有的副本上都失败。对于同一个事务而言,虽然在所有副本上执行的事务操作都完全一样,但可能在某些副本上可以提交,在某些副本上不能提交。这是因为,在某些副本上,其他的事务可能与本地事务有冲突(例如死锁),从而造成在有些副本上事务可以提交,有些副本上事务无法提交。这里不深入讨论事务冲突的问题,只是将问题背景介绍情况,该类问题可以通过阅读经典的数据库系统相关资料了解。


流程描述

按本文的分类,两阶段提交协议是一种典型的”中心化副本控制”协议。在该协议中,参与的节点分为两类:一个中心化协调者节点(coordinator)和N个参与者节点(participant)。每个参与者节点即上文背景介绍中的管理数据库副本的节点。


两阶段提交的思路比较简单,在第一阶段,协调者询问所有的参与者是否可以提交事务(请参与者投票),所有参与者像协调者投票。在第二阶段,协调者根据所有参与者的投票结果做出是否事务可以全局提交的决定,并通知所有的参与者执行该决定。在一两阶段提交流程中,参与者不能改变自己的投票结果。两阶段提交协议的可以全局提交的前提是所有的参与者都同意提交事务,只要一个参与者投票选择放弃事务,则事务必须被放弃。


两阶段提交者协调者流程

1.写本地日志”begin_commit”,并进入wait状态;

2.向所有参与者发送”prepare”消息;

3.等待并接收参与者发送的对”prepare”消息的响应;

3.1 若收到任何一个参与者发送的”vote-abort”消息;

3.1.1 写本地”global-abort”日志,进入ABORT;

3.1.2 向所有的参与者发送”global-abort”消息;

3.1.3 进入abort状态;

3.2 若收到所有参与者发送的”vote-commit”消息;

3.2.1 写本地”global-commit”日志,进入commit状态;

3.2.2 向所有的参与者发送”global-commit”消息;

4.等待并接收参与者发送的对”global-abort消息”或”global-commit消息”的确认响应信息,一旦收到所有参与者的确认消息,写本地”end_transaction”日志流程结束。


两阶段提交参与者流程

1.写本地日志”init”记录,进入INIT状态;

2.等待并接受协调者发送的”prepare消息”,收到后

2.1 若参与者可以提交本次事务

2.1.1 写本地日志”ready”,进入READY状态

2.1.2 向协调者发送”vote-commit”消息

2.1.3 等协调者的消息

2.1.3.1 若收到协调者的”global-abort”消息

2.1.3.1.1 写本地日志“abort”,进入ABORT状态

2.1.3.1.2 向协调者发送对”global-abort”的确认消息

2.1.3.2 若收到协调者的”global-commit”消息

2.1.3.2.1 写本地日志”commit”,进入COMMIT状态

2.1.3.2.2 向协调者发送对”global-commit”的确认消息

2.2若参与者无法提交本次事务

2.2.1 写本地日志”abort”,进入ABORT状态

2.2.2 向协调者发送”vote-abort”消息

2.2.3 流程对该参与者结束

2.2.4 若后续收到协调者的”global-abort”消息可以响应

3.即使流程结束,但任何时候收到协调者发送的”global-abort”消息或”global-commit”消息也都要发送一个对应的确认消息。


成功:


失败:


异常处理

宕机恢复

两阶段提交协议中,使用了日志技术从而在宕机后可以恢复流程状态。这里简单分析一下两阶段提交使用日志做宕机恢复的过程。


协调者宕机恢复

协调者宕机恢复后,首先通过日志查找到宕机前的状态。


如果日志中最后是”begin_commit”记录,说明宕机前协调者处于WAIT状态,协调者可能已经发送过”prepare消息”也可能还没发送,但协调者一定还没有发送过”global-commit消息”或者”global-abort消息”,即事务的全局状态还没有确定。此时,协调者可以重新发送”prepare消息”继续两阶段提交流程,即使参与者已经发送过对”prepare消息”的响应,也不过是再次重传之前的响应而不会影响协议的一致性。


如果日志中最后是”global-commit”或”global-abort”记录,说明宕机前协调者处于COMMOIT或ABORT状态。此时协调者只需重新向所有的参与者发送”global-commit”或”global-abort”消息就可以继续两阶段提交流程。


参与者宕机恢复

参与者宕机恢复后,首先通过日志查找宕机前的状态。


如果日志中最后是”init”记录,说明参与者处于INIT状态,还没有对本次事务做出投票选择,参与者可以继续流程等待协调者发送的”prepare消息”。


如果日志中最后是”ready”记录,说明参与者处于READY状态,此时说明参与者已经就本次事务做出了投票选择,但宕机前参与者是否已经向协调者发送”vote-commit”消息并不可知。所以此时参与者可以向协调者重发”vote-commit”,并继续协议流程。


如果日志最后是”commit”或”abort”记录,说明参与者已经收到过协调者的”global-commit消息”(处于COMMIT状态)或者”global-abort消息”(处于ABORT状态)。至于是否向协调者发送过对”global-commit”或”global-abort”的确认消息则未知。但即使没有发送过确认消息,由于协调者会不断重发”global-commit”或”global-abort”,只需在收到这些消息时发送确认消息即可,不影响协议的全局一致性。


响应超时

协议主要的异常最终会体现在流程中”等待消息”超时上,即等待了一个足量长的时间后,不能接收到需要的消息,使得流程无法进行下去。下面逐一分析这些超时的原因和对协议的影响。


协调者在WAIT状态超时

协调者在WAIT状态超时,即协调者等待参与者对”prepare消息”的响应超时,在超时时间内始终不能收到所有参与者的投票结果而收到的响应都是”vote-commit”消息,从而协调者无法确定该事务是否可以提交。这种超时的可能原因有:


1.协调者与某个参与者网络中断,协调者的”prepare”消息无法发送到参与者,或者参与者的响应消息无法发送到协调者。

2.参与者宕机,如果某个参与者宕机,则无法响应协调者的”prepare”消息,只有等到该参与者恢复才能响应消息。


对于这种超时,协调者可以选择直接放弃整个事务,向所有参与者发送”global-abort”消息,进入ABORT状态。由于协调者在超时前并没有发送任何”global-abort”或者”global-commit”消息,所有协调者此时放弃事务不影响协议的一致性。


协调者在COMMIT或ABORT状态超时

协调者在COMMIT或ABORT状态超时,即协调者等待参与者对”global-commit”或”global-abort”消息的响应时超时,从而协调者无法确认两阶段提交是否完成。这种超时可能的原因有:


1.协调者与某个参与者网络中断,协调者的”global-commit”或”global-abort”消息无法发送到参与者,或者参与者的响应消息无法发送到协调者。

2.参与者宕机,如果某个参与者宕机,则无法响应协调者的”global-commit”或”global-abort”,只有等待该参与者恢复后才能响应消息。


对于这种超时,协调者只能不断重发”global-commit”或”global-abort”消息给尚未响应的参与者,直到所有的参与者都发送响应。可以认为,两阶段提交协议对于这种超时的相关异常没有很好的容错机制,整个流程只能阻塞在这里,且流程状态处于未知。也许所有的参与者都完成了各自的流程,只是由于协调者无法收到响应,整个两阶段提交协议就无法完成。


参与者在INIT状态超时

参与者等待协调者的”prepare”消息超时,此种异常的原因可能是协调者宕机或者协调者与参与者网络中断。对于这种超时,参与者可以进入ABORT状态,这样即使后续收到了”prepare”消息,也不影响协议的一致性也不会阻塞其他流程,唯一的缺点就是,该事务可能原本就可以提交,现在却被放弃。


参与者在READY状态超时

参与者在READY状态等待协调者发送的”global-commit”或”global-abort”消息超时。出现这种超时的原因可能是协调者宕机也可能是网络中断。


因为参与者处于READY状态,说明参与者之前一定已经发送了”vote-commit”消息,从而参与者已经不能改变自己的投票选择。此时,参与者只能不断重发”vote-commit”消息,直到收到协调者的”global-commit”或”global-abort”消息后流程才可继续。可以这么认为,两阶段提交协议对于这种超时的相关异常也没有很好的容错机制,整个流程只能阻塞在这里,对于参与者而言流程处于未知状态,参与者即不能提交本地节点上的事务,也不能放弃本地节点事务。


协议分析

两阶段提交协议在工程实践中真正使用的较少,主要原因有以下几点:


第一、两阶段提交协议的容错能力较差。从上文的分析可以看出,两阶段提交协议在某些情况下存在流程无法执行下去的情况,且也无法判断流程状态。在工程中好的分布式协议往往总是可以在即使发生异常的情况下也能执行下去。例如,回忆lease机制,一旦lease发出,无论出现任何异常,lease服务器节点总是可以通过时间判定出lease是否有效,也可以用等待lease超时的方法收回lease权限,整个lease协议的流程不存在任何流程被阻塞而无法执行下去的情况,与lease机制的简单有戏相比,两阶段提交的效益显得较为复杂且容错能力差。


第二、两阶段提交协议的性能较差。一次成功的两阶段提交协议流程中,协调者与每个参与者之间至少需要两轮交互4个消息”prepare”、”vote-commit”、”global-commit”、”确认global-commit”。过多的交互次数会降低性能。另一方面,协调者需要等待所有的参与者的投票结果,一旦存在较慢的参与者,会影响全局的流程执行速度。


虽然存在一些改进的两阶段提交协议可以提高容错能力和性能,然而类似的协议依旧是工程中使用较少的一类协议,其理论价值大于实践意义。


参考资料:《分布式系统原理介绍》作者:刘杰



技术与生活,微服务架构,分享交流,不做井底之蛙。

长按二维码即可关注

好看|求转发