百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

Zookeeper : 源码入门 zookeeper底层实现原理

yuyutoo 2024-10-16 15:47 6 浏览 0 评论

一. 前言

出于对集群选举流程的好奇 , 所以把 Zookeeper 源码拉下来跑了一下 , 这篇文档对这个过程做一个简单的记录.

要想看懂任何源码 , 第一步就是要跑起来 . 这一篇主要介绍 , 如果快速的跑源码 ,同时简单介绍其中的关键点 , 便于处理

二 . 源码的运行

2.1 主启动流程

// Step 1 : Git 拉取
https://github.com/apache/zookeeper.git

// Step 2 : 本地运行 (Intellij)
1. 找到 zookeeper-server 子模块
2. 找到 对应的启动类 
3. 通过命令启动项目

// PS : 这里查看了 Zookeeper 运行包 (ZkServer.cmd)的内容 , 决定先采用相同的方式启动 >>>>
setlocal
call "%~dp0zkEnv.cmd"

set ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain
echo on
call %JAVA% "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" -cp "%CLASSPATH%" %ZOOMAIN% "%ZOOCFG%" %*

endlocal

复制代码

2.2 其他启动类

进入源码可以看到很多其他的启动类 , 这里查阅了一下 API Doc , 大概了解了一下

2.3 源码的主要逻辑

源码的主要入口类为 QuorumPeerMain , 而其他的类会由 QuorumPeerMain 发起调用 (例如 : ) , 这里提供一个简单的调用流程图 >>>

二 . 启动流程

来简单看一下 QuorumPeerMain 启动时做了什么 :

3.1 QuorumPeerMain 简介

当使用该类的main()方法启动程序时,第一个参数被用作配置文件的路径 , 配置文件中可以包含如下信息 :

  • dataDir : ZooKeeper数据所在目录。
  • dataLogDir : ZooKeeper事务日志存放目录。
  • clientPort : 用于与客户端通信的端口。
  • tickTime : 一个滴答的持续时间,单位为毫秒。这是ZooKeeper中的基本时间单位。
  • initLimit : 跟踪者等待与leader初始同步的最大节拍数。
  • syncLimit : 跟踪者等待来自leader的消息(包括心跳)的最大节拍数。
  • server.id : 这是具有给定id的服务器将用于仲裁协议的主机:port[:port]。
# 以下是我的配置
tickTime=2000
initLimit=10
syncLimit=5
dataDir=D:\\java\\workspace\\git\\zookeeper\\temp
clientPort=2181
复制代码

3.2 IDEA 配置

VM Options

"-Dzookeeper.root.logger=INFO,CONSOLE" -cp "D:\java\workspace\git\zookeeper\zookeeper\zookeeper-server\target\classes;D:\java\workspace\git\zookeeper\zookeeper\zookeeper-server\target\lib\*;D:\java\workspace\git\zookeeper\zookeeper\bin\..\*;D:\java\workspace\git\zookeeper\zookeeper\bin\..\lib\*;D:\java\workspace\git\zookeeper\zookeeper\bin\..\conf"
复制代码

Program arguments

D:\java\workspace\git\zookeeper\zoo.cfg
复制代码

其中比较重要的就是 log 级别和你的源码路径 , 配置完成后一般项目就能正常跑起来了

三 . 请求与接收

请求和接收跑通了才是一切的基础 , Zk 的核心对象分别为 ClientCnxn 和 ServerCnxn

从 log 中不难发现 , 默认是走的 NIO

[main:NIOServerCnxnFactory@89] - binding to port 0.0.0.0/0.0.0.0:2181
[NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@222] - Accepted socket connection from /127.0.0.1:53152
[NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:ZooKeeperServer@903] - Connection request from old client /127.0.0.1:53152; will be dropped if server is in r-o mode
[NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:ZooKeeperServer@942] - Client attempting to renew session 0x100001d6f1f0009 at /127.0.0.1:53152
[NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:ZooKeeperServer@687] - Invalid session 0x100001d6f1f0009 for client /127.0.0.1:53152, probably expired
[NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@1056] - Closed socket connection for client /127.0.0.1:53152 which had sessionid 0x100001d6f1f0009
复制代码

3.1 客户端发起请求

以 SetData 为例 , 主要经过如下流程 :

  • C- ZooKeeper # 相关逻辑
  • C- ClientCnxn # submitRequest : 构建 Packet , 同时加入 Queue 中
  • C- ClientCnxnSocketNIO : 发起处理请求
