【詳解】Tomcat是如何監控並刪除超時Session的?

前言

偶然發現Tomcat會話時間的半小時,並非說「會話建立後只有半小時的有效使用時間」,而是說「會話空閒半小時後會被刪除」。索性就翻了一下源碼。作了一番整理。session

注:空閒時間,指的是同一個會話兩次請求之間的間隔時間app

Session相關類圖

  • HttpSession就是你們Servlet層能夠直接使用的Session.
  • Session是Tomcat內部使用的接口,能夠作一些內部調用
  • StandardSession是標準的HttpSession實現,同時它也實現了Session接口,用於Tomcat內部管理
  • StandardSessionFacade,類名已經指明它就是一個「門面類」,它內部會引用一個StandardSession的對象,但對外只提供HttpSession規定的方法。

Manager相關類圖

StandardManager與PersitentManager都是Manager的實現,可是它們在存儲Session對象的方式上有所不一樣。ide

StandarManagerthis

1.Tomcat運行時,把Session存儲在內存中spa

2.Tomcat關閉時(注意是正常的關閉操做,如執行stop.sh,而非忽然崩潰),會把Session寫入到磁盤中,等到Tomcat重啓後再把Session加載進來線程

PersistentManagerdebug

1.老是把Session存儲在磁盤中。code

Manager與Context的關係

在Tomcat中,一個Context就是部署到Tomcat中的一個應用(Webapp)。每個Context都有一個單獨的Manager對象來管理這個應用的會話信息。對象

Manager如何存儲Session

Manager對象會使用一個Map來存儲Session對象blog

  • Key  => SessionId
  • Value  => Session Object
    /**
     * The set of currently active Sessions for this Manager, keyed by
     * session identifier.
     */
    protected Map<String, Session> sessions = new ConcurrentHashMap<>();

當一個請求到達Context的時候,若是它帶有JSESSIONID的Cookie,Manager就能依此找到關聯的Session對象,放入到Request對象中。

Manager的按期檢查

Manager接口有一個backgroundProcess()方法,顧名思義就是後臺處理。

    /**
     * This method will be invoked by the context/container on a periodic
     * basis and allows the manager to implement
     * a method that executes periodic tasks, such as expiring sessions etc.
     */
    public void backgroundProcess();

注:Container接口也有這個方法,這個方法通常在容器啓動(start)的時候,開啓一個額外的線程來循環執行這個backgroundProcess方法。Context的這個方法啓動後,會執行Loader和Manager的backgroundProcess方法。

咱們來看看這個方法都作了些什麼?

/**
 * {@inheritDoc}
 * <p>
 * Direct call to {@link #processExpires()}
 */
@Override
public void backgroundProcess() {
    count = (count + 1) % processExpiresFrequency;
    if (count == 0)  //若是達到檢查頻率則開始檢查
        processExpires();
}

/**
 * Invalidate all sessions that have expired.
 */
public void processExpires() {

    long timeNow = System.currentTimeMillis();
    Session sessions[] = findSessions(); //獲取全部session對象
    int expireHere = 0 ; //過時session的數量,不要被這個變量名騙了

    if(log.isDebugEnabled())
        log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
    for (int i = 0; i < sessions.length; i++) {
        if (sessions[i]!=null && !sessions[i].isValid()) {
            expireHere++;
        }
    }
    long timeEnd = System.currentTimeMillis();
    if(log.isDebugEnabled()) //打印記錄
         log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
    processingTime += ( timeEnd - timeNow );

}

不少人看到這裏,可能會有跟我同樣的疑惑,即這裏面根本就沒有使Session過時失效的操做,好像只作了狀態檢查。不事後來看到了Session的isValid方法的實現就都明白了。

/**
 * Return the <code>isValid</code> flag for this session.
 */
@Override
public boolean isValid() {

    if (!this.isValid) {
        return false;
    }

    if (this.expiring) {
        return true;
    }

    if (ACTIVITY_CHECK && accessCount.get() > 0) {
        return true;
    }

    //關鍵所在
    //若是有設置最大空閒時間
    //就獲取此Session的空閒時間進行判斷
    //若是已超時,則執行expire操做
    if (maxInactiveInterval > 0) { 
        int timeIdle = (int) (getIdleTimeInternal() / 1000L);
        if (timeIdle >= maxInactiveInterval) {
            expire(true);
        }
    }

    return this.isValid;
}

 那這個空閒時間怎麼獲得?

 其實就是記錄上一次請求的時間,而後與本次請求的時間進行比對,獲得時間差。

總結

總的來講,就是用一個線程來輪詢會話狀態,若是某個會話的空閒時間超過設定的最大值,則將該會話銷燬。

相關文章
相關標籤/搜索