zookeeper的分析源码方面看timeout

来源:互联网 发布:spss数据分析满意度 编辑:程序博客网 时间:2024/04/30 15:18

    这两天看了一下zookeeper的相关的源码,版本基于3.4.5,代码结构还是比较清晰的;
这里重点分析一下zookeeper client和server端之间的通信以及相关的异常处理机制。  
1、客户端
  客户端几个主要的类为Zookeeper、ClientCnxn、SendThread、ClientCnxnSocketNIO。
客户端通过Zookeeper相关的API和server进行同步或者异步通信,包括create、delete、exists、getChildren、getData、setData、sync等,
在实现中zookeeper可以调用ClientCnxn.submit同步等待返回结果或者调用ClientCnxn.queuePacket把消息放到outgoing队列中异步发送并提供callback进行异步回调;
ClientCnxn负责管理client端的socket IO,维护zookeeper server列表,透明的在server之间连接切换;ClientCnxn有一个SendThread后台线程,
负责对outgoing queue中的消息发送和server端返回消息接收;ClientCnxnSocketNIO代表一个server端的NIO socket 长连接连接,负责和server端底层进行通信。
SendThread服务于queueQueue 进行package的发送接收,并启动后台线程心跳建立连接;pendingQueue用于存放已经发送出去,未回复的包,在收到回复后,
从pendingQueue队列中删除;SendThread从socket读取服务端返回的结果后,通过readResponse对消息进行处理,根据返回的replyHeader中xid进行相应的后续处理,
当xid=-2时候,表示这是一个ping的返回,当xid=-4,代表这是一个auth 的返回,当xid=-1时候,代表这是一个zookeeper节点变化导致的通知watch执行的消息返回,
返回的消息用watchEvent包装,发送到EventThread中的waitingEvents队列中,EventThread后台线程从队列中拉取消息执行watcher中的process逻辑。

   客户端和服务端之间的NIO socket连接模型这里就不多说了,以前的NIO 系列blog中对这些有比较多的阐述,
这里说一下客户端和服务端的长连接的session失效、超时、连接断掉的问题,客户端又是如何处理的;
ClientCnxn包括这几个变量,
private int connectTimeout;
private volatile int negotiatedSessionTimeout;
private int readTimeout;
private final int sessionTimeout;在client连接到server后,server返回给client确认信息(包括服务器返回给客户端的真实的timeout时间--negotiatedSessionTimeout),
client read结果(SendThread.run-->ClientCnxnSocketNIO.doTransport-->doIO),设置相关的timeout参数。
在这里初始化,
   sendThread.onConnected(conRsp.getTimeOut(), this.sessionId,
                conRsp.getPasswd(), isRO);
比如,
           readTimeout = negotiatedSessionTimeout * 2 / 3;
            connectTimeout = negotiatedSessionTimeout / hostProvider.size();
            hostProvider.onConnected();
            sessionId = _sessionId;
            sessionPasswd = _sessionPasswd;
这些timeout代表client端在超时的这段时间里,没有读到从server端返回的消息(比如发送ping 数据到server,server给返回信息)
在ClientCnxnSocketNIO.doTransport中,进行select(waitTimeOut)操作,先updateNow
如果有socket可以读数据,则读数据后,updateLastRec,没数据可读的话(不更新updateLastRec),下次SendThread.run循环,就可能会出现读超时。
在SendThread.run循环中判断是否超时
                           to = readTimeout - clientCnxnSocket.getIdleRecv();//(now - lastHeard);
                    } else {
                        to = connectTimeout - clientCnxnSocket.getIdleRecv();
                    }
                    if (to <= 0) {
//超时
                        throw new SessionTimeoutException(
                                "Client session timed out, have not heard from server in "
                                        + clientCnxnSocket.getIdleRecv() + "ms"
                                        + " for sessionid 0x"
                                        + Long.toHexString(sessionId));
                    }
client不断的发送sendPing()心跳,以维持在server端的session有效。
   if (state.isConnected()) {
                        int timeToNextPing = readTimeout / 2
                                - clientCnxnSocket.getIdleSend();
                        if (timeToNextPing <= 0) {
                            sendPing();
                            clientCnxnSocket.updateLastSend();
                        } else {
                            if (timeToNextPing < to) {
                                to = timeToNextPing;
                            }
                        }
                    }
在SendThread.run循环中,client发送sendPing()心跳,以维持在server端的session有效。
   if (state.isConnected()) {
                        int timeToNextPing = readTimeout / 2
                                - clientCnxnSocket.getIdleSend();
                        if (timeToNextPing <= 0) {
                            sendPing();
                            clientCnxnSocket.updateLastSend();
                        } else {
                            if (timeToNextPing < to) {
                                to = timeToNextPing;
                            }
                        }
                    }
如果客户端访问服务端而服务端认为客户端已经超时了或者服务端宕机时,客户端会调用SendThread.cleanup操作,销毁sockect,
把sockKey设置为null,这样在SendThread.run的while循环中会判断isConn,进而重新连接一个server.
参见SendThread.run中的异常处理部分
      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());
                        cleanup();//在cleanup中销毁sockect,把sockKey设置为null,这样在SendThread.run的while循环中会判断isConn,进而重新连接一个server.
         } 
ClientWatchManager管理客户端所有得watcher,并进行分类,dataWatches、existWatches、childWatches。

 


2、服务端
NIOServerCnxnFactory,服务端进行NIO操作(Select,Channel)的类,每建立一个客户端连接,就生成一个NIOServerCnxn,
这里说一下session的超时时间设置,
socket channel收到消息建立连接请求的时候,NIOServerCnxn.readPayload-->NIOServerCnxnreadConnectRequest()--->zkServer.processConnectRequest(this, incomingBuffer);
ZookeeperServer,可见如果客户端发来的sessionTimeout超过min-max这个范围,server会自动截取为min或max
   minSessionTimeout 单位毫秒。默认2倍tickTime
   maxSessionTimeout 单位毫秒。默认20倍tickTime
  (tickTime也是一个配置项。是Server内部控制时间逻辑的最小时间单位)

public void processConnectRequest(ServerCnxn cnxn, ByteBuffer incomingBuffer)
        int minSessionTimeout = getMinSessionTimeout();
        if (sessionTimeout < minSessionTimeout) {
            sessionTimeout = minSessionTimeout;
        }
        int maxSessionTimeout = getMaxSessionTimeout();
        if (sessionTimeout > maxSessionTimeout) {
            sessionTimeout = maxSessionTimeout;
        }
        cnxn.setSessionTimeout(sessionTimeout);
SessionTracker保存客户端session的列表,判断session是否过期,一种是在处理服务端请求的packg,一种是LearnerHandler.run后台线程运行,
会调用SessionTracker.touchSession(ServerCnxn cnxn)进行判断是否超时间
   void touch(ServerCnxn cnxn) throws MissingSessionException {
        if (cnxn == null) {
            return;
        }
        long id = cnxn.getSessionId();
        int to = cnxn.getSessionTimeout();
        if (!sessionTracker.touchSession(id, to)) {//无论client是重连,还是其他,超过session timeout时候没连上,就表示session过期了
            throw new MissingSessionException(
                    "No session with sessionid 0x" + Long.toHexString(id)
                    + " exists, probably expired and removed");
        }
    }

如果session过期就删除session信息,包括这个会话创建的临时节点和注册的Watcher

 

原创粉丝点击