ZooKeeper ZAB协议

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ZooKeeper ZAB协议相关的知识,希望对你有一定的参考价值。

参考技术A 最近在学习ZooKeeper,一直想写篇相关博文记录下学习内容,碍于自己是个拖延症重度患者总是停留在准备阶段,直到今天心血来潮突然想干点什么,于是便一不做二不休,通过对《从Paxos到Zookeeper 分布式一致性原理与实践》一书中ZAB相关内容的总结,以及对一些优秀博文的整理码出来这篇简文。本文首先对ZooKeeper进行一个简单的介绍,然后重点介绍ZooKeeper采用的ZAB(ZooKeeper Atomic Broadcast)一致性协议算法,加深自己对ZAB协议的理解的同时也希望与简友们一起分享讨论。

ZooKeeper是一个具有高效且可靠的分布式协调服务, 由Yahoo创建的基于Google的Chubby开源实现,并于2010年11月正式成为Apache软件基金会的顶级项目。

ZooKeeper是一个典型的分布式数据一致性解决方案,分布式应用程序可基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。

保证如下分布式一致性特性

顺序一致性

从同一个客户端发起的事务请求,最终将会严格地按照其发生顺序被应用到ZooKeeper中。

原子一致性

所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,即要么整个集群所有机器都成功应用了某一个事务,要么都没有应用,一定不会出现集群中部分机器应用了该事务,而另外一部分没有应用的情况。

单一视图

无论客户端连接的是哪个ZK服务器,其看到的服务端数据模型都是一致的。

可靠性

一旦服务端成功地应用了一个事务并完成对客户端的响应,那么该事务所引起的服务端状态变更将会被一直保留下来,除非另外一个事务又对其进行了变更。

实时性

通常人们看到实时性的第一反应是,一旦一个事务被成功应用,那么客户端能够立即从服务端上读取到这个事务变更后的最新数据状态。但这个需要注意的是, ZooKeeper仅仅保证在一定时间段内 , 客户端最终一定能够从服务端上读取到最新的数据状态 。

Leader 一个ZooKeeper集群同一时间只会有一个实际工作的Leader,它会发起并维护与各Follwer及Observer间的心跳。所有的写操作必须要通过Leader完成再由Leader将写操作广播给其它服务器。

Follower 一个ZooKeeper集群可能同时存在多个Follower,它会响应Leader的心跳。Follower可直接处理并返回客户端的读请求,同时会将写请求转发给Leader处理,并且负责在Leader处理写请求时对请求进行投票。

Observer 角色与Follower类似,但是无投票权。

ZooKeeper Atomic Broadcast (ZAB, ZooKeeper原子消息广播协议)是ZooKeeper实现分布式数据一致性的核心算法,ZAB借鉴Paxos算法,但又不像Paxos算法那样,是一种通用的分布式一致性算法, 它是一种特别为ZooKeeper专门设计的支持崩溃恢复的原子广播协议 。

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

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

ZAB协议整体可划分为两个基本的模式: 消息广播和崩溃恢复

按协议原理可细分为四个阶段: 选举(Leader Election)、发现(Discovery)、同步(Synchronization)和广播(Broadcast)

按协议实现分为三个时期: 选举(Fast Leader Election)、恢复(Recovery Phase)和广播(Broadcast Phase)

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消息后,也会完成对事务的提交。

上面讲解的ZAB协议的这个基于原子广播协议的消息广播过程,在正常运行情况下运行非常良好,但是一旦Leader服务器出现崩溃或者由于网络原因导致Leader服务器失去了与过半Follower的联系,那么就会进入崩溃恢复模式。在ZAB协议中,为了保证程序的正确运行,整个恢复过程结束后需要选举出一个新的Leader服务器。因此,ZAB协议需要一个高效且可靠的Leader选举算法,从而确保能够快速选举出新的Leader。同时,Leader选举算法不仅仅需要让Leader自己知道其自身已经被选举为Leader,同时还需要让集群中的所有其他服务器也快速地感知到选举产生的新的Leader服务器。崩溃恢复主要包括Leader选举和数据恢复两部分,下面将详细讲解 Leader选举和数据恢复流程 。

