Zookeeper会话--zookeeper会话创建以及会话管理

Zookeeper会话–zookeeper会话创建以及会话管理

之前分析了zookeeper服务器集群的启动,可以说峰回路转,算是把服务器启动的流程熟悉了一下,那么我们需要怎么连接到服务器集群开启一次客户端连接的会话呢?这次就来分析一下zookeeper的会话过程。

Zookeeper客户端:

无论我们使用zookeeper的zkclient客户端还是acutor,其实都是对zookeeper的API进行了二次封装,将原来复杂的API操作封装为可供客户端调用的API,所以,在分析连接zookeeper集群时还需要了解下zookeeper的客户端API是如何封装的,这次则使用zkclient这个zookeeper客户端API进行分析:

首先,当使用zk客户端的时候会去创建一个ZKClient,这个可以说这是一个很好的入口,基本上从客户端连接到服务器集群需要的就是通过API来连接:
在这里插入图片描述
这里可以看到,ZkClient继承了Watcher接口,Java其实通过事件监听的方式实现了对于Watcher的监听,不再像原生API那样需要传入一个对象了,也就是所说屏蔽了原来原生API中对Watcher的传入,这样使用Listener的方式更加贴近Java工程师的习惯。那看下其构造方法吧:
在这里插入图片描述
一般的我们会使用这三个构造参数来初始化ZkClient的对象,在平时也基本使用服务器地址的字符串,连接超时和会话超时三个参数,但是最后都会调用下面的构造方法:

public ZkClient(final IZkConnection zkConnection, final int connectionTimeout, final ZkSerializer zkSerializer, final long operationRetryTimeout) {
        if (zkConnection == null) {
            throw new NullPointerException("Zookeeper connection is null!");
        }
        _connection = zkConnection;
        _zkSerializer = zkSerializer;
        _operationRetryTimeoutInMillis = operationRetryTimeout;
        _isZkSaslEnabled = isZkSaslEnabled();
        connect(connectionTimeout, this);
    }

可以看到上面实例化了zkConnection对象,也就是说明从开始创建到上面的初始化方法,首先要保证的是zkConnnection不能是null,可以看到最后调用了connect方法:

    public void connect(final long maxMsToWaitUntilConnected, Watcher watcher) throws ZkInterruptedException, ZkTimeoutException, IllegalStateException {
        boolean started = false;
        acquireEventLock();
        try {
            setShutdownTrigger(false);
            _eventThread = new ZkEventThread(_connection.getServers());
            _eventThread.start();
            _connection.connect(watcher);

            LOG.debug("Awaiting connection to Zookeeper server");
            boolean waitSuccessful = waitUntilConnected(maxMsToWaitUntilConnected, TimeUnit.MILLISECONDS);
            if (!waitSuccessful) {
                throw new ZkTimeoutException("Unable to connect to zookeeper server '" + _connection.getServers() + "' with timeout of " + maxMsToWaitUntilConnected + " ms");
            }
            started = true;
        } finally {
            getEventLock().unlock();

            // we should close the zookeeper instance, otherwise it would keep
            // on trying to connect
            if (!started) {
                close();
            }
        }
    }

首先启动了一个新的守护线程(zkEentThread):

ZkEventThread(String name) {
        setDaemon(true);
        setName("ZkClient-EventThread-" + getId() + "-" + name);
    }

然后调用ZkConnection的connect方法,开始真正的连接:

    public void connect(Watcher watcher) {
        _zookeeperLock.lock();
        try {
            if (_zk != null) {
                throw new IllegalStateException("zk client has already been started");
            }
            try {
                LOG.debug("Creating new ZookKeeper instance to connect to " + _servers + ".");
                _zk = new ZooKeeper(_servers, _sessionTimeOut, watcher);
            } catch (IOException e) {
                throw new ZkException("Unable to connect to " + _servers, e);
            }
        } finally {
            _zookeeperLock.unlock();
        }
    }

