ZK网络通信源码剖析


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数据处理调用链路复杂,多处存在内部类,代码结构不清晰,写法比较经典


文章作者: fFee-ops
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 fFee-ops !
评论
  目录