两阶段提交协议是一种经典的强一致性中心化副本控制协议。工程中该协议有较多的问题。
(1)问题背景
两阶段提交(two phase commit)协议最早用在分布式数据库中,实现分布式事务。分布式数据库中,需要一种控制协议,使得事务要么在所有的副本上都提交,要么在所有副本上都失败,但对同一事务而言,不同副本可能出现事务冲突,不能提交的现象。
(2)流程描述
l 节点类型:一个中心化协调者节点(coordinator)和N个参与者节点(participant),每个参与者节点即上体提到的管理数据库副本的节点。
l 思路:在第一阶段,协调者询问所有的参与者是否可以提交事务(请参与者投票),所有参与者向协调者投票。在第二阶段,协调者根据所有参与者的投票结果做出是否事务可以全局提交的决定,并通知所有参与者执行该决定。
l 注:在两阶段提交流程中,参与者不能改变自己的投票结果。两阶段提交协议的可以全局提交的前提是所有参与者都同意提交事务,只要有一个参与者投票选择放弃(abort)事务,则事务必须被放弃。
l 协议流程如下:
n 两阶段提交协调者流程
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”日志流程结束
l 两阶段提交协调者流程
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”消息也都要发送一个对应的确认消息
(3)异常处理
a)宕机恢复
[1]协调者宕机恢复
协调者宕机恢复后,首先通过日志查找到宕机前的状态
若日志最后是“begin_commit”记录
n 状态:宕机前协调者处于WAIT状态,prepare消息不确定发没发,但global-commit或global-abort一定没发
n 操作:协调者可以重发prepare消息,继续两阶段提交流程,即使参与者已发过对prepare消息的想要,也不过是再次重传之前的响应不影响协议的一致性
若日志最后是“global-abort”或“global-commit”记录
n 状态:宕机前协调者处于COMMIT或ABORT状态
n 操作:协调者只需重新向所有的参与者发送“global-abort”或“global-commit”就可以继续两阶段提交流程
[2]参与者宕机恢复
参与者宕机恢复后,首先通过日志查找宕机前的状态。
若日志最后是“init”记录
n 状态:参与者处于INIT状态,还没有对本次事务做出投票选择
n 操作:参与者可以继续流程等待协调者发送的“prepare”消息
若日志最后是“ready”记录
n 状态:参与者处于READY状态,参与者已经就本次 做出了投票选择
n 操作:宕机前参与者是否已向协调者发送“vote-commit”消息并不可知,故此时参与者可以向协调者重发“vote-commit”,并继续协议流程
若日志最后是“commit”或“abort”记录
n 状态:参与者处于COMMIT状态或ABORT状态,已经收到协调者的“global-commit”或“global-abort”消息
n 操作:参与者是否向协调者发送过确认消息未可知,但即使没发送过,由于协调者会不断重发“global-abort”或“global-commit”,只需在收到这些消息时发送确认消息即可,不影响全局一致性
b)响应超时
协议的主要异常最终体现在流程中“等待消息”超时上,即等待了一个足够长的时间后,不能接收到需要的消息,使得流程无法进行下去
[1]协调者在WAIT状态超时
即协调者等待参与者对“prepare消息”的响应超时,在超时时间内个不能收到所有的参与者的投票结果而收到的响应都是“vote-commit”消息,从而协调者无法确定该事务是否可以提交,这种超时可能的原因有:
协调者与某个参与者网络中断,协调者的“prepare”无法送到参与者,或参与者的响应消息无法发送到协调者
参与者宕机,无法响应协调者,只有等该参与者恢复后才能响应
处理:协调者可直接放弃整个事务,向所有参与者发送“global-abort”,进入ABORT状态,由于此前并未发送过“global-abort”或“global-commit”,故不影响协议一致性
[2]协调者在COMMIT或ABORT状态超时
协调者在COMMIT或ABORT状态超时,即协调者等待参与者对“global-commit”或“global-abort”的响应时超时,从而协调者无法确认两阶段提交是否完成,这种超时可能的原因有:
l 协调者与某个参与者网络中断,协调者的“global-commit” 或“global-abort”消息无法发送到参与者,或者参与者的响应消息无法发送到协调者。
l 参与者宕机,如果某个参与者宕机,则无法响应协调者的“global-commit” 或“global-abort”,只有等该参与者恢复后才能响应消息。
处理:协调者智能不断重发“global-abort”或“global-commit”消息给尚未响应的这,知道所有参与者都发送响应。
分析:两阶段提交协议对这种超时的相关异常没有很好的容错机制,整个流程只能阻塞在这里,且流程状态未知。可能所有参与者都完成了各自流程,指示由于协调者无法收到相应,整个两阶段提交协议就无法完成
c)参与者在INIT状态超时
参与者等待协调者的“prepare”消息时超时
原因可能是协调者宕机或协调者与参与者网络中断
对于这种超时,参与者可以进入ABORT状态
这样即使后续收到了“prepare”消息,也不影响协议的一致性也不会阻塞其他流程
唯一缺点是,该事务原本可能可以提交,现在却被放弃
d)参与者在READY状态超时
参与者在READY状态等待协调者发送的“global-commit”或“global-abort”消息超时
原因:可能是协调者宕机也可能是网络中断
处理:因为READY状态意味着参与者已发送过“vote-commit”消息,由于参与者不能改变自己的决定,故只能不断重发“vote-commit”,直到收到协调者的“global-abort”或“global-commit”后流程才可继续
分析:两阶段提交协议对这种超时的相关异常没有很好的容错机制,整个流程只能阻塞在这里,且流程状态未知。参与者既不能提交本地节点上的事务,也不能放弃本地节点事务。
(4)协议分析
两阶段提交协议在工程实践中使用较少的原因有以下几点:
复杂且容错能力较差,两阶段提交协议在某些情况下存在流程无法执行下去,且也无法判断流程状态的情况。工程中好的分布式协议应该在发生异常的情况下也能执行下去。如lease机制,一旦lease发出,无论发生任何异常,lease服务器节点总可以通过时间判定lease是否有效,也可等待lease超时来收回lease权限,整个流程不存在被阻塞无法执行下去的情况。
性能较差,一次成功的流程中,协调者与每个参与者之间至少需要两轮交互,4个消息“prepare”、“vote-commit”、“global-commit”、“确认global-commit”。过多的交互次数会降低性能。另一方面,协调者需要等待所有的参与者的投票结果,一旦存在较慢的参与者,会影响全局流程执行速度