关于java:图解-ZooKeeper-的选举机制

44次阅读

共计 7479 个字符,预计需要花费 19 分钟才能阅读完成。

<p align=”center”> 本文作者:HelloGitHub-老荀</p>

Hi,这里是 HelloGitHub 推出的 HelloZooKeeper 系列,收费开源、乏味、入门级的 ZooKeeper 教程,面向有编程根底的老手。

我的项目地址:https://github.com/HelloGitHu…

明天开始咱们将持续深刻 ZK 选举相干的常识

一、选举的根本规定

ZKr~这次我决定一反常态,先不讲故事了~先得聊聊在 ZK 选举中十分重要的一些货色。

1.1 zxid

zxid 就是咱们之前提到的事务编号,是一个 8 字节的整型数字,然而 ZK 设计的时候把这一个数字拆成了两局部应用,一鱼两吃!

8 个字节的整数一共有 64 位长度,前 32 位用来记录 epoch,后 32 位就是用来计数。你可能要问了?epoch?是啥?

zxid 初始化是 0,也就是这样

00000000000000000000000000000000 00000000000000000000000000000000

每一次写申请都会减少后 32 位,假如当初进行了 10 次写申请(无论该申请有没有真的批改到数据),zxid 就会变成这样

00000000000000000000000000000000 00000000000000000000000000001010

当进行一次选举的时候,前 32 位就会减少 1,并且清零后 32 位

00000000000000000000000000000001 00000000000000000000000000000000

除了选举以外,当后 32 位彻底用完(变成全 1,也就是 ZK 失常执行了 2^32 – 1 次写申请都没进行过一次选举,牛逼!)也会让前 32 位减少 1,相当于进位

# 进位前
00000000000000000000000000000000 11111111111111111111111111111111
# 进位后
00000000000000000000000000000001 00000000000000000000000000000000

到这里我就能够答复大家后面的问题了,epoch 就是 zxid 前 32 位的这个数字,epoch 自身的翻译是“纪元,时代”的意思,意味着更新换代,而 zxid 的后 32 位数字仅仅是写申请的计数罢了

1.2 myid

在之前的小故事里,我给 ZK 的集群中的各个节点都起了一个好记的名字(神特么好记!)。然而 ZK 官网本人是如何给每一个集群中的节点起名字的呢?用的就是 myid!

ZK 的启动配置 zoo.cfg 中有一项 dataDir 指定了数据寄存的门路(默认是 /tmp/zookeeper),在此门路下新建一个文本文件,命名为 myid,文本内容就是一个数字,这个数字就是以后节点的 myid

/tmp
└── zookeeper
    ├── myid
    └── ...

而后在 zoo.cfg 是这样配置集群信息

server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888

这个 server. 之后的数字就是 myid,这个 myid 在整个集群中,各个节点之间是不能反复的。我遗记之前在哪儿看到的了,说是 myid 只能是 1 到 255 的数字,我始终信以为真,直到这次,我本着谨严的态度去做了实际,所有以事实为主,并且我的试验笼罩了 3.4、3.5、3.6 三大版本(都是三台机器的简略集群),论断是:myid 只有是不等于 -1 就行(-1 是一个固定的值会导致以后节点启动报错),不能大于 Long.MAX_VALUE 或者小于 Long.MIN_VALUE,然而如果在以后的节点中配置了 zookeeper.extendedTypesEnabled=true 那以后节点的最大 myid 是 254(正数不影响,我也不晓得这个 254 的用意,然而代码中确实有判断)是不是奇怪的常识又减少了呢~

对于配置更多的信息,之后独自再整顿,明天就点到为止

1.3 选举规定

晓得了下面这些有什么用呢?十分重要!因为选举 Leader 齐全看的就是这几个值

  • epoch
  • 写申请次数
  • myid

优先级从上到下逐级比拟,谁大谁就更有资格成为 Leader,以后级一样就比拟下一级,直到分出输赢为止!因为 myid 是不能反复的,所以最终是肯定能分出输赢的!

好了,当初大家晓得了最根本的选举规定了~让咱们进入下一节吧

二、三马之争

马果果 肯定想不到,这辈子本人能够和两位鼎鼎大名的明星企业家等量齐观,让咱们一起去看看产生了什么吧~

2.1 筹备动工

之前 马果果 规定了三个办事处在对外倒闭前必须选出一个 Leader,在正式开始选之前,每一个办事处也有一些筹备工作须要做:

  • 每一个办事处必须得晓得一共有多少个办事处
  • 额定延聘一些专门负责和其余办事处沟通的话务员
  • 筹备好一个票箱用来对投票统计和归票
  • 为每一个办事处设置一个固定的 myid

所以当初办公室的安排变成了这样(我省略了之前章节的其余因素):

