深入理解两阶段提交协议

Posted by Thh on April 9, 2016

两阶段提交协议(two phase commit protocol,以下简称2PC协议)作为最简单原子提交协议,在很多需要使用分布式事务的场景中会经常用到。下面将尝试深入而简单的阐释2PC协议,并给出一个简单的demo实现。

2PC协议中的两种角色

2PC协议中存在着一个协调者(Coordinator)和多个参与者(Participant)。协调者负责接收参与者发起的事务请求,向所有参与者发起投票,收集投票结果,做出Commit或者Abort决定,并将决定通知给参与者。参与者负责监听来自协调者的投票请求,以及最后的决定。一旦协调者的决定是Abort,所有参与者都必须回滚本地事务,否则所有参与者都提交本地事务。

2PC协议的一般过程

假设各个环节没有异常和失败,典型的2PC协议过程是这样的。

  1. 协调者给所有的参与者发送投票请求。
  2. 参与者收到投票请求,会给协调者回复YES或者NO。如果回复NO, 那该参与者决定Abort,并结束流程。
  3. 协调者收集齐所有参与者的投票,如果所有参与者都投YES,并且协调者本身投YES,那协调者将决定Commit,并将Commit消息发送给所有参与者。否则,协调者决定Abort,并将Abort消息发送给所有投YES票的参与者(投NO票的参与者在第2步时已经中止,不用通知)。协调者结束流程。
  4. 每个投YES票的参与者等待协调者的Commit或者Abort消息。当收到消息时,依据消息做出决定,然后结束流程。

其中1、2步是投票阶段,3、4步是决定阶段。

异常处理

所有分布式系统都必须能够处理网络中断、超时或系统故障等等异常情况,2PC协议也不例外。因此,这里将逐步分析2PC协议各个阶段可能会出现的异常,以及可用的处理方式。

第1阶段

协调者给参与者发送投票请求,有可能发送的过程中协调者系统故障。故障恢复以后,协调者需要能够决定故障前正在请求的事务的状态。为了应对这种情况下,需要在协调者开始给参与者发送投票请求之前,记录状态为start2PC。这样在故障恢复以后,通过查看状态记录,协调者知道当时的事务投票请求发送时出现了故障,于是可以直接决定Abort,并记录状态Abort。

如果发送投票过程中,参与者故障或者参与者与协调者网络超时或中断,导致发送失败,都视为投票失败,协调者可直接直接决定Abort,并记录状态Abort。

two-phase-commit-vote-process

第2阶段

参与者在投票之后,到收到协调者回复之前,这段时间对事务的最终状态是不确定的。如果由于某种原因,没有收到协调者的回复,该参与者必须通过向协调者或其他参与者咨询来确定最终决定,这个过程叫cooperative termination。

cooperative_term

参与者收到请求,如果投票YES, 需要先记录状态YES, 然后再回复YES协调者。这样,如果参与者在发送请求之后挂掉,通过查看状态记录,知道自己投了YES,但最终决定还需要向协调者或者其他参与者咨询。如果得知最后的决定是Commit,此时参与者就提交本地事务,并记录状态为Commit;如果最后的决定是Abort,参与者就回滚本地事务,并记录状态为Abort。

如果参与者投票NO,情况就比较简单,直接记录Abort即可。

第3阶段

协调者在等待参与者回复的时候,可能由于参与者系统故障、网络中断或超时,导致没有收到回复。此时协调者直接决定Abort, 记录Abort状态,并把决定发送给参与者。而故障恢复之后的参与者可自己向协调者查询事务决定,然后根据决定处理本地事务。

协调者收到参与者回复之后,如果决定Commit,则先记录状态Commit,然后发送Commit消息给所有参与者。这样,如果协调者在记录Commit状态之前故障,那恢复之后,通过检查会发现状态是start2PC,协调者可以直接决定Abort;如果协调者在记录Commit状态之后故障,此时可能已通知部分参与者,等到故障恢复时,未收到Commit通知的参与者可咨询协调者最终的决定。

第4阶段

参与者在等待协调者决定的时候,可能出现故障,也可能协调者的消息由于网络原因没能送达。这种情况下,参与者可在故障恢复后或者超时后咨询协调者或者其他参与者,获取最终的决定。

历史记录的删除

如果2PC协议中的参与者或者协调者要删除事务的历史记录,必须满足以下几个条件,才能保证事务删除之后对其他参与者的事务最终状态的确定不产生影响。

  1. 必须保证本地删除的记录是最终状态(Commit或者Abort状态)。
  2. 必须保证删除的事务在其他参与者那也是最终状态。

第一条是为了保障所有未做出决定的事务不会被删除,因为可能会在下次故障恢复中做出决定;第二条是为了保证如果有参与者的本地事务不是最终状态,通过咨询协调者或者其他参与者,可以得到该事务的最终状态。

在实际操作中,可以采取这样的策略:参与者删除记录时,只考虑第1条,而协调者同时考虑1、2两条,这样协调者可以保证如果有事务的状态不是最终状态,找它就可以确定。

Demo实现

我实现了一个2PC协议的demo项目:https://github.com/segeon/2pc。该demo除了没实现历史记录删除的约束外,其余的协议要求和异常处理都已实现。

参考资料

http://research.microsoft.com/en-us/people/philbe/chapter7.pdf