概述
本文讲述zookeeper的NIOServerCnxnFactory类的对象启动过程的实现代码。
该类实现了zookeeper的主要的后台服务,用来接收并处理客户端的各种命令,处理各种任务。
NIOServerCnxnFactory.start()启动服务
该函数用来启动后台服务,主要完成以下事项:
- 创建一个WorkerService,该线程执行器的创建和管理。
- 启动所有的SelectorThread线程,处理已经和客户端建立好的连接发送过来的连接请求。
- 启动AcceptThread线程,该线程用来接收客户端的连接请求,完成连接,并把完成的连接交给SelectorThread线程接手
- 启动expirerThread线程,该线程用来处理断开,或产生异常的连接
代码实现如下:
public void start() {
stopped = false;
// 启动worker线程池
if (workerPool == null) {
workerPool = new WorkerService(
"NIOWorker", numWorkerThreads, false);
}
// 启动selecotr线程池
for(SelectorThread thread : selectorThreads) {
if (thread.getState() == Thread.State.NEW) {
thread.start();
}
}
// 启动accept线程
// ensure thread is started once and only once
if (acceptThread.getState() == Thread.State.NEW) {
acceptThread.start();
}
// 启动终止线程
if (expirerThread.getState() == Thread.State.NEW) {
expirerThread.start();
}
}
AcceptThread运行流程
实现该线程的主类是:AcceptThread
功能:该线程主要是接收来自客户端的连接请求,并完成三次握手,建立tcp连接。主要的实现流程如下:
- 在run()函数中实现线程的主要逻辑。在run()函数中主要调用select()函数。
- 在select()函数中,会调用java的nio库中的函数:selector.select()对多个socket进行监控,看是否有读、写事件发生。
- 若没有读、写事件发生,该函数会一直阻塞。
- 若有能够accepted事件发生,则调用doAccept()函数进行处理。
- 在函数doAccept中,会调用socket的accept函数,来完成和客户端的三次握手,建立起tcp连接。然后把已经完成连接的socket,设置成非阻塞:sc.configureBlocking(false);
- 接下来选择一个selector线程,并把连接好的socket添加到该selector线程的acceptedQueue队列中,该队列的声明是:private final Queue acceptedQueue,定义是:acceptedQueue = new LinkedBlockingQueue();添加的函数是:addAcceptedConnection。
- 可见,accepted队列是一个阻塞队列,添加到该队列后,就需要selector线程来接管已连接socket的后续的消息,所以需要唤醒selector队列。
- 在addAcceptedConnection把已连接socket添加到阻塞队列中后,调用wakeupSelector();唤醒对应的selector线程。该函数很简单,就是直接调用Selector的wackup()函数。
SelectorThread
该线程接管连接完成的socket,接收来自该socket的命令处理命令,把处理结果返回给客户端。
构造函数
创建了两个阻塞队列,一个用于接收,一个用于监控还没有完成连接的socket。
public SelectorThread(int id) throws IOException {
super("NIOServerCxnFactory.SelectorThread-" + id);
this.id = id;
// socket完成接收的队列
acceptedQueue = new LinkedBlockingQueue<SocketChannel>();
// 选择队列
updateQueue = new LinkedBlockingQueue<SelectionKey>();
}
主处理流程
主处理流程在run函数中,实现代码如下:
public void run() {
try {
while (!stopped) {
try {
select();
processAcceptedConnections();
processInterestOpsUpdateRequests();
} catch (RuntimeException e) {
LOG.warn("Ignoring unexpected runtime exception", e);
} catch (Exception e) {
LOG.warn("Ignoring unexpected exception", e);
}
}
...
}
}
处理客户端请求总体流程
- 在主流程中,会调用select()函数来监控socket是否有读和写事件,若有读和写事件会调用handleIO(key)函数对事件进行处理。
- 在handleIO中,会启动woker线程池中的一个worker来处理这个事件,处理事件的主类是ScheduledWorkRequest,最终会调用run函数中的workRequest.doWork();来处理请求。
- 在IOWorkRequest.doWork()中会判断key的合法性,然后调用NIOServerCnxn.doIO(key)来处理事件,
- 在doIO函数中,对读的事件会调用readPayload()函数来处理,对于写事件会调用handleWrite(k)来处理。
- 当消息接收完成后会调用readRequest()函数来处理消息,之后会调用ZooKeeperServer.processPacket函数来分解消息包并处理请求。
- 在processPacket函数中会根据认证方式的不同来处理,最后会调用submitRequest函数来处理不同类型的请求。
- 最后进入请求的处理链:PrepRequestProcessor->SyncRequestProcessor->FinalRequestProcessor,在这个处理链中共有三个阶段,消息被不同的阶段进行处理,处理完成后交给下一个阶段的对象进行处理,最后完成整个处理过程。
ConnectionExpirerThread
该类的主要任务是从ExpiryQueue cnxnExpiryQueue这个终止队列中获取已经终止的session,并对这些session和连接进行关闭。
该类继承了ZooKeeperThread类,所以实现代码都在run()函数中。
关键代码如下:
public void run() {
...
for (NIOServerCnxn conn : cnxnExpiryQueue.poll()) {
conn.close();
}
...
}
总结
本文讲述了zookeeper中的关键组件NIOServerCnxnFactory类的启动运行过程。