Zookeeper之Leader选举源码分析
Zookeeper源码下载地址:https://github.com/apache/zookeeper
1.选举流程
Zookeeeper的Leader选举会分两个过程。
服务启动时的leader选举
每个节点启动的时候状态都是LOOKING,处于观望状态,接下来就开始进行选leader流程。
进行leader选举,至少需要两台机器,我们选取3台机器组成的服务器集群为例。在集群初始化阶段,当有一台服务器server1启动时,它本身是无法进行和完成leader选举的,当第二台服务器server2启动时,这个时候两台机器可以相互通信,每台机器都试图找到leader,于是进入leader选举过程。过程如下:
(1)每个server发出一个投票。由于是初始情况,server1和server2都会将自己作为leader服务器来进行投票,每次投票会包含所推举的服务器的mid、ZXID和epoch,使用(myid,ZXID,epoch)来表示。此时server1的投票为(1,0),server2的投票为(2,0),然后各自将这个投票发给集群中其它机器。
(2)接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票(epoch)、是否来自LOOKING状态的服务器。
(3)处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则如下:
-
优先检查ZXID。ZXID比较大的服务器优先作为leader
-
如果ZXID相同,那么就比较myid。myid较大的服务器作为leader服务器
对于server1而言,它的投票是(1,0),接收server2的投票为(2,0),首先会比较两者的ZXID,均为0,再比较myid,此时server2的myid最大,于是更新自己的投票为(2,0),然后重新投票,对于server2而言,它不需要更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。
(4)统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接收到相同的投票信息,对于server1、server2而言,都统计出集群中已经有两台机器接受了(2,0)的投票信息,此时便认为已经选出了leader。
(5)改变服务器状态。一旦确定了leader,每个服务器就会更新自己的状态,如果是follower,那么就变更为FOLLOWING,如果是leader,就变更为LEADING。
运行过程中的leader选举
当集群中的 leader 服务器出现宕机或者不可用的情况时,那么整个集群将无法对外提供服务,而是进入新一轮的leader 选举,服务器运行期间的 leader 选举和启动时期的 leader 选举基本过程是一致的。
(1)变更状态。leader宕机后,剩下的非observer服务器都会将自己的服务器状态变更为LOOKING,然后开始进入leader选举过程。
(2)每个server会发出一个投票。在运行期间,每个服务器上的ZXID可能不同,此时假定server1的ZXID为123,server3的ZXID为122;在第一轮投票中,server1和server3都会投给自己,产生投票(1,123),(3,122),然后各自将投票发送给集群中所有机器。接收来自各个服务器的投票。
(3)处理投票。与启动时选举过程相同。此时server1因为ZXID高于server3,server1将会被推举为leader
(4)统计投票。与启动时过程相同。
(5)改变服务器的状态。与启动时过程相同。
2.源码分析
初始化并启动:
这里会判断zookeeper是单机模式还是集群模式,如果是集群模式,就会进入对应的选举代码。
设置各种参数。大部分配置都是在conf里面的配置文件中配置。
启动主线程。QuorumPeer重写了Thread.start方法。
调用quorumpeer的start方法
loadDataBase,主要是从本地文件中恢复数据以及获取最新的zxid
初始化leaderElection
配置选举算法,选举算法有3种,可以通过zoo.cfg进行配置,默认是fast选举
继续看FastLeaderElection的初始化动作。主要初始化了业务层的发送队列和接收队列
接下来看fle.start()。主要是对发送线程和接收线程的初始化。
wsThread和wrThread的初始化动作在FastLeaderElection 的 starter 方法里面进行,这里面有两个内部类,一个是 WorkerSender,一个是 WorkerReceiver,负责发送投票信息和接收投票信息
然后再回到QuorumPeer类。初始化完成,调用super.start()方法,即运行QuorumPeer的run方法
前面的部分主要是做JMX监控注册
重要的部分在while循环中
调用setCurrentVote(makeLEStrategy().lookForLeader())。根据策略决定FastLeaderElection中的选举算法。
LOOKFORLEADER开始选举
消息如何广播,看sendNotifications
WorkerSender
3.FastLeaderElection 选举过程
其实在这个投票过程中就涉及到几个类
FastLeaderElection:FastLeaderElection 实现了 Election 接口,实现各服务器之间基于 TCP 协议进行选举
Notification:内部类,Notification 表示收到的选举投票信息(其他服务器发来的选举投票信息),其包含了被选举者的 id、zxid、选举周期等信息
ToSend:ToSend 表示发送给其他服务器的选举投票信息,也包含了被选举者的 id、zxid、选举周期等信息
Messenger: Messenger 包 含 了 WorkerReceiver 和WorkerSender 两个内部类;
WorkerReceiver: 实现了 Runnable 接口,是选票接收器。其会不断地从 QuorumCnxManager 中获取其他服务器发来的选举消息,并将其转换成一个选票,然后保存到recvqueue 中
WorkerSender: 也实现了 Runnable 接口,为选票发送器,其会不断地从 sendqueue 中获取待发送的选票,并将其传递到底层 QuorumCnxManager 中