您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
zookeeper的Leader选举源码解析
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
zookeeper的Leader选举源码解析
自猿其说Tech
2022-07-21
IP归属:未知
5425浏览
### 1 Leader选举机制 Leader选举机制采用半数选举算法。 每一个zookeeper服务端称之为一个节点,每个节点都有投票权,把自己的选票投向每一个有选举权的节点,当其中一个节点选举出票数过半,这个节点就会成为Leader。其它节点成功Follower ### 2 Leader选举集群配置 重命名zoo_sample.cfg文件为zoo1.cfg ,zoo2.cfg,zoo3.cfg,zoo4.cfg 修改zoo.cfg文件,修改值如下: ``` zoo1.cfg文件内容: dataDir=/export/data/zookeeper-1 clientPort=2181 server.1=127.0.0.1:2001:3001 server.2=127.0.0.1:2002:3002:participant server.3=127.0.0.1:2003:3003:participant server.4=127.0.0.1:2004:3004:observer zoo2.cfg文件内容: dataDir=/export/data/zookeeper-2 clientPort=2182 server.1=127.0.0.1:2001:3001 server.2=127.0.0.1:2002:3002:participant server.3=127.0.0.1:2003:3003:participant server.4=127.0.0.1:2004:3004:observer zoo3.cfg文件内容: dataDir=/export/data/zookeeper-3 clientPort=2183 server.1=127.0.0.1:2001:3001 server.2=127.0.0.1:2002:3002:participant server.3=127.0.0.1:2003:3003:participant server.4=127.0.0.1:2004:3004:observer zoo4.cfg文件内容: dataDir=/export/data/zookeeper-4 clientPort=2184 server.1=127.0.0.1:2001:3001 server.2=127.0.0.1:2002:3002:participant server.3=127.0.0.1:2003:3003:participant server.4=127.0.0.1:2004:3004:observer ``` server.第几号服务器(对应myid文件内容)=ip:数据同步端口:选举端口:选举标识 - participant默认参与选举标识,可不写. observer不参与选举 在/export/data/zookeeper-1,/export/data/zookeeper-2,/export/data/zookeeper-3,/export/data/zookeeper-4目录下创建myid文件,文件内容分别写1 ,2,3,4,用于标识sid(全称:Server ID)赋值. 启动三个zookeeper实例 - bin/zkServer.sh start conf/zoo1.cfg - bin/zkServer.sh start conf/zoo2.cfg - bin/zkServer.sh start conf/zoo3.cfg 每启动一个实例,都会读取启动参数配置zoo.cfg文件,这样实例就可以知道自己作为服务端身份信息sid,和集群中有多少个实例参与选举。 ### 3 Leader选举流程  前提: 设定票据数据格式vote(sid,zxid,epoch) - sid是Server ID每台服务的唯一标识,是myid文件内容 - zxid是数据事务id号 - epoch为选举周期,为方便理解下面讲解内容暂定为1初次选举,不写入下面内容里。 **按照顺序启动sid=1,sid=2节点** 第一轮投票: - sid=1节点:初始选票为自己,将选票vote(1,0)发送给sid=2节点 - sid=2节点:初始选票为自己,将选票vote(2,0)发送给sid=1节点 - sid=1节点:收到sid=2节点选票vote(2,0)和当前自己的选票vote(1,0),首先比对zxid值,zxid越大代表数据最新,优先选择zxid最大的选票,如果zxid相同,选举最大sid。当前投票选举结果为vote(2,0),sid=1节点自己的选票变为vote(2,0) - sid=2节点:收到sid=1节点选票vote(1,0)和当前自己的选票vote(2,0),参照上述选举方式,选举结果为vote(2,0),sid=2节点自己的选票不变。 - 第一轮投票选举结束 第二轮投票: 1. sid=1节点:当前自己的选票为vote(2,0),将选票vote(2,0)发送给sid=2节点。 2. sid=2节点:当前自己的选票为vote(2,0),将选票vote(2,0)发送给sid=1节点。 3. sid=1节点:收到sid=2节点选票vote(2,0)和自己的选票vote(2,0), 按照半数选举算法,总共3个节点参与选举,已有2个节点选举出相同选票,推举sid=2节点为Leader,自己角色变为Follower。 4. sid=2节点:收到sid=1节点选票vote(2,0)和自己的选票vote(2,0),按照半数选举算法推举sid=2节点为Leader,自己角色变为Leader。 这时启动sid=3节点后,集群里已经选举出leader,sid=1和sid=2节点会将自己的leader选票发回给sid=3节点,通过半数选举结果还是sid=2节点为leader. #### 3.1 Leader选举采用多层队列架构 zookeeper选举底层主要分为选举应用层和消息传输队列层,第一层应用层队列统一接收和发送选票,而第二层传输层队列,是按照服务端sid分成了多个队列,是为了避免给每台服务端发送消息互相影响。比如对某台机器发送不成功不会影响正常服务端的发送。  ### 4 解析代码入口类 1. 通过查看zkServer.sh文件内容找到服务启动类 2. org.apache.zookeeper.server.quorum.QuorumPeerMain ### 5 选举流程代码解析  1)加载配置文件QuorumPeerConfig.parse(path) 针对 Leader选举关键配置信息如下: - 读取dataDir目录找到myid文件内容,设置当前应用sid标识,做为投票人身份信息。下面遇到myid变量为当前节点自己sid标识。 - - 设置peerType当前应用是否参与选举 - new QuorumMaj()解析server.前缀加载集群成员信息,加载allMembers所有成员,votingMembers参与选举成员,observingMembers观察者成员,设置half值votingMembers.size()/2.  2)QuorumPeerMain.runFromConfig(config) 启动服务 3)QuorumPeer.startLeaderElection() 开启选举服务 - 设置当前选票new Vote(sid,zxid,epoch)  - 创建选举管理类:QuorumCnxnManager - 初始化recvQueue<Message(sid,ByteBuffer)>接收投票队列(第二层传输队列) - 初始化queueSendMap<sid,queue>按sid发送投票队列(第二层传输队列) - 初始化senderWorkerMap<sid,SendWorker>发送投票工作线程容器,表示着与sid投票节点已连接。 - 初始化选举监听线程类QuorumCnxnManager.Listener   4)开启选举监听线程QuorumCnxnManager.Listener - 创建ServerSockket等待大于自己sid节点连接,连接信息存储到senderWorkerMap<sid,SendWorker>, - sid>self.sid才可以连接过来。   5)创建FastLeaderElection快速选举服务 - 初始选票发送队列sendqueue(第一层队列) - 初始选票接收队列recvqueue(第一层队列) - 创建线程WorkerSender - 创建线程WorkerReceiver   6)开启WorkerSender和WorkerReceiver线程 WorkerSender线程自旋获取sendqueue第一层队列元素 - sendqueue队列元素内容为相关选票信息详见ToSend类 - 首先判断选票sid是否和自己sid值相同,相等直接放入到recvQueue队列中。 - 不相同将sendqueue队列元素转储到queueSendMap<sid,queue>第二层传输队列中。   WorkerReceiver线程自旋获取recvQueue第二层传输队列元素转存到recvqueue第一层队列中。   ### 6 选举核心逻辑 1)启动线程QuorumPeer 开始Leader选举投票makeLEStrategy().lookForLeader() sendNotifications()向其它节点发送选票信息,选票信息存储到sendqueue队列中。sendqueue队列由WorkerSender线程处理。   自旋recvqueue队列元素获取投票过来的选票信息  如果未收到选票信息,manager.contentAll()自动连接其它socket节点   如上图所示,sid>self.sid才可以创建连接Socket和SendWorker,RecvWorker线程,存储到senderWorkerMap<sid,SendWorker>中。对应第2步中的sid<self.sid逻辑,保证集群中所有节点之间只有一个通道连接。  自旋从recvqueue队列中获取到选票信息。开始进行选举: - 判断当前选票和接收过来的选票周期是否一致 - 大于当前周期更新当前选票信息,再次发送投票 - 周期相等:当前选票信息和接收的选票信息进行PK   - 竞选周期大于当前周期为true - 竞选周期相等,竞选zxid大于当前zxid为true - 竞选周期相等,竞选zxid等于当前zxid,竞选sid大于当前sid为true - 经过上述条件判断为true将当前选票信息替换为竞选成功的选票,同时再次将新的选票投出去。    recvset是存储每个sid推举的选票信息 第一轮 sid1:vote(1,0,1) ,sid2:vote(2,0,1) 第二轮 sid1:vote(2,0,1) ,sid2:vote(2,0,1) 最终经过选举信息vote(2,0,1)为推荐leader,并用推荐leader在recvset选票池里比对持相同票数量为2个。因为总共有3个节点参与选举,sid1和sid2都选举sid2为leader,满足票数过半要求,故确认sid2为leader. - setPeerState更新当前节点角色 proposedLeader选举出来的sid和自己sid相等,设置为Leader 上述条件不相等,设置为Follower或Observing. - 更新currentVote当前选票为Leader的选票vote(2,0,1)。 ### 7 总结 通过对Leader选举源码的解析,我们了解到: 1. 多个应用节点之间网络通信采用BIO方式进行相互投票,同时保证每个节点之间只使用一个通道,减少网络资源的消耗,足以见得在BIO分布式中间件开发中的技术重要性。 2. 基于BIO的基础上,灵活运用多线程和内存消息队列完好实现多层队列架构,每层队列由不同的线程分工协作,提高快速选举性能目的。 3. 同时为我们在BIO,多线程技术上的实践带来了宝贵的经验 ------------ ###### 自猿其说Tech-JDL京东物流技术与数据智能部 ###### 作者:梁吉超
原创文章,需联系作者,授权转载
上一篇:如何实现数据库读一致性
下一篇:流程引擎的架构设计
自猿其说Tech
文章数
426
阅读量
2242977
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2242977
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号