现有的选举算法有一下四种: 基于UDP的LeaderElection 、 基于UDP的FastLeaderElection 、 基于UDP和认证的FastLeaderElection 和 基于TCP的FastLeaderElection ,在3.4.10版本中弃用其他三种算法

myid —— zk服务器唯一ID

zxid ——  最新事务ID

高32位 是Leader的 epoch ,从1开始,每次选出新的Leader,epoch加一;

低32位 为该epoch 内的序号 ,每次epoch变化,都将低32位的序号重置;

保证了zkid的 全局递增性 。

logicClock 表示这是该服务器发起的第多少轮投票,从1开始计数

state 当前服务器的状态

self_id 当前服务器的 唯一ID

self_zxid 当前服务器上所保存的数据的 最大事务ID ,从0开始计数

vote_id 被推举的服务器的 唯一ID

vote_zxid 被推举的服务器上所保存的数据的 最大事务ID ,从0开始计数

LOOKING  不确定Leader状态。该状态下的服务器认为当前集群中没有Leader,会发起Leader选举。

FOLLOWING  跟随者状态。表明当前服务器角色是Follower,并且它知道Leader是谁。

LEADING  领导者状态。表明当前服务器角色是Leader。

OBSERVING  观察者状态, 不参与选举 ,也不参与集群写操作时的投票。

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

ZooKeeper规定所有有效的投票都必须在同一轮次中。每个服务器在开始新一轮投票时,会先对自己维护的logicClock进行自增操作。

每个服务器在广播自己的选票前,会将自己的投票箱清空。该投票箱记录了所收到的选票。例:服务器2投票给服务器3,服务器3投票给服务器1,则服务器1的投票箱为(2, 3), (3, 1), (1, 1)。 票箱中只会记录每一投票者的最后一票 ,如投票者更新自己的选票,则其它服务器收到该新选票后会在自己票箱中更新该服务器的选票。

每个服务器最开始都是通过广播把票投给自己。

服务器会尝试从其它服务器获取投票,并记入自己的投票箱内。如果无法获取任何外部投票,则会确认自己是否与集群中其它服务器保持着有效连接。如果是,则再次发送自己的投票;如果否,则马上与之建立连接。

根据选票logicClock -> vote_zxid -> vote_id依次判断

1 判断选举轮次

收到外部投票后,首先会根据投票信息中所包含的logicClock来进行不同处理:

外部投票的logicClock > 自己的logicClock:   说明该服务器的选举轮次落后于其它服务器的选举轮次,立即清空自己的投票箱并将自己的logicClock更新为收到的logicClock,然后再对比自己之前的投票与收到的投票以确定是否需要变更自己的投票,最终再次将自己的投票广播出去;

外部投票的logicClock < 自己的logicClock: 当前服务器直接忽略该投票,继续处理下一个投票;

外部投票的logickClock = 自己的: 当时进行选票PK。

2 选票PK

选票PK是基于(self_id, self_zxid)与(vote_id, vote_zxid)的对比:

若logicClock一致,则对比二者的vote_zxid,若外部投票的vote_zxid比较大,则将自己的票中的vote_zxid与vote_myid更新为收到的票中的vote_zxid与vote_myid并广播出去,另外将收到的票及自己更新后的票放入自己的票箱。如果票箱内已存在(self_myid, self_zxid)相同的选票,则直接覆盖

若二者vote_zxid一致,则比较二者的vote_myid,若外部投票的vote_myid比较大,则将自己的票中的vote_myid更新为收到的票中的vote_myid并广播出去,另外将收到的票及自己更新后的票放入自己的票箱

如果已经确定有过半服务器认可了自己的投票(可能是更新后的投票),则终止投票。否则继续接收其它服务器的投票。

投票终止后,服务器开始更新自身状态。若过半的票投给了自己,则将自己的服务器状态更新为LEADING,否则将自己的状态更新为FOLLOWING。

注: 图中箭头上的(1,1,0) 三个数一次代表该选票的服务器的 LogicClock 、被推荐的服务器的myid (即 vote_myid ) 以及被推荐的服务器的最大事务ID(即 vote_zxid );

(1, 1) 2个数分别代表投票服务器myid(即 self_myid )和被推荐的服务器的myid (即 vote_myid )

选举流程如下:

自增选票轮次&初始化选票&发送初始化选票

首先,三台服务器自增选举轮次将LogicClock=1;然后初始化选票,清空票箱;最后发起初始化投票给自己将各自的票通过广播的形式投个自己并保存在自己的票箱里。

接受外部投票&更新选票

以Server 1 为例,分别经历 Server 1 PK Server 2 和 Server 1 PK Server 3 过程

Server 1 PK Server 2

Server 1 接收到Server 2的选票(1,2,0) 表示,投票轮次LogicClock为1,投给Server 2,并且Server 2的最大事务ID,ZXID 为0;

这时Server 1将自身的选票轮次和Server 2 的选票轮次比较,发现 LogicClock=1相等 ,接着Server 2比较比较最大事务ID,发现也 zxid=0也相等 ,最后比较各自的myid,发现Server 2的 myid=2 大于自己的myid=1 ;

根据选票PK规则,Server 1将自己的选票由 (1, 1) 更正为 (1, 2),表示选举Server 2为Leader,然后将自己的新选票 (1, 2)广播给 Server 2 和 Server 3,同时更新票箱子中自己的选票并保存Server 2的选票,至此Server 1票箱中的选票为(1, 2) 和 (2, 2);

Server 2收到Server 1的选票同样经过轮次比较和选票PK后确认自己的选票保持不变,并更新票箱中Server 1的选票由(1, 1)更新为(1, 2),注意此次Server 2自己的选票并没有改变所有不用对外广播自己的选票。

Server 1 PK Server 3

更加Server 1 PK Server 2的流程类推,Server 1自己的选票由(1, 2)更新为(1, 3), 同样更新自己的票箱并广播给Server 2 和 Server 3;

Server 2再次接收到Server 1的选票(1, 3)时经过比较后根据规则也要将自己的选票从(1, 2)更新为(1, 3), 并更新票箱里自己的选票和Server 1的选票,同时向Server 1和 Server 3广播;

同理 Server 2 和 Server 3也会经历上述投票过程,依次类推,Server 1 、Server 2 和Server 3 在俩俩之间在经历多次 选举轮次比较 和 选票PK 后最终确定各自的选票。

选票确定后服务器根据自己票箱中的选票确定各自的角色和状态,票箱中超过半数的选票投给自己的则为Leader,更新自己的状态为LEADING,否则为Follower角色,状态为FOLLOWING,

成为Leader的服务器要主动向Follower发送心跳包,Follower做出ACK回应,以维持他们之间的长连接。

1.《从Paxos到Zookeeper 分布式一致性原理与实践》 [倪超著]

2. 《实例详解ZooKeeper ZAB协议、分布式锁与领导选举》

3.《ZooKeeper’s atomic broadcast protocol:Theory and practice》[Andr ́e Medeiros]

zookeeper

  • 什么是Zab协议
  • Zab 协议的作用
  • Zab 协议原理
  • Zab 协议核心
  • Zab 协议内容
  • 原子广播
  • 崩溃恢复
  • 如何保证数据一致性
  • Zab 协议如何数据同步
  • 如何处理需要丢弃的 Proposal
  • Zab 协议实现原理
  • 选主过程

什么是Zab协议?

Zab协议 的全称是 Zookeeper Atomic Broadcast (Zookeeper原子广播)。
Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性

  1. Zab协议是为分布式协调服务Zookeeper专门设计的一种 支持崩溃恢复原子广播协议 ,是Zookeeper保证数据一致性的核心算法。Zab借鉴了Paxos算法,但又不像Paxos那样,是一种通用的分布式一致性算法。它是特别为Zookeeper设计的支持崩溃恢复的原子广播协议

  2. 在Zookeeper中主要依赖Zab协议来实现数据一致性,基于该协议,zk实现了一种主备模型(即Leader和Follower模型)的系统架构来保证集群中各个副本之间数据的一致性。
    这里的主备系统架构模型,就是指只有一台客户端(Leader)负责处理外部的写事务请求,然后Leader客户端将数据同步到其他Follower节点。

