-
- 参考:https://www.bilibili.com/video/BV1tt41137sT?p=4
- 视频中带注释的源码:https://github.com/boomblog/zookeeper-vip1/blob/master/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java
- 启动
- 传参
- 脚本启动的时候 .sh 发现调用了一个类 传入配置文件参数 就启动了
- 我们可以通过idea 加入参数,也能启动。
- 找伙伴
- 一开始先启动server1,server1会尝试连接server2和server3,结果都失败,然后会重试
- 然后我们来个客户端连接的话,因为没有leader,会连上就断开连上就断开,不断重试,直到有leader。
- 加载数据
- 读入我们的配置
- 从硬盘中把持久化的内容读到内存里
- 开启socket : cnxnFactory.start()
- 使用NIO
- 开启领导选举
- 选举传输层(负责传输选票)QuorumCnxManager
- 每个服务器要向其他服务器发送选票
- 每个服务器对应一个队列 queueSendMap,里面放发给这个服务器的选票
- 每个服务器要从其他服务器接收选票
- ArrayBlockingQueue<Message> recvQueue
- 每个服务器要向其他服务器发送选票
- 创建个listener,接收别的小伙伴的请求(创建个新线程)
- 选举应用层(负责pk等逻辑操作)
https://mp.weixin.qq.com/s/z73f6rQXYvh2byfkO8tMoA? 备注下,这两层的示意图- 这一层也有自己的sendQueue和recvQueue
- 选举传输层(负责传输选票)QuorumCnxManager
- 真正开始选举:super.start() 就是 QuorumPeer.run(): FastLeaderElection
- 先投给自己
- 接受其他小伙伴的选票
- 如果拿不到别人的选票(取队列为空),才会去建立连接(connectAll)
- 如果两个server 相互主动连接对方,怎么办?
- 拿到对方的id,和自己的id比较,如果对方的大,关掉连接。
- 所以永远是小的做为server端,大的作为客户端建立连接。
- 如果两个server 相互主动连接对方,怎么办?
- 如果拿不到别人的选票(取队列为空),才会去建立连接(connectAll)
- pk
- 排序顺序:epoch zxid server_id
- 选票其实也是长这样(epoch, zxid, server_id)
- 投票
- 从server n接收到一个选票,将其更新成赢了的的选票(可能是他,也可能是我自己)放进以server_id为key的map中(投票箱)
- 统计
- 统计过半数
- 传参
- 跟上面的流程对应的代码:
- org.apache.zookeeper.server.quorum.QuorumPeerMain#runFromConfig
- quorumPeer.start();开启线程
- org.apache.zookeeper.server.quorum.QuorumPeer#run。MainLoop,根据status做不同的事情
- case LOOKING(刚启动就是这个,正在寻找老大)
- org.apache.zookeeper.server.quorum.QuorumPeer#startLeaderElection
- currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());
先投自己
- this.electionAlg = createElectionAlgorithm(electionType);
默认是用3:fastElection。然后会启动两个线程,疯狂从两个队列(WorkerSender、WorkerReceiver)poll- WorkerSender:就是把选票发出去
- WorkerReceiver:接受选票并处理
- currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());
- setCurrentVote(makeLEStrategy().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. */开始新的一轮选举,当QuorumPeer改变状态为LOOKING,就会调用这个方法,然后会发通知给所有其他小伙伴- HashMap<Long, Vote> recvset
收到的选票,也就是本地投票箱
- HashMap<Long, Vote> outofelection
- 选票上写上自己
- sendNotifications(); 选票发送。实际上是填好选票放到发送队列里。别的线程来负责发送。
- while ((self.getPeerState() == ServerState.LOOKING) &&(!stop))
只要还在LOOKING状态,而且程序没有停止,就一直循环下面逻辑:- Notification n = recvqueue.poll
接收队列拖出一个通知
- 如果是空就继续发通知sendNotification
- 如果有一个队列都清空了(haveDelivered)(queueSendMap),就再sendNotification【sendqueue(应用层,只有一个队列)和queueSendMap(传输层,分不同的小伙伴有不同的队列)】
- 否则,尝试连接所有其他小伙伴
- 如果收到有效选票
- switch (n.state)
- case LOOKING:
收到的选票也在LOOKING- 如果对方的epoch比较大
- 那就更新自己存的
- 清空收到的选票recvset
- 按照规则排序,对方选票牛皮则跟着对方投,否则还按自己的投
epoch > zxid > serverid
- 如果对方的epoch比自己小,可能是莫名其妙哪出问题了,打debug日志然后break循环
- 对方的epoch跟我一样
- 那也按照规则排序,谁牛皮就按谁的投
- 把对方的选票放进投票箱recvset
- 如果termPredicate返回true:从recvqueue中清空选票,只要找到一张更牛皮的选票就把票放回去继续循环。如果是真的队列空了,就结束了,看看是不是自己当了老大,是的话就把state改成LEADING,否则改成FOLLOWING
/** * Termination predicate. Given a set of votes, determines if have * sufficient to declare the end of the election round. 瞅一眼投票箱,看看是不是足够宣告投票结束。- 其中一种校验方法是获得过半数的选票
- 如果对方的epoch比较大
- case FOLLOWING ||LEADING:
- case LOOKING:
- Notification n = recvqueue.poll
- HashMap<Long, Vote> recvset
- org.apache.zookeeper.server.quorum.QuorumPeer#startLeaderElection
- case FOLLOWING
- case LEADING
- case LOOKING(刚启动就是这个,正在寻找老大)
- org.apache.zookeeper.server.quorum.QuorumPeer#run。MainLoop,根据status做不同的事情
- quorumPeer.start();开启线程
ZooKeeper 选举过程 源码
最新推荐文章于 2023-10-11 20:28:44 发布