2019独角兽企业重金招聘Python工程师标准>>>

在上一篇文章中我们大致浏览了zookeeper的启动过程,并且提到在Zookeeper的启动过程中leader选举是非常重要而且最复杂的一个环节。那么什么是leader选举呢?zookeeper为什么需要leader选举呢?zookeeper的leader选举的过程又是什么样子的?本文的目的就是解决这三个问题。

首先我们来看看什么是leader选举。其实这个很好理解,leader选举就像总统选举一样,每人一票,获得多数票的人就当选为总统了。在zookeeper集群中也是一样,每个节点都会投票,如果某个节点获得超过半数以上的节点的投票,则该节点就是leader节点了。

国家选举总统是为了选一个最高统帅,治理国家。那么zookeeper集群选举的目的又是什么呢?其实这个要清楚明白的解释还是挺复杂的。我们可以简单点想这个问题:我们有一个zookeeper集群,有好几个节点。每个节点都可以接收请求,处理请求。那么,如果这个时候分别有两个客户端向两个节点发起请求,请求的内容是修改同一个数据。比如客户端c1,请求节点n1,请求是set a = 1; 而客户端c2,请求节点n2,请求内容是set a = 2;

那么最后a是等于1还是等于2呢? 这在一个分布式环境里是很难确定的。解决这个问题有很多办法,而zookeeper的办法是,我们选一个总统出来,所有的这类决策都提交给总统一个人决策,那之前的问题不就没有了么。

那我们现在的问题就是怎么来选择这个总统呢? 在现实中,选择总统是需要宣讲拉选票的,那么在zookeeper的世界里这又如何处理呢?我们还是show code吧。

在QuorumPeer的startLeaderElection方法里包含leader选举的逻辑。Zookeeper默认提供了4种选举方式,默认是第4种: FastLeaderElection。

我们先假设我们这是一个崭新的集群,崭新的集群的选举和之前运行过一段时间的选举是有稍许不同的,后面会提及。

节点状态: 每个集群中的节点都有一个状态 LOOKING, FOLLOWING, LEADING, OBSERVING。都属于这4种,每个节点启动的时候都是LOOKING状态,如果这个节点参与选举但最后不是leader,则状态是FOLLOWING,如果不参与选举则是OBSERVING,leader的状态是LEADING。

开始这个选举算法前,每个节点都会在zoo.cfg上指定的监听端口启动监听(server.1=127.0.0.1:20881:20882),这里的20882就是这里用于选举的端口。

在FastLeaderElection里有一个Manager的内部类,这个类里有启动了两个线程:WorkerReceiver, WorkerSender。为什么说选举这部分复杂呢,我觉得就是这些线程就像左右互搏一样,非常难以理解。顾名思义,这两个线程一个是处理从别的节点接收消息的,一个是向外发送消息的。对于外面的逻辑接收和发送的逻辑都是异步的。

这里配置好了,QuorumPeer的run方法就开始执行了,这里实现的是一个简单的状态机。因为现在是LOOKING状态,所以进入LOOKING的分支,调用选举算法开始选举了:

setCurrentVote(makeLEStrategy().lookForLeader());

而在lookForLeader里主要是干什么呢?首先我们会更新一下一个叫逻辑时钟的东西,这也是在分布式算法里很重要的一个概念,但是在这里先不介绍,可以参考后面的论文。然后决定我要投票给谁。不过zookeeper这里的选举真直白,每个节点都选自己(汗),选我,选我,选我...... 然后向其他节点广播这个选举信息。这里实际上并没有真正的发送出去,只是将选举信息放到由WorkerSender管理的一个队列里。

synchronized(this){//逻辑时钟           logicalclock++;//getInitLastLoggedZxid(), getPeerEpoch()这里先不关心是什么,后面会讨论
    updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
}//getInitId() 即是获取选谁,id就是myid里指定的那个数字,所以说一定要唯一
private long getInitId(){if(self.getQuorumVerifier().getVotingMembers().containsKey(self.getId()))       return self.getId();else return Long.MIN_VALUE;
}//发送选举信息,异步发送
sendNotifications();

现在我们去看看怎么把投票信息投递出去。这个逻辑在WorkerSender里,WorkerSender从sendqueue里取出投票,然后交给QuorumCnxManager发送。因为前面发送投票信息的时候是向集群所有节点发送,所以当然也包括自己这个节点,所以QuorumCnxManager的发送逻辑里会判断,如果这个要发送的投票信息是发送给自己的,则不发送了,直接进入接收队列。

