源码分析
客户端
架构

我们可以认为ZK的Client由三个主要模块组成:Zookeeper, WatcherManager, ClientCnxn。
Zookeeper
Zookeeper是ZK Client端的真正接口,用户可以操作的最主要的类,当用户创建一个Zookeeper实例以后,几乎所有的操作都被这个实例包办了,用户不用关心怎么连接到Server,Watcher什么时候被触发等等令人伤神的问题。
WatcherManager
WatcherManager,顾名思义,它是用来管理Watcher的,Watcher是ZK的一大特色功能,允许多个Client对一个或多个ZNode进行监控,当ZNode有变化时能够通知到监控这个ZNode的各个Client。我们把一个ZK Client简单看成一个Zookeeper实例,那么这个实例内部的WatcherManager就管理了ZK Client绑定的所有Watcher。
ClientCnxn
ClientCnxn是管理所有网络IO的模块,所有和ZK Server交互的信息和数据都经过这个模块,包括给ZK Server发送Request,从ZK Server接受Respon;se,以及从ZK Server接受Watcher Event。ClientCnxn完全管理了网络,从外部看来网络操作是透明的。
SendThread
创建同 Server 之间的 socket 链接
判断链接是否超时
定时发送心跳任务
将ZooKeeper指令发送给Server
EventThread
执行监听
执行异步回调
调用过程

服务端
单例模式
NIOServerCnxnFactory
NIOServerCnxnFactory是zookeeper默认的处理与客户端通信的类。其内部维护了三个线程类:
AbstractSelectThread:SelectorThread类和AcceptThread类的共同父类,维护了一个selector选择器对象。
AcceptThread:管理新的ZooKeeper client连接请求,实际上就是利用父类的选择器selector监听ServerSocketChannel的“SelectionKey.OP_ACCEPT”事件,一旦来新的请求,负责建立好和客户端的连接SocketChannel,并从SelectorThread线程池分配一个线程,将该SocketChannel连接放入SelectorThread线程维护的acceptedQueue队列中。
SelectorThread:监听AcceptThread分配的已经建立的SocketChannel连接上发生的数据读写事件,并执行实际数据读写。实际上就是利用父类的选择器selector监听在acceptedQueue队列中建立好的连接的数据读写事件,一旦读写事件发生,调用handleIO函数处理读写请求。
ZKDatabase
ZKDatabase 用于管理ZooKeeper的中的节点数据。当中主要维护了DataTree对象和FileTxnSnapLog对象。
DataTree:在DataTree中维护一个叫做nodes的ConcurrentHashMap<String, DataNode>,用于在内存中持有完整的节点信息。key为节点的全路径,value为DataNode对象。
FileTxnSnapLog:FileTxnSnapLog由两部分组成,一个是 FileSnap, 一个是FileTxnLog。FileSnap 用于存放基于某个时间点状态的 ZooKeeper 节点信息快照,FileTxnLog 用于存放数据对节点信息的具体更改操作。
数据加载
数据加载在ZooKeeper服务端启动时,会调用ZooKeeperServer::loadData()方法,来加载snapDir目录下的snapshot.zxid文件和dataDir目录下log.zxid文件(dataDir和dataLogDir在zoo.cfg中配置),将snapshot.zxid中记录的数据快照信息加载到内存中的DataTree上,执行log.zxid中记录的操作,将内存中的数据恢复到上次关闭服务时的状态。(只加载snapshot.zxid文件的zxid之后的log操作日志文件)。
RequestProcessor 任务链

在ZooKeeperServer::setupRequestProcessors中创建了三个RequestProcessor对象,分别是 FinalRequestProcessor , SyncRequestProcessor 和 PrepRequestProcessor ,其中PrepRequestProcessor 和 SyncRequestProcessor 类分别继承自 Thread 类,作为独立线程运行。
PrepRequestProcessor自身的独立线程不断从队列中拉去Request对象,调用pRequest(request)。在pRequest中,根据Request的不同种类,将Request转变为不同的Record对象,通过addChangeRecord将ChangeRecord加入ZooKeeperServer.outstandingChanges 中,此时节点数据并没有同步到DataTree中。
SyncRequestProcessor作为 PrepRequestProcessor 的下游消费者,负责将Transaction写入TxnLog中,并定时构建快照文件。zkDatabase.append中会将Request写入Transaction Log File,如果发现当前的Txn条数超过阈值,则启动一个快照线程,将DataTree作为快照实例到磁盘中。在takeSnapshot中,通过序列化当前的DataTree结构,将snapShot保存到磁盘上当SyncRequestProcessor的处理条数超过阈值1000条时,调用flush()命令,将任务逐个传递给下游的RequestProcessor进行处理。
FinalRequestProcessor调用zks.processTxn(),将请求信息合并到DataTree中清理掉zks.outstandingChanges中的冗余数据,防止outstandingChanges无限增长。
集群模式
Last updated