Zookeeper 客户端会随机的链接到 zookeeper 集群中的一个节点,如果是读请求,就直接从当前节点中读取数据;如果是写请求,那么节点就会向 Leader 提交事务,Leader 接收到事务提交,会广播该事务,只要超过半数节点写入成功,该事务就会被提交。

Zab 协议的特性
1)Zab 协议需要确保那些已经在 Leader 服务器上提交(Commit)的事务最终被所有的服务器提交
2)Zab 协议需要确保丢弃那些只在 Leader 上被提出而没有被提交的事务

 
技术图片
模型图

 

Zab 协议实现的作用

1)使用一个单一的主进程(Leader)来接收并处理客户端的事务请求(也就是写请求),并采用了Zab的原子广播协议,将服务器数据的状态变更以 事务proposal (事务提议)的形式广播到所有的副本(Follower)进程上去。

2)保证一个全局的变更序列被顺序引用
Zookeeper是一个树形结构,很多操作都要先检查才能确定是否可以执行,比如P1的事务t1可能是创建节点"/a",t2可能是创建节点"/a/bb",只有先创建了父节点"/a",才能创建子节点"/a/b"。

为了保证这一点,Zab要保证同一个Leader发起的事务要按顺序被apply,同时还要保证只有先前Leader的事务被apply之后,新选举出来的Leader才能再次发起事务。

3)当主进程出现异常的时候,整个zk集群依旧能正常工作


 

Zab协议原理

Zab协议要求每个 Leader 都要经历三个阶段:发现,同步,广播

  • 发现:要求zookeeper集群必须选举出一个 Leader 进程,同时 Leader 会维护一个 Follower 可用客户端列表。将来客户端可以和这些 Follower节点进行通信。

  • 同步:Leader 要负责将本身的数据与 Follower 完成同步,做到多副本存储。这样也是提现了CAP中的高可用和分区容错。Follower将队列中未处理完的请求消费完成后,写入本地事务日志中。

  • 广播:Leader 可以接受客户端新的事务Proposal请求,将新的Proposal请求广播给所有的 Follower。


 

Zab协议核心

Zab协议的核心:定义了事务请求的处理方式

1)所有的事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被叫做 Leader服务器。其他剩余的服务器则是 Follower服务器

2)Leader服务器 负责将一个客户端事务请求,转换成一个 事务Proposal,并将该 Proposal 分发给集群中所有的 Follower 服务器,也就是向所有 Follower 节点发送数据广播请求(或数据复制)

3)分发之后Leader服务器需要等待所有Follower服务器的反馈(Ack请求),在Zab协议中,只要超过半数的Follower服务器进行了正确的反馈后(也就是收到半数以上的Follower的Ack请求),那么 Leader 就会再次向所有的 Follower服务器发送 Commit 消息,要求其将上一个 事务proposal 进行提交。

 
技术图片
事务请求处理

 

Zab协议内容

Zab 协议包括两种基本的模式:崩溃恢复消息广播

协议过程

当整个集群启动过程中,或者当 Leader 服务器出现网络中弄断、崩溃退出或重启等异常时,Zab协议就会 进入崩溃恢复模式,选举产生新的Leader。

当选举产生了新的 Leader,同时集群中有过半的机器与该 Leader 服务器完成了状态同步(即数据同步)之后,Zab协议就会退出崩溃恢复模式,进入消息广播模式

这时,如果有一台遵守Zab协议的服务器加入集群,因为此时集群中已经存在一个Leader服务器在广播消息,那么该新加入的服务器自动进入恢复模式:找到Leader服务器,并且完成数据同步。同步完成后,作为新的Follower一起参与到消息广播流程中。

协议状态切换

当Leader出现崩溃退出或者机器重启,亦或是集群中不存在超过半数的服务器与Leader保存正常通信,Zab就会再一次进入崩溃恢复,发起新一轮Leader选举并实现数据同步。同步完成后又会进入消息广播模式,接收事务请求。

保证消息有序

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


 

消息广播

1)在zookeeper集群中,数据副本的传递策略就是采用消息广播模式。zookeeper中农数据副本的同步方式与二段提交相似,但是却又不同。二段提交要求协调者必须等到所有的参与者全部反馈ACK确认消息后,再发送commit消息。要求所有的参与者要么全部成功,要么全部失败。二段提交会产生严重的阻塞问题。

2)Zab协议中 Leader 等待 Follower 的ACK反馈消息是指“只要半数以上的Follower成功反馈即可,不需要收到全部Follower反馈”

 

 
技术图片
消息广播流程图


 

消息广播具体步骤

1)客户端发起一个写操作请求。

2)Leader 服务器将客户端的请求转化为事务 Proposal 提案,同时为每个 Proposal 分配一个全局的ID,即zxid。

3)Leader 服务器为每个 Follower 服务器分配一个单独的队列,然后将需要广播的 Proposal 依次放到队列中取,并且根据 FIFO 策略进行消息发送。

4)Follower 接收到 Proposal 后,会首先将其以事务日志的方式写入本地磁盘中,写入成功后向 Leader 反馈一个 Ack 响应消息。

5)Leader 接收到超过半数以上 Follower 的 Ack 响应消息后,即认为消息发送成功,可以发送 commit 消息。

6)Leader 向所有 Follower 广播 commit 消息,同时自身也会完成事务提交。Follower 接收到 commit 消息后,会将上一条事务提交。

zookeeper 采用 Zab 协议的核心,就是只要有一台服务器提交了 Proposal,就要确保所有的服务器最终都能正确提交 Proposal。这也是 CAP/BASE 实现最终一致性的一个体现。

Leader 服务器与每一个 Follower 服务器之间都维护了一个单独的 FIFO 消息队列进行收发消息,使用队列消息可以做到异步解耦。 Leader 和 Follower 之间只需要往队列中发消息即可。如果使用同步的方式会引起阻塞,性能要下降很多。


 

崩溃恢复

一旦 Leader 服务器出现崩溃或者由于网络原因导致 Leader 服务器失去了与过半 Follower 的联系,那么就会进入崩溃恢复模式。

在 Zab 协议中,为了保证程序的正确运行,整个恢复过程结束后需要选举出一个新的 Leader 服务器。因此 Zab 协议需要一个高效且可靠的 Leader 选举算法,从而确保能够快速选举出新的 Leader 。

Leader 选举算法不仅仅需要让 Leader 自己知道自己已经被选举为 Leader ,同时还需要让集群中的所有其他机器也能够快速感知到选举产生的新 Leader 服务器。

崩溃恢复主要包括两部分:Leader选举数据恢复

 

Zab 协议如何保证数据一致性

假设两种异常情况:
1、一个事务在 Leader 上提交了,并且过半的 Folower 都响应 Ack 了,但是 Leader 在 Commit 消息发出之前挂了。
2、假设一个事务在 Leader 提出之后,Leader 挂了。

要确保如果发生上述两种情况,数据还能保持一致性,那么 Zab 协议选举算法必须满足以下要求:

Zab 协议崩溃恢复要求满足以下两个要求
1)确保已经被 Leader 提交的 Proposal 必须最终被所有的 Follower 服务器提交
2)确保丢弃已经被 Leader 提出的但是没有被提交的 Proposal

根据上述要求
Zab协议需要保证选举出来的Leader需要满足以下条件:
1)新选举出来的 Leader 不能包含未提交的 Proposal
即新选举的 Leader 必须都是已经提交了 Proposal 的 Follower 服务器节点。
2)新选举的 Leader 节点中含有最大的 zxid
这样做的好处是可以避免 Leader 服务器检查 Proposal 的提交和丢弃工作。

Zab 如何数据同步