public void toSend(Long sid, ByteBuffer b) {if (self.getId() == sid) {b.position(0);addToRecvQueue(new Message(b.duplicate(), sid));} else {//发送给别的节点,判断之前是不是发送过if (!queueSendMap.containsKey(sid)) {//这个SEND_CAPACITY的大小是1,所以如果之前已经有一个还在等待发送,则会把之前的一个删除掉,发送新的ArrayBlockingQueue<ByteBuffer> bq = new ArrayBlockingQueue<ByteBuffer>(SEND_CAPACITY);queueSendMap.put(sid, bq);addToSendQueue(bq, b);} else {ArrayBlockingQueue<ByteBuffer> bq = queueSendMap.get(sid);if(bq != null){addToSendQueue(bq, b);} else {LOG.error("No queue for server " + sid);}}//这里是真正的发送逻辑了
             connectOne(sid);}}

connectOne就是真正发送了。在发送之前会先把自己的id和选举地址发送过去。然后判断要发送节点的id是不是比自己的id大,如果大则不发送了。如果要发送又是启动两个线程:SendWorker,RecvWorker(这种一个进程内许多不同种类的线程,各自干活的状态真的很难理解)。发送逻辑还算简单,就是从刚才放到那个queueSendMap里取出,然后发送。并且发送的时候将发送出去的东西放到一个lastMessageSent的map里,如果queueSendMap里是空的,就发送lastMessageSent里的东西,确保对方一定收到了。

看完了SendWorker的逻辑,再来看看数据接收的逻辑吧。还记得前面提到的有个Listener在选举端口上启动了监听么,现在这里应该接收到数据了。我们可以看到receiveConnection方法。在这里,如果接收到的的信息里的id比自身的id小,则断开连接,并尝试发送消息给这个id对应的节点(当然,如果已经有SendWorker在往这个节点发送数据,则不用了)。

如果接收到的消息的id比当前的大,则会有RecvWorker接收数据,RecvWorker会将接收到的数据放到recvQueue里。

而FastLeaderElection的WorkerReceiver线程里会不断地从这个recvQueue里读取Message处理。在WorkerReceiver会处理一些协议上的事情,比如消息格式等。除此之外还会看看接收到的消息是不是来自投票成员。如果是投票成员,则会看看这个消息里的状态,如果是LOOKING状态并且当前的逻辑时钟比投票消息里的逻辑时钟要高,则会发个通知过去,告诉谁是leader。在这里,刚刚启动的崭新集群,所以逻辑时钟基本上都是相同的,所以这里还没判断出谁是leader。不过在这里我们注意到如果当前节点的状态是LOOKING的话,接收逻辑会将接收到的消息放到FastLeaderElection的recvqueue里。而在FastLeaderElection会从这个recvqueue里读取东西。

这里就是选举的主要逻辑了:totalOrderPredicate

protected boolean totalOrderPredicate(long newId, long newZxid, long newEpoch, long curId, long curZxid, long curEpoch) {return ((newEpoch > curEpoch) || ((newEpoch == curEpoch) &&((newZxid > curZxid) || ((newZxid == curZxid) && (newId > curId)))));}

1. 判断消息里的epoch是不是比当前的大,如果大则消息里id对应的server我就承认它是leader

2. 如果epoch相等则判断zxid,如果消息里的zxid比我的大我就承认它是leader

3. 如果前面两个都相等那就比较一下server id吧,如果比我的大我就承认它是leader。

关于前面两个东西暂时我们不去关心它,对于新启动的集群这两者都是相等的。

那这样看来server id的大小也是leader选举的一环啊(有的人生下来注定就不平凡,这都是命啊)。

最后我们来看看,很多文章所介绍的,如果超过一半的人说它是leader,那它就是leader的逻辑吧

private boolean termPredicate(HashMap<Long, Vote> votes,Vote vote) {HashSet<Long> set = new HashSet<Long>();//遍历已经收到的投票集合,将等于当前投票的集合取出放到set中for (Map.Entry<Long,Vote> entry : votes.entrySet()) {if (self.getQuorumVerifier().getVotingMembers().containsKey(entry.getKey())&& vote.equals(entry.getValue())){set.add(entry.getKey());}}//统计set,也就是投某个id的票数是否超过一半return self.getQuorumVerifier().containsQuorum(set);}public boolean containsQuorum(Set<Long> ackSet) {return (ackSet.size() > half);}

最后一关:如果选的是自己,则将自己的状态更新为LEADING,否则根据type,要么是FOLLOWING,要么是OBSERVING。

到这里选举就结束了。

这里介绍的是一个新集群启动时候的选举过程,启动的时候就是根据zoo.cfg里的配置,向各个节点广播投票,一般都是选投自己。然后收到投票后就会进行进行判断。如果某个节点收到的投票数超过一半,那么它就是leader了。

了解了这个过程,我们来看看另外一个问题:

一个集群有3台机器,挂了一台后的影响是什么?挂了两台呢?

挂了一台:挂了一台后就是收不到其中一台的投票,但是有两台可以参与投票,按照上面的逻辑,它们开始都投给自己,后来按照选举的原则,两个人都投票给其中一个,那么就有一个节点获得的票等于2,2 > (3/2)=1 的,超过了半数,这个时候是能选出leader的。

挂了两台: 挂了两台后,怎么弄也只能获得一张票, 1 不大于 (3/2)=1的,这样就无法选出一个leader了。

在前面介绍时,为了简单我假设的是这是一个崭新的刚启动的集群,这样的集群与工作一段时间后的集群有什么不同呢?不同的就是epoch和zxid这两个参数。在新启动的集群里这两个一般是相等的,而工作一段时间后这两个参数有可能有的节点落后其他节点,至于是为什么,这个还要在后面的存储和处理额胡断请求的文章里介绍。

* 关于逻辑时钟,我们的分布式大牛Leslie Lamport曾写过一篇论文:Time, Clocks, and the Ordering of Events in a Distributed System

转载于:https://my.oschina.net/yafeiok/blog/875884

Zookeeper-Zookeeper leader选举相关推荐

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

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

  2. Zookeeper的Leader选举

    简单理解Zookeeper的Leader选举 原文链接:简单理解Zookeeper的Leader选举_程裕强的博客-CSDN博客_zk的leader选举 Leader选举是保证分布式数据一致性的关键所 ...

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

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

  4. zookeeper的Leader选举机制详解

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

  5. Zookeeper之Leader选举源码分析

    Zookeeper源码下载地址:https://github.com/apache/zookeeper 1.选举流程 Zookeeeper的Leader选举会分两个过程. 服务启动时的leader选举 ...

  6. Zookeeper的Leader选举-选举过程介绍比较清晰

    http://www.cnblogs.com/leesf456/p/6107600.html 一.前言 前面学习了Zookeeper服务端的相关细节,其中对于集群启动而言,很重要的一部分就是Leade ...

  7. Zookeeper里Leader选举算法

    ZooKeepe集群中的三种服务器角色:Leader.Follower.Observer,本文主要概述Leader选举算法相关的知识. 一.Zookeeper里三种角色 1.Leader:Leader ...

  8. zookeeper的leader选举机制

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

  9. 使用Zookeeper实现leader选举-Leader Latch

    参与选举的所有节点,会创建一个顺序节点,其中最小的节点会设置为master节点, 没抢到Leader的节点都监听前一个节点的删除事件,在前一个节点删除后进行重新抢主,当master节点手动调用clos ...

  10. 使用Zookeeper实现leader选举

    在分布式计算中,leader election是很重要的一个功能,这个选举过程是这样子的:指派一个进程作为组织者,将任务分发给各节点.在任务开始前,哪个节点都不知道谁是leader或者coordina ...

最新文章

  1. 麻省理工学院揭秘男女配对的真相
  2. “强化学习之父”萨顿:预测学习马上要火,AI将帮我们理解人类意识
  3. 在AFN中使用NSXMLParser解析服务器返回的XML数据
  4. 一队老耗子,每个月都生一对小耗子,小耗子长3个月,第四个月开始变成老耗子开始生, // 假设所有耗子都不死,请问20个月之后共有多少只耗子?...
  5. plsql如何执行单个语句_在单个try-with-resources语句中仔细指定多个资源
  6. 朴素贝叶斯分类器python_朴素贝叶斯分类器及Python实现
  7. 国外知名的开源项目托管网站
  8. WCMS V9方向说明
  9. C# int转string 每三位加一个逗号
  10. tomcat 实现域名crm.test.com訪问
  11. SOCKS5 协议原理详解与应用场景分析
  12. 本地视频做成html网页,使用HTML和JavaScript播放本地的媒体(视频音频)文件的方法...
  13. 机器学习 | 实战(一)Decision_tree_红酒数据集
  14. 学生HTML个人网页作业作品 基于HTML+CSS+JavaScript明星个人主页(15页)
  15. 火狐配置 about:config介绍:
  16. [软件项目管理] BCWS、BCWP、ACWP的理解
  17. Qt工程中如何添加.pri文件
  18. 刷脸支付商用之火真正出现燎原的苗头
  19. C语言播放音乐的两种方式
  20. 网络功能虚拟化NFV

热门文章

  1. Mysql主从常见问题
  2. 一种在MVC3框架里面设置模板页的方法,不使用_ViewStart
  3. u-boot分析——struct gd_t与struct bd_t
  4. 安装卸载功能 [测试思路]
  5. PMCAFF“音乐+社交”产品马拉松大赛圆满结束,10支队伍大放异彩
  6. 【独家】百度移动云高级美女产品经理:O2O日趋火爆,下个估值过亿的上门美业将花落谁家?...
  7. 【干货】你如何寻找APP?发现优秀 APP 的一些途径
  8. React服务端渲染实现(基于Dva)
  9. python基本库系列一:Request
  10. vue14 自定义过滤器