JavaEE 企业级分布式高级架构师(八)Zookeeper学习笔记(4)

源码篇

将 Zookeeper 源码导入到Idea

由于 Zookeeper 是使用 Ant 进行构建的,所以若想将 Zookeeper 的源码导入 Eclipse 或 IDEA 中进行源码分析,需要首先安装 Ant 工具,然后再执行 ant eclipse 命令,会生成 Eclipse 工程,然后再导入到 Eclipse 或 IDEA 中。

下载并安装Ant

  • Ant官网:http://ant.apache.org/
    在这里插入图片描述
    在这里插入图片描述
  • 将下载的 Ant 安装 zip 包进行解压,然后在系统环境变量中添加 ANT_HOME,并将 Ant 安装目录下的 bin 目录添加到系统环境变量 Path 中。
    在这里插入图片描述
    在这里插入图片描述
  • 在命令行窗口的任意目录下执行 ant -version 命令,可以看到版本号,则说明 Ant 安装成功。
    在这里插入图片描述

构建Eclipse工程

  • 在命令行窗口中进入到 zk 解压目录,执行 ant eclipse 命令。其会根据 build.xml 文件对当前的 Zookeeper 源码进行构建,将其构建为一个 Eclipse 工程。不过,对于该构建过程,其 JDK 最好能够满足 build.xml 文件的要求。打开 build.xml 文件,可以看到 zk 源码使用 JDK6 编写并编译的,所以最好使用该指定的 JDK,这样可以保证将来构建出的 Eclipse 工程中没有错误。
    在这里插入图片描述
  • 这里,我们使用的是 JDK8 构建出的 Eclipse 工程的相关代码并没有报错:
    在这里插入图片描述
    在这里插入图片描述
  • 构建成功后,在 zk 源码目录中可以看到 Eclipse 工程的相关文件
    在这里插入图片描述

导入到IDEA

  • 打开 IDEA,选择导入工程,找到 zk 的源码解压目录,直接导入:
    在这里插入图片描述
    在这里插入图片描述

Leader选举源码

选举算法源码中的总思路

Zookeeper支持三种选举算法,但默认的是 FastLeaderElection 算法,该算法是 ZAB 协议在 Leader 选举中的工程应用,所以直接找到该类进行分析。该类中的最为重要的方法为 lookForLeader(),是选举 Leader 的核心方法。该方法大体思路可以划分为三块:

选举前的准备工作
  • 选举前需要做一些准备工作,例如:创建选举对象、创建选举过程中需要用到的集合、初始化选举时限等。
将自己作为初始化 Leader 投出去
  • 在当前 Server 第一次投票时会先将自己作为 Leader,然后将自己的选票广播给其它所有 Server。
验证自己的投票与大家的投票谁更适合做Leader
  • 在“我选我”后,当前 Server 同样会接收到其它 Server 发送来的选票通知(Notification)。通过 while 循环,遍历所有接收到的选票通知,比较谁更适合做 Leader。若找到一个比自己更适合的 Leader,则修改自己选票,重新将新的选票广播出去。
  • 最终,在对某 Server 的选票超过半数时,新的 Leader 产生。然后再做一些收尾工作,例如清空选举过程中所使用的集合,以备下次使用;再例如,生成最终的选票,以备其它 Server 来同步数据。

源码解读

  • 需要注意,对源码的阅读主要包含两方面。一个是对重要类、重要成员变量、重要方法的注释的阅读;一个是对重要方法的业务逻辑的分析。
一些重要的类
  • FastLeaderElection
    在这里插入图片描述
  • QuorumCnxManager
    在这里插入图片描述
  • ServerId 为 1 的 Server 中 QuorumCnxManager 对象维护的消息发送 Map
Key(ServerId)Value(队列)
2队列(存放向2号Server发送失败的消息副本)
3队列(存放向3号Server发送失败的消息副本)
4队列(存放向4号Server发送失败的消息副本)
* 若所有队列都为空:说明 Server1 的消息发送全部成功
* 若所有队列均不空:说明 Server1 发送给所有其它server的消息全部失败,即说明 Server1 与集群已经失联
* 若某一队列不为空:说明 Server1 与集群的连接是没有问题的,但个别Server与Server1间的连接出现了问题
  • QuorumPeer
    在这里插入图片描述
一些重要的成员变量
  • 在 FastLeaderElection 类中定义了这样一些成员变量:
    在这里插入图片描述
    在这里插入图片描述
  • 其中还有一个重要的静态内部类 Notification:
    在这里插入图片描述
