ZK网络通信源码剖析
Zookeeper 作为一个服务器,自然要与客户端进行网络通信, ZooKeeper 中使用 ServerCnxnFactory
管理与客户端的连接,其有两个实现,一个是NIOServerCnxnFactory
,使用Java原生 NIO 实现;一个是NettyServerCnxnFactory
,使用netty实现;
使用 ServerCnxn 代表一个客户端与服务端的连接。从单机版启动中可以发现 Zookeeper 默认通信组件为 NIOServerCnxnFactory ,他们和ServerCnxnFactory 的关系如下图:
概览图:
NIOServerCnxnFactory工作流程源码剖析
NIOServerCnxnFactory 启动时会启动四类线程:
它们的关系图如下:
AcceptThread剖析
为了更容易理解AcceptThread,我把它的结构和方法调用关系画了一个详细的流程图,如下图:
在 NIOServerCnxnFactory 类中有一个 AccpetThread
线程,为什么说它是一个线程?我们看下它的继承关系: AcceptThread > AbstractSelectThread > ZooKeeperThread > Thread
,该线程接收来自客户端的连接,并将其分配给selector thread
(启动一个线程)。
该线程执行流程: run()
执行selector.select()
,并调用 doAccept()
接收客户端连接,因此我们可以着重关注 doAccept() 方法,该类源码如下:
doAccept() 方法用于处理客户端链接,当客户端链接 Zookeeper 的时候,首先会调用该方法,调用该方法执行过程如下:doAccept()
方法源码如下:
上面代码中 addAcceptedConnection
方法如下:
SelectorThread剖析
同样为了更容易梳理 SelectorThread ,我也把它的结构和方法调用关系梳理成了流程图,如下图:
该线程的主要作用是从Socket读取数据,并封装成 workRequest
,并将 workRequest 交给workerPool 工作线程池处理,同时将acceptedQueue
中未处理的链接取出,并为每个链接绑定OP_READ
读事件,并封装对应的上下文对象 NIOServerCnxn
。 SelectorThread 的run方法如下:
run() 方法中会调用 select()
,而 select() 中的核心调用地方是 handleIO()
,我们看名字其实就知道这里是处理客户端请求的数据,但客户端请求数据并非在SelectorThread
线程中处理,我们接着看 handleIO() 方法。handleIO()
方法会封装当前 SelectorThread 为 IOWorkRequest ,并将 IOWorkRequest 交给workerPool
来调度,而 workerPool 调度才是读数据的开始,源码如下:
WorkerThread剖析
WorkerThread
相比上面的线程而言,调用关系颇为复杂,设计到了多个对象方法调用,主要用于处理IO,但并未对数据做出处理,数据处理将由业务链对象RequestProcessor
处理,调用关系图如下:
ZooKeeper 中通过 WorkerService
管理一组 worker thread 线程,前面我们在看 SelectorThread 的时候,能够看到workerPool
的schedule方法被执行,如下图:
我们跟踪 workerPool.schedule(workRequest);
可以发现它调用了WorkerService.schedule(workRequest) > WorkerService.schedule(WorkRequest, long) ,
该方法创建了一个新的线程 ScheduledWorkRequest
,并启动了该线程,源码如下:ScheduledWorkRequest
实现了 Runnable 接口,并在 run() 方法中调用了 IOWorkRequest
中的doWork
方法,在该方法中会调用 doIO 执行IO数据处理,源码如下:
IOWorkRequest 的 doWork 源码如下:
接下来的调用链路比较复杂,我们把核心步骤列出,在能直接看到数据读取的地方详细分析源码。上面方法调用链路:NIOServerCnxn.doIO()>readPayload()>readRequest()>ZookeeperServer.processPacket()
,最后一步方法是获取核心数据的地方,我们可以修改下代码读取数据:
ConnectionExpirerThread剖析
后台启动 ConnectionExpirerThread 清理线程清理过期的 session ,线程中无限循环,执行工作如下:
ZK通信优劣总结
Zookeeper在通信方面默认使用了NIO,并支持扩展Netty实现网络数据传输。相比传统IO,NIO在网络数据传输方面有很多明显优势,也就是BIO和NIO的区别。
ZK在使用NIO通信虽然大幅提升了数据传输能力,但也存在一些代码诟病问题:
1:Zookeeper通信源码部分学习成本高,需要掌握NIO和多线程
2:多线程使用频率高,消耗资源多,但性能得到提升
3:Zookeeper数据处理调用链路复杂,多处存在内部类,代码结构不清晰,写法比较经典