Session
1.sessionId <機器的SID,當前時間>生成一個sessionId,這是全局唯一的。
2.TimeOut 會話的超時時間,注意,這個值和客戶端ZooKeeper對象指定的超時不一定相同
3.TickTime
4.isClosing 當SessionTracker檢測到會話已經失效了,就會將這個會話的isClosing標記為true,之后這個會話將不在處理任何新的請求
SessionTracker
SessionTracker負責管理Session的整個會話生命周期。
SessionTracker的創建
如何管理多個session?
ExpireTime1 [session,session,session]
ExpireTime2 [session,session,session]
新創建的Session,其expireTime=curTime+SessionTimeout
leader服務器每隔expirationInterval時間就進行會話超時檢查。
客戶端向服務器發送請求,服務器就進行一次會話激活
客戶端如果在sessionTimeout/3時間內,沒有向服務器發送過任何請求,就主動發送一個PING請求,服務器收到
該請求后激活會話。
創建SessionTracker
在org.apache.zookeeper.server.ZooKeeperServer的startup方法中,會創建SessionTracker,然后啟動它。
public void startup() { if (sessionTracker == null) { createSessionTracker(); }
//SessionTrackerImpl繼承了Thread,因此實際上他也是個線程,這里就是調用start方法執行線程。 startSessionTracker(); setupRequestProcessors(); registerJMX(); synchronized (this) { running = true; notifyAll(); } }
protected void createSessionTracker() { sessionTracker = new SessionTrackerImpl(this, zkDb.getSessionWithTimeOuts(), tickTime, 1); }
激活會話
synchronized public boolean touchSession(long sessionId, int timeout) { if (LOG.isTraceEnabled()) { ZooTrace.logTraceMessage(LOG, ZooTrace.CLIENT_PING_TRACE_MASK, "SessionTrackerImpl --- Touch session: 0x" + Long.toHexString(sessionId) + " with timeout " + timeout); }
//獲取Session SessionImpl s = sessionsById.get(sessionId); // Return false, if the session doesn't exists or marked as closing if (s == null || s.isClosing()) { return false; }
//計算超時時間點 long expireTime = roundToInterval(System.currentTimeMillis() + timeout);
//表明沒有超時
if (s.tickTime >= expireTime) { // Nothing needs to be done return true; }
//將Session從舊的set移動到新的set中
//首先取出tickTime對應的set,然后從set中移除掉Session SessionSet set = sessionSets.get(s.tickTime); if (set != null) { set.sessions.remove(s); }
//遷移到新的set中 s.tickTime = expireTime; set = sessionSets.get(s.tickTime); if (set == null) { set = new SessionSet(); sessionSets.put(expireTime, set); } set.sessions.add(s); return true; }
會話超時檢查
@Override synchronized public void run() { try { while (running) { currentTime = System.currentTimeMillis();
//如果還未超時 if (nextExpirationTime > currentTime) { this.wait(nextExpirationTime - currentTime); continue; }
SessionSet set;
//已經超時了,很粗暴的直接從sessionSets中移走過期的session,這里做的真是太棒了 set = sessionSets.remove(nextExpirationTime); if (set != null) {
//將每一個過期的session標記為close。 for (SessionImpl s : set.sessions) { setSessionClosing(s.sessionId);
//注意這里哦,很重要的。這里就是調用ZooKeeperServer.expire來關閉session expirer.expire(s); } }
//計算新的過期時間 nextExpirationTime += expirationInterval; } } catch (InterruptedException e) { LOG.error("Unexpected interruption", e); } LOG.info("SessionTrackerImpl exited loop!"); }
public void expire(Session session) { long sessionId = session.getSessionId(); LOG.info("Expiring session 0x" + Long.toHexString(sessionId) + ", timeout of " + session.getTimeout() + "ms exceeded");
//因為會話已經超時了,所以關閉它 close(sessionId); }
private void close(long sessionId) { submitRequest(null, sessionId, OpCode.closeSession, 0, null, null); }
清理會話
找出這個session創建的所有臨時節點,就是去ZooKeeper內存數據庫中,根據sesionID來獲取這個session創建的所有的臨時節點信息,對每一個節點創建節點刪除請求,從內存數據庫中移除該會話的臨時節點。
將session從SessionTrackerImpl中移除
關閉ServerCnxn。
KeeperException.ConnectionLoseException
客戶端捕獲到這種異常,只需要簡單的等待org.apache.zookeeper.ZooKeeper自動重新連接上一個ZooKeeper機器即可,當重新連上了之后,客戶端會受到Sync_Connected通知。
SESSION_EXPIRED
由於客戶端在會話超時時間內沒有向服務器發送PING,服務器認為會話已經過期,然后就會將其標記為失效了。如果之后客戶端重新連接上了某一個機器,那么就會出現會話過期異常了。在這種情況下,只能創建一個新的ZooKeeper對象,建立一個新的會話了。
SessionMovedException