首先我们要了解,什么是分布式一致性问题?
这个问题,我还真没找到明确定义。不过我个人的理解是:在一个分布式系统中,如何使多方达到一致性状态的问题,包括不同数据副本之间的一致性,数据修改的一致性,决策的一致性等等。关于这个问题,人们提出了许多经典的解决方案,下面我们就简单介绍几种。
2PC
是Two-Phase-Commit的缩写,即二阶段提交,是计算机网络尤其是在数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务处理过程中能保持原子性和一致性而设计的一种算法。
顾名思义,二段提交协议是将事务的提交过程分成了两个阶段来进行处理。
阶段一:提交事务请求。阶段二:执行事务提交。
用大白话简单讲就是:协调者(协调分布式一致性的角色)向各个参与者(真正执行事务的角色)询问,你们的事务能不能做啊?都吱个声。然后各个参与者就执行事务操作,但并不提交,不论执行成功还是失败,都向协调者汇报。至此,第一阶段结束。倘若第一阶段协调者收到的都是执行成功的消息,那么就进行事务提交操作,直至所有参与者都提交成功。但凡有一个参与者执行失败或等待超时,那么则进行事务回滚操作,直至所有参与者都回滚成功。
3PC
讲三阶段提交前,我们来看一看二阶段提交存在的优缺点。
优点是:原理简单,实现方便。缺点也很明显:同步阻塞,单点问题,脑裂。
二阶段提交由于同步阻塞,明显存在效率低下的问题,终归是太保守了。因而人们又提出了三阶段提交:
1.CanCommit:
这个阶段主要是协调者询问参与者是否可以执行事务提交操作,目的是保证执行分布式事务之前,各个参与者是处于一种一致性状态。
2.PreCommit:
这一阶段与2PC的第一阶段类似,只要CanCommit返回的都是成功,则执行事务操作但不提交;否则中断事务。
3.DoCommit
这一阶段与3PC的第二阶段类似,只要PreCommit返回的都是成功,则提交事务;否则回滚事务。但值得注意的是,一旦进入第三阶段,如果出现故障导致协调者与参与者之间无法进行通信,即参与者无法及时接收到来自协调者的doCommit或是abort请求,针对这种情况,参与者都会在等待超时之后,继续进行事务提交。
由此可见,3PC最大的优点就是降低了参与者阻塞范围(在第三阶段),并且能够在出现单点故障后继续达成一致。但很明显存在数据不一致性风险。
Paxos
Paxos是一种提高分布式系统容错性的一致性算法,也可以说是最著名的一致性算法了。甚至Google Chubby的作者Mike Burrows说过这个世界上只有一种一致性算法,那就是Paxos,其它的算法都是残次品。
Paxos算法也是出了名的难理解,但它本质上也是个分两阶段提交的选举算法,利用了鸽巢原理,遵循“过半与最新”原则。
阶段一:Prepare阶段
1.1【倡议者视角】倡议者选择倡议编号n,然后向大多数(即超过半数以上)接受者发送Prepare请求,请求中附带倡议编号n。
1.2【接受者视角】对于某个接受者来说,如果接收到带有倡议编号n的Prepare请求,则做如下判断:若倡议编号n比此接受者之前响应过的任何其它Prepare请求附带的倡议编号都大,那么此接受者会给倡议者以响应,并承诺不会响应之后接收到的其它任何倡议编号小于n的请求,另外,如果接受者曾经响应过2.2阶段的Accept请求,则将所有响应的Accept请求中倡议编号最高的倡议内容发送给倡议者,倡议内容包括两项信息:Accept请求中的倡议编号以及其倡议值。若倡议编号n不比此接受者之前响应过的任何其它Prepare请求附带的倡议编号都大,那么此接受者不会给倡议者以响应。
阶段二:Accept阶段
2.1【倡议者视角】如果倡议者接收到过半接受者关于带有倡议编号n的Prepare请求的响应,那么倡议者向这些接受者发送Accept请求,Accept请求附带两个信息:倡议编号n以及倡议值v。倡议值v的选择方式如下:如果在1.2阶段接受者返回了自己曾经接受的具有最高倡议编号Accept请求倡议内容,则从这些倡议内容里面选择倡议编号最高的并将其倡议值作为倡议值v;如果1.2阶段没有收到任何接受者的Accept请求倡议内容,则可以自主任意赋值给倡议值v。
2.2【接受者视角】如果接受者接收到了任意倡议编号为n的Accept请求,则接受者接受此请求,除非在此期间接受者响应过具有比n更高编号的Prepare请求。这里有个特点,一旦接受了Accept请求后,这个倡议值v值就定下了。
通过以上两阶段过程即可选出唯一的倡议值,对于 学习者来说,其需要从接受者那里获知到底是哪个倡议值被选出。一个直观的方法如下:每当接受者执行完2.2步骤,即接受某个Accept请求后,由其通知所有学习者其所接受的倡议,这样,学习者很快习得是哪个倡议被最终选出。但是这种方式会导致大量通信,因为任意一个接受者会通知任意一个学习者,如果有m个接受者,n个学习者,则需要m*n次通信。一个替代策略是: 从众多学习者中选择一个作为代表,由其从接受者那里获知最终被选出的倡议,然后再由其通知其它学习者,这样可以将通信量降为m+n。但是这个方案中如果这个学习者代表发生故障,其它学习者无从知晓倡议值。考虑到健壮性和通信量两个因素,可以采取折中方法:选出若干学习者作为代表,由这些代表从接受者那里获知最终倡议值,然后通知其它学习者。通过以上流程,如果有多个并发进程提出各自的倡议值, Paxos就可以保证从中选出且只选出一个唯一确定的倡议值,以此来达到副本状态机保持状态一致的目标。
此文只是对Paxos的应用场景以及Paxos协议本身进行了介绍,而 Paxos最难理解性在于是什么因素导致协议以此种方式呈现以及其正确性证明过程而非最终协议本身内容。
ZAB
ZAB(Zookeeper Atomic Broadcast)协议是专门为zookeeper设计的一致性协议。为什么没有直接使用Paxos算法呢?这里就要说到Paxos算法的缺点了。
Paxos算法虽然通用,可靠,但终归效率太低。Paxos算法在出现竞争的情况下,其收敛速度很慢,甚至可能出现活锁的情况,例如当有三个及三个以上的proposer在发送prepare请求后,很难有一个proposer收到半数以上的回复而不断地执行第一阶段的协议。因此,为了避免竞争,加快收敛的速度,在算法中引入了一个Leader这个角色,在正常情况下同时应该最多只能有一个参与者扮演Leader角色,而其它的参与者则扮演Acceptor的角色。
在这种优化算法中,只有Leader可以提出议案,从而避免了竞争使得算法能够快速地收敛而趋于一致;而为了保证Leader的健壮性,又引入了Leader选举,再考虑到同步。
ZAB协议包括两种基本的模式:崩溃恢复和消息广播
当整个服务框架在启动过程中,或是当Leader服务器出现网络中断崩溃退出与重启等异常情况时,ZAB就会进入恢复模式并选举产生新的Leader服务器。
当选举产生了新的Leader服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后,ZAB协议就会退出崩溃恢复模式,进入消息广播模式。
当有新的服务器加入到集群中去,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么新加入的服务器会自动进入数据恢复模式,找到Leader服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。
以上其实大致经历了三个步骤:
1.崩溃恢复:主要就是Leader选举过程。
2.数据同步:Leader服务器与其他服务器进行数据同步。
3.消息广播:Leader服务器将数据发送给其他服务器。