您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
zookeeper的Leader选举源码解析
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
zookeeper的Leader选举源码解析
自猿其说Tech
2022-07-21
IP归属:未知
210400浏览
### 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选举流程 ![](//img1.jcloudcs.com/developer.jdcloud.com/6a439ba0-27c9-4cd5-9a04-eab7fa5600f120220721201804.png) 前提: 设定票据数据格式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分成了多个队列,是为了避免给每台服务端发送消息互相影响。比如对某台机器发送不成功不会影响正常服务端的发送。 ![](//img1.jcloudcs.com/developer.jdcloud.com/5067479d-6262-4417-8967-4b4fec5a943720220721201902.png) ### 4 解析代码入口类 1. 通过查看zkServer.sh文件内容找到服务启动类 2. org.apache.zookeeper.server.quorum.QuorumPeerMain ### 5 选举流程代码解析 ![](//img1.jcloudcs.com/developer.jdcloud.com/0b885bc1-1eb7-411a-b80b-8d258a1494c320220721201921.png) 1)加载配置文件QuorumPeerConfig.parse(path) 针对 Leader选举关键配置信息如下: - 读取dataDir目录找到myid文件内容,设置当前应用sid标识,做为投票人身份信息。下面遇到myid变量为当前节点自己sid标识。 - - 设置peerType当前应用是否参与选举 - new QuorumMaj()解析server.前缀加载集群成员信息,加载allMembers所有成员,votingMembers参与选举成员,observingMembers观察者成员,设置half值votingMembers.size()/2. ![](//img1.jcloudcs.com/developer.jdcloud.com/c784572c-0ff7-4ae1-94eb-9cf7c2ce540520220721202012.png) 2)QuorumPeerMain.runFromConfig(config) 启动服务 3)QuorumPeer.startLeaderElection() 开启选举服务 - 设置当前选票new Vote(sid,zxid,epoch) ![](//img1.jcloudcs.com/developer.jdcloud.com/d6e8119c-f17b-4960-a529-40c9784a580e20220721202030.png) - 创建选举管理类:QuorumCnxnManager - 初始化recvQueue<Message(sid,ByteBuffer)>接收投票队列(第二层传输队列) - 初始化queueSendMap<sid,queue>按sid发送投票队列(第二层传输队列) - 初始化senderWorkerMap<sid,SendWorker>发送投票工作线程容器,表示着与sid投票节点已连接。 - 初始化选举监听线程类QuorumCnxnManager.Listener ![](//img1.jcloudcs.com/developer.jdcloud.com/6664270f-33a1-4e8c-886a-b89adc7b5fd420220721202045.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/5968c3e8-1f64-44dd-916d-afdb46cfed9c20220721202053.png) 4)开启选举监听线程QuorumCnxnManager.Listener - 创建ServerSockket等待大于自己sid节点连接,连接信息存储到senderWorkerMap<sid,SendWorker>, - sid>self.sid才可以连接过来。 ![](//img1.jcloudcs.com/developer.jdcloud.com/4eaa20a5-7932-4c9f-bc88-5d3bfe1a54ea20220721202113.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/1d6b20d8-48e9-4c65-a23d-253d584662e020220721202123.png) 5)创建FastLeaderElection快速选举服务 - 初始选票发送队列sendqueue(第一层队列) - 初始选票接收队列recvqueue(第一层队列) - 创建线程WorkerSender - 创建线程WorkerReceiver ![](//img1.jcloudcs.com/developer.jdcloud.com/e352cb75-79a6-4bbd-ac2e-e13c4b06516820220721202150.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/209fdc59-cf33-473e-b3f4-8f30e772b16820220721202155.png) 6)开启WorkerSender和WorkerReceiver线程 WorkerSender线程自旋获取sendqueue第一层队列元素 - sendqueue队列元素内容为相关选票信息详见ToSend类 - 首先判断选票sid是否和自己sid值相同,相等直接放入到recvQueue队列中。 - 不相同将sendqueue队列元素转储到queueSendMap<sid,queue>第二层传输队列中。 ![](//img1.jcloudcs.com/developer.jdcloud.com/9e963187-60d5-4704-955d-f8a69bb0d58920220721202215.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/082b1b71-95ef-4b40-98e6-f3b007752b9820220721202223.png) WorkerReceiver线程自旋获取recvQueue第二层传输队列元素转存到recvqueue第一层队列中。 ![](//img1.jcloudcs.com/developer.jdcloud.com/a079538a-e46d-42e0-92b7-5dfec177bcaf20220721202238.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/79583fda-30b8-4760-874f-5f78227084e220220721202244.png) ### 6 选举核心逻辑 1)启动线程QuorumPeer 开始Leader选举投票makeLEStrategy().lookForLeader() sendNotifications()向其它节点发送选票信息,选票信息存储到sendqueue队列中。sendqueue队列由WorkerSender线程处理。 ![](//img1.jcloudcs.com/developer.jdcloud.com/19313ca8-2374-42ff-8cd9-61124e3567fd20220721202309.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/fffcf90e-e4d6-405c-90f1-e18eb63b41f320220721202323.png) 自旋recvqueue队列元素获取投票过来的选票信息 ![](//img1.jcloudcs.com/developer.jdcloud.com/8adc3b93-7539-4754-8aae-5a2847aa013620220721202337.png) 如果未收到选票信息,manager.contentAll()自动连接其它socket节点 ![](//img1.jcloudcs.com/developer.jdcloud.com/9483b588-d65a-478d-a692-b0813cc5fea320220721202350.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/ccc9842d-5975-4e80-9f8a-660d9879ad3620220721202400.png) 如上图所示,sid>self.sid才可以创建连接Socket和SendWorker,RecvWorker线程,存储到senderWorkerMap<sid,SendWorker>中。对应第2步中的sid<self.sid逻辑,保证集群中所有节点之间只有一个通道连接。 ![](//img1.jcloudcs.com/developer.jdcloud.com/7251978e-a260-4b43-b456-0a512890e1f420220721202411.jpg) 自旋从recvqueue队列中获取到选票信息。开始进行选举: - 判断当前选票和接收过来的选票周期是否一致 - 大于当前周期更新当前选票信息,再次发送投票 - 周期相等:当前选票信息和接收的选票信息进行PK ![](//img1.jcloudcs.com/developer.jdcloud.com/f9bb61f6-7cb9-41b6-bb22-927b1ce1cc0720220721202425.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/59e36361-6df8-4bc2-bccd-b7a3cacbb94b20220721202434.png) - 竞选周期大于当前周期为true - 竞选周期相等,竞选zxid大于当前zxid为true - 竞选周期相等,竞选zxid等于当前zxid,竞选sid大于当前sid为true - 经过上述条件判断为true将当前选票信息替换为竞选成功的选票,同时再次将新的选票投出去。 ![](//img1.jcloudcs.com/developer.jdcloud.com/9efbf814-c629-497d-9087-2557159f94e620220721202448.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/7a7398c9-4e69-4919-958a-f708ef2a199f20220721202456.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/39a199f3-d58f-423c-b30d-71f15f6148ce20220721202502.png) 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
阅读量
2149964
作者其他文章
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
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号