1. zookeeper中的一致性协议-ZAB协议

在深入了解ZK之前,相信很多同学都会认为ZK就是Paxos算法的一个实现。但事实上,ZK并没有完全采用Paxos算法,而是使用了一种称为ZooKeeper Atomic Broadcast(ZAB,ZooKeeper原子消息广播协议)的协议作为其数据一致性的核心算法。

ZAB协议是为分布式协调服务ZooKeeper专门设计的一种支持崩渍恢复的原子广播协议。ZAB协议的开发设计人员在协议设计之初并没有要求其具有很好的扩展性,最初只是为雅虎公司内部那些高吞吐量、低延迟、健壮、简单的分布式系统场景设计的。在ZooKeeper的官方文档中也指出,ZAB协议并不像Paxos算法那样,是一种通用的分布式一致性算法,它是一种特别为ZooKeeper设计的崩溃可恢复的原子消息广播算法。

ZAB 协议的核心是定义了对于那些会改变ZooKeeper服务器数据状态的事务请求的处理方式,即:

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

1.1 ZAB协议的两种基本模式

ZAB协议包括两种基本的模式,分别是崩愤恢复和消息广播。

消息广播:

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

在ZAB协议的二阶段提交过程中,移除了中断逻辑,所有的Follower服务器要么正常反馈Leader提出的事务Proposal,要么就抛弃Leader服务器。同时,ZAB协议将二阶段提交中的中断逻辑移除意味着我们可以在过半

的Follower服务器已经反馈Ack之后就开始提交事务Proposal了,而不需要等待集群中所有的Follower服务器都反馈响应。

当然,在这种简化了的二阶段提交模型下,是无法处理Leader服务器崩溃退出而带来的数据不一致问题的,因此在ZAB协议中添加了另一个模式,即采用崩愤恢复模式来解决这个问题。另外,整个消息广播协议是基于具有FIFO特性的TCP协议来进行网络通信的,因此能够很容易地保证消息广播过程中消息接收与发送的顺序性。

在整个消息广播过程中,Leader服务器会为每个事务请求生成对应的Proposal来进行广播,并且在广播事务Proposal之前,Leader服务器会首先为这个事务Proposal 分配一个全局单调递增的唯一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的联系,那么就会进入崩溃恢复模式。在ZAB协议中,为了保证程序的正确运行,整个恢复过程结束后需要选举出一个新的Leader服务器。

为了确保ZAB可以快速的选出一个Leader,同时让新Leader知道自己被选举为Leader和让集群中的机器也快速的感知到产生了新的Leader,ZAB需要确保一些特性:

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

    假设一个事务在Leader服务器上被提交了,并且已经得到过半Follower服务器的Ack反馈,但是在它将Commit消息发送给所有Follower机器之前,Leader服务器挂了。

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

    如果在崩愤恢复过程中出现一个需要被丢弃的提案,那么在崩溃恢复结束后需要跳过该事务Proposal。,假设初始的Leader服务器Server1在提出了一个事务Proposal3之后就崩愤退出了,从而导致集群中的其他服务器都没有收到这个事务Proposal。于是,当Server恢复过来再次加入到集群中的时候,ZAB协议需要确保丢弃Proposal3这个事务。

结合上面提到的这两个崩愤恢复过程中需要处理的特殊情况,就决定了ZAB 协议必须设计这样一个Leader选举算法:

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

1.2 数据同步

所有正常运行的服务器,要么成为Leader,要么成为Follower并和Leader保持同步。Leader服务器需要确保所有的Follower服务器能够接收到每一条事务Proposal,并且能够正确地将所有已经提交了的事务Proposal应用到内存数据库中去。具体的,Leader服务器会为每一个

Follower服务器都准备一个队列,并将那些没有被各Follower服务器同

步的事务以Proposal以消息的形式逐个发送给Follower服务器,并在每一个Proposal消息后面紧接着再发送一个Commit消息,以表示该事务已经被提交。等到Follower服务器将所有其尚未同步的事务Proposal都从Leader服务器上同步过来并成功应用到本地数据库中后,Leader服务器就会将该Follower服务器加入到真正的可用Follower列表中,并开始之后的其他流程。

1.3 ZAB算法描述

整个ZAB 协议主要包括消息广播和崩愤恢复两个过程,进一步可以细分为三个阶段,分别是发现(Discovery)、同步(Synchronization)和广播(Broadcast)阶段。组成ZAB协议的每一个分布式进程,会循环地执行这三个阶段,我们将这样一个循环称为一个主进程周期。

