Zookeeper 集群及其选举机制
- 1.安装 Zookeeper 集群
- 2.如何选取 Leader
1.安装 Zookeeper 集群
我们之前说了,Zookeeper 集群是由一个领导者(Leader
)和多个追随者(Follower
)组成,但这个领导者是怎么选出来的呢?我们貌似没有在配置文件中看到有关领导者和追随者的参数啊?
在此之前先来看看 Zookeeper 内部的一些机制:
- 半数机制:只要有半数以上的节点存活,则集群可用,所以 Zookeeper 集群的节点数量适合为奇数。
- 虽然在配置文件中没有指定领导者和追随者,但 Zookeeper 在工作时,有一个节点为 Leader,其它则为 Follower,而 Leader 是通过内部的选举机制临时产生的。
那么领导者到底是怎么选出来的呢?很简单,每台服务器都有一个 id
(这里的 id
后面说),当启动的服务器超过半数的时候,就会选择 id
最大的 Server 成为领导者。比如有五台服务器,半数就是 2.5 2.5 2.5,因此当启动三台的时候就可以选出领导者。至于剩余的两台,启动之后只能成为追随者,因为领导者已经选出来了。关于这里的细节,一会儿再详细聊。
那么怎么指定服务器的 id
呢?还记得配置文件中的 dataDir
参数吗,在该参数指定的目录下创建一个 myid
文件(文件必须叫这个名字),然后在里面写上服务器的 id
即可。
[root@satori zkData]# echo 2 > myid
这里给 id
设置为 2,因为一会要搭建由三个节点组成的集群,而我希望当前节点成为 Leader,所以它的 id 应该为 2,其它的两个节点的 id
显然分别为 1 和 3。这样按着 id
从小到大的顺序启动时,该节点就会成为 Leader。
下面来我们来搭建 zookeeper 集群,总共三个节点:
- IP:
82.157.146.194
,主机名:satori
- IP:
121.37.165.252
,主机名:koishi
- IP:
123.60.7.226
,主机名:marisa
satori
节点就是当前一直在用的节点,剩余的两个节点的 Zookeeper 也已经安装完毕。那么问题来了,我们要如何将这三个节点组成一个集群呢?显然还需要修改配置文件,先在 satori
节点进行修改。
# koishi 节点
server.1=121.37.165.252:2888:3888
# satori 节点
server.2=0.0.0.0:2888:3888
# marisa 节点
server.3=123.60.7.226:2888:3888
将集群中都有哪些节点写在 zoo.cfg
中,解释一下具体含义,首先两个冒号把等号右边分成了三部分,第一部分就不用说了,IP 地址或者主机名,用于定位节点; 2888 2888 2888 是 Leader 和 Follower 交换信息 的端口,因为副本要进行同步; 3888 3888 3888 是 交换选举信息 的端口,因为要选出 Leader。
然后我们注意到 satori
节点的 IP 设置成了 0.0.0.0
,这是因为当前的三个节点不在同一个网段,IP 用的都是公网 IP,而公网 IP 在绑定服务的时候会失败。所以在绑定的时候,其它节点的 IP 要写成公网 IP,自身节点的 IP 要写成 0.0.0.0
。因此其它两个节点的 zoo.cfg
文件就应该这么改:
########## koishi 节点配置 ##########
# koishi 节点
server.1=0.0.0.0:2888:3888
# satori 节点
server.2=82.157.146.194:2888:3888
# marisa 节点
server.3=123.60.7.226:2888:3888########## marisa 节点配置 ##########
# koishi 节点
server.1=121.37.165.252:2888:3888
# satori 节点
server.2=82.157.146.194:2888:3888
# marisa 节点
server.3=0.0.0.0:2888:3888
但是在 生产中,一个集群内的节点应该都位于同一网段,然后将配置文件中的 IP 全部换成内网 IP 即可。这样彼此之间可以通过内网访问,而内网的访问速度要远远快于公网,并且还不需要走公网的流量。但我当前的三台云服务器不在同一个网段,所以只能用公网 IP,并且绑定的时候,将节点自身的 IP 换成 0.0.0.0
。
至于等号左边的 server.
是固定的,后面的数字表示节点的 id
,而节点 id
我们说了,通过在 myid
文件中进行指定。而节点 id
决定了,最终由谁担任领导者。其中 satori
节点的 id
为 2 2 2,刚刚已经改过了,然后将 koishi
和 marisa
两个节点的 id
分别改为 1 1 1 和 3 3 3,然后就大功告成了。
然后我们来启动 Zookeeper,由于 satori
节点的 Zookeeper 已经启动了,我们在修改完配置文件之后,需要重新启动。
但是我们查看状态的时候,发现出错了,相信原因很好想。因为配置文件中指定了三个节点,而剩余两个节点的 Zookeeper 还没启动。下面我们来启动一下,然后再次查看状态。
当剩余的两个节点启动之后,再次查看状态,发现 Mode 变成了 Leader。显然集群已经启动成功,至于剩余的两个节点,显然就是 Follower。
此时集群就启动成功了,但是关于领导者和追随者的选举问题,我们还得再说一说。
2.如何选取 Leader
领导者选举分为两种情况:
- 集群第一次启动的时候,选举领导者。
- 运行过程中领导者挂了,从追随者当中选择一个作为领导者。
我们先来看第一种情况,假设集群当中有 5 个节点,id
分别为 1 到 5,来看看选举过程是怎样的?这里 5 个节点按照 id
从小到大顺序启动。
- 首先 server1 启动,发起一次选举,每个节点都有投票权,并且默认都会投给自己。此时 server1 有 1 1 1 票,但还不够半数以上( 3 3 3 票),选举无法完成,于是 server1 将状态保持为
LOOKING
; - 然后 server2 启动,再发起一次选举,重新投票。server1 和 server2 仍会把票投给自己,然后再交换选票信息。由于 server1 发现 server2 的
id
比自己大,于是会将自己的票改投给 server2。此时 server1 有 0 0 0 票,server2 有 2 2 2 票,但仍然没有哪个节点拥有超过半数的票,选举无法完成,server1 和 server2 状态都保持为LOOKING
; - 接下来 server3 启动,再发起一次选举,相信整个过程不需要解释了。老规矩还是先投给自己,再交换选票信息,然后 server1 和 server2 发现自己的
id
都没有 server3 大,于是都会将票改投给 server3。此时 server1 和 server2 的票数为 0 0 0,server3 的票数为 3 3 3,由于 server3 的票数已超过半数,所以成功当选为 Leader,状态变为LEADING
。而 server1、server2 则成为 Follower,状态改为FOLLOWING
。 - 所以 5 5 5 个节点,启动 3 3 3 个之后就能选择出 Leader。然后 server4 又启动了,于是也发起一次选举,并把票投给自己。但 server1、server2、server3 已经不是 LOOKING 状态,所以它们不会更改自己的选票信息,最终结果 server3 仍有 3 3 3 票,server4 只有 1 1 1 票。少数服从多数,于是会再将自己的选票交给 server3,成为 Follower,状态改为
FOLLOWING
。 - 同理,最后 server5 启动,结果就是 server3 有 4 4 4 票,自己只有 1 1 1 票。少数服从多数,于是将自己的选票交给 server3,成为 Follower。
所以整个过程,关键点有两个:
- 每个
server
启动之后都会发起选举,并将票投给自己。然后交换选票信息,并将票投给id
最大的server
。 - 一旦选择出 Leader,其它节点自动成为 Follower。而后启动的
server
,不论id
多大,也只能成为 Follower。
以上就是集群第一次启动的时候,选举领导者。
但如果在运行过程中,领导者挂了该怎么办呢?显然要再选举出一个新的领导者。所以当集群中的追随者发现自己连接不上领导者的时候,就会开始进入 Leader 选举,但此时是存在两种可能的。
- 领导者真的挂了。
- 领导者没有挂,只是追随者因为某些原因无法和领导者建立连接。比如 server5 发现连接不上 server3 了,于是它认为领导者挂了,便开启 Leader 选举。但事实上 server3 并没有挂,其它追随者都能正常连接,只是 server5 因为某些原因连接不上罢了。
先来解释第二种情况,server5 认为 server3 挂了之后,便会发起 Leader 选举,呼吁其它追随者进行投票。但是其它追随者发现领导者并没有挂,于是会拒绝 server5 的选举申请,并告知它当前已存在的领导者信息。对于 server5 而言,只需要和已存在的领导者重新建立连接,并进行数据同步即可。
server3:老子还没挂呢!!!😤
但如果是第一种情况,领导者真的挂了,该怎么办?比如这里的领导者 server3,在运行的时候,节点突然宕机了。
要解释这个问题,我们需要引入一些新的概念。
sid
:就是我们一直说的服务器id
,用于唯一标识集群中的节点。zxid
:事务id
,客户端在发起一次写请求的时候,都会带有zxid
,用于标识一次服务器状态的变更。所以 Zookeeper 也是有事务的,保证每次写数据的时候,要么全部写完,要么不写,不会出现只写一半的情况。另外每个节点都有自己的zxid
,它们的值也不一定相同。epoch
:Leader 任期的编号,就好比古代皇帝,每个皇帝在当政的时候都有自己的年号。并且每投完一次票,这个编号就会增加。
现在假设 server3 挂了,那么要重新选举 Leader,而选举规则如下:
- 先比较节点之间的
epoch
,epoch
大的直接当选。 epoch
相同,再比较zxid
,zxid
大的当选。epoch
和zxid
都相同,则比较sid
,sid
大的当选。
关于这么做背后的原理,我们先暂且不表,等到后面介绍 Paxos 协议的时候再细说。而且这里的 epoch
具体是干什么用的,估计也有人不太清楚,这些我们也留到后面再说。