什么是领导者选举

Zookeeper作为一个分布式中间件,为了提高自身的可用性,其内部是多节点以集群模式部署的,官网上的架构图放在下方

根据官网的介绍,每一个Server节点都保存着其他节点的信息,即节点间保持信息互通;只有当集群中大多数节点可用时,整个集群才可用。

集群中的节点有以下几个状态:

  • LEADER
    领导者,集群中有且仅存在一个;其负责集群中其他节点的探活,以及不停地同步最新数据给其他节点
  • FOLLOWING
    跟随者,就是集群中负责工作的小弟
  • OBSERVER
    观察者,这个节点作用和FOLLOWING相似,但不会进行领导者竞选
  • LOOKING
    处于这个状态的节点,说明需要通过领导者选举来确定自己的状态,因此会发送选举信息给其他节点

通过Zookeeper对节点状态的定义可见,领导者选举的目的就是在集群中选出一个主要的节点,其他节点的数据更新都会先发给领导者,再由领导者同步给其他节点。

明白了领导者选举是什么,接着来看领导者选举的过程,其过程的思路在分布式系统设计方面,很值得学习。

领导者选举图解

在整个选举过程中有几个概念要先说明下:

  1. 每一个LOOKING节点都是候选人
  2. 选举是根据节点的能力,在众多节点中进行多次pk出来的
  3. 每次pk的结果叫做选票,会广播给集群中所有候选人

整个领导者选举的过程大致如下,该图示表达了在整个过程中,节点通过选举算法选出一个候选人后,会将该选票通知到其他所有节点。

接着,让我们跟着源码,来看看选举过程的具体实现方式吧。

领导者选举源码解读

既然领导者的选举首先是发生在节点启动时,那么我们就把节点启动作为入口,顺着往下寻找选举的代码实现。

节点启动

集群模式的启动方法是在QuorumPeerMain类中(有集群就有单节点,单节点的启动类是ZooKeeperServerMain),有一个main(),这里省去了无关代码,我们简单来看下;

public class QuorumPeerMain {public static void main(String[] args) {QuorumPeerMain main = new QuorumPeerMain();// 完成初始化配置并启动节点main.initializeAndRun(args);}protected void initializeAndRun(String[] args) throws ConfigException, IOException, AdminServerException {QuorumPeerConfig config = new QuorumPeerConfig();if (args.length == 1) {// 解析配置文件config.parse(args[0]);}if (args.length == 1 && config.isDistributed()) {// 集群模式启动runFromConfig(config);} else {// 单机模式启动ZooKeeperServerMain.main(args);}}
}

还记得Zookeeper的启动参数吧,通常是zkServer.sh start后面会跟上一个config文件路径对吧,这个路径就作为main()的arg[0]参数被传递了进来,具体可以查看zkServer.sh这个脚本。

接着集群模式的启动进入了runFromConfig(config),这个方法比较长,主要做了这么几件事:

  1. 创建一个链接工厂ServerCnxnFactory,这个工厂有两种实现,分别是JDK的NIO和Netty,默认是NIO
  2. 接着初始化了一个QuorumPeer实例,并进行一长串的参数赋值
  3. 调用quorumPeer.start()启动节点
  4. 调用quorumPeer.join(),使main()一直阻塞

如此以来就完成了节点的启动,从以上几个步骤,我们可以得出几个很重要的结论:

