Zookeeper系列——3Zookeeper源码分析之Session管理及请求处理

本文深入剖析Zookeeper的Session创建、刷新和过期流程,详解客户端与服务端的交互过程,包括AcceptThread、SelectorThread和WorkerThread的作用,以及RequestProcessor在处理请求中的角色。通过源码分析,揭示Zookeeper如何确保Session的稳定性和请求的高效处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习目标

  1. 理清Zookeeper的Session创建、刷新和过期流程分析

  2. 明确Zookeeper的核心业务调用链

第1章 Session创建

上文给大家讲过Zookeeper的应用了,实际上Zookeeper分为两个模块,server端和client端,server端实现了所有的zookeeper业务逻辑,而client端就是封装了server端的一些方法调用。既然存在两个模块,那肯定涉及到了网络通信,ZooKeeper中使用ServerCnxnFactory管理与客户端的连接,其有两个实现,一个是NIOServerCnxnFactory,使用Java原生NIO实现;一个是NettyServerCnxnFactory,使用netty实现;使用ServerCnxn代表一个客户端与服务端的连接。从单机版启动中可以发现Zookeeper默认通信组件为NIOServerCnxnFactory。接下来我们先来看看Zookeeper中通信的流程图,然后再详细分析源码。

 下面给出了我们客户端发起连接请求及服务端建立会话的全流程图

1.1 客户端发送请求

上文中已经讲过,建立连接是通过new ZooKeeper方法完成的,在ZooKeeper的构造方法中会创建一个ClientCnxn对象,并调用该对象的start方法,在该方法中会启动两个线程任务:sendThread和eventThread。

而sendThread线程就是我们去建立连接的核心线程,在该线程的run方法中实际上是通过一个while循环,不断的执行,如果是第一次进来会去创建连接,如果连接状态是CONNECTED的话,则会最大不超过10秒去发送一次Ping请求保证连接不断开。

源码比较长,有些不重要的代码就直接省略了。

public void run() {
    //发送Ping的间隔
    final int MAX_SEND_PING_INTERVAL = 10000; //10 seconds
    while (state.isAlive()) {
        try {
            //如果状态是CONNECTING的话就去创建连接
            if (!clientCnxnSocket.isConnected()) {
                startConnect(serverAddress);
            }

			//如果已经连接成功,则最大不超过10秒发送一次心跳
            if (state.isConnected()) {
                //这段逻辑实际上就是控制心跳的是发送间隔,避免过多的发送
                int timeToNextPing = readTimeout / 2 - clientCnxnSocket.getIdleSend() - 
                    ((clientCnxnSocket.getIdleSend() > 1000) ? 1000 : 0);
                if (timeToNextPing <= 0 || clientCnxnSocket.getIdleSend() > MAX_SEND_PING_INTERVAL) {
                    sendPing();
                    clientCnxnSocket.updateLastSend();
                } else {
                    if (timeToNextPing < to) {
                        to = timeToNextPing;
                    }
                }
            }
        } 
}
void connect(InetSocketAddress addr) throws IOException {
    SocketChannel sock = createSock();
    try {
        //会调用ZK的服务端完成会话创建
        registerAndConnect(sock, addr);
    } catch (IOException e) {

    }
}

void registerAndConnect(SocketChannel sock, InetSocketAddress addr) 
    throws IOException {
    sockKey = sock.register(selector, SelectionKey.OP_CONNECT);
    //调用NIO开启会话
    boolean immediateConnect = sock.connect(addr);

}

1.2 服务端接收连接

服务端由NIOServerCnxnFactory启动线程去接收请求,NIOServerCnxnFactory启动时会启动四类线程:

  • AcceptThread:该线程接收来自客户端的连接,并将其分配给SelectorThread(启动一个线程)。

  • SelectorThread:该线程执行select(),由于在处理大量连接时,select()会成为性能瓶颈,因此启动多个SelectorThread,使用系统属性zookeeper.nio.numSelectorThreads配置该类线程数,默认个数为 核心数/2。

  • WorkerThread:该线程执行基本的套接字读写,使用系统属性zookeeper.nio.numWorkerThreads配置该类线程数,默认为核心数∗2核心数∗2.如果该类线程数为0,则另外启动一线程进行IO处理,见下文worker thread介绍。

  • ConnectionExpirationThread:若连接上的session已过期,则关闭该连接。

1.2.1 AcceptThread

该线程会接收客户端的请求

public void run() {
    while (!stopped && !acceptSocket.socket().isClosed()) {
        select();
    }
}
private void select() {
    try {
        //查找就绪的连接
        selector.select();

        Iterator<SelectionKey> selectedKeys =
            selector.selectedKeys().iterator();
        while (!stopped && selectedKeys.hasNext()) {

            if (key.isAcceptable()) {
                //1:和当前服务建立链接。
                //2:获取远程客户端计算机地址信息。
                //3:判断当前链接是否超出最大限制。
                //4:调整为非阻塞模式。
                //5:轮询获取一个SelectorThread,将当前链接分配给该SelectorThread。
                //6:将当前请求添加到该SelectorThread的acceptedQueue中,并唤醒该SelectorThread。
                if (!doAccept()) {
                    pauseAccept(10);
                }
            } 
        }
    } 
}

进入到doAccept方法中

private boolean doAccept() {
			...
            try {
                //建立连接
                sc = acceptSocket.accept();
                accepted = true;
                //获取远程计算机地址信息
                InetAddress ia = sc.socket().getInetAddress();
                int cnxncount = getClientCnxnCount(ia);

                //判断是否超出最大客户端连接的限制
                if (maxClientCnxns > 0 && cnxncount >= maxClientCnxns){
                    ...
                }

                LOG.debug("Accepted socket connection from "
                         + sc.socket().getRemoteSocketAddress());
                //调整此通道的阻塞模式
                sc.configureBlocking(false);

                //轮询将此连接分配给一个SelectorThread
                if (!selectorIterator.hasNext()) {
                    selectorIterator = selectorThreads.iterator();
                }
                SelectorThread selectorThread = selectorIterator.next();
                //将新连接加入SelectorThread的acceptedQueue中,并唤醒SelectorThread
                if (!selectorThread.addAcceptedConnection(sc)) {
                    ...
                }
                acceptErrorLogger.flush();
            } catch (IOException e) {
				...
            }
            return accepted;
        }
    }
public boolean addAcceptedConnection(SocketChannel accepted) {
    //将accepted添加到acceptedQueue
    if (stopped || !acceptedQueue.offer(accepted)) {
        return false;
    }
    //唤醒SelectorThread
    wakeupSelector();
    return true;
}

在addAcceptedConnection方法中会唤醒SelectorThread,所以,接下来,逻辑会进入到SelectorThread.run方法中

1.2.2 SelectorThread

该线程的主要作用是从Socket读取数据,并封装成workRequest,并将workRequest交给workerPool工作线程池处理,同时将acceptedQueue中未处理的连接取出,并未每个连接绑定OP_READ读事件,并封装对应的上下文对象NIOServerCnxn。SelectorThread的run方法如下:

public void run() {

    //读取就绪的IO事件,交由worker thread处理,在ZookeeperServer的processPacket()中处理数据
    select();
    //把acceptedQueue队列中接收的连接,取出来注册OP_READ事件,
    //并添加NIOServerCnxn对象与当前key绑定
    //相当于给每个连接添加附加对象NIOServerCnxn(上下文对象)
    processAcceptedConnections();
    //遍历所有updateQueue,更新updateQueue中连接的监听事件
    processInterestOpsUpdateRequests();
}

先来看看proces

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木木_2024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值