1)完成 Leader 选举后(新的 Leader 具有最高的zxid),在正式开始工作之前(接收事务请求,然后提出新的 Proposal),Leader 服务器会首先确认事务日志中的所有的 Proposal 是否已经被集群中过半的服务器 Commit。

2)Leader 服务器需要确保所有的 Follower 服务器能够接收到每一条事务的 Proposal ,并且能将所有已经提交的事务 Proposal 应用到内存数据中。等到 Follower 将所有尚未同步的事务 Proposal 都从 Leader 服务器上同步过啦并且应用到内存数据中以后,Leader 才会把该 Follower 加入到真正可用的 Follower 列表中。

Zab 数据同步过程中,如何处理需要丢弃的 Proposal

在 Zab 的事务编号 zxid 设计中,zxid是一个64位的数字。

其中低32位可以看成一个简单的单增计数器,针对客户端每一个事务请求,Leader 在产生新的 Proposal 事务时,都会对该计数器加1。而高32位则代表了 Leader 周期的 epoch 编号。

epoch 编号可以理解为当前集群所处的年代,或者周期。每次Leader变更之后都会在 epoch 的基础上加1,这样旧的 Leader 崩溃恢复之后,其他Follower 也不会听它的了,因为 Follower 只服从epoch最高的 Leader 命令。

每当选举产生一个新的 Leader ,就会从这个 Leader 服务器上取出本地事务日志充最大编号 Proposal 的 zxid,并从 zxid 中解析得到对应的 epoch 编号,然后再对其加1,之后该编号就作为新的 epoch 值,并将低32位数字归零,由0开始重新生成zxid。

Zab 协议通过 epoch 编号来区分 Leader 变化周期,能够有效避免不同的 Leader 错误的使用了相同的 zxid 编号提出了不一样的 Proposal 的异常情况。

基于以上策略
当一个包含了上一个 Leader 周期中尚未提交过的事务 Proposal 的服务器启动时,当这台机器加入集群中,以 Follower 角色连上 Leader 服务器后,Leader 服务器会根据自己服务器上最后提交的 Proposal 来和 Follower 服务器的 Proposal 进行比对,比对的结果肯定是 Leader 要求 Follower 进行一个回退操作,回退到一个确实已经被集群中过半机器 Commit 的最新 Proposal


 

实现原理

Zab 节点有三种状态

  • Following:当前节点是跟随者,服从 Leader 节点的命令。
  • Leading:当前节点是 Leader,负责协调事务。
  • Election/Looking:节点处于选举状态,正在寻找 Leader。

代码实现中,多了一种状态:Observing 状态
这是 Zookeeper 引入 Observer 之后加入的,Observer 不参与选举,是只读节点,跟 Zab 协议没有关系。

节点的持久状态

  • history:当前节点接收到事务 Proposal 的Log
  • acceptedEpoch:Follower 已经接受的 Leader 更改 epoch 的 newEpoch 提议。
  • currentEpoch:当前所处的 Leader 年代
  • lastZxid:history 中最近接收到的Proposal 的 zxid(最大zxid)

Zab 的四个阶段

1、选举阶段(Leader Election)
节点在一开始都处于选举节点,只要有一个节点得到超过半数节点的票数,它就可以当选准 Leader,只有到达第三个阶段(也就是同步阶段),这个准 Leader 才会成为真正的 Leader。

Zookeeper 规定所有有效的投票都必须在同一个 轮次 中,每个服务器在开始新一轮投票时,都会对自己维护的 logicalClock 进行自增操作

每个服务器在广播自己的选票前,会将自己的投票箱(recvset)清空。该投票箱记录了所受到的选票。
例如:Server_2 投票给 Server_3,Server_3 投票给 Server_1,则Server_1的投票箱为(2,3)、(3,1)、(1,1)。(每个服务器都会默认给自己投票)

前一个数字表示投票者,后一个数字表示被选举者。票箱中只会记录每一个投票者的最后一次投票记录,如果投票者更新自己的选票,则其他服务器收到该新选票后会在自己的票箱中更新该服务器的选票。