有了这些筹备工作当前所有办事处都能够进入选举的阶段了,并且村委会规定了几种状态用于示意以后办事处正处在的阶段:

  • LOOKING,正在寻找 Leader,处于此阶段的办事处不能对外提供服务
  • LEADING,以后办事处就是 Leader,能够对外提供服务
  • FOLLOWING,以后办事处正在追随 Leader,能够对外提供服务

很显著刚刚筹备好的各个办事处当初都处于 LOOKING 状态,上面让咱们正式进入选举流程吧

2.2 开始选举

因为各个办事处刚筹备好,所以彼此之间还没有通过信,又加上大家都是姓马的,心里面都是想当老大的,所以每一个办事都会率先拟一张写着本人的选票发给其余办事处。次要有这些信息:

  • sid:我是谁
  • leader:我选谁
  • state:我以后的状态
  • epoch:我以后的 epoch
  • zxid:我抉择的 leader 的最大的事务编号

马果果 举例:

马小云 马小腾 也一样,一开始都选了本人做 Leader 候选人,并且都把本人认为的候选人(以后场景下就是本人)的票别离发送给了其余两位(以及本人)

2.2.1 马果果视角

每个办事处各自也会收到来自其余办事处的选票(也有可能是本人的),每拿到一张选票,都须要和以后本人认为的 Leader 候选人做比拟,实践上本人投给本人的选票会先一步达到本人的票箱,因为不须要通过通信缩小了传输的门路,本人的选票和本人的候选人是统一的所以不须要比拟,只须要在票箱中记上一笔,咱们还是以 马果果 举例:

=》的右边是办事处的名字,左边是该办事处选的 Leader。以后投票统计是指,以后节点所选的 Leader 取得的选票统计。

假如他再收到了 马小云 的选票:

  • 马果果 首先看到的是 马小云 也处在 LOOKING 状态
  • 接着就会比拟本人候选人和 马小云 的选票(右边代表以后办事处的候选人,左边代表收到的选票信息,下同)
e:0                        ==        e:0
z:0                     ==        z:0
l: 马果果(69)    >         l: 马小云(56)

最终因为 马果果 的 myid 69 要比 马小云 的 myid 56 要大,所以 马果果 最终胜出!尽管 马小云 胜出了,然而以后投票统计是不能批改的,因为 马小云 这一轮的选票就是选的 马小云,须要期待他从新改票后再投能力批改投票统计。

之后会往投票箱记录:

紧接着是 马小腾 的投票:

e:0                        ==        e:0
z:0                     ==        z:0
l: 马果果(69)    >         l: 马小腾(49)

马果果 还是胜出!

记录投票箱:

每次收到投票的时候,马果果 都会根据以后的投票统计进行归票,然而很遗憾选举依然无奈完结,因为完结的规定必须有某一个办事处取得半数以上的选票,当初只有一个 马果果 本人的选票,不满足半数以上,所以马果果只能再等等了。

而在 马果果 这边忙的热气腾腾的同时,马小云 马小腾 也在进行着同样的动作。

2.2.2 马小云视角

咱们这省略形容 马小云 记录本人选票的过程,假如他这边是先收到 马果果 的选票,是怎么解决的呢?

e:0                        ==        e:0
z:0                     ==        z:0
l: 马小云(56)    <         l: 马果果(69)

马小云 看到本人认为的 Leader 候选人被 马果果 的选票击败了,所以将本人的候选人改为 马果果,并将新的选票从新播送进来

而后在本人的投票箱中记录:

为了叙述的完整性,咱们还是把 马小腾 的票也看完

e:0                        ==        e:0
z:0                     ==        z:0
l: 马果果(69)    >         l: 马小腾(49)

马果果 还是胜出了,所以 马小云 的投票箱最终变成这样:

讲道理接下来应该以 马小腾 为主视角,再讲一遍方才的过程,然而能够认为简直和 马小云 是一样的,为了故事的顺畅,咱们须要回到 马果果 的视角,因为 马小云 输给 马果果 之后改票了,又发了一轮选票

2.2.3 马果果视角(再)

马果果 又再一次收到了 马小云 的选票(改票后),投票箱就会改成这样:

收到这个投票后,以后投票统计就会减少 马小云 的记录,而后 马果果 进行归票就发现了这次本人的选票超过半数了,而后会进行二次确认,会期待一会看看还能不能收到更新的选票,这里假如没有收到更新的投票,就会进行判断,以后过半数的候选人是不是本人?如果是的话,那本人就是 Leader,不是的话,本人就是 Follower。

很显著,马果果 就是 Leader,而后会把本人的状态批改为 LEADING。

与此同时,马小云 马小腾 也进行归票,归票后果本人为 Follower,把本人状态批改为 FOLLOWING,而后各自都会和 Leader 进行数据的同步,同步实现之后整个办事处就都能够对外提供服务了。

2.3 马小腾停电啦

选举自身波及到集群间的通信、节点本身的状态治理和状态变更,自身就是一个比较复杂的过程,方才只是举例了一个最简略的启动选举流程,上面会举更多的例子帮忙大家能了解整个选举的逻辑。

当初假如办事处平安无事得对外提供了一段时间服务后,马小腾 的办事处忽然停电了,就不能和另外两马进行通信了,而另外两马在一段时间内都没有收到过 马小腾 的信息的时候就晓得,出事了!然而各自盘点了下目前依然还有两个办事处能够对外提供服务,是达到整个集群总数的半数以上的,是能够持续让村民们来办理业务的,所以当初整个集群变成了这样:

没过一会,因为电力公司的踊跃抢修,马小腾 的办事处复原供电了,从新倒闭了,然而每一个办事处在倒闭前都是处在 LOOKING 状态的,还是会优先投票给本人,并会通过复盘本地的存档来失去本人办事处最新的数据,假如 马小腾 停电前是这样:

e:0                    
z:21                     
l: 马小腾(49)
LOOKING

他和之前一样会给另外两个办事处发本人的选票

但和之前的状况不同,无论是 马果果 还是 马小云 他们当初都处在工作的状态,收到了 马小腾 的选票后就会把以后的 Leader 也就是 马果果 的选票信息以及本人以后的状态发送给他。

马果果 发送的选票信息:

e:0                    
z:30                     
l: 马果果(69)
LEADING

马小云 发送的选票信息:

e:0                    
z:30                     
l: 马果果(69)
FOLLOWING

马小腾 收到两位的选票信息后,晓得了以后的 Leader 是 马果果 ,并且 马果果 自己也确认了是 LEADING 状态,就马上把本人的状态批改为了 FOLLOWING 状态,并且会和之前一样与 Leader 进行数据的同步,对于具体怎么同步的,我打算留到之后再进行解说~

同步之后,马小腾 的状态变成了和 马小云 一样的了。


我再假如这里有一个平行世界,回到 马小腾 刚复原完供电筹备倒闭上线的时候,此时的 马小腾 的状态假如是这样的:

e:1                    
z:7                     
l: 马小腾(49)
LOOKING

哪怕 epoch 比目前的 Leader 还要大,其实照情理是更有资格当 Leader,然而因为以后集群中的其余办事处曾经有了一个明确的 Leader,马小腾 也只能忍无可忍(谁让你停电了呢)还是以 Follower 的身份退出到集群中来,并且依然以以后 Leader 的信息来同步,你也能够了解为降级(把本人的 epoch 降级回 0)

职场就是这么仁慈,你略微请个长假再回来可能曾经是物是人非了~

2.4 马果果又病啦

马果果 毕竟年事已高,又又又生病了,办事处只能含泪关门,然而和上一次 马小腾 停电不同,这次是作为 Leader 的 马果果 进行服务了,因为之前定下的规定,整个办事处集群必须得有一个 Leader。当初 马小云 马小腾 发现 Leader 分割不上了,阐明 Leader 无奈服务了,他们就晓得必须选出一个新的 Leader。于是纷纷将本人的状态都批改为 LOOKING 状态,并且再次把候选人选为本人,从新向其余依然能够提供服务的办事处播送本人的选票(以后这个场景就是相互发选票了)。

无论谁收到选票后通过比拟后都会晓得是 马小腾 胜出

e:1                        ==        e:1
z:77                     <         z:80
l: 马小云(56)              l: 马小腾(49)

马小云 会把本人的候选人批改为 马小腾 之后从新再把本人的选票收回去,当初 马小腾 就取得了 2 票通过,同时也满足大于整个办事处集群半数以上,所以 马小腾 马小云 各自批改状态为 LEADING 和 FOLLOWING 后,并且会和之前说的一样,把 epoch 加 1 同时清空计数局部,最初从新复原对村民提供服务。

马果果 这边病好当前,会从新倒闭和之前的例子一样也是先从 LOOKING 状态开始,最初会从其余两马那里得悉目前的 Leader 是 马小腾 之后,就会被动和 马小腾 同步数据并以 Follower 的身份退出到办事处集群中对外提供服务。

2.5 招商引资

办事处的热气腾腾被村委会看在了眼里,心想只有三个办事处就能达到这样的成果,如果有更多的办事处呢?于是和三马磋商了下,决定对外招商引入社会资本,让他们本人依照现有模式建设新的办事处,这样村委会不必出一分钱,村民还能取得切实的益处,秒啊!

此举一度引来社会资本的大量关注,然而磋商过后,三马又感觉如果过多的引入内部力量势必会减弱本人手中的势力,所以又出了一个规定,三马自封为 Participant 只有他们三个才有资格进行 Leader 的竞选,而引入的社会资本所创立的办事处只能作为 Observer 退出办事处的集群中对外提供只读服务,没有资格竞争 Leader,这样就能够在不减少选举复杂程度的同时,晋升整个办事处集群对读申请的吞吐量。

要申明以后节点是 Observer,须要在 zoo.cfg 中先配置 peerType=observer

同时申明的集群信息最初要多加一个 :observer 用来标识,这样其余节点也会晓得以后 myid 为 1 和 2 都是 Observer

server.69=maguoguo:2888:3888
server.56=maxiaoyun:2888:3888
server.49=maxiaoteng:2888:3888
server.1=dongdong:2888:3888:observer
server.2=jitaimei:2888:3888:observer

而在 LOOKING 状态的 Observer 一开始的 Leader 候选人也会选本人,然而选票信息被设置成了这样,以 东东 举例:

e:Long.MIN_VALUE                    
z:Long.MIN_VALUE                     
l: 东东(1)
LOOKING

因为 epoch 被设置成了最小值所以这个选票等同于形同虚设,能够被间接疏忽,并且在三马那里会保护一个 Participant 的列表,如果他们收到了来自 Participant 以外的办事处的选票会间接抉择疏忽,所以能够说 Observer 的选票对选举后果是齐全没有影响的。最终是期待 Participant 之间的选举后果告诉,Observer 本身批改状态为 OBSERVING,开始和 Leader 进行同步数据,这点和 Follower 没区别,之后 Observer 和 Follower 会统称为 Learner

2.6 小结

  • 竞选 Leader 看的是 epoch、写申请操作数、myid 三个字段,顺次比拟谁大谁就更有资格成为 Leader
  • 获选超过半数以上的办事处正式成为 Leader,批改本人状态为 LEADING
  • 其余 Participant 批改为 FOLLOWING,Observer 则批改为 OBSERVING
  • 如果集群中曾经存在一个 Leader,其余办事处如果中途退出的话,间接追随该 Leader 即可
  • 还得提一句,如果以后可提供服务的节点曾经有余半数以上了,那么这个选举就永远无奈选出后果,每个节点都会始终处在 LOOKING 状态,整个办事处集群也就无奈对外提供服务了

三、猿话一下

扯蛋扯完了,当初用咱的行话对有一些概念再深刻一下。

首先我必须要说的是,故事里的三马,为了肯定的节目成果,我形容成了三个角色,然而理论中 ZK 服务端是不会做这样的辨别的,都是雷同的代码,依据不同的配置启动,才有了运行期间 Leader、Follower、Observer 的角色之分,所以更贴近于理论的应该相似于火影里的影分身或者龙珠里的残像拳之类的(如同混入了什么奇怪的货色)。

我画了下选举的简略流程图:

其余中央我基本上都讲过了,这里再讲下红色局部,因为可能一些网络因素,收回去的选票对方却没收到,这个发动从新播送投票就是为了能让对方再从新发一次刚刚的选票。

同监听客户端 2181 端口不同的是,服务端集群之间互相通信,间接应用的是原生的 Socket 并没有应用 NIO 或者是 Netty,因为服务端节点一共就这么几个而且针对每一个其余节点都会启动一个线程去监听,所以间接采纳了这种比拟原始的并且是阻塞的形式通信,更简略间接,而且假如对方服务不可用了的话,Socket 会间接报错退出。

收发选票也是采纳了 ZK 中十分常见的生产者 - 消费者模式,别离保护了两个阻塞队列,一个对应发送进来的选票,一个对应收到的选票,各自应用一个子线程去轮询该阻塞队列。

之前的 ZK 是领有 3 种选举策略的,尽管另外两种之前都是被废除的状态,不倡议应用,然而通过配置文件还是能够强行应用的。不过在最新的 3.6.2 中另两种策略间接从源码中删除了,当初只有一种选举的策略,源码中对应 FastLeaderElection,另外两个我也没钻研过,就不开展了。

对于服务端之间的心跳检测:

  • 服务端之间的心跳检测(PING)是由 Leader 发动的,发向所有集群中的其余节点
  • Follower 收到 PING 后会回一个 PING 给 Leader 并带上本人这边的客户端会话数据
  • 而 Leader 收到 Follower 的 PING 后,就会对这些客户端进行会话连贯

对于会话相干的知识点留到之后再说~

四、总结

明天咱们介绍了选举的规定,以及举例了一些选举的场景并加以阐明。为了介绍 Follower 或者 Observer 是如何在选举实现之后和 Leader 同步数据的,下一篇咱们会先介绍 ZK 是如何进行长久化的,期待一下吧,ZKr~

老规矩,如果你有任何对文章中的疑难也能够是倡议或者是对 ZK 原理局部的疑难,欢送来仓库中提 issue 给咱们,或者来语雀话题探讨。

地址:https://www.yuque.com/kaixin1…

正文完
 0