分布式一致性——二阶段提交协议,三阶段提交协议

分布式系统,这早已不是什么新鲜的东西了,在分布式系统的架构设计过程中,往往会存在分布式一致性的问题,经过前人的辛苦探究,提出了二阶段提交协议,三阶段提交协议,Paxos算法等等,用来解决分布式一致性的问题。今天我就讲一下,二阶段提交协议和三阶段提交协议的过程,以及它们的优缺点。

2PC,就是二阶段提交协议。顾名思义,就是将事务的提交过程分成两个阶段来处理。那我们来看看这两个阶段分别做了什么。

阶段一

   1.提交事务请求:

      协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应。

   2.执行事务:

      各个参与者节点执行事务操作。并将Undo和Redo信息记入事务日志中。

   3.各参与者向协调者反馈事务询问的响应:

      如果参与者成功执行了事务操作,那么就反馈给协调者Yes响应,表示事务可以执行;如果参与者没有成功执行事务,那么就反馈给协调者No响应,表示事务不可以执行。

阶段二:

   1.执行事务提交:

      假如协调者从所有参与者获得的响应都为Yes,那么就执行事务提交,协调者向所有参与者节点发出Commit请求。

   2.参与者提交事务:  

      参与者接收到Commit请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源,并向协调者发送Ack消息。

   3.完成事务:

      协调者接收到所有参与者反馈的Ack消息后,完成事务。

这里我画了一张时序图,来说明其中的过程,如图所示:

 

中断事务

假如任何一个参与者反馈给协调者No响应,或者在等待超时之后,协调者没有收到所有参与者的响应,那么就会中断事务。

   1.发送回滚请求

      协调者向所有参与者发送Rollback请求。

   2.事务回滚

     参与者接收到Rollback请求后,会利用其在阶段一中记录的Undo信息来执行事务回滚操作,完成回滚之后释放在整个事务执行期间占用的资源,并向协调者发送Ack消息,协调者接收到所有的参与者反馈的Ack消息后,完成事务中断。

中断的过程就是这样,下面一张时序图,描述中断事务的过程。

二阶段提交协议的整个过程就是这样,这种先请求后提交的方式,能够保证所有节点在进行事务处理过程中保持原子性,进行统一的提交或回滚,可以看作是一个强一致性的算法。那我们就来思考一下,它有哪些优点,以及会带来什么问题。

优点:简单好用,实现方便。

缺点:同步阻塞,单点问题,还是会有数据不一致的情况出现。

同步阻塞:在二阶段提交事务的过程中,所有参与该事务操作的逻辑都处于阻塞状态,各个参与者在等待其他参与者的响应过程中,无法做其他的操作。

单点问题:在二阶段提交协议中,协调者这个角色是很重要的,一旦协调者挂掉,将导致整个提交流程无法运转,若是在第二阶段协调者挂掉,参与者将一直处于锁定事务资源的状态,无法继续完成事务操作。

数据不一致:若是在第二阶段,协调者在向所有参与者发送commit请求时,由于局部网络的故障,只有部分参与者受到commit请求,提交事务,这样没有受到commit请求的就没有提交事务,出现了数据不一致的情况。

3PC,就是三阶段提交协议,顾名思义,就是把事务的提交过程分为三个阶段,实际上它就是在原来二阶段提交上做了一部分改进,那我们就来看看它的过程,以及它到底改进了什么。

阶段一:CanCommit

     事务询问:

     协调者向所有的参与者发送一个包含事务内容的canCommit请求,询问是否可以执行事务提交操作,并开始等待各个参与者的响应。参与者接收请求后,正常情况下,如果认为其自身可以顺利执行事务,那么会反馈Yes响应,并进入预备状态,否则反馈No响应。

阶段二:PreCommit

在阶段二,协调者根据各参与者的反馈情况来决定是否可以进行事务的PreCommit操作,正常情况下,包含两种可能。一种是参与者全部反馈Yes响应,则进入预提交阶段,另一种时有参与者反馈No响应或者等待超时后,没有收到全部的反馈响应,则会中断事务,我们来分别看一下这两种情况。

第一种:预提交:

   协调者向所有的参与者发送PreCommit请求,并进入Prepared阶段。

   参与者接收到preCommit的请求,执行事务操作,并将Undo和Redo信息记录到事务日志中,然后向协调者反馈Ack响应,等待最终的指令提交或中止

第二种:中断事务:

   协调者向所有的参与者发出abort请求,参与者无论是收到协调者的abort请求,还是等待请求过程中超时,参与者都会中断事务。

阶段三:doCommit

这一阶段,即将开始真正的事务提交,同样也会存在两种可能的情况。

第一种:执行提交:

   1.发送提交请求:

      进入这一阶段:假如协调者处于正常的工作状态,并且它接收到了来自所有参与者的Ack响应,那么它将从预提交状态转换到提交状态,并向所有的参与者发送doCommit请求。

   2.事务提交:

     参与者接收到doCommit请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源,并向协调者发送Ack消息。协调者接收到所有参与者反馈的Ack消息后,完成事务。

第二种:中断事务:

   进入这一阶段,假设协调者处于正常状态(这个很关键),并且有任意一个参与者向协调者反馈了No响应,或者等待超时之后,协调者无法接收到所有参与者的反馈响应。那么就会中断事务。

   协调者向所有参与者节点发送abort请求。

   各个参与者接收到abort请求后,会利用其在阶段二中记录的Undo信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源,并向参与者发送Ack消息。协调者接收到所有参与者反馈的Ack消息后,中断事务。

一旦进入到阶段三,可能会存在两种故障:1.协调者出现问题;2.协调者和参与者之间的网络出现故障。

无论出现哪种情况,最终都会导致参与者无法及时接收到来自协调者doCommit或是abort请求,针对这样的异常情况,参与者都会在等待超时之后,继续提交事务操作。

到这里三阶段提交协议就讲完了,相比较于二阶段提交协议,它降低了参与者的阻塞范围,并在出现单点故障后,继续达成一致。同样三阶段提交协议也会带来数据不一致的问题,在第二阶段,协调者向所有参与者发送preCommit请求时,如果这时网络出现问题,协调者无法和参与者进行正常通信,参与者此时仍然会提交事务,而协调者等待超时后,进行事务中断,这样就会带来数据不一致的情况。

总结:二阶段提交协议和三阶段提交协议都是解决分布式数据一致性问题的优秀算法,尽管从根本上并不能保证绝对的一致性,但也并不影响它们在实际工程上的使用。有句话是这么说的,在存在消息丢失的不可靠信道上试图通过消息传递的方式达到一致性是不可能的,有兴趣的朋友可以研究一下paxos算法以及它的证明过程。