重要方法 lookForLeader()

在这里插入图片描述

第一部分:选举前的准备工作
  • 选举前的准备工作:记录选举开始时间,初始化两个Map用于统计Leader选举的选票和非法选票。

在这里插入图片描述

第二部分:毛遂自荐
  • 将自己作为初始化Leader投出去

在这里插入图片描述

  • sendNotifications()方法

在这里插入图片描述

第三部分(核心):接收选票
  • 比较自己的选票与接收到的其它Server的选票,谁更合适做Leader。
  • 核心代码如下:

在这里插入图片描述

  • 流程图

在这里插入图片描述

第四部分:统计选票
  • 统计选票:判断当前选举是否可以结束了,当前的选票若在recvset中有过半的支持,则结束选举。

在这里插入图片描述

  • 合法性校验的方法——validVoter
// 只要被判别的主机出现在QuorumServer中,其就是合法的
private boolean validVoter(long sid) {
    return self.getVotingView().containsKey(sid);
}
  • 比较谁更适合做Leader的方法——totalOrderPredicate
// 比较new选票与cur选票谁更适合做Leader,若new更适合,则返回true,否则返回false
protected boolean totalOrderPredicate(long newId, long newZxid, long newEpoch, long curId, long curZxid, long curEpoch) {
    LOG.debug("id: " + newId + ", proposed id: " + curId + ", zxid: 0x" +
            Long.toHexString(newZxid) + ", proposed zxid: 0x" + Long.toHexString(curZxid));
    // 判断new主机的权重是否为0。注意,只有Observer的权重才是0
    // 即判断new主机是否是Observer
    if(self.getQuorumVerifier().getWeight(newId) == 0){
        return false;
    }
    
    /*
     * We return true if one of the following three cases hold:
     * 1- New epoch is higher
     * 2- New epoch is the same as current epoch, but new zxid is higher
     * 3- New epoch is the same as current epoch, new zxid is the same
     *  as current zxid, but server id is higher.
     */
    // Leader产生的比较规则
    return ((newEpoch > curEpoch) || 
            ((newEpoch == curEpoch) &&
            ((newZxid > curZxid) || ((newZxid == curZxid) && (newId > curId)))));
}
  • 判断是否可以终止选举的方法——termPredicate
protected boolean termPredicate(HashMap<Long, Vote> votes,Vote vote) {
    HashSet<Long> set = new HashSet<Long>();

    /*
     * First make the views consistent. Sometimes peers will have
     * different zxids for a server depending on timing.
     */
    // 遍历所有投票
    for (Map.Entry<Long,Vote> entry : votes.entrySet()) {
        if (vote.equals(entry.getValue())){
            set.add(entry.getKey());
        }
    }
    // 判断当前set集合中的元素数量是否过半(zk集群数量的一半)
    return self.getQuorumVerifier().containsQuorum(set);
}
  • 判断当前主机与集群是否失联和重连方法
// QuorumCnxManager.java
/**
 * Try to establish a connection with each server if one
 * doesn't exist.
 */
public void connectAll(){
    long sid;
    // 连接其它每一台主机
    for(Enumeration<Long> en = queueSendMap.keys();
        en.hasMoreElements();){
        sid = en.nextElement();
        connectOne(sid);
    }      
}
/**
 * Check if all queues are empty, indicating that all messages have been delivered.
 */
// 检测,若所有队列为空,则表明当前主机与集群没有失联,那么它的消息已经全部发送出去了
boolean haveDelivered() {
    for (ArrayBlockingQueue<ByteBuffer> queue : queueSendMap.values()) {
        LOG.debug("Queue size: " + queue.size());
        if (queue.size() == 0) {
            return true;
        }
    }

    return false;
}
第五部分:无需选举的情况
  • ① n.state == OBSERVING

