2011-04-13 35 views
4

我有一個任務,向站點管理員顯示用戶名列表以及每個用戶當前使用多少個Tomcat會話(以及一些其他支持相關信息)。Tomcat中的Http Sessions生命週期

我保持身份驗證用戶作爲應用程序上下文屬性如下(省略不必要的細節)。

Hashtable<String, UserInfo> logins //maps login to UserInfo

其中的UserInfo被定義爲

class UserInfo implements Serializable { 
String login; 
private transient Map<HttpSession, String> sessions = 
     Collections.synchronizedMap(
      new WeakHashMap<HttpSession, String>() //maps session to sessionId 
    ); 
... 
} 

每個成功登錄存儲會話到這個sessions地圖。 我的HttpSessionsListener實現sessionDestroyed()從此映射中刪除銷燬的會話,並且如果sessions.size()== 0從logins中刪除UserInfo。

我有時會有0個會話顯示給某些用戶。同行評審和單元測試表明代碼是正確的。所有會話都是可序列化的。

Tomcat是否有可能將會話從內存卸載到硬盤驅動器,例如,當有一段時間不活動(會話超時設置爲40分鐘)?有沒有其他的場景會議從GC的角度來看是「丟失」的,但是HttpSessionsListener.sessionDestroyed()沒有被調用?

J2SE 6,Tomcat 6或7 standalone,行爲在任何操作系統上都是一致的。

回答

1

您是否發現重新啓動Tomcat後出現問題? Tomcat會在成功關閉期間將活動會話序列化到磁盤,然後在啓動時反序列化 - 我不確定這是否會導致對HttpSessionListener.sessionCreated()的調用,因爲會話不是嚴格創建的,只是反序列化不正確(!),但可以相當容易地測試)。

你是否還將結果與Tomcat經理會話統計進行了比較?它會跟蹤活動會話的數量,並且應該與您的數字保持一致,否則,您知道您的代碼是錯誤的。

另外,可能與您的問題無關,但是您有使用Hashtable和WeakHashMap的好原因嗎?如果我需要一個線程安全的Map實現,我傾向於使用ConcurrentHashMap,它的性能要好得多。

+0

ConcurrentHashMap是個不錯的主意。沒有什麼理由,只是一些遺留的考慮,不應再相關。我不認爲涉及的服務器重新啓動,但我會仔細檢查 - 我的會話畢竟是暫時的。無論如何我必須解決這個問題,如果你是對的,我會標記你的答案。 – 2011-04-14 14:10:35

+0

是什麼讓你認爲你是HttpSession的瞬態?會話映射上的transient修飾符似乎是多餘的,因爲UserInfo類沒有實現Serializable(這是混淆的地方嗎?)。而後來認爲HttpSession作爲一個映射關鍵字並不完全正確,因此它的equals()和hashcode()依賴於Tomcat(或其他應用服務器)選擇的實現。將交換密鑰作爲會話ID和值作爲HttpSession將會更好。 – martin 2011-04-14 20:38:25

+0

UserInfo實現了Serializable,因爲這個Web應用程序可以在HA集羣中工作,並在不刪除會話(也需要序列化應用程序上下文屬性)的情況下「繼續」重新啓動。在代碼示例中省略它是我的錯誤。 – 2011-04-20 17:02:06

1

而不是從頭開始寫東西,請檢查psi探針。

http://code.google.com/p/psi-probe/

這可能僅僅是一個簡單的降解決您的問題。

+0

真棒工具,謝謝。肯定會派上用場。不幸的是,我需要的功能是向登錄用戶顯示當前的會話數量以及他們使用的客戶端/他們在加速支持呼叫時所處的狀態。 – 2011-04-21 17:15:32

+0

嗯。只是看着你的代碼,不知道發佈什麼是什麼問題。你能發佈一個更完整的例子嗎? – 2011-04-21 19:38:08

3

由於這個問題接近5k的意見,我認爲這將是有益的提供一個工作解決方案的例子。

問題中概述的方法是錯誤的 - 它不會處理服務器重新啓動並且不會擴展。這是一個更好的方法。

首先,你的HttpServlet需要處理用戶登錄和退出的東西沿着這些線路:

public class ExampleServlet extends HttpServlet { 

    @Override 
    protected void service(HttpServletRequest req, HttpServletResponse resp) 
       throws ServletException, IOException { 
     String action = req.getParameter("do"); 
     HttpSession session = req.getSession(true); 
     //simple plug. Use your own controller here. 
     switch (action) { 
      case "logout": 
       session.removeAttribute("user"); 
       break; 
      case "login": 
       User u = new User(session.getId(), req.getParameter("login")); 
       //store user on the session 
       session.setAttribute("user",u);      
       break; 
     } 
    } 
} 

用戶豆必須是序列化,並具有在反序列化到自己重新註冊:

class User implements Serializable { 

    private String sessionId; 
    private String login; 

    User(String sessionId, String login) { 
     this.sessionId = sessionId; 
     this.login = login; 
    } 

    public String getLogin() { return login; } 

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { 
     in.defaultReadObject(); 
     //re-register this user in sessions 
     UserAttributeListener.sessions.put(sessionId,this); 
    } 

} 

您還需要一個HttpAttributeListener來妥善處理會話生命週期:

public class UserAttributeListener implements HttpSessionAttributeListener { 

    static Map<String, User> sessions = new ConcurrentHashMap<>(); 

    @Override 
    public void attributeAdded(HttpSessionBindingEvent event) { 
     if ("user".equals(event.getName())) 
      sessions.put(event.getSession().getId(), (User) event.getValue()); 
    } 

    @Override 
    public void attributeRemoved(HttpSessionBindingEvent event) { 
     if ("user".equals(event.getName())) 
      ExampleServlet.sessions.remove(event.getSession().getId()); 
    } 

    @Override 
    public void attributeReplaced(HttpSessionBindingEvent event) { 
     if ("user".equals(event.getName())) 
      ExampleServlet.sessions.put(event.getSession().getId(), 
         (User)event.getValue()); 
    } 
} 

當然,你需要在web.xml中註冊偵聽:

<listener> 
    <listener-class>com.example.servlet.UserAttributeListener</listener-class> 
</listener> 

之後,你可以隨時訪問UserAttributeListener的靜態地圖獲得多少會話正在運行,有多少會話的想法每個用戶都在使用等等。理想情況下,您會有一些更復雜的數據結構,保證自己的獨立單例類具有適當的訪問方法。取決於用例,使用具有寫入時複製併發策略的容器也可能是一個好主意。

+0

謝謝你,但你能解釋一下在什麼情況下,通過什麼過程調用User.readObject()? – Black 2015-07-13 12:46:53

+1

@Framcis:如果您重新啓動Tomcat並使用標記web.xml,Tomcat(或其他Web容器)將序列化您的會話屬性,然後重新啓動,然後從硬盤讀取它。這是您需要反序列化器在全局映射中重新註冊用戶的地方。 – 2015-07-13 12:59:51