ZAB协议

zab协议是zookeeper的核心内容,学好zab对于理解zookeeper是最基本的一环,本文主要介绍ZAB协议。

主要内容

所有事物请求必须由一个全局唯一的服务器来协调处理。这样的服务器成为Leader服务器,而余下的其他服务器则成为Follwer服务器。Leader服务器负责将一个客户端事物请求转换成一个事物Proposal(提议),并将改Proposal分发给集群中所有的Follwer服务器。之后Leader需要等待所有Follower的反馈,一旦超过半数的Follower服务器进行了正确的反馈后,那么Leader就会再次向所有的Follower服务器分发Commit消息,要求其将前一个Propersal进行提交。

协议介绍

ZAB协议两种基本的模式为崩溃恢复和消息广播。

当整个服务框架在启动中,或者当Leader服务器出现网络中断、崩溃退出与重启等异常情况时,zab协议就会进入崩溃恢复模式并选举出新的Leader服务器。当选举产生了新的Leader服务器,同时集群中已经有过半的服务器与新的Leader服务器完成状态同步之后,ZAB协议就会退出崩溃恢复模式。

其中,状态同步是指数据同步,用来保证集群中存在过半的机器能够和Leader服务器数据状态保持一致。当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步,那么整个集群就会进入消息广播模式了。

当一台同样遵守ZAB协议的服务启动后加入到集群中时,如果集群中已经有一个Leader服务器在负责进行消息广播,那么新加入的服务器就会直接进入数据恢复模式(找到Leader所在的服务器,并与其进行数据同步)然后参与到消息广播中。因为ZAB协议中只允许唯一的一个Leader服务器来进行事务请求的处理。

Leader服务器在接收到客户端的事务请求后,会生成对应的事务提案并发起一轮广播;而如果集群中的其他机器接收到客户端的事务请求,那么这些非Leader服务器首先会将这个事务请求转发给Leader服务器,由Leader服务器发起一轮广播。

当Leader服务器出现崩溃退出或者机器重启,或者集群中已经不存在过半的服务器与该Leader服务器保持正常通讯时,那么重新开始新一轮的原子广播事务操作之前,所有集群中的进程首先会使用崩溃恢复协议来使彼此达到一个一致的状态。于是整个ZAB流程就会从消息广播模式进入到崩溃恢复模式。

一个机器要成为新的Leader,必须获得过半的进程的支持,同时由于每个进程都有可能会崩溃,因此ZAB协议在运行期间,前后会出现多个Leader,并且一个进程可能会多次成为Leader。进入崩溃恢复模式后,只要集群中存在过半的服务器能够彼此进行正常通信,那么就可以产生一个新的Leader并在此进入消息广播模式。

消息广播

 ZAB协议的消息广播过程使用的是一个原子广播协议,类似于一个二阶段提交过程,针对客户端的事务请求,Leader 服务器会为其生成对应的事务Proposal,并将其发送给集群中其他所有的服务器,然后再分别收集各自的选票,最后进行事务提交。如下如图所示:

 


ZAB协议


 

ZAB协议中涉及的二阶段提交和2pc有所不同。在ZAB协议的二阶段提交过程中,移除了中断逻辑,所有Follower服务器要么正常反馈Leader提出的事务Proposal,要么就抛弃Leader服务器。ZAB协议中,只要集群中过半的服务器已经反馈ACK,就开始提交事务了,不需要等待集群中所有的服务器都反馈响应。这种模型是无法处理Leader服务器崩溃退出而带来的数据不一致问题的,因此在ZAB协议中添加了另一个模式,即采用崩溃恢复模式来解决这个问题。此外,整个消息广播协议是基于具有FIFO特性的TCP协议来进行网络通信的,因此能够很容易保证消息广播过程中消息接受与发送的顺序性。

在整个消息广播过程中,Leader服务器会为每个事务请求生成对应的Proposal来进行广播,并且在广播事务Proposal之前,Leader服务器会首先为这个事务分配一个全局单调递增的唯一ID,我们称之为事务ID(即ZXID)。由于ZAB协议需要保证每一个消息严格的因果关系,因此必须将每一个事务Proposal按照其ZXID的先后顺序来进行排序与处理。

在消息广播过程中,Leader服务器会为每一个Follower服务器各自分配一个单独的队列,然后将需要广播的事务Proposal依次放入这些队列中,并且根据FIFO策略进行消息发送。每一个Follower服务器在接受到这个事务Proposal之后,都会首先将其以事务日志的形式写入到本地磁盘中去,并且在成功写入后反馈给Leader服务器一个ACK响应。当Leader服务器接收到超过半数Follower的ACK响应后,就会广播一个Commit消息给所有Follower服务器以通知其将事务进行提交,同时Leader自身也会完成事务的提交,而每一个Follower服务器收到Commit消息之后,也会完成对事务的提交。

 

崩溃服务

一旦Leader服务器出现崩溃,或者说网络原因导致Leader服务器失去了与过半的Follower的联系,那么就会进入崩溃恢复模式。为了保证程序的正常运行,整个恢复过程后需要选举一个新的Leader服务器。因此,ZAB协议需要一个高效可靠的Leader选举算法,从而确保能够快速的选举出新的Leader。同时,新的Leader选举算法不仅仅需要让Leader自己知道其自身已经被选举为Leader,同时还需要让集群中所有的其他机器也能够快速的感知选举产生的新的Leader服务器。

ZAB协议规定了如果一个事务Proposal在一台机器上被处理成功,那么应该在所有的机器上都被处理成功,哪怕机器出现崩溃。

接下来看看在崩溃恢复过程中,可能出现的两个数据不一致的隐患以及针对这些情况ZAB所需要保证的特性。

ZAB协议需要确保那些已经在Leader服务器上提交的事务最终被所有服务器都提交

假设一个事务在Leader服务器上被提交了,并且已经得到了过半Follower服务器的ACK反馈,但是它将在Commit消息发送给所有Follower机器之前,Leader服务器挂了,ZAB协议需要确保这个事务最终能够在所有的服务器上提交成功,否则将出现不一致。

ZAB协议需要确保丢弃那些只在Leader服务器上被提出的事务

如果在崩溃恢复过程中出现一个需要被丢弃的提案,那么崩溃恢复后需要跳过该事务Proposal。

 

ZAB协议必须设计这样一个Leader选举算法,能够确保提交已经被Leader提交的事务Proposal,同时丢弃已经被跳过的事务Proposal。针对这个要求,如果让Leader选举算法能够保证新选举出来的Leader服务器拥有集群中所有机器最高编号(即ZXID最大)的事务Proposal,那么就可以保证这个新选举出来的Leader一定具有所有已经提交的提案。更重要的是,如果让具有最高编号事务Proposal的机器来成为Leader,就可以省去Leader服务器检查Proposal的提交和丢弃这一步操作了。

 

数据同步

完成Leader选举之后,在正式开始工作(即接收客户端的事务请求,然后提出新的提案)之前,Leader服务器会首先确认事务日志中的所有Proposal都已经被集群中过半的机器提交了,即是否完成数据同步。

集群中所有的正常运行的服务器,要么成为Leader,要么成为Follower并和Leader保持同步。Leader服务器需要确保所有的Follwer服务器能够接收到每一条事务Proposal,并且能够正确地将所有已经提交了的事务Proposal应用到内存数据库中。Leader服务器会为每一个Followe服务器都准备一个队列,并将没有被各Follower服务器同步的事务以Proposal消息形式逐个发送到Follower服务器,并在每一个Proposal消息后紧跟着再发送一个Commit消息,以表示这个事务已经被提交。等到Follower服务器将所有尚未同步的事务Proposal都从Leader服务器上同步过来并成功应用到本地数据库中后,Leader服务器就会将改Follower服务器加入到真正可用的Follower列表中,并开始之后的其他流程。

ZAB协议如何处理那些需要被丢弃的事务Proposal的?在ZAB协议的事务编号ZXID设计中,ZXID是一个64位数字,其中低32位可以看做一个简单的单调递增的计数器,针对客户端的每一个事务请求,Leader服务器在产生一个新的事务Proposal的时候,都会对改计数器进行加一操作;而高32位则代表了leader周期epoch的编号,每当选举产生一个新的Leader服务器,就会从这个Leader服务器取出本地日志中最大事务Proposal的ZXID,并从ZXID中解析出对应的epoch值,然后再对其进行加1操作,之后的编号就会作为新的epoch,并将低32位  置0来开始生成新的ZXID。ZAB协议中的这一通过epoch编号来区分Leader周期变化的策略,能够有效避免不同的Leader服务器错误的使用相同的ZXID编号提出不一样的事务的情况。

基于这样的策略,当一个包含了上一个Leader周期中尚未提交过的事务服务器启动时,肯定无法成为leader。因为当前集群中肯定包含一个Quorum集合,该集合中机器一定包含了更高的epoch的事务Proposal。



深入了解ZAB协议

通常在由一组进程P[]= {P1,P2,P3...Pn}组成的分布式系统中,其每一个进程都具有各自的存储设备,各进程之间通过相互通信来实现消息的传递。一般的,在这样的一个分布式系统中,每一个进程随时有可能会出现一次或多次的崩溃退出,当然,这个进程会在恢复之后再次加入到进程组P[]中去。如果一个进程正常工作,那么称该进程处于UP状态,如果进程崩溃了,称为DOWN状态。如果说,集群中存在过半的处于UP状态的进程组组成一个进程子集之后,就可以进行正常的消息广播了。

主进程周期

为了保证主进程每次广播出来的事务消息都是一致的,我们必须确保ZAB协议只有在充分完成崩溃恢复阶段之后,新的主进程才可以使开始生成新的事务消息广播。为了实现这个目的,我们假设各个进程都实现了类似于ready(e)这样的一个函数调用,在运行过程中,ZAB协议能够非常明确的告诉上层系统(指主进程和其他副本进程)是否可以开始进行事务消息广播,同时,在调用ready(e)函数之后,ZAB还需要为当前主进程设置一个实例值。实例值用于唯一标识当前主进程的周期,在消息广播的时候,主进程使用该实例值来设置事务标识中的epoch字段——当然,ZAB协议需要保证实例值在不同的主进程周期中是全局唯一的。
























上一篇:Cassandra开山鼻祖:数据驱动企业变革将会是企业的新价值高地


下一篇:vs2017通过snippet代码片断进行标准化注释