两阶段提交协议

两阶段提交协议是一种经典的强一致性中心化副本控制协议。

虽然在工程中该协议有较多的问题,但研究该协议能很好的理解分布式系统的几个典型问题。

问题背景

两阶段提交(two phase commit )协议是一种历史悠久的分布式控制协议。

最早用于在分布式数据库中,实现分布式事务。

这里有必要首先简单介绍一下两阶段提交的最初问题背景,从而能更好的理解该协议。

业务背景

在经典的分布式数据库模型中,同一个数据库的各个副本运行在不同的节点上,每个副本的数据要求完全一致。

数据库中的操作都是事务 ( transaction),一个事务是一系列读、写操作,事务满足ACID。

每个事务的最终状态要么是提交 ( commit),要么是失败(abort) 。一旦一个事务成功提交,那么这个事务中所有的写操作中成功,否则所有的写操作都失败。

在单机上 ,事务靠日志技术或 MVCC 等技术实现。

在分布式数据库中,需要有一种控制协议,使得事务要么在所有的副本上都提交,要么在所有的副本上都失败。

对同一个事务而言,虽然在所有副本上执行的事务操作都完全一样,但可能在某些副本上可以提交,在某些副本上不能提交。

这是因为,在某些副本上,其他的事务可能与本事务有冲突(例如死锁),从而造成在有些副本上事务可以提交,而有些副本上事务无法提交。

流程描述

按本文的分类,两阶段提交协议是一种典型的“中心化副本控制”协议。

在该协议中,参与的节点分为两类:

一个中心化协调者节点( coordinator )和 N 个参与者节点( participant )。

每个参与者节点即上文背景介绍中的管理数据库副本的节点。

两阶段提交的思路比较简单。

在第一阶段,协调者询问所有的参与者是否可以提交事务(请参与者投票) 所有参与者向协调者投票 。

在第二阶段,协调者根据所有参与者的投票结果做出是否事务可以全局提交的决定, 并通知所有的参与者执行该决定。

在一个两阶段提交流程中,参与者不能改变自己的投票结果。

两阶段提交协议的可以全局提交的前提是所有的参与者都同意提交事务,只要有一个参与者投票选择放弃(abort)事务,则事务必须被放弃 。

协议流程

两阶段提交协调者流程

(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.1.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.4 等待协调者的消息

2.1.4.1 若收到协调者的“ global abort ”消息 2.1.4.1.1 写本地日志“ abort 进入 ABORT 状态 2.1.4.1.2向协调者发送对“ global abort ”的确认消息

2.1.4.2 若收到协调者的“ global commit ”消息 2.1.4.2.1 写本地日志“ commit ””,进入 COMMIT 状态 2.1.4.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 ”记录,说明宕机前协调者处于 COMMIT 或 ABORT 状态。

此时协调者只需重新向所有的参与者发送“ global commit 消息”或“ global abort 消息”就可以继续两阶段提交流程。

参与者宕机恢复

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

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

如果日志中最后是“ready 记录,说明参与者处于 REDAY 状态,此时说明参与者已经就本次事务做出了投票选择,但宕机前参与者是否已经向协调者发送“ 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 机制( 2.3 ),一旦 lease 发出,无论出现任何异常, Lease 服务器节点总是可以通过时间判定出 Lease 是否有效,也可以用等待 Lease 超时 的方法收回 Lease 权限,整个 Lease 协议的流程不存在任何流程被阻塞而无法执行下去的情况。

与Lease 机制的简单有效相比,两阶段提交的协议显得较为复杂且容错能力差。

第二、两阶段提交协议的性能较差。

一次成功的两阶段提交协议流程中, 协调者与每个参与者之间至少需要两轮交互 4 个消息“ prepare 、“ vote commit ”、 global commit ”、“确认 global commit ”。

过多的交互次数会降低性能。

另一方面,协调者需要等待所有的参与者的投票结果,一旦存在较慢的参与者,会影响全局流程执行速度。

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

参考资料

《分布式系统原理》