Zookeeper 简介
ZooKeeper 由雅虎研究院开发,后来捐赠给了 Apache。ZooKeeper 是一个开源的分布式应用程序协调服务器
,其为分布式系统提供一致性
服务。其一致性是通过基于 Paxos 算法
的ZAB 协议
完成的。其主要功能包括:配置维护、域名服务、分布式同步、集群管理等。
zookeeper 的官网: http://zookeeper.apache.org
其他类似产品:Consul、Doozerd、Etcd
zk 是如何保证分布式系统的一致性的呢?是因为 zk 具有以下几方面的特点:
- 顺序一致性:从同一个客户端发起的多个事务请求(写操作请求),最终会严格按照其发起顺序记录到 zk 中。
- 原子性:所有事务请求的结果在集群中所有 Server 上的应用情况是一致的。要么全部应用成功,要么都没有成功,不会出现部分成功,部分失败的情况。
- 单一视图:无论客户端连接的是集群中的哪台 Server,其读取到的数据模型中的数据都是一致的。
- 可靠性:一旦某事务被成功应用到了 zk,则会一直被保留下来,除非另一个事务将其修改。
- 最终一致性一旦一个事务被成功应用,zk 可以保证在一段较短的时间内,客户端最终一定能够从服务端读取到最新的数据。但不能保证实时读取到。
Paxos 算法
对于 zk 理论的学习,最重要也是最难的知识点就是 Paxos 算法。所以我们首先学习 Paxos算法。
算法简介
Paxos 算法是莱斯利·兰伯特(Leslie Lamport)1990 年提出的一种基于消息传递的、具有高容错性的一致性算法。Google Chubby 的作者 Mike Burrows 说过,世上只有一种一致性算法,那就是 Paxos,所有其他一致性算法都是 Paxos 算法的不完整版。Paxos 算法是一种公认的晦涩难懂的算法,并且工程实现上也具有很大难度。较有名的 Paxos 工程实现有 Google Chubby、ZAB、微信的 PhxPaxos 等。
Paxos 算法是用于解决什么问题的呢?Paxos 算法要解决的问题是,在分布式系统中如何就某个决议达成一致。
Paxos 与拜占庭将军问题
拜占庭将军问题是由 Paxos 算法作者莱斯利·兰伯特提出的点对点通信中的基本问题。该问题要说明的含义是,在不可靠信道上试图通过消息传递的方式达到一致性是不可能的
。所以,Paxos 算法的前提是不存在拜占庭将军问题
,即信道是安全的、可靠的,集群节点间传递的消息是不会被篡改的。
一般情况下,分布式系统中各个节点间采用两种通讯模型:共享内存(Shared Memory)、消息传递(Messages Passing)。而 Paxos 是基于消息传递通讯模型的。
-
消息传递模型中是没有中心节点的
,类似于去中心化的概念,每个节点之间通过消息传递信息。 - 共享内存:多线程开发的时候,线程之间通信其实就是通过共享内存的方式,单机中的多线程和分布式系统的多应用他们各自之间的协调工作,其实是相似的,问题就是不同通讯模型下保持一致性的方案不同。
算法描述
这里只是做了一种简单情况的描述,关于该算法的推导与证明,我在知乎上看到了一个比较好的回答,链接:https://www.zhihu.com/question/19787937 朱一聪的回答
三种角色
在 Paxos 算法中有三种角色,分别具有三种不同的行为。但很多时候,一个进程可能同时充当着多种角色。
- Proposer:提案者
- Acceptor:表决者
- Learner:同步者
Paxos 算法的一致性
Paxos 算法的一致性主要体现在以下几点:
- 每个提案者在提出提案时都会首先获取到一个具有全局唯一性的、递增的提案编号 N,即在整个集群中是唯一的编号 N,然后将该编号赋予其要提出的提案。
- 每个表决者在 accept 某提案后,会将该提案的编号 N 记录在本地,这样每个表决者中保存的已经被 accept 的提案中会存在一个编号最大的提案,其编号假设为 maxN。每个表决者仅会 accept 编号大于自己本地 maxN 的提案。
- 在众多提案中最终只能有一个提案被选定。
- 一旦一个提案被选定,则其它服务器会主动同步(Learn)该提案到本地。
- 没有提案被提出则不会有提案被选定。
算法过程描述
Paxos 算法的执行过程划分为两个阶段:准备阶段 prepare(预提案阶段) 与接受阶段 accept(提案阶段)。
A、prepare 阶段
- 1 提案者(Proposer)准备提交一个编号为 N 的提议,于是其首先向所有表决者(Acceptor)发送
prepare(N)
请求,用于试探
集群是否支持该编号的提议。 - 2 每个表决者(Acceptor)中都保存着自己曾经 accept 过的提议中的最大编号 maxN。当一个表决者接收到其它主机发送来的
prepare(N)
请求时,其会比较 N 与 maxN 的值。有以下几种情况:
- a) 若 N 小于 maxN,则说明该提议已过时,当前表决者采取不回应或回应 Error 的方式来拒绝该 prepare 请求;
- b) 若 N 大于 maxN,则说明该提议是可以接受的,当前表决者会首先将该 N 记录下来,并将其曾经已经 accept 的编号最大的提案
Proposal(myid,maxN,value)
反馈给提案者,以向提案者展示自己支持的提案意愿。其中第一个参数 myid 表示该提案的提案者标识 id,第二个参数表示其曾接受的提案的最大编号 maxN,第三个参数表示该提案的真正内容 value。当然,若当前表决者还未曾 accept 过任何提议,则会将Proposal(myid,null,null)
反馈给提案者。 - c) 在 prepare 阶段 N 不可能等于 maxN。这是由 N 的生成机制决定的。要获得 N 的值,其必定会在原来数值的基础上采用同步锁方式增一。
B、 accept 阶段
- 1 当提案者(Proposer)发出
prepare(N)
后,若收到了超过半数的表决者(Accepter)的反馈,那么该提案者就会将其真正的提案(即反馈中编号最大的提案的内容作为真正提案的内容)
Proposal(myid,N,value)
发送给所有的表决者。 - 2 当表决者(Acceptor)接收到提案者发送的
Proposal(myid,N,value)
提案后,会再次拿出自己曾经 accept 过的提议中的最大编号 maxN,或曾经记录下的 prepare 的最大编号,让 N与它们进行比较,若 N 大于等于这两个编号,则当前表决者 accept 该提案,并反馈给提案者。若 N 小于这两个编号,则表决者采取不回应或回应 Error 的方式来拒绝该提议。 - 3 若提案者没有接收到超过半数的表决者的 accept 反馈,则有两种可能的结果产生。一是放弃该提案,不再提出;二是重新进入 prepare 阶段,递增提案号,重新提出 prepare请求。
- 4 若提案者接收到的反馈数量超过了半数,则其会向外广播两类信息:
- a) 向曾 accept 其提案的表决者发送“可执行数据同步信号”,即让它们执行其曾接收到的提案;
- b) 向未曾向其发送 accept 反馈的表决者发送“提案 + 可执行数据同步信号”,即让它们接受到该提案后马上执行。
演示
只是演示了一种简单情况,便于理解:
prepare 阶段:
- Server-1 向自己,和Server-2 发送了一个编号为2的预提案(
预提案,只有编号
)(没有向Server-3发送是为了模拟网络异常 等收不到回复的情况) - 此时Server-1 和 和Server-2 之前都没有接受过提案,所以会接编号2的预提案,并反馈给Server-1,对于Server-1来说会收到两个Accept反馈
- Server-2 向自己,和Server-3 发送了一个编号为1的预提案
- 由于Server-2 已经接收过编号2的预提案,所以会拒绝Server-2编号1的预提案,而Server-3会接受编号为1的预提案,此时对于Server-2来说只收到一个Accept反馈
- Server-3 向自己,和Server-2 发送了一个编号为3的预提案
- Server-2 和 和Server-3 发现编号3都比自己已经接受的提案编号大,所以都会接受编号为3的预提案,对于Server-3来说收到两个Accept反馈
- 由于Server-2 被拒绝了,这个时候尝试重新发送新的提案,于是向Server-2和Server-3发送编号为4的预提案
- 此时Server-2 和 和Server-3 发现编号4都比自己已经接受的提案编号大,所以都会接受编号为4的预提案,对于Server-2来说也收到两个Accept反馈
accept 阶段:
prepare 阶段时,每个Server都收到了两个接受反馈,所以假如他们刚好在prepare阶段同时结束,一起进入accept阶段,他们都会在accept 阶段发送自己认为的真正的提案
:
- Server-1 向 Server-1和Server-2发送编号2的提案,只有 Server-1 能够接受,提案不通过
- Server-2 向 Server-2和Server-3发送编号4的提案,可以收到两个接受反馈,超过半数,提案通过
- Server-3 向 Server-2和Server-3发送编号3的提案,没有任何Server接受反馈,提案不通过
可以看到,最终只有一个提案可以通过。
没有接收到超过半数表决者 accept 反馈的Server,可以选择放弃该提案,等待已经提案通过的Server通知并更新,也可以选择递增提案号,重新提出 prepare请求,假如prepare请求能够通过(超过半数反馈),那么此时在prepare阶段肯定能收到包含“已经通过的提案”的反馈,然后在accept阶段将已经通过的提案的内容作为本次新提案的内容广播出去,所以即使是新的提案(编号更大的提案),但提案的内容还是和已经通过的提案内容一致,仍然能保证一致性!
注意:上面演示的只是一种可能出现的简单情况,实际上每个Server在发起提案时,每个提案的prepare阶段和accept 阶段都是交错的,并不是所有提案prepare阶段都结束了才一起进入accept阶段,但是即使这样,仍然能保持一致性
提问 1:
在Prepare阶段已经比较过了,并且已经通过了,为什么在Accept阶段还需要进行比较?
提问 2:
在 Prepare 阶段与 Accept 阶段都进行了比较,为什么在发送 COMMIT 信号量时无需进行比较?
Paxos 算法的活锁问题
活锁定义(摘自百度百科):
活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。处于活锁的实体是在不断的改变状态,活锁有可能自行解开。
Paxous什么情况会出现死锁?
假如编号1的提案prepare阶段已经通过(超过半数),在accept阶段发送前,或者发送中消息还没有到达其他Server时,有新的编号2提案,比编号1提案先到达,并且超过半数,(此时编号1提案是accpet阶段,编号2是prepare阶段)此时编号1的提案在到达时肯定收不到半数accpet的,而编号2的提案在accept阶段发送前,又有新的提案编号3在prepare阶段先到达并且超过半数通过…以此类推,永远也不会决定出一个提案通过。
前面所述的Paxos算法在实际工程应用过程中,根据不同的实际需求存在诸多不便之处,所以也就出现了很多对于基本 Paxos 算法的优化算法,以对 Paxos 算法进行改进,例如,MultiPaxos、Fast Paxos、EPaxos。
例如,Paxos 算法存在“活锁问题”,Fast Paxos 算法对 Paxos 算法进行了改进:只允许一个进程提交提案,即该进程具有对 N 的唯一操作权。该方式解决了“活锁”问题。
ZAB 协议
ZAB 协议简介
ZAB ,Zookeeper Atomic Broadcast,zk 原子消息广播协议
,是专为 ZooKeeper 设计的一种支持崩溃恢复
的原子广播协议,在 Zookeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性。
Zookeeper 使用一个单一主进程来接收并处理客户端的所有事务请求,即写请求。当服务器数据的状态发生变更后,集群采用 ZAB 原子广播协议,以事务提案 Proposal 的形式广播到所有的副本进程上。ZAB 协议能够保证一个全局的变更序列,即可以为每一个事务分配一个全局的递增编号 xid。
当 Zookeeper 客户端连接到 Zookeeper 集群的一个节点后,若客户端提交的是读请求,那么当前节点就直接根据自己保存的数据对其进行响应;如果是写请求且当前节点不是Leader,那么节点就会将该写请求转发给 Leader,Leader 会以提案的方式广播该写操作,只要有超过半数节点同意该写操作,则该写操作请求就会被提交。然后 Leader 会再次广播给所有订阅者,即 Learner,通知它们同步数据。
ZAB 与 Paxos 的关系
ZAB 协议是 Paxos 算法的一种工业实现算法
。但两者的设计目标不太一样。ZAB 协议主要用于构建一个高可用的分布式数据主从
系统,即 Follower 是 Leader 的从机,Leader 挂了,马上就可以选举出一个新的 Leader,但平时它们都对外提供服务。而 Fast Paxos 算法则是用于构建一个分布式一致性状态机
系统,确保系统中各个节点的状态都是一致的。
zookeeper如果leader挂了,从机在一开始选举的时候每个人都推荐自己,这个时候体现的是Paxos算法,正常使用期间,则是Fast Paxos算法
三类角色
为了避免 Zookeeper 的单点问题,zk 也是以集群的形式出现的。zk 集群中的角色主要有以下三类:
- Leader:事务请求的唯一处理者,也可以处理读请求。
- Follower:可以直接处理客户端的读请求,并向客户端响应;但其不会处理事务请求,其只会将客户端事务请求转发给Leader来处理;对Leader发起的事务提案具有表决权;同步 Leader 中的事务处理结果;Leader 选举过程的参与者,具有选举权与被选举权。(就好像正式工)
- Observer:可以理解为不参与 Leader 选举的 Follower,在 Leader 选举过程中没有选举权与被选举权;同时,对于 Leader 的提案没有表决权。用于协助 Follower 处理更多的客户端读请求。Observer 的增加,会提高集群读请求处理的吞吐量,但不会增加事务请求的通过压力,不会增加 Leader 选举的压力。(就好像临时工)
这三类角色在不同的情况下又有一些不同的名称:
- Learner:学习者,即要从 Leader 中同步数据的 Server,即 Follower 与 Observer。Learner = Follower + Observer
- QuorumServer:QuorumPeer,Participant,法定服务器,法定主机,参与者。在集群正常服务状态下,具有表决权的服务器称为 QuorumServer,或 QuorumPeer;在 Leader选举过程中,具有选举权与被选举权的服务器,称为 Participant。QuorumServer = Leader + Follower = Participant
三个数据
在 ZAB 中有三个很重要的数据:
- zxid:其为一个 64 位长度的 Long 类型,其中高 32 位表示 epoch,低 32 位表示 xid。
- epoch:(时期、年号)每个 Leader 选举结束后都会生成一个新的 epoch,并会通知到集群中所有其它 Server,包含 Follower 与 Observer。
- xid:事务 id,是一个流水号。
三种模式
ZAB 协议中对 zkServer 的状态描述有三种模式。这三种模式并没有十分明显的界线,它们相互交织在一起。
- 恢复模式:在集群启动过程中,或 Leader 崩溃后,系统都需要进入恢复模式,以恢复系统对外提供服务的能力。其包含两个重要阶段:Leader 选举与初始化同步。
- 广播模式:其分为两类:
初始化广播
与更新广播。
- 初始化广播:leader刚选举成功,需要发广播通知其他服务器
- 更新广播:提案通过后自己已经更新数据,然后通知其他服务器更新同步
- 同步模式:(站在Learner的角度)其分为两类:
初始化同步
与更新同步
。
- 初始化同步:leader选举成功后初始化广播新的epoch,其他服务器接收到后可以从leader同步数据,即leader有自己没有的数据
- 更新同步:leader提案通过后更新数据,然后发送更新广播,其他服务器接收到后更新该数据
四种状态
zk 集群中的每一台主机,在不同的阶段会处于不同的状态。每一台主机具有四种状态。
- LOOKING:选举状态
- 当leader宕机或者整个集群重启的时候,所有主机都会处于LOOKING状态,直到选举出新的Leader以后
- FOLLOWING:Follower 的正常工作状态
- OBSERVING:Observer 的正常工作状态
- LEADING:Leader 的正常工作状态
同步模式与广播模式
(1) 初始化广播
前面我们说过,恢复模式具有两个阶段:Leader 选举与初始化同步(广播)。当完成 Leader选举后,此时的 Leader 还是一个准 Leader,其要经过初始化同步后才能变为真正的 Leader。
具体过程如下:
- 1 为了保证 Leader 向 Learner 发送提案的有序,Leader 会为每一个 Learner 服务器准备一个队列
- 2 Leader 将那些
没有被各个 Learner 同步的事务
封装为 Proposal - 3 Leader 将这些 Proposal 逐条发给各个 Learner,并在每一个 Proposal 后都紧跟一个COMMIT 消息,表示该事务已经被提交,Learner 可以直接接收并执行
- 4 Learner 接收来自于 Leader 的 Proposal,并将其更新到本地
- 5 当 Learner 更新成功后,会向准 Leader 发送 ACK 信息
- 6
Leader 服务器在收到来自 Learner 的 ACK 后就会将该 Learner 加入到真正可用的 Follower列表或 Observer 列表。没有反馈 ACK,或反馈了但 Leader 没有收到的 Learner,Leader不会将其加入到相应列表。
(2) 消息广播算法(更新广播)
当集群中的 Learner 完成了初始化状态同步,那么整个 zk 集群就进入到了正常工作模式了。
如果集群中的 Learner 节点收到客户端的事务请求,那么这些 Learner 会将请求转发给Leader 服务器。然后再执行如下的具体过程:
- 1 Leader 接收到事务请求后,为事务赋予一个全局唯一的 64 位自增 id,即 zxid,通过zxid 的大小比较即可实现事务的有序性管理,然后将事务封装为一个 Proposal。
- 2 Leader 根据 Follower 列表获取到所有 Follower,然后再将 Proposal 通过这些 Follower 的队列将提案发送给各个 Follower。
- 3 当 Follower 接收到提案后,会先将提案的 zxid 与本地记录的事务日志中的最大的 zxid进行比较。若当前提案的 zxid 大于最大 zxid,则将当前提案记录到本地事务日志中,并向 Leader 返回一个 ACK。
- 4 当 Leader 接收到过半的 ACKs 后,Leader 就会向所有 Follower 的队列发送 COMMIT消息,向所有 Observer 的队列发送 Proposal。
- 5 当 Follower 收到 COMMIT 消息后,就会将日志中的事务正式更新到本地。当 Observer收到 Proposal 后,会直接将事务更新到本地。
- 6 无论是 Follower 还是 Observer,在同步完成后都需要向 Leader 发送成功 ACK。
问题一:zookeeper更新广播,更新同步过程,是Fast-Paxos算法,只允许一个进程提交提案,所以Leader提交的提案编号肯定是最大的,不用比也知道肯定比那些learner手里的max-zxid大,那为什么第三步还需要比较?
在更新同一个数据的情况下
,因为假如提案3通过了,但是只更新了一半的机器,有些机器因为网络原因还有没有收到提案3并更新,这个时候提案4通过了,并且更新了值,这个时候之前网络原因还没到的提案3来了,如果不比较,会覆盖掉最新的提案
问题二:如果两个提案修改的不是同一个数据的情况下,假如编号5提案想把一个值从1改成5,提案通过并更新了一半的机器,有些机器因为网络原因还没有收到编号5的提案,这个时候编号8的提案通过更新了另一个数据,该数据和编号5提案修改的数据不是同一个,这个时候,某个机器因为网络原因编号5的提案在编号8提案之后才到,导致编号5的提案被拒绝,值1没有变成5,数据不一致
当然上述场景是不会存在的,因为Follower 在接收到提案后,会先用提案的zxid在本地找有没有这个zxid的事务,如果有判断其内容和leader提案的内容是否相同,如果相同什么也不做,如果不同,会在本地递归找zxid-1的事务,继续和Leader里面zxid相同的事务比较,看提案的内容是否相同,就这样一直递归,知道找到相同的zxid事务,然后从Leader中将比zxid大的所有事务同步。这个就是Learner、Follower、Observer和Leader做数据同步的过程
无论是 Follower 还是 Observer,在同步完成后都需要向 Leader 发送成功 ACK,这个ACK有什么用?
(3) Observer 的数量问题
虽然Observer同步数据都是并行的,数量多少区别不大,但是Observer 数量很多的时候:
第一,网络压力变大
第二,Leader的压力变大
第三,网络的各种其他情况会增多
所以Observer 数量很多,需要的同步时间就更长一些
Observer 数量一般与 Follower 数量相同。并不是 Observer 越多越好,因为 Observer 数量的增多虽不会增加事务操作压力,但其需要从 Leader 同步数据,Observer 同步数据的时间是小于等于 Follower 同步数据的时间的
(规定!
)。当 Follower 同步数据完成,Leader 的 Observer列表中的 Observer 主机将结束同步。那些完成同步的 Observer 将会进入到另一个对外提供服务的列表。那么,那些没有同步了数据无法提供服务的 Observer 主机就形成了资源浪费。
所以,对于事务操作发生频繁的系统,不建议使用过多的 Observer。
-
Leader 中存在两个关于 Observer 的列表
:all(包含所有 Observer)与 service(包含与Leader 同步过数据的 Observer) - service 列表是动态变化的。对于没有进入到 service 列表中的 Observer,其会通过心跳与 Leader 进行连接,一旦连接成功,马上就会从 Leader 同步数据,同步完成后向 Leader发送 ACK。Leader 在接收到其 ACK 后会将其添加到 service 列表。
-
若客户端连接上了不在 service 列表中的 Observer,那么这个 Observer 是不能提供服务的
。因为该 Observer 的状态不是 Observering。这个状态是通过 Observer 与 Leader 间的心跳来维护的。 - Leader 中对于 Follower 也同样存在两个列表:all 与 service。其功能与 Observer 的相似。但不同点是,若 Leader 收到的 Follower 同步完成的 ACK 数量没有过半,则认为同步失败,会重新进行广播,让 Follower 重新进行同步。
恢复模式的三个原则
当集群正在启动过程中,或 Leader 崩溃后,集群就进入了恢复模式。对于要恢复的数据状态需要遵循三个原则。
(1) Leader 的主动出让原则
若集群中 Leader 收到的 Follower 心跳数量没有过半,此时 Leader 会自认为自己与集群的连接已经出现了问题,其会主动修改自己的状态为 LOOKING,去查找新的 Leader。为了防止集群出现脑裂
。
而其它 Server 由于有过半的主机认为已经丢失了 Leader,所以它们会发起新的 Leader选举,选出一个新的 Leader。
(2) 已被处理过的消息不能丢原则
当 Leader 收到超过半数 Follower 的 ACKs 后,就向各个 Follower 广播 COMMIT 消息,批准各个 Server 执行该写操作事务。当各个 Server 在接收到 Leader 的 COMMIT 消息后就会在本地执行该写操作,然后会向客户端响应写操作成功。
但是如果在非全部 Follower 收到 COMMIT 消息之前 Leader 就挂了,这将导致一种后果:部分 Server 已经执行了该事务,而部分 Server 尚未收到 COMMIT 消息,所以其并没有执行该事务。当新的 Leader 被选举出,集群经过恢复模式后需要保证所有 Server 上都执行了那些已经被部分 Server 执行过的事务。
实现:首先leader选举的时候先根据zxid比较,最大的才有可能当选,所以当选后在初始化广播和初始化同步的时候,就能够保证 已被处理过的消息不会丢,从机会先根据当前本地最大事务编号和leader比较,如果内容不同,会将当前事务-1,再次和leader比较,一直递归判断,直到相同,然后从该事务zxid开始,将leader比该zxid大的所有数据都同步到本地。
(3) 被丢弃的消息不能再现原则
当在 Leader 新事务已经通过,其已经将该事务更新到了本地,但所有 Follower 还都没有收到 COMMIT 之前,Leader 宕机了(比前面叙述的宕机更早),此时,所有 Follower 根本就不知道该 Proposal 的存在。当新的 Leader 选举出来,整个集群进入正常服务状态后,之前挂了的 Leader 主机重新启动并注册成为了 Follower。若那个别人根本不知道的 Proposal还保留在那个主机,那么其数据就会比其它主机多出了内容,导致整个系统状态的不一致。所以,该 Proposa 应该被丢弃。类似这样应该被丢弃的事务,是不能再次出现在集群中的,应该被清除。
实现:实际上通过(2)里面的同步步骤就可以把该事务数据清楚
Leader 选举
在集群启动过程中,或 Leader 宕机后,集群就进入了恢复模式。恢复模式中最重要的阶段就是 Leader 选举。
(1) Leader 选举中的基本概念
- A、myid:也称为 ServerId(代码里面体现的是ServerId),这是 zk 集群中服务器的唯一标识。例如,有三个 zk 服务器,那么编号分别是 1,2,3。
- B、 逻辑时钟:逻辑时钟,Logicalclock,是一个整型数(代码里面是个Atomiclock),该概念在选举时称为 logicalclock,而在选举结束后称为 epoch。
即 epoch 与logicalclock 是同一个值,在不同情况下的不同名称
。
(2) Leader 选举算法
在集群启动过程中的 Leader 选举过程(算法)与 Leader 断连后的 Leader 选举过程稍微有一些区别,基本相同。
A、集群启动中的 Leader 选举
若进行 Leader 选举,则至少需要两台主机,这里以三台主机组成的集群为例。
在集群初始化阶段,当第一台服务器 Server1 启动时,其会给自己投票,然后发布自己的投票结果。投票包含所推举的服务器的 myid 和 ZXID,使用(myid, ZXID)来表示,此时 Server1的投票为(1, 0)。由于其它机器还没有启动所以它收不到反馈信息,Server1 的状态一直属于Looking,即属于非服务状态。
当第二台服务器 Server2 启动时,此时两台机器可以相互通信,每台机器都试图找到Leader,选举过程如下:
- (1) 每个 Server 发出一个投票。此时 Server1 的投票为(1, 0),Server2 的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
- (2) 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自 LOOKING 状态的服务器。(检查两方面,一是投票者,而是被投票者是否合法)
- (3) 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行 PK,PK规则如下:
优先检查 ZXID。ZXID 比较大的服务器优先作为 Leader。
如果 ZXID 相同,那么就比较 myid。myid 较大的服务器作为 Leader 服务器。
对于 Server1 而言,它的投票是(1, 0),接收 Server2 的投票为(2, 0)。其首先会比较两者的 ZXID,均为 0,再比较 myid,此时 Server2 的 myid 最大,于是 Server1 更新自己的投票为(2, 0),然后重新投票。对于 Server2 而言,其无须更新自己的投票,只是再次向集群中所有主机发出上一次投票信息即可。
- (4) 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息。对于 Server1、Server2 而言,都统计出集群中已经有两台主机接受了(2, 0)的投票信息,此时便认为已经选出了新的 Leader,即 Server2。
- (5) 改变服务器状态。一旦确定了 Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为 FOLLOWING,果是 Leader,就变更为 LEADING。
- (6) 添加主机。在新的 Leader 选举出来后 Server3 启动,其想发出新一轮的选举。但由于当前集群中各个主机的状态并不是 LOOKING,而是各司其职的正常服务,所以其只能是以Follower 的身份加入到集群中。
B、 宕机后的 Leader 选举
在 Zookeeper 运行期间,Leader 与非 Leader 服务器各司其职,即便当有非 Leader 服务器宕机或新加入时也不会影响 Leader。但是若 Leader 服务器挂了,那么整个集群将暂停对外服务,进入新一轮的 Leader 选举,其过程和启动时期的 Leader 选举过程基本一致。
假设正在运行的有 Server1、Server2、Server3 三台服务器,当前 Leader 是 Server2,若某一时刻 Server2 挂了,此时便开始新一轮的 Leader 选举了。选举过程如下:
- (1) 变更状态。Leader 挂后,余下的非 Observer 服务器都会将自己的服务器状态由FOLLOWING 变更为 LOOKING,然后开始进入 Leader 选举过程。
- (2) 每个 Server 会发出一个投票,仍然会首先投自己。不过,在运行期间每个服务器上的 ZXID 可能是不同,此时假定 Server1 的 ZXID 为 111,Server3 的 ZXID 为 333;在第一轮投票中,Server1 和 Server3 都会投自己,产生投票(1, 111),(3, 333),然后各自将投票发送给集群中所有机器。
- (3) 接收来自各个服务器的投票。与启动时过程相同。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自 LOOKING 状态的服务器。
- (4) 处理投票。与启动时过程相同。针对每一个投票,服务器都需要将别人的投票和自己的投票进行 PK。对于 Server1 而言,它的投票是(1, 111),接收 Server3 的投票为(3, 333)。其首先会比较两者的 ZXID,Server3 投票的 zxid 为 333 大于 Server1 投票的 zxid 的 111,于是
Server1 更新自己的投票为(3, 333),然后重新投票
。对于 Server3 而言,其无须更新自己的投票,只是再次向集群中所有主机发出上一次投票信息即可
。 - (5) 统计投票。与启动时过程相同。对于 Server1、Server2 而言,都统计出集群中已经有两台主机接受了(3, 333)的投票信息,此时便认为已经选出了新的 Leader,即 Server3。
- (6) 改变服务器的状态。与启动时过程相同。一旦确定了 Leader,每个服务器就会更新自己的状态。Server1 变更为 FOLLOWING,Server3 变更为 LEADING。