  • QuorumPeer就是代表当前节点的对象实例
  • QuorumPeer是一个线程实现类
  • 一定实现了线程的run(),并且领导者选举的核心方法就在里面

领导者选举

我们顺着来到QuorumPeerrun()实现,这里主要关注这么一个循环方法,简化如下

@Override
public void run() {while(running){// 枚举当前节点状态switch(getPeerState()){case LOOKING: doSomething...    // 状态为待选举case OBSERVING: doSomething... // 状态为观察者case FOLLOWING: doSomething... // 状态为从节点case LEADING: doSomething...   // 状态为领导节点}}
}

也就是说,当前节点启动之后会进入这个循环,getPeerState()会获取当前节点的状态,接着执行不同的case逻辑,只有当节点是LOOKING状态时才需要领导者选举,于是我们来看LOOKING执行的关键逻辑。

case LOOKING:// shuttingDownLE是个bool标志位,LE是 Leader Election的缩写if (shuttingDownLE) {// 停止领导者选举标志置为falseshuttingDownLE = false;// 领导者选举开始前的准备工作// 1.生成一张投给自己的选票// 2.初始化选举算法,目前仅支持FastLeaderElection,即为默认startLeaderElection();}// 执行选举逻辑,然后保存结果选票setCurrentVote(makeLEStrategy().lookForLeader());break;

看到startLeaderElection()方法名,可能会认为这就是执行选举的方法,然而并不是,这个方法只是在选举前的初始化,注释我写在了代码中,很容易理解。

public synchronized void startLeaderElection() {if (getPeerState() == ServerState.LOOKING) {// 生成一张投给自己的选票currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());}// 初始化选举算法,目前仅支持FastLeaderElection,即为默认this.electionAlg = createElectionAlgorithm(electionType);
}

真正执行选举逻辑的方法是lookForLeader(),尽管这个方法是一个名为Election的接口提供的,但是这个接口只有一个实现类,就是FastLeaderElection,我们直接进入lookForLeader()看选举的核心逻辑

lookForLeader()

在这个方法上有一句注释,非常清楚地解释了这个方法的作用

/*** Starts a new round of leader election. Whenever our QuorumPeer* changes its state to LOOKING, this method is invoked, and it* sends notifications to all other peers.*/

开始新的一轮领导选举,只要当节点状态变成LOOKING时,就会调用这个方法,然后它会将选票发送给其他所有节点。

方法开始首先会给自己投一票,并将选票发送给其他所有节点,调用的是sendNotifications(),注意的是,这个方法并不是调用网络请求直接发送,而是塞进了待发送队列,这个队列就是LinkedBlockingQueue,与之相伴的还有一个接收队列

LinkedBlockingQueue<ToSend> sendqueue;
LinkedBlockingQueue<Notification> recvqueue;

所以,而具体的发送是由一个发送线程去执行的,这个一会再聊。

接着该方法进入一个while循环,第一件事就是从接收队列(recvqueue)中取出一个选票

/*1. Remove next notification from queue, times out after 2 times2. the termination time*/
Notification n = recvqueue.poll(notTimeout, TimeUnit.MILLISECONDS);

我们来看下Notification对象的定义和结构,类上的注释解释了这个类的作用,大概意思表明,这个类是用来在发生选票时,通知其他节点的数据结构。

/**3. Notifications are messages that let other peers know that4. a given peer has changed its vote, either because it has5. joined leader election or because it learned of another6. peer with higher zxid or same zxid and higher server id*/
public static class Notification {/** 格式化版本*/public static final int CURRENTVERSION = 0x2;int version;/** 意向领导*/ long leader;/** 意向领导的zxid,代表数据版本的id*/ long zxid;/** 当前选举的届数*/ long electionEpoch;/** 发送节点的状态*/ QuorumPeer.ServerState state;/** 节点id*/ long sid;QuorumVerifier qv;/** 意向领导处于的届数*/ long peerEpoch;
}

当节点收到一个选票时,会首先判断当前选举的届数,然后再开始选票pk,代码逻辑如下

// 当选票的届数大于当前节点所处的届数,
if (n.electionEpoch > logicalclock.get()) {// 届数更新为最新一届logicalclock.set(n.electionEpoch);// 清空投票箱recvset.clear();// 进行选票pkif (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch, getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) {updateProposal(n.leader, n.zxid, n.peerEpoch);} else {updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());}sendNotifications();// 当选票的届数小于当前节点所处的届数,说明该选票无效,故直接忽略
} else if (n.electionEpoch < logicalclock.get()) {LOG.debug("Notification election epoch is smaller than logicalclock. n.electionEpoch = 0x{}, logicalclock=0x{}",Long.toHexString(n.electionEpoch),Long.toHexString(logicalclock.get()));break;// 当选票的届数等于当前节点所处的届数,直接进行选票pk
} else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch, proposedLeader, proposedZxid, proposedEpoch)) {updateProposal(n.leader, n.zxid, n.peerEpoch);sendNotifications();
}

通过注释应该能轻松看懂逻辑,关键的选票pk逻辑就在totalOrderPredicate()中,进入看看

protected boolean totalOrderPredicate(long newId, long newZxid, long newEpoch, long curId, long curZxid, long curEpoch) {/** We return true if one of the following three cases hold:* 1- New epoch is higher* 2- New epoch is the same as current epoch, but new zxid is higher* 3- New epoch is the same as current epoch, new zxid is the same*  as current zxid, but server id is higher.*/return ((newEpoch > curEpoch)|| ((newEpoch == curEpoch)&& ((newZxid > curZxid)|| ((newZxid == curZxid)&& (newId > curId)))));
}

为了严谨对待选举,这里比较了很多信息,官方也写了注释,我们逐一来看:

  1. 先比较选举的届数,届数高的说明是最新一届,胜出
  2. 再比较zxid,也就是看谁的数据最新,最新的胜出
  3. 最后比较serverid,这是配置文件指定的,节点id大者胜出

通过这个方法就比较出了一个获胜方,接着把这个胜出者封装成Vode对象,放进投票箱中,然后调用sendNotifications()把选票发送到别的节点

// 投票箱是个map,是在刚进入lookForLeader()就实例化了,这里只是为了说明其数据结构
Map<Long, Vote> recvset = new HashMap<Long, Vote>();
// 封装成票,放进投票箱
recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));