ZAB协议算法表述说明:

FFollower f处理过的最后一个事务 Proposal
FFollower f 处理过的历史事务Proposal 中最后一个事务Proposal 的事务标识ZXID
h每一个Follower f通常都已经处理(接受)了不少事务Proposal ,并且会有一个针对已经处理过的事务的集合, 将其表示为h, 表示Follower f 已经处理过的事务序列
I初始化历史记录,在某一个主进程周期epoch e 中, 当准Leader 完成阶段一之后,此时它的h就被标记为I

下面我们就从发现、同步和广播这三个阶段展开来讲解ZAB 协议的内部原理。

阶段1:发现

阶段一主要就是Leader选举过程,用于在多个分布式进程中选举出主进程,准Leader L和Follower F的工作流程分别如下:

  1. 步骤F.1.1: Follower F将自己最后接受的事务Proposal的epoch值CEPOCH(F.p)发送给准Leader L。
  2. 步骤L.1.1 当接收到来自过半Follower的CEPOCH(F.p)消息后,准Leader L会生成NEWEPOCH(e’)悄息给这些过半的Follower。

    关于这个epoch值e’,准Leader L 会从所有接收到的CEPOCH(F.p)消息中选取出最大的epoch值,然后对其进行加l操作,即为e’。
  3. 步骤F.1.2:当Follower接收到来自准Leader L 的NEWEPOCH(e’)消息后,如果其检测到当前的CEPOCH(F.p)值小于e’,那么就会将CEPOCH(F.p)赋值为e’,同时向这个准Leader L反馈Ack消息。在这个反馈消息(ACK-E(F,h)中,包含了当前该Follower 的epoch CEPOCH(F.p),以及该Follower的历史事务Proposal集合:h。

当Leader L接收到来自过半Follower的确认消息Ack之后,Leader L就会从这过半服务器中选取出一个Follower F,并使用其作为初始化事务集合I。

阶段2:同步

在这一阶段中,Leader L和Follower F的工作流程分别如下。

  1. 步骤L.2.1: Leader L会将e’和Ie’以NEWLEADER(e’,I)消息的形式发送给所有Quorum中的Follower。
  2. 步骤F.2.1:当Follower接收到来自Leader L的NEWLEADER(e’,I)消息后,如果Follower发现CEPOCH(F)那么直接进入下一轮循环,因为此时Follower发现自己还在上一轮,或者更上轮,无法参与本轮的同步。如果CEPOCH(F.p) = e’,那么Follower就会执行事务应用操作。具体的,对于每一个事务Proposal:

1.4 ZAB与Paxos算法的区别

ZAB 协议并不是Paxos算法的一个典型实现,在讲解ZAB和Paxos之间的区别之前,我们首先来看下两者的联系:

  1. 两者都存在一个类似于Leader进程的角色,由其负责协调多个Follower进程的运行。
  2. Leader进程都会等待超过半数的Follower做出正确的反馈后,才会将一个提案进行提交。
  3. 在ZAB协议中,每个Proposal中都包含了一个epoch值,用来代表当前的Leader周期,在Paxos算法中,同样存在这样的一个标识,只是名字变成了Ballot。

在Paxos算法中,一个新选举产生的主进程会进行两个阶段的工作。第一阶段被称为读阶段,在这个阶段中,这个新的主进程会通过和所有其他进程进行通信的方式来收集上一个主进程提出的提案,井将它们提交。第二阶段被称为写阶段,在这个阶段,当前主进程开始提出它自己的提案。在Paxos算法设计的基础上,ZAB协议额外添加了一个同步阶段。在同步阶段之前,ZAB协议也存在一个和Paxos算法中的读阶段非常类似的过

程,称为发现(Discovery)阶段。在同步阶段中,新的Leader会确保存在过半的Follower已经提交了之前Leader周期中的所有事务Proposal。这一同步阶段的引入,能够有效地保证Leader在新的周期中提出事务Proposal之前,所有的进程都已经完成了对之前所有事务Proposal的提交。一旦完成同步阶段后,那么ZAB就会执行和Paxos算法类似的写阶段。

总的来讲,ZAB协议和Paxos算法的本质区别在于,两者的设计目标不太一样。ZAB协议主要用于构建一个高可用的分布式数据主备系统,例如ZooKeeper,而Paxos算法则是用于构建一个分布式的一致性状态机系统。

05-08 14:55