可以看到,在这里开始真正的初始化ZooKeeper的实例,追溯到上文的_connection.connect(timeconnectionout, this)可以看出,这里是把zkClient注册到ZooKeeper中,因为ZkClient实现了Watcher接口,所以这个zkclient客户端可以去监听Watcher中变化的事件。可以比较下zookeeper原生API的初始化方式:
在这里插入图片描述
可以看到,如果使用zkclint去创建,创建对话的过程确实容易了很多,接着看zkclient相关的内容,刚才说道创建Zooeeper的对象,那么通过其构造函数可以看下是怎么构建这个对象的:

    public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
            boolean canBeReadOnly)
        throws IOException
    {
        LOG.info("Initiating client connection, connectString=" + connectString
                + " sessionTimeout=" + sessionTimeout + " watcher=" + watcher);

        watchManager.defaultWatcher = watcher;

        ConnectStringParser connectStringParser = new ConnectStringParser(
                connectString);
        HostProvider hostProvider = new StaticHostProvider(
                connectStringParser.getServerAddresses());
        cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
                hostProvider, sessionTimeout, this, watchManager,
                getClientCnxnSocket(), canBeReadOnly);
        cnxn.start();
    }

可以看到最终调用的这个构造方法,那么也就明晰了,zkclient确实简化了连接zk的过程,使用方便,其实到这里才真正的去构造ZooKeeper对象去真正的连接服务器集群。

ZooKeeper创建对话:

接着上面的代码分析:首先,设置了默认的watcher,如果我们通过zkclient访问的,显然此时的watcher就是zkclient对象。之后,通过ConnectStringParser去解析传入的字符串,其实也就是解析配置的集群的地址,将这些地址保存在connectStringParser中,之后将解析的结果再次封装未HostProvider对象:

public StaticHostProvider(Collection<InetSocketAddress> serverAddresses) {
    this.resolver = new Resolver() {
        @Override
        public InetAddress[] getAllByName(String name) throws UnknownHostException {
            return InetAddress.getAllByName(name);
        }
    };
    init(serverAddresses);
}

接着看下init方法:

private void init(Collection<InetSocketAddress> serverAddresses) {
    if (serverAddresses.isEmpty()) {
        throw new IllegalArgumentException(
                "A HostProvider may not be empty!");
    }

    this.serverAddresses.addAll(serverAddresses);
    Collections.shuffle(this.serverAddresses);
}

可以看到,将传入的服务器地址解析结果保存在了HostProvider对象中。之后则创建了客户端网络连接器cnxn,通过其构造参数可以看出,目前缺少socket,而调用getClientCnxnSocket方法得到一个socket,所以顺便一看下其是怎么得到通信使用的socket对象的:

private static ClientCnxnSocket getClientCnxnSocket() throws IOException {
    String clientCnxnSocketName = System
            .getProperty(ZOOKEEPER_CLIENT_CNXN_SOCKET);
    if (clientCnxnSocketName == null) {
        clientCnxnSocketName = ClientCnxnSocketNIO.class.getName();
    }
    try {
        return (ClientCnxnSocket) Class.forName(clientCnxnSocketName).getDeclaredConstructor()
                .newInstance();
    } catch (Exception e) {
        IOException ioe = new IOException("Couldn't instantiate "
                + clientCnxnSocketName);
        ioe.initCause(e);
        throw ioe;
    }
}

这里可以看出,默认状态下选择的是NOI的方式,所以启动的是ClientCnxnSocketNIO对对象,如果配置了其他的客户端网络连接器,则会从属性中得到其对应的CnxnSocketNIO来实现socket的连接。

    public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper,
            ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket, boolean canBeReadOnly)
            throws IOException {
        this(chrootPath, hostProvider, sessionTimeout, zooKeeper, watcher,
             clientCnxnSocket, 0, new byte[16], canBeReadOnly);
    }
//
public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper,
        ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket,
        long sessionId, byte[] sessionPasswd, boolean canBeReadOnly) {
    this.zooKeeper = zooKeeper;
    this.watcher = watcher;
    this.sessionId = sessionId;
    this.sessionPasswd = sessionPasswd;
    this.sessionTimeout = sessionTimeout;
    this.hostProvider = hostProvider;
    this.chrootPath = chrootPath;

    connectTimeout = sessionTimeout / hostProvider.size();
    readTimeout = sessionTimeout * 2 / 3;
    readOnly = canBeReadOnly;

    sendThread = new SendThread(clientCnxnSocket);
    eventThread = new EventThread();

}

其实在这个客户端连接器中,重要的部分就是zookeeper这个对象,此时交给了客户端连接器来管理,同时新创建了SendThread和EventThread。先看下SendThread:

SendThread(ClientCnxnSocket clientCnxnSocket) {
    super(makeThreadName("-SendThread()"));
    state = States.CONNECTING;
    this.clientCnxnSocket = clientCnxnSocket;
    setDaemon(true);
}
///
EventThread() {
            super(makeThreadName("-EventThread"));
            setDaemon(true);
        }

这里可以知道,我们之前在ZooKeeper出使化创建的连接器的socket此时被传递给了ClientCnxn,此时客户端连接器有了发送消息的通道–SendThread,同样的,EventThread线程也存在与客户端连接器中,负责监听各种变化产生的事件,如 None (-1),NodeCreated (1),NodeDeleted (2),NodeDataChanged (3),NodeChildrenChanged (4),DataWatchRemoved (5), ChildWatchRemoved (6);这样可以说socket的通信到我们的客户端连接器已经打通了。此时客户端的状态设置为CONNECTING。最后开始启动:

public void start() {
    sendThread.start();
    eventThread.start();
}

那分别看下run方法做了什么事情吧,首先看下SendThread线程的run方法吧:

@Override
        public void run() {
            // final LinkedBlockingDeque<Packet> outgoingQueue = new LinkedBlockingDeque<Packet>();
            // 客户端对象构造时就会创建一个唯一的outgoingQueue
            clientCnxnSocket.introduce(this, sessionId, outgoingQueue);

            // Time.currentElapsedTime() 开始计时
            clientCnxnSocket.updateNow();
            // 初始化,还是用于计时发送时间使用的:定义如下:
//            void updateLastSendAndHeard() {
//                this.lastSend = now;
//                this.lastHeard = now;
//            }
            clientCnxnSocket.updateLastSendAndHeard();
            int to;
            long lastPingRwServer = Time.currentElapsedTime();
            final int MAX_SEND_PING_INTERVAL = 10000; //10 seconds
            InetSocketAddress serverAddress = null;
            //state表示ZooKeeper客户端状态,CONNECTING, ASSOCIATING, CONNECTED, CONNECTEDREADONLY,
            //CLOSED, AUTH_FAILED, NOT_CONNECTED
            //只要不是CLOSED和AUTH_FAILED,都表示客户端值alive的,就一直进行循环发送数据
            while (state.isAlive()) {
                try {
                    if (!clientCnxnSocket.isConnected()) {
                        // 断开的链接,不必重连
                        // don't re-establish connection if we are closing
                        if (closing) {
                            break;
                        }
                        if (rwServerAddress != null) {
                            serverAddress = rwServerAddress;
                            rwServerAddress = null;
                        } else {
                            // 开始链接时,rwServerAddress为null, 获取hostProvider中的地址
                            serverAddress = hostProvider.next(1000);
                        }
                        // 开始尝试连接
                        startConnect(serverAddress);
                        // 更新下时间,便于统计操作耗时
                        clientCnxnSocket.updateLastSendAndHeard();
                    }

                    // 如果是连接状态
                    if (state.isConnected()) {
                        // determine whether we need to send an AuthFailed event.
                        // 检测客户端是否是开启了sasl认证类型的客户端
                        if (zooKeeperSaslClient != null) {
                            boolean sendAuthEvent = false;
                            // 如果客户端为开启了sasl认证的客户端,则sasl状态设置为初始化INITIAL
                            if (zooKeeperSaslClient.getSaslState() == ZooKeeperSaslClient.SaslState.INITIAL) {
                                try {
                                    zooKeeperSaslClient.initialize(ClientCnxn.this);
                                } catch (SaslException e) {
                                   LOG.error("SASL authentication with Zookeeper Quorum member failed: " + e);
                                    state = States.AUTH_FAILED;
                                    sendAuthEvent = true;
                                }
                            }
                            KeeperState authState = zooKeeperSaslClient.getKeeperState();
                            //无论授权成功还是失败,都需要发送authEvernt
                            if (authState != null) {
                                if (authState == KeeperState.AuthFailed) {
                                    // An authentication error occurred during authentication with the Zookeeper Server.
                                    state = States.AUTH_FAILED;
                                    sendAuthEvent = true;
                                } else {
                                    if (authState == KeeperState.SaslAuthenticated) {
                                        sendAuthEvent = true;
                                    }
                                }
                            }

                            if (sendAuthEvent) {
                                // 向eventThread队列中,本质上接受事件的队列为:
                                //final LinkedBlockingQueue<Object> waitingEvents =
                                //            new LinkedBlockingQueue<Object>();
                                // zookeeper 客户端支持的数据类型:
                                // None (-1),NodeCreated (1),NodeDeleted (2),
                                //NodeDataChanged (3),NodeChildrenChanged (4),
                                //DataWatchRemoved (5), ChildWatchRemoved (6);
                                eventThread.queueEvent(new WatchedEvent(

                                      Watcher.Event.EventType.None,
                                      authState,null));
                                if (state == States.AUTH_FAILED) {
                                  eventThread.queueEventOfDeath();
                                }
                            }
                        }
                        to = readTimeout - clientCnxnSocket.getIdleRecv();
                    } else {
                        to = connectTimeout - clientCnxnSocket.getIdleRecv();
                    }
                    // 如果连接超时了,打印超时信息
                    if (to <= 0) {
                        String warnInfo;
                        warnInfo = "Client session timed out, have not heard from server in "
                            + clientCnxnSocket.getIdleRecv()
                            + "ms"
                            + " for sessionid 0x"
                            + Long.toHexString(sessionId);
                        LOG.warn(warnInfo);
                        throw new SessionTimeoutException(warnInfo);
                    }
                    if (state.isConnected()) {
                    	//1000(1 second) is to prevent race condition missing to send the second ping
                    	//also make sure not to send too many pings when readTimeout is small 
                        int timeToNextPing = readTimeout / 2 - clientCnxnSocket.getIdleSend() - 
                        		((clientCnxnSocket.getIdleSend() > 1000) ? 1000 : 0);
                        //send a ping request either time is due or no packet sent out within MAX_SEND_PING_INTERVAL
                        if (timeToNextPing <= 0 || clientCnxnSocket.getIdleSend() > MAX_SEND_PING_INTERVAL) {
                            // 发送ping消息,检测连接是否还存在
                            sendPing();
                            clientCnxnSocket.updateLastSend();
                        } else {
                            if (timeToNextPing < to) {
                                to = timeToNextPing;
                            }
                        }
                    }

                    // If we are in read-only mode, seek for read/write server
                    // 如果连接了只读模式的服务器,则需要找寻读写权限的服务器
                    if (state == States.CONNECTEDREADONLY) {
                        long now = Time.currentElapsedTime();
                        int idlePingRwServer = (int) (now - lastPingRwServer);
                        if (idlePingRwServer >= pingRwTimeout) {
                            lastPingRwServer = now;
                            idlePingRwServer = 0;
                            pingRwTimeout =
                                Math.min(2*pingRwTimeout, maxPingRwTimeout);
                            // ping 读写权限的服务器
                            pingRwServer();
                        }
                        to = Math.min(to, pingRwTimeout - idlePingRwServer);
                    }

                    // doTransport--> ClientCnxnSocketNIO#doTransport--> doIO
                    clientCnxnSocket.doTransport(to, pendingQueue, ClientCnxn.this);
                    // 错误处理
                } catch (Throwable e) {
                    if (closing) {
                        if (LOG.isDebugEnabled()) {
                            // closing so this is expected
                            LOG.debug("An exception was thrown while closing send thread for session 0x"
                                    + Long.toHexString(getSessionId())
                                    + " : " + e.getMessage());
                        }
                        break;
                    } else {
                        // this is ugly, you have a better way speak up
                        if (e instanceof SessionExpiredException) {
                            LOG.info(e.getMessage() + ", closing socket connection");
                        } else if (e instanceof SessionTimeoutException) {
                            LOG.info(e.getMessage() + RETRY_CONN_MSG);
                        } else if (e instanceof EndOfStreamException) {
                            LOG.info(e.getMessage() + RETRY_CONN_MSG);
                        } else if (e instanceof RWServerFoundException) {
                            LOG.info(e.getMessage());
                        } else if (e instanceof SocketException) {
                            LOG.info("Socket error occurred: {}: {}", serverAddress, e.getMessage());
                        } else {
                            LOG.warn("Session 0x{} for server {}, unexpected error{}",
                                            Long.toHexString(getSessionId()),
                                            serverAddress,
                                            RETRY_CONN_MSG,
                                            e);
                        }
                        // At this point, there might still be new packets appended to outgoingQueue.
                        // they will be handled in next connection or cleared up if closed.
                        cleanAndNotifyState();
                    }
                }
            }
            // 关闭socket,并通过事件通知客户端
            synchronized (state) {
                // When it comes to this point, it guarantees that later queued
                // packet to outgoingQueue will be notified of death.
                cleanup();
            }
            clientCnxnSocket.close();
            if (state.isAlive()) {
                eventThread.queueEvent(new WatchedEvent(Event.EventType.None,
                        Event.KeeperState.Disconnected, null));
            }
            eventThread.queueEvent(new WatchedEvent(Event.EventType.None,
                        Event.KeeperState.Closed, null));
            ZooTrace.logTraceMessage(LOG, ZooTrace.getTextTraceLevel(),
                    "SendThread exited loop for session: 0x"
                           + Long.toHexString(getSessionId()));
        }

上述的过程概括起来说就是:首先创建了sendThread和EventThread,然后启动了这两个守护线程,其中sendThread不断的向outgoingQueue中放入要发送的Packet,eventThread则是不断将watcher和event的pair放入eventQueue中。

其次,获取HostProvider的的地址,其实这个类或保留集群的信息,所以在上面的run方法中,获取地址后则会调用startConnection方法其开启连接,此时这里应用了委派模式,这里委派给了默认的zookeeper的NIO实现的客户端连接器。

private void startConnect(InetSocketAddress addr) throws IOException {
            // initializing it for new connection
            saslLoginFailed = false;
            // 是否第一次连接,默认为true
            if(!isFirstConnect){
                try {
                    Thread.sleep(r.nextInt(1000));
                } catch (InterruptedException e) {
                    LOG.warn("Unexpected exception", e);
                }
            }
            // 如果不是第一次连接,可能当前客户端的状态为其他状态,如CLOSE等,此时
            // 如果继续执行启动逻辑,那需要先改变当前客户端的状态
            // 更改当前该客户端的状态
            state = States.CONNECTING;

            String hostPort = addr.getHostString() + ":" + addr.getPort();
            MDC.put("myid", hostPort);
            setName(getName().replaceAll("\\(.*\\)", "(" + hostPort + ")"));
            if (clientConfig.isSaslClientEnabled()) {
                try {
                    if (zooKeeperSaslClient != null) {
                        zooKeeperSaslClient.shutdown();
                    }
                    zooKeeperSaslClient = new ZooKeeperSaslClient(SaslServerPrincipal.getServerPrincipal(addr, clientConfig),
                        clientConfig);
                } catch (LoginException e) {
                    // An authentication error occurred when the SASL client tried to initialize:
                    // for Kerberos this means that the client failed to authenticate with the KDC.
                    // This is different from an authentication error that occurs during communication
                    // with the Zookeeper server, which is handled below.
                    LOG.warn("SASL configuration failed: " + e + " Will continue connection to Zookeeper server without "
                      + "SASL authentication, if Zookeeper server allows it.");
                    eventThread.queueEvent(new WatchedEvent(
                      Watcher.Event.EventType.None,
                      Watcher.Event.KeeperState.AuthFailed, null));
                    saslLoginFailed = true;
                }
            }
            // 打印连接状态
            logStartConnect(addr);

            // 连接开始,打开socket
            clientCnxnSocket.connect(addr);
        }

最后基于初始化得到的客户端连接器的socket建立连接:

void connect(InetSocketAddress addr) throws IOException {
        SocketChannel sock = createSock();
        try {
            // 注册和连接
            registerAndConnect(sock, addr);
        } catch (UnresolvedAddressException | UnsupportedAddressTypeException | SecurityException | IOException e) {
            LOG.error("Unable to open socket to {}" + addr);
            sock.close();
            throw e;
        }
        initialized = false;

        /*
         * Reset incomingBuffer
         */
        lenBuffer.clear();
        incomingBuffer = lenBuffer;
    }

其真正的执行方法是registerAndConnect方法,但是在其方法的逻辑中可以知道,再注册后判断了是否之已经连接了,如果连接则调用primeConnection方法开始初始化会话的session,watchers等,可以这么理解,前者的socket连接只是打通了通信通道,但是真正的会话即两个进程之间的信息通信还没建立,所以下面的方法就完成了会话的建立,正如前面所说,连接请求会被放入到outgoingQueue队列中,等待发送。

void primeConnection() throws IOException {
            // 连接端口
            LOG.info("Socket connection established, initiating session, client: {}, server: {}",
                    clientCnxnSocket.getLocalSocketAddress(),
                    clientCnxnSocket.getRemoteSocketAddress());
            isFirstConnect = false;
            //创建sessionid:之前时候有连接过的sessionid,如果有则使用上次的sessionid没有这设置为0
            // 所以在第一次建立连接的时候,sessionid为0
            long sessId = (seenRwServerBefore) ? sessionId : 0;

            // 创建连接请求
            ConnectRequest conReq = new ConnectRequest(0, lastZxid,
                    sessionTimeout, sessId, sessionPasswd);
            // We add backwards since we are pushing into the front
            // Only send if there's a pending watch
            // TODO: here we have the only remaining use of zooKeeper in
            // this class. It's to be eliminated!
            if (!clientConfig.getBoolean(ZKClientConfig.DISABLE_AUTO_WATCH_RESET)) {
                ......
                        }

                        SetWatches sw = new SetWatches(setWatchesLastZxid,
                                                       dataWatchesBatch,
                                                       existWatchesBatch,
                                                       childWatchesBatch);
                        RequestHeader header = new RequestHeader(-8, OpCode.setWatches);
                        Packet packet = new Packet(header, new ReplyHeader(), sw, null, null);
    
                        outgoingQueue.addFirst(packet);
                    }
                }
            }

            // 如果设置了授权信息,则还需在请求的Packet中加入授权信息
            for (AuthData id : authInfo) {
                // requestHeader只有xid和type,type类型保存在OpCode中
                outgoingQueue.addFirst(new Packet(new RequestHeader(-4,
                        OpCode.auth), null, new AuthPacket(0, id.scheme,
                        id.data), null, null));
            }
            // 添加到队列的头部,如果有授权信息的话,上述的授权信息的请求则跟随在连接请求后一个等待发出
            outgoingQueue.addFirst(new Packet(null, null, conReq,
                    null, null, readOnly));

            // 读写权限设置
            clientCnxnSocket.connectionPrimed();
            if (LOG.isDebugEnabled()) {
                LOG.debug("Session establishment request sent on "
                        + clientCnxnSocket.getRemoteSocketAddress());
            }
        }

服务器端响应:

前面的分析已经可以实现将Packet发送给服务器端了,但是服务器端得到消息Packet处理之后,客户端如何去响应呢?首先,客户端首先对自己的状态作出判断,判断自己时候已经和初始化,如果没有完成初始化则默认为是会话请求,这时候会直接交给readConnectResult方法去处理:

void readConnectResult() throws IOException {
        if (LOG.isTraceEnabled()) {
            StringBuilder buf = new StringBuilder("0x[");
            for (byte b : incomingBuffer.array()) {
                buf.append(Integer.toHexString(b) + ",");
            }
            buf.append("]");
            LOG.trace("readConnectResult " + incomingBuffer.remaining() + " "
                    + buf.toString());
        }
        ByteBufferInputStream bbis = new ByteBufferInputStream(incomingBuffer);
        BinaryInputArchive bbia = BinaryInputArchive.getArchive(bbis);
        ConnectResponse conRsp = new ConnectResponse();
        // 反序列化
        conRsp.deserialize(bbia, "connect");

        // read "is read-only" flag
        boolean isRO = false;
        try {
            isRO = bbia.readBool("readOnly");
        } catch (IOException e) {
            // this is ok -- just a packet from an old server which
            // doesn't contain readOnly field
            LOG.warn("Connected to an old server; r-o mode will be unavailable");
        }

        this.sessionId = conRsp.getSessionId();
        sendThread.onConnected(conRsp.getTimeOut(), this.sessionId,
                conRsp.getPasswd(), isRO);
    }

如果是普通请求则交给readResponse方法去解析Response对象。然后开始处理Response,ClientCnxnSocket会对接收到的服务器响应进行反序列化然后得到ConnectResponse对象,并获取对话的Id。

void readResponse(ByteBuffer incomingBuffer) throws IOException {
    ByteBufferInputStream bbis = new ByteBufferInputStream(
            incomingBuffer);
    BinaryInputArchive bbia = BinaryInputArchive.getArchive(bbis);
    ReplyHeader replyHdr = new ReplyHeader();

    replyHdr.deserialize(bbia, "header");
    if (replyHdr.getXid() == -2) {
        // -2 is the xid for pings
        if (LOG.isDebugEnabled()) {
            LOG.debug("Got ping response for sessionid: 0x"
                    + Long.toHexString(sessionId)
                    + " after "
                    + ((System.nanoTime() - lastPingSentNs) / 1000000)
                    + "ms");
        }
        return;
    }
    if (replyHdr.getXid() == -4) {
        // -4 is the xid for AuthPacket               
        if(replyHdr.getErr() == KeeperException.Code.AUTHFAILED.intValue()) {
            state = States.AUTH_FAILED;                    
            eventThread.queueEvent( new WatchedEvent(Watcher.Event.EventType.None, 
                    Watcher.Event.KeeperState.AuthFailed, null) );                                  
        }
        if (LOG.isDebugEnabled()) {
            LOG.debug("Got auth sessionid:0x"
                    + Long.toHexString(sessionId));
        }
        return;
    }
    if (replyHdr.getXid() == -1) {
        // -1 means notification
        if (LOG.isDebugEnabled()) {
            LOG.debug("Got notification sessionid:0x"
                + Long.toHexString(sessionId));
        }
        WatcherEvent event = new WatcherEvent();
        event.deserialize(bbia, "response");

        // convert from a server path to a client path
        if (chrootPath != null) {
            String serverPath = event.getPath();
            if(serverPath.compareTo(chrootPath)==0)
                event.setPath("/");
            else if (serverPath.length() > chrootPath.length())
                event.setPath(serverPath.substring(chrootPath.length()));
            else {
               LOG.warn("Got server path " + event.getPath()
                     + " which is too short for chroot path "
                     + chrootPath);
            }
        }

        WatchedEvent we = new WatchedEvent(event);
        if (LOG.isDebugEnabled()) {
            LOG.debug("Got " + we + " for sessionid 0x"
                    + Long.toHexString(sessionId));
        }

        eventThread.queueEvent( we );
        return;
    }

    // If SASL authentication is currently in progress, construct and
    // send a response packet immediately, rather than queuing a
    // response as with other packets.
    if (clientTunneledAuthenticationInProgress()) {
        GetSASLRequest request = new GetSASLRequest();
        request.deserialize(bbia,"token");
        zooKeeperSaslClient.respondToServer(request.getToken(),
          ClientCnxn.this);
        return;
    }

    Packet packet;
    synchronized (pendingQueue) {
        if (pendingQueue.size() == 0) {
            throw new IOException("Nothing in the queue, but got "
                    + replyHdr.getXid());
        }
        packet = pendingQueue.remove();
    }
    /*
     * Since requests are processed in order, we better get a response
     * to the first request!
     */
    try {
        if (packet.requestHeader.getXid() != replyHdr.getXid()) {
            packet.replyHeader.setErr(
                    KeeperException.Code.CONNECTIONLOSS.intValue());
            throw new IOException("Xid out of order. Got Xid "
                    + replyHdr.getXid() + " with err " +
                    + replyHdr.getErr() +
                    " expected Xid "
                    + packet.requestHeader.getXid()
                    + " for a packet with details: "
                    + packet );
        }

        packet.replyHeader.setXid(replyHdr.getXid());
        packet.replyHeader.setErr(replyHdr.getErr());
        packet.replyHeader.setZxid(replyHdr.getZxid());
        if (replyHdr.getZxid() > 0) {
            lastZxid = replyHdr.getZxid();
        }
        if (packet.response != null && replyHdr.getErr() == 0) {
            packet.response.deserialize(bbia, "response");
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Reading reply sessionid:0x"
                    + Long.toHexString(sessionId) + ", packet:: " + packet);
        }
    } finally {
        finishPacket(packet);
    }
}

根据对话的id去连接对话,如果此时对话连接成功需要做两件事:

  1. 通知SendThread线程进一步对会话参数进行设置,例如readTimeOut和connectTimeout,并再次更新客户端的状态。
  2. 需要通知地址管理器HostProvider已经成功连接的地址。

之后则会创建事件SyncConnected-None,用来通知上层应用会话创建成功,并将这个事件传递给EventThread线程。同样的为了将这个事件放入waitingEvent中,需要查询监听这个事件的watcher,然后将其组装成一个pair放入waitingEvent队列中,finishpacket会完成这些操作。之后,EventThread不断的从上述队列中取出待处理的watcher对象,然后直接调用process方法,达到触发watcher的目的。到这里客户端连接以及创建会话的过程全部完成了。

@Override
public void process(WatchedEvent event) {
    LOG.debug("Received event: " + event);
    _zookeeperEventThread = Thread.currentThread();

    boolean stateChanged = event.getPath() == null;
    boolean znodeChanged = event.getPath() != null;
    boolean dataChanged = event.getType() == EventType.NodeDataChanged || event.getType() == EventType.NodeDeleted || event.getType() == EventType.NodeCreated
            || event.getType() == EventType.NodeChildrenChanged;

    getEventLock().lock();
    try {

        // We might have to install child change event listener if a new node was created
        if (getShutdownTrigger()) {
            LOG.debug("ignoring event '{" + event.getType() + " | " + event.getPath() + "}' since shutdown triggered");
            return;
        }
        if (stateChanged) {
            processStateChanged(event);
        }
        if (dataChanged) {
            processDataOrChildChange(event);
        }
    } finally {
        if (stateChanged) {
            getEventLock().getStateChangedCondition().signalAll();

            // If the session expired we have to signal all conditions, because watches might have been removed and
            // there is no guarantee that those
            // conditions will be signaled at all after an Expired event
            // TODO PVo write a test for this
            if (event.getState() == KeeperState.Expired) {
                getEventLock().getZNodeEventCondition().signalAll();
                getEventLock().getDataChangedCondition().signalAll();
                // We also have to notify all listeners that something might have changed
                fireAllEvents();
            }
        }
        if (znodeChanged) {
            getEventLock().getZNodeEventCondition().signalAll();
        }
        if (dataChanged) {
            getEventLock().getDataChangedCondition().signalAll();
        }
        getEventLock().unlock();
        LOG.debug("Leaving process event");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值