这一阶段的目的就是为了选出一个准 Leader ,然后进入下一个阶段。
协议并没有规定详细的选举算法,后面会提到实现中使用的 Fast Leader Election。

 
技术图片
选举流程


 

2、发现阶段(Descovery)
在这个阶段,Followers 和上一轮选举出的准 Leader 进行通信,同步 Followers 最近接收的事务 Proposal 。
一个 Follower 只会连接一个 Leader,如果一个 Follower 节点认为另一个 Follower 节点,则会在尝试连接时被拒绝。被拒绝之后,该节点就会进入 Leader Election阶段。

这个阶段的主要目的是发现当前大多数节点接收的最新 Proposal,并且准 Leader 生成新的 epoch ,让 Followers 接收,更新它们的 acceptedEpoch

 
技术图片
发现流程


 

3、同步阶段(Synchronization)
同步阶段主要是利用 Leader 前一阶段获得的最新 Proposal 历史,同步集群中所有的副本
只有当 quorum(超过半数的节点) 都同步完成,准 Leader 才会成为真正的 Leader。Follower 只会接收 zxid 比自己 lastZxid 大的 Proposal。

 
技术图片
同步流程


 

4、广播阶段(Broadcast)
到了这个阶段,Zookeeper 集群才能正式对外提供事务服务,并且 Leader 可以进行消息广播。同时,如果有新的节点加入,还需要对新节点进行同步。
需要注意的是,Zab 提交事务并不像 2PC 一样需要全部 Follower 都 Ack,只需要得到 quorum(超过半数的节点)的Ack 就可以。

 
技术图片
广播流程

 


 

协议实现

协议的 Java 版本实现跟上面的定义略有不同,选举阶段使用的是 Fast Leader Election(FLE),它包含了步骤1的发现指责。因为FLE会选举拥有最新提议的历史节点作为 Leader,这样就省去了发现最新提议的步骤。

实际的实现将发现和同步阶段合并为 Recovery Phase(恢复阶段),所以,Zab 的实现实际上有三个阶段。

Zab协议三个阶段:

1)选举(Fast Leader Election)
2)恢复(Recovery Phase)
3)广播(Broadcast Phase)

Fast Leader Election(快速选举)
前面提到的 FLE 会选举拥有最新Proposal history (lastZxid最大)的节点作为 Leader,这样就省去了发现最新提议的步骤。这是基于拥有最新提议的节点也拥有最新的提交记录

  • 成为 Leader 的条件:
    1)选 epoch 最大的
    2)若 epoch 相等,选 zxid 最大的
    3)若 epoch 和 zxid 相等,选择 server_id 最大的(zoo.cfg中的myid)

节点在选举开始时,都默认投票给自己,当接收其他节点的选票时,会根据上面的 Leader条件 判断并且更改自己的选票,然后重新发送选票给其他节点。当有一个节点的得票超过半数,该节点会设置自己的状态为 Leading ,其他节点会设置自己的状态为 Following

 

 
技术图片
选举过程


 

Recovery Phase(恢复阶段)
这一阶段 Follower 发送他们的 lastZxid 给 Leader,Leader 根据 lastZxid 决定如何同步数据。这里的实现跟前面的 Phase 2 有所不同:Follower 收到 TRUNC 指令会终止 L.lastCommitedZxid 之后的 Proposal ,收到 DIFF 指令会接收新的 Proposal。

history.lastCommitedZxid:最近被提交的 Proposal zxid
history.oldThreshold:被认为已经太旧的已经提交的 Proposal zxid


 
技术图片
恢复阶段


作者:_Zy
链接:https://www.jianshu.com/p/2bceacd60b8a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

以上是关于ZooKeeper ZAB协议的主要内容,如果未能解决你的问题,请参考以下文章

zookeeper中的ZAB协议理解

Zookeeper的ZAB协议

Zookeeper之Zab协议

zookeeper(16)源码分析-ZAB协议

Zookeeper--09---Zookeeper集群 ZAB协议

Zookeeper深入原理(3) - Zab协议