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

本文涉及的产品
云原生网关 MSE Higress,422元/月
MSE Nacos/ZooKeeper 企业版试用,1600元额度,限量50份
服务治理 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 源码来理解。

相关文章
|
6月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
331 3
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
362 2
|
JavaScript 前端开发
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
这篇文章主要讨论了axios的使用、原理以及源码分析。 文章中首先回顾了axios的基本用法,包括发送请求、请求拦截器和响应拦截器的使用,以及如何取消请求。接着,作者实现了一个简易版的axios,包括构造函数、请求方法、拦截器的实现等。最后,文章对axios的源码进行了分析,包括目录结构、核心文件axios.js的内容,以及axios实例化过程中的配置合并、拦截器的使用等。
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
|
10月前
|
存储 SQL 算法
阿里面试:每天新增100w订单,如何的分库分表?这份答案让我当场拿了offer
例如,在一个有 10 个节点的系统中,增加一个新节点,只会影响到该新节点在哈希环上相邻的部分数据,其他大部分数据仍然可以保持在原节点,大大减少了数据迁移的工作量和对系统的影响。狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由”。在 3 - 5 年的中期阶段,随着业务的稳定发展和市场份额的进一步扩大,订单数据的增长速度可能会有所放缓,但仍然会保持在每年 20% - 30% 的水平。
阿里面试:每天新增100w订单,如何的分库分表?这份答案让我当场拿了offer
|
10月前
|
算法 NoSQL 应用服务中间件
阿里面试:10WQPS高并发,怎么限流?这份答案让我当场拿了offer
在 Nacos 的配置管理界面或通过 Nacos 的 API,创建一个名为(与配置文件中 dataId 一致)的配置项,用于存储 Sentinel 的流量控制规则。上述规则表示对名为的资源进行流量控制,QPS 阈值为 10。resource:要保护的资源名称。limitApp:来源应用,default表示所有应用。grade:限流阈值类型,1 表示 QPS 限流,0 表示线程数限流。count:限流阈值。strategy:流控模式,0 为直接模式,1 为关联模式,2 为链路模式。
阿里面试:10WQPS高并发,怎么限流?这份答案让我当场拿了offer
|
JavaScript 前端开发
【Vue面试题二十七】、你了解axios的原理吗?有看过它的源码吗?
文章讨论了Vue项目目录结构的设计原则和实践,强调了项目结构清晰的重要性,提出了包括语义一致性、单一入口/出口、就近原则、公共文件的绝对路径引用等原则,并展示了单页面和多页面Vue项目的目录结构示例。
|
12月前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
292 2
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
892 37
|
分布式计算 负载均衡 算法
Hadoop-31 ZooKeeper 内部原理 简述Leader选举 ZAB协议 一致性
Hadoop-31 ZooKeeper 内部原理 简述Leader选举 ZAB协议 一致性
149 1