在这里插入图片描述

  • ② n.state == FOLLOWING 或 LEADING:以下三种场景会接收到Follower或Leader发送来的通知
    • 场景一:当有新的主机(非Observer)要加入一个正常运行的集群时,其初始状态为LOOKING,其会调用lookForLeader(),然后向所有集群中的主机发送通知。当Leader、Follower接收到该主机所发送的通知后,它们就会向其回复通知,此时的通知状态就是FOLLOWING或LEADING。
    • 场景二:当Leader挂了,并不是所有Follower同时感知到的,会有个先后顺序。对于先感知到Leader挂了的主机,其状态马上会变为LOOKING,然后向其他主机发送通知。其它主机由于还没有感知到Leader已经挂了,所以它们的状态仍为FOLLOWING。所以它们在向这台主机回复的通知状态为FOLLOWING。
    • 场景三:在本轮选举中已经选举出了新的Leader,但并不是所有participant同时都知道的,也会有个先后顺序。新选举出的Leader的状态为LEADING,知道Leader选举出来的主机的状态为Following,但还不知道的主机状态仍为 LOOKING。此时的主机在向其它主机发送过通知后,接收到的通知就有LEADING、FOLLOWING状态的通知。
  • 代码分析:

在这里插入图片描述

客户端连接Server源码解析

创建Client对象-zkClient

  • 分析入口:
public class ZKClientTest {
    public static void main(String[] args){
        // 创建 zkclient
        ZkClient zkClient = new ZkClient(Constants.ZK_CLUSTER_SERVER);
        // ...
    }
}

在这里插入图片描述

  • 连接 zk 集群:

在这里插入图片描述

启动Client-Curator

  • 分析入口:

在这里插入图片描述

  • 从start()跟进去:

在这里插入图片描述

  • 然后再继续跟踪 reset()方法:

在这里插入图片描述

创建zk对象

在这里插入图片描述

  • 客户端连接请求处理线程:

在这里插入图片描述

  • 获取连接地址:

在这里插入图片描述

  • 进行连接:

在这里插入图片描述

  • 获取NIO的channel,并完成注册和连接

在这里插入图片描述

小结
  • 1、创建一个zk集群字符串解析器,将解析出的zk server地址写入到了一个列表
  • 2、创建一个地址解析器并将解析出的地址进行一次shuffle
  • 3、从第一次shuffle的server地址列表中轮询选出一个server地址进行连接
    • 3.1、获取指定主机名对应的所有 由ip构成的地址
    • 3.2、如果地址直接就是由ip构成的情况,直接返回该ip构成的地址即可
    • 3.3、否则,对一个主机名获取到的所有由ip构成的地址再进行一次shuffle
    • 3.4、对shuffle过的结果,直接取第一个地址

会话连接超时——客户端维护

  • 连接 zk 服务器后,更新连接请求的发送时间和心跳时间:
    在这里插入图片描述
  • 判断连接状态,并处理:
    在这里插入图片描述

会话空闲超时管理——服务端维护

分桶策略

在这里插入图片描述

  • 分桶策略是指,将空闲超时时间相近的会话放到同一个桶中来进行管理,以减少管理的复杂度。在检查超时时,只需要检查桶中剩下的会话即可,因为没有超时的会话已经被移出了桶,而桶中存在的会话就是超时的会话。
  • zk 对于会话空闲的超时管理并非是精确的管理,即并非是一超时马上就执行相关的超 时操作。
  • 分桶依据:一个桶的大小为 ExpirationInterval 时间。只要 ExpirationTime 落入到同一个桶中,系统就会对其中的会话超时进行统一管理。
ExpirationTime = CurrentTime + SessionTimeout;
BucketTime = (ExpirationTime/ExpirationInterval + 1) * ExpirationInterval
创建sessionTracker源码解析
  • 分析入口:ZooKeeperServer#startup
/**
 * 该方法在 zk 启动时会被调用
 */
public synchronized void startup() {
    if (sessionTracker == null) {
        // 创建一个会话跟踪器
        createSessionTracker();
    }
    // 启动会话跟踪器线程
    startSessionTracker();
    setupRequestProcessors();

    registerJMX();

    setState(State.RUNNING);
    notifyAll();
}
  • 跟下方法createSessionTracker:

在这里插入图片描述

  • 进入SessionTrackerImpl:

在这里插入图片描述

  • addSession()方法:

在这里插入图片描述

  • touchSession()方法:

在这里插入图片描述

处理客户端连接请求
  • 分析入口:ZooKeeperServer#processConnectRequest

在这里插入图片描述

  • 重新打开会话reopenSession:

在这里插入图片描述

  • 创建新的会话:createSession

在这里插入图片描述

处理客户端读写请求
  • 分析入口:ZooKeeperServer#processPacket

在这里插入图片描述

  • 提交会话请求:

在这里插入图片描述

  • 这里小结下touchSession()调用关系:这个方法比较重要

在这里插入图片描述

sessionTracker线程的启动
  • 分析入口:

在这里插入图片描述

  • startSessionTracker()方法:

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值