面试官问我zookeeper选举过程,我当场给他讲了源码

本文涉及的产品
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 面试官问我zookeeper选举过程,我当场给他讲了源码

集群概述


zookeper  在生产环境中通常都是通过集群方式来部署的,以保证高可用,  下面是 zookeeper 官网给出的一个集群部署结构图:


640.jpg


从上图可以得出, zookeeper server 的每个节点都和主节点保持通讯的,每个节点上面都存储有数据和日志的备份,只有当大多数节点可用集群才是可用的。本文主要是基于 zookeeper 3.8.0 讲解, 主要是通过源码的维度来分析 zookeeper 选举过程 对于 zookeeper 的源码编译大家可以参考:编译运行Zookeeper源码


集群节点状态


集群节点状态定义在 QuorumPeer#ServerState 枚举,主要是包含 LOOKINGFOLLOWINGLEADINGOBSERVING 四个状态, 下面是定义的代码和说明


public enum ServerState {
    // 寻找leader状态。当服务器处于该状态时,它会认为当- 前集群中没有leader,因此需要进入leader选举状态。
    LOOKING,
    // 跟随者状态。表明当前服务器角色是 follower。
    FOLLOWING,  
    // 领导者状态。表明当前服务器角色是 leader。
    LEADING,
    // 观察者状态。表明当前服务器角色是 observer。
    OBSERVING
}


Leader 选举过程


启动和初始化


QuorumPeerMain 是 zookeeper 的启动类, 通过 main 方法启动


// 不展示非核心代码
public static void main(String[] args) {
    QuorumPeerMain main = new QuorumPeerMain();
    main.initializeAndRun(args);
}
protected void initializeAndRun(String[] args) throws ConfigException, IOException, AdminServerException {
    // 集群模式启动
    if (args.length == 1 && config.isDistributed()) {
        runFromConfig(config);
    } else {
    }
}
public void runFromConfig(QuorumPeerConfig config) throws IOException, AdminServerException {
    // quorumPeer 启动
 quorumPeer.start();
}


QuorumPeer 是一个线程实例类,当调用 start 方法过后会致性 QuorumPeer#run() 方法, 进行集群状态的判断最终进入是否执行选举或者同步集群节点数据信息等一系列的操作,下面是核心代码:


@Override
public void run() {
    try {
        while (running) {
            switch (getPeerState()) {
                case LOOKING:
                    // 投票给自己
                    setCurrentVote(makeLEStrategy().lookForLeader());
                    break;
                case OBSERVING:
                    setObserver(makeObserver(logFactory));
                    observer.observeLeader();
                    break;
                case FOLLOWING:
                    setFollower(makeFollower(logFactory));
                    follower.followLeader();
                    break;
                case LEADING:
                    setLeader(makeLeader(logFactory));
                    leader.lead();
                    setLeader(null);
                    break;
            }
        }
    } finally {
    }
}


进行选举


FastLeaderElection 是选举的核心类 ,在这个类里面有对投票和选票的处理过程


public Vote lookForLeader() throws InterruptedException {
    // 创建一个当前选举周期的投票箱
    Map<Long, Vote> recvset = new HashMap<Long, Vote>();
    // 创建一个投票箱。这个投票箱和recvset 不一样。
    // 存储当前集群中如果已经存在Leader了的投票
    Map<Long, Vote> outofelection = new HashMap<Long, Vote>();
    int notTimeout = minNotificationInterval;
    synchronized (this) {
        // 递增本地选举周期
        logicalclock.incrementAndGet();
        // 为自己投票
        updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
    }
    // 广播投票
    sendNotifications();
    SyncedLearnerTracker voteSet = null;
    // 如果当前服务器的状态为Looking,和stop参数为false,那么进行选举
    while ((self.getPeerState() == ServerState.LOOKING) && (!stop)) {
        if (n.electionEpoch > logicalclock.get()) {
            logicalclock.set(n.electionEpoch);
            recvset.clear();
            // totalOrderPredicate 投票 PK
            if (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 (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch, proposedLeader, proposedZxid, proposedEpoch)) {
            updateProposal(n.leader, n.zxid, n.peerEpoch);
            sendNotifications();
        }
        // 监听通信层接收的投票
        Notification n = recvqueue.poll(notTimeout, TimeUnit.MILLISECONDS);
        // 放入投票箱
        recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
        // 过半逻辑
        voteSet = getVoteTracker(recvset, new Vote(proposedLeader, proposedZxid, logicalclock.get(), proposedEpoch));
    }
}


totalOrderPredicate 主要是选票 PK 的逻辑,我们再来看看代码:


protected boolean totalOrderPredicate(long newId, long newZxid, long newEpoch, long curId, long curZxid, long curEpoch) {
    if (self.getQuorumVerifier().getWeight(newId) == 0) {
        return false;
    }
    return ((newEpoch > curEpoch)
            || ((newEpoch == curEpoch)
                && ((newZxid > curZxid)
                    || ((newZxid == curZxid)
                        && (newId > curId)))));
}


选举过程是这个样子的 ,其实官方也给出了注释:


  1. 先比较选举的届数,届数高的说明是最新一届,胜出
  2. 再比较zxid,也就是看谁的数据最新,最新的胜出
  3. 最后比较serverid,这是配置文件指定的,节点id大者胜出 选举完成后通过 sendNotifications(); 通知其他的节点。


过程总结


前面我粗略的讲解 zookeeper 从启动过程在到选举,选举结果同步的,以及如何进行投票的选举结果确认过程,但是 zookeeper 作为一个高性能、高可靠的分布式协调中间件,在很多设计的细节也是非常的优秀的。


投票过程


通常情况下,在投票的过程中 zxid 越大越有可能成为 leader 主要是由于 zxid 越大该节点的数据越多,这样的话就可以减少数据的同步过程中节点事务的撤销和日志文件同步的比较过程,以提升性能。下面是 5 个 zookeeper 节点选举的过程。


640.png


注:  (sid, zxid),  当前场景为 server1 ,server2 出现故障 , server3 的 zxid = 9 , server4 和 server5 的 zxid 为 8. 进行两轮选举,最终选出 sever3 为 leader 节点


多层网络架构


在前面的分析过程中我省略了 Zookeeper 节点之间通讯的 NIO 操作, 这部分简单来讲 zookeeper 将他们划分为传输层和业务层。通过 SendWorkerRecvWorker 处理网络层数据包, WorkerSenderWorkerReceiver 处理业务层的数据。


640.png


这里会涉及到多线程操作,zookeeper 在源码中也给出了大量的日志信息,对于初学者有一定的难度,对此大家可以参考下面的 Zookeeper 选举源码流程 这部分的流程图来辅助分析。


Leader 选举源码流程


结合上面的梳理,我对 zookeeper 启动和选举的流程做了一个比较详细的梳理。大家可以结合 zookeeper 源码来理解。

相关实践学习
基于MSE实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
92 2
|
5月前
|
JavaScript 前端开发
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
这篇文章主要讨论了axios的使用、原理以及源码分析。 文章中首先回顾了axios的基本用法,包括发送请求、请求拦截器和响应拦截器的使用,以及如何取消请求。接着,作者实现了一个简易版的axios,包括构造函数、请求方法、拦截器的实现等。最后,文章对axios的源码进行了分析,包括目录结构、核心文件axios.js的内容,以及axios实例化过程中的配置合并、拦截器的使用等。
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
|
14天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
1月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
57 2
|
5月前
|
JavaScript 前端开发
【Vue面试题二十七】、你了解axios的原理吗?有看过它的源码吗?
文章讨论了Vue项目目录结构的设计原则和实践,强调了项目结构清晰的重要性,提出了包括语义一致性、单一入口/出口、就近原则、公共文件的绝对路径引用等原则,并展示了单页面和多页面Vue项目的目录结构示例。
|
4月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
505 37
|
3月前
|
分布式计算 负载均衡 算法
Hadoop-31 ZooKeeper 内部原理 简述Leader选举 ZAB协议 一致性
Hadoop-31 ZooKeeper 内部原理 简述Leader选举 ZAB协议 一致性
40 1
|
4月前
|
存储 负载均衡 算法
分布式-Zookeeper-Master选举
分布式-Zookeeper-Master选举
面试官: 请你手写一份 Call()源码,看完此篇不用担心!
面试官: 请你手写一份 Call()源码,看完此篇不用担心!
|
6月前
|
存储 数据库
zookeeper 集群环境搭建及集群选举及数据同步机制
zookeeper 集群环境搭建及集群选举及数据同步机制
127 2

热门文章

最新文章