接着会调用voteSet.hasAllQuorums()检查是否收到了所有节点发来的选票,如果还没收齐,就又回到while循环,调用recvqueue.poll()取出选票;如果收齐了,则根据之前的选票pk,已经得出了最终的领导者,若是自己就把自己的状态设为LEADER,否则设为FOLLOWING或者OBSERVING。

至此,集群中的领导者就被选出来了。

总结

我们来回顾一下,在这个过程中,每一个节点都会不断地发送和接收选票,随着优秀节点的选票越来越多,每个节点都在修正自己的判断,最终会选出一个领导者,与所有节点保持信息一致。

Zookeeper的领导者选举机制解析相关推荐

  1. 分布式开发必须了解的Zookeeper的Leader选举机制(源码解析)

    分布式开发必须知道的Zookeeper知识及其的Leader选举机制(ZAB原子广播协议)   ZooKeeper是Hadoop下的一个子项目,它是一个针对大型分布式系统的可靠协调系统,提供的功能包括 ...

  2. zookeeper的Leader选举机制详解

    转载自:https://www.toutiao.com/i6701570306445672963/?tt_from=copy_link&utm_campaign=client_share&am ...

  3. 面试官:说一说Zookeeper中Leader选举机制

    哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新,可以微信搜索[小奇JAVA面试]第一时间阅 ...

  4. Zookeeper:Zookeeper的主从选举机制

    ZAB 协议,全称 Zookeeper Atomic Broadcast(Zookeeper 原子广播协议),是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的一致性协议.基于该协议 ...

  5. zookeeper的leader选举机制

    什么是zxid和myid? zxid: zookeeper为了保证数据的有序性,会给每一个写操作的数据,编写一个全局唯一的zxid. zxid是一个64位的数字:前32位会是由当前节点参与的选举次数决 ...

  6. Kafka3.x核心速查手册三、服务端原理篇-2、Broker选举机制

    2.Controller Broker选举机制 ​ 在Kafka集群进行工作之前,需要选举出一个Broker来担任Controller角色,负责整体管理集群内的分区和副本状态.选举Controller ...

  7. 2021年大数据ZooKeeper(六):ZooKeeper选举机制

    目录 ​​​​​​ZooKeeper选举机制 概念 全新集群选举 非全新集群选举 ZooKeeper选举机制 zookeeper默认的算法是FastLeaderElection,采用投票数大于半数则胜 ...

  8. ZooKeeper入门(五)配置集群和选举机制

    一. 配置集群 1. 准备多台ZooKeeper服务器 2. 配置ZooKeeper服务器 在每台服务器的conf/zoo.cfg文件中添加如下内容: server.20=192.168.4.20:2 ...

  9. 理解zookeeper选举机制

    转载:https://www.cnblogs.com/shuaiandjun/p/9383655.html 一.zookeeper集群 配置多个实例共同构成一个集群对外提供服务以达到水平扩展的目的,每 ...

最新文章

  1. C#游戏开发快速入门 2.1 构建游戏场景
  2. Plist文件的创建与读写
  3. wince6.0远程控制工具_【创新创效】门架业务主动监测工具和远程控制电源开关...
  4. html+css+javascript 网页设计 从入门到精通_绵阳美工设计学习
  5. 信息收集——子域名收集
  6. 【Maven入门教程】Maven的基本概念
  7. JavaScript Cookie的操作
  8. [转帖]解密微软中间语言MSIL之调试程序(1)
  9. mvc6 mysql_MVC+EF6使用MySQL+CodeFirst的详细配置
  10. try except 异常捕获的方法、断言的使用
  11. 【Kettle】Kitchen和Pan的命令行参数
  12. 电子商务平台的搭建技巧与成功案例资料集
  13. 设计思维(Design Thinking)
  14. 文件和数据格式化之数据的组织维度
  15. 云开发【云函数的使用】
  16. 沃信科技T3 Sota安装配置手册(1-4章)
  17. 期末小结(一). 专业技术
  18. 柯美c7000服务器显示00,柯尼卡美能达bizhub PRESS C7000 故障排除.pdf
  19. 利用Eigen完成一元线性回归
  20. 《精力管理》读书摘录

热门文章

  1. 战略制定4大关键要点
  2. 自主研发项目三之微信上门洗车系统
  3. Linux文件属主和属组 概念
  4. 怎么用feign远程调用别人的接口_Feign - HTTP接口调用- 单独使用 - 实战
  5. python图表制作方法_Python中一种简单的动态图表制作方法
  6. 丁奇的MySQL实战45讲 学习笔记[链接]
  7. Python基础总结(四) 字符串
  8. 我为什么放弃一个25000星的开源项目
  9. 基于Skeleton的手势识别:SAM-SLR-v2
  10. 微信公众号发送模版消息详细过程