public Stat setData(final String path, byte[] data, int version) throws KeeperException, InterruptedException {

    final String clientPath = path;
    PathUtils.validatePath(clientPath);
    
    // serverPath ->  /testWatch
    final String serverPath = prependChroot(clientPath);

    RequestHeader h = new RequestHeader();
    h.setType(ZooDefs.OpCode.setData);
    
    // 构建 request
    SetDataRequest request = new SetDataRequest();
    request.setPath(serverPath);
    request.setData(data);
    request.setVersion(version);
    
    SetDataResponse response = new SetDataResponse();
    
    // 通过 ClientCnxn 发起请求
    ReplyHeader r = cnxn.submitRequest(h, request, response, null);
    if (r.getErr() != 0) {
        throw KeeperException.create(KeeperException.Code.get(r.getErr()), clientPath);
    }
    return response.getStat();
}
复制代码

底层 还是用的 NIO 调用 , 后面详细看看

3.2 服务端处理请求

Zookeeper 的核心管理类为 ZooKeeperServer , 其中包括以下方法 :

  • processConnectRequest : 处理连接请求

核心一 : processConnectRequest

创建连接是一切的起点 , 主要通过以下流程调用到该类

C- ZooKeeperServer : 仅保留核心代码
public void processConnectRequest(ServerCnxn cnxn, ByteBuffer incomingBuffer) throws IOException {
    BinaryInputArchive bia = BinaryInputArchive.getArchive(new ByteBufferInputStream(incomingBuffer));
    
    // 参数准备
    ConnectRequest connReq = new ConnectRequest();
    connReq.deserialize(bia, "connect");
    boolean readOnly = false;
    
    readOnly = bia.readBool("readOnly");
    cnxn.isOldClient = false;
    
    // 超时时间 : 4000
    int sessionTimeout = connReq.getTimeOut();
    // 连接密码 , 不存在则为一个空数组
    byte passwd[] = connReq.getPasswd();
    
    // 最小最大会话超时时间
    int minSessionTimeout = getMinSessionTimeout();
    int maxSessionTimeout = getMaxSessionTimeout();

    // 设置超时时间
    cnxn.setSessionTimeout(sessionTimeout);
    cnxn.disableRecv();
    
    // 如果session 存在
    long sessionId = connReq.getSessionId();
    if (sessionId != 0) {
    
        long clientSessionId = connReq.getSessionId();
        // 先关闭再重新打开会话 
        serverCnxnFactory.closeSession(sessionId);
        cnxn.setSessionId(sessionId);
        reopenSession(cnxn, sessionId, passwd, sessionTimeout);
    } else {
        // 创建一个会话
        createSession(cnxn, passwd, sessionTimeout);
    }
}
复制代码

核心二 : Request 请求的处理

这里有一个稍微有点绕的多线程处理 , 后面再详细介绍一下 ,先看下主要的调用流程

  • C- PrepRequestProcessor # pRequest : 由配置类发起的first 请求处理器
  • C- SyncRequestProcessor # processRequest : 将请求加入 Queue 中(核心)
  • C- SyncRequestProcessor # run : 其中会一直循环处理 Request
  • C- FinalRequestProcessor # processRequest : 发起 Process 调用

PS : 其中循环的处理很不错 , 值得深入学习一下

四 . Zookeeper 存储的数据结构

另外一大重点就是了解一下数据是以什么样的结构保存到Zookeeper 中的 , Zookeeper 中存在以下几个核心的数据存储对象 :

  • ZKDatabase : 数据中心
  • DataTree : 数据数 , 核心数据对象

4.1 数据的获取

以数据的获取为例 , 经历了以下流程 :

  • C- SyncRequestProcessor # run : 注意 , 这个是一个不断从 queue 中获取数据的过程
  • C- FinalRequestProcessor # processRequest
  • C- ZKDatabase # getNode
  • C- DataTree # getNode
public class DataTree {
    // 核心存储对象
    private final ConcurrentHashMap<String, DataNode> nodes = new ConcurrentHashMap<String, DataNode>();

    // watches 对象集合
    private final WatchManager dataWatches = new WatchManager();
    private final WatchManager childWatches = new WatchManager();

    /** the root of zookeeper tree */
    private static final String rootZookeeper = "/";

    /** the zookeeper nodes that acts as the management and status node **/
    private static final String procZookeeper = Quotas.procZookeeper;

    /** this will be the string thats stored as a child of root */
    private static final String procChildZookeeper = procZookeeper.substring(1);

    /**
     * the zookeeper quota node that acts as the quota management node for
     * zookeeper
     */
    private static final String quotaZookeeper = Quotas.quotaZookeeper;

    /** this will be the string thats stored as a child of /zookeeper */
    private static final String quotaChildZookeeper = quotaZookeeper
            .substring(procZookeeper.length() + 1);

    /**
     * the path trie that keeps track fo the quota nodes in this datatree
     */
    private final PathTrie pTrie = new PathTrie();
    
}
复制代码

总结

内容不多 , 但是比较重要 , 这几个环节弄清楚后 , 后面围着整个环节抽丝剥茧就行了

后续文章已经整理得差不多了 , 稍微修改一下后续发出来 , 文章写的比较早 , 版本比较旧 ,但是核心是差不多得

附录 :

Zookeeper 项目结构

//─zookeeper-assembly                
//─zookeeper-client
│  └─zookeeper-client-c        
//─zookeeper-compatibility-tests
│  └─zookeeper-compatibility-tests-curator                                  
//─zookeeper-contrib
│  ├─zookeeper-contrib-fatjar               
│  ├─zookeeper-contrib-huebrowser                   
│  ├─zookeeper-contrib-loggraph                                    
│  ├─zookeeper-contrib-monitoring       
│  ├─zookeeper-contrib-rest                                    
│  ├─zookeeper-contrib-zkfuse         
│  ├─zookeeper-contrib-zkperl          
│  ├─zookeeper-contrib-zkpython          
│  ├─zookeeper-contrib-zktreeutil   
│  └─zookeeper-contrib-zooinspector                                  
//─zookeeper-docs                    
//─zookeeper-it                           
//─zookeeper-jute
//─zookeeper-metrics-providers
│  └─zookeeper-prometheus-metrics
//─zookeeper-recipes
│  ├─zookeeper-recipes-election 
│  ├─zookeeper-recipes-lock                             
│  └─zookeeper-recipes-queue
//─zookeeper-server

复制代码

参考文档


作者:AntBlack
链接:https://juejin.cn/post/7004833249566392351
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关推荐

网站建设:从新手到高手

现代化网站应用领域非常广泛,从个人形象网站展示、企业商业网站运作、到政府公益等服务网站,各行各业都需要网站建设。大体上可以归结四类:宣传型网站设计、产品型网站制作、电子商务型网站建设、定制型功能网站开...

JetBrains 推出全新 AI 编程工具 Junie,助力高效开发

JetBrains宣布推出名为Junie的全新AI编程工具。这款工具不仅能执行简单的代码生成与检查任务,还能应对编写测试、验证结果等复杂项目,为开发者提供全方位支持。根据SWEBench...

AI也能写代码!代码生成、代码补全、注释生成、代码翻译轻松搞定

清华GLM技术团队打造的多语言代码生成模型CodeGeeX近期更新了新的开源版本「CodeGeeX2-6B」。CodeGeeX2是多语言代码生成模型CodeGeeX的第二代模型,不同于一代CodeG...

一键生成前后端代码,一个36k星的企业级低代码平台

「企业级低代码平台」前后端分离架构SpringBoot2.x,SpringCloud,AntDesign&Vue,Mybatis,Shiro,JWT。强大的代码生成器让前后端代码一键生成,无需写任...

Gitee 代码托管实战指南:5 步完成本地项目云端同步(附避坑要点)

核心流程拆解:远程仓库的搭建登录Gitee官网(注册账号比较简单,大家自行操作),点击“新建仓库”,建议勾选“初始化仓库”和“设置模板文件”(如.gitignore),避免上传临时文件。...

jeecg-boot 源码项目-强烈推荐使用

JEECGBOOT低代码开发平台...

JetBrains推出全新AI编程工具Junie,强调以开发者为中心

IT之家2月1日消息,JetBrains发文,宣布推出一款名为Junie的全新AI编程工具,官方声称这款AI工具既能执行简单的代码生成与检查等基础任务,也能应对“编写测试、验证结...

JetBrains旗下WebStorm和Rider现已加入“非商用免费”阵营

IT之家10月25日消息,软件开发商JetBrains今日宣布,旗下WebStorm(JavaScript开发工具)和Rider(.NET开发工具)现已加入“非商用免费”阵营。如果...

谈谈websocket跨域

了解websocketwebsocket是HTML5的新特性,在客户端和服务端提供了一个基于TCP连接的双向通道。...

websocket调试工具

...

利用webSocket实现消息的实时推送

1.什么是webSocketwebSocket实现实现推送消息WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。以前的推送技术使用Ajax轮询,浏览器需...

Flutter UI自动化测试技术方案选型与探索

...

为 Go 开发的 WebSocket 库

#记录我的2024#...

「Java基础」Springboot+Websocket的实现后端数据实时推送

这篇文章主要就是实现这个功能,只演示一个基本的案例。使用的是websocket技术。...

【Spring Boot】WebSocket 的 6 种集成方式

介绍...

取消回复欢迎 发表评论: