一般情況下,zk用戶端與服務端建立串連後,會在2/3*sessionTime*1/2的時候發送一個心跳訊息到服務端,保持會話更新。但是可能在某個時候網路中斷可能導致用戶端無法串連上服務端,此時用戶端會不停的依次重試各個伺服器,一直到串連上某個伺服器為止。如果在未串連上這段時間內,服務端session已經到期,(參見SessionTracker的實現SessionTrackerImpl),它是依靠一個線程對到期的session進行清理,並關閉掉串連。
synchronized public void run() { try { while (running) { currentTime =System.currentTimeMillis(); if (nextExpirationTime >currentTime) { this.wait(nextExpirationTime - currentTime); continue; } SessionSet set; set =sessionSets.remove(nextExpirationTime); if (set != null) { for (SessionImpl s :set.sessions) { sessionsById.remove(s.sessionId); expirer.expire(s); } } nextExpirationTime +=expirationInterval; } } catch (InterruptedException e) { LOG.error("Unexpected interruption", e); } LOG.info("SessionTrackerImpl exited loop!");}
用戶端在session到期後這段時間後,串連上某個伺服器,並發送ConnectRequest,附帶串連中斷前的sessionid,以及lastZxid 等等訊息,請求重新與伺服器建立串連。伺服器發現是一個ConnectRequest請求,於是readConnectRequest,如果sessionid不為0,則表示是需要恢複原來這個串連。
if (connReq.getSessionId() != 0) { long clientSessionId = connReq.getSessionId(); LOG.info("Client attempting to renew session 0x" +Long.toHexString(clientSessionId) + " at " +sock.socket().getRemoteSocketAddress()); factory.closeSessionWithoutWakeup(clientSessionId); setSessionId(clientSessionId); zk.reopenSession(this, sessionId, passwd, sessionTimeout); } else { LOG.info("Client attempting to establish new session at " +sock.socket().getRemoteSocketAddress()); zk.createSession(this, passwd, sessionTimeout); }
reopenSession恢複時,會對session做一些校正
public void reopenSession(ServerCnxn cnxn, long sessionId, byte[]passwd, int sessionTimeout) throws IOException, InterruptedException { if (!checkPasswd(sessionId, passwd)) { cnxn.finishSessionInit(false); } else { revalidateSession(cnxn, sessionId, sessionTimeout); }} protected void revalidateSession(ServerCnxncnxn, long sessionId, int sessionTimeout) throwsIOException, InterruptedException { boolean rc =sessionTracker.touchSession(sessionId, sessionTimeout); if (LOG.isTraceEnabled()) { ZooTrace.logTraceMessage(LOG,ZooTrace.SESSION_TRACE_MASK, "Session 0x" + Long.toHexString(sessionId) + " is valid: " +rc); } cnxn.finishSessionInit(rc); }
由於sessionid已經到期被刪除,所以touchSession時由於找不到sessionid,這裡rc會返回false, 根據rc的值,finishSessionInit會確定發送什麼樣的ConnectResponse給用戶端。
public voidfinishSessionInit(boolean valid)ConnectResponsersp = new ConnectResponse(0, valid ? sessionTimeout : 0, valid ? sessionId : 0,// send 0 if session is no // longer valid valid ?zk.generatePasswd(sessionId) : new byte[16]);
如果rc=false,同時服務端會發送關閉串連的指令。
用戶端收到響應後,發現sessionTimeout時間小於或者等於0,則表示session到期。觸發expired事件,並拋出異常。
negotiatedSessionTimeout =conRsp.getTimeOut(); if (negotiatedSessionTimeout <=0) { zooKeeper.state =States.CLOSED; eventThread.queueEvent(newWatchedEvent( Watcher.Event.EventType.None, Watcher.Event.KeeperState.Expired, null)); eventThread.queueEventOfDeath(); throw newSessionExpiredException( "Unable toreconnect to ZooKeeper service, session 0x" +Long.toHexString(sessionId) + " has expired"); }
整個用戶端執行個體退出,這個執行個體不能再次重用了,如果還需要串連伺服器,則需要重新建立新的zookeeper執行個體。