欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Tomcat中的session是如何管理的?

程序员文章站 2022-07-15 16:35:59
...
最近有个朋友问我:
引用
tomcat的session存在哪里?

答:内存中。又问:
引用
那既然都保存在内存中,要清除过期的sesion时,又怎么能区分是哪个应用的session而不会清除错误?

答:(当时没看过这块的代码),说由于每个应用对应一个Session管理,此处应该有类似于数组之类的东西,将该应用的session管理起来,当该应用移除或者某些sesion过期时可以准确的删除掉。最近看了下代码,对比下源码,发现回复的还行,没有特别大的出入。

我们的应用中一般都会用到session,那这个session在应用服务器内部到底是怎么保存到处理的呢?

当我们请求一个应用时,如果页面中用到了session,此时的创建session的调用链如下:

Daemon Thread [http-bio-8090-exec-1] (Suspended (breakpoint at line 145 in SessionIdGenerator))	
	owns: SocketWrapper<E>  (id=234)	
	SessionIdGenerator.generateSessionId() line: 145	
	StandardManager(ManagerBase).generateSessionId() line: 807	
	StandardManager(ManagerBase).createSession(String) line: 653	
	Request.doGetSession(boolean) line: 2892	
	Request.getSession(boolean) line: 2315	
	RequestFacade.getSession(boolean) line: 898	
	RequestFacade.getSession() line: 910	
	PageContextImpl._initialize(Servlet, ServletRequest, ServletResponse, String, boolean, int, boolean) line: 146	
	PageContextImpl.initialize(Servlet, ServletRequest, ServletResponse, String, boolean, int, boolean) line: 125	
	JspFactoryImpl.internalGetPageContext(Servlet, ServletRequest, ServletResponse, String, boolean, int, boolean) line: 112	
	JspFactoryImpl.getPageContext(Servlet, ServletRequest, ServletResponse, String, boolean, int, boolean) line: 65	
	index.jsp line: not available	
	index_jsp(HttpJspBase).service(HttpServletRequest, HttpServletResponse) line: 70	
	index_jsp(HttpServlet).service(ServletRequest, ServletResponse) line: 728	
	JspServletWrapper.service(HttpServletRequest, HttpServletResponse, boolean) line: 432	
	JspServlet.serviceJspFile(HttpServletRequest, HttpServletResponse, String, boolean) line: 390	
	JspServlet.service(HttpServletRequest, HttpServletResponse) line: 334	
	JspServlet(HttpServlet).service(ServletRequest, ServletResponse) line: 728	
	ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 305	
	ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 210	
	StandardWrapperValve.invoke(Request, Response) line: 222	
	StandardContextValve.invoke(Request, Response) line: 123	
	NonLoginAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 472	
	StandardHostValve.invoke(Request, Response) line: 171	
	ErrorReportValve.invoke(Request, Response) line: 99	
	AccessLogValve.invoke(Request, Response) line: 936	
	StandardEngineValve.invoke(Request, Response) line: 118	
	CoyoteAdapter.service(Request, Response) line: 408	
	Http11Processor(AbstractHttp11Processor<S>).process(SocketWrapper<S>) line: 1009	
	Http11Protocol$Http11ConnectionHandler(AbstractProtocol$AbstractConnectionHandler<S,P>).process(SocketWrapper<S>, SocketStatus) line: 589	
	JIoEndpoint$SocketProcessor.run() line: 310	
	ThreadPoolExecutor$Worker.runTask(Runnable) line: 895	
	ThreadPoolExecutor$Worker.run() line: 918	
	TaskThread(Thread).run() line: 662


以上调用链的部分关键代码:

	
	 /**
     * Construct and return a new session object, based on the default
     * settings specified by this Manager's properties.  The session
     * id specified will be used as the session id.  
     * If a new session cannot be created for any reason, return 
     * <code>null</code>.
     * 
     * @param sessionId The session id which should be used to create the
     *  new session; if <code>null</code>, a new session id will be
     *  generated
     * @exception IllegalStateException if a new session cannot be
     *  instantiated for any reason
     */
    @Override
    public Session createSession(String sessionId) {
        
        if ((maxActiveSessions >= 0) &&
                (getActiveSessions() >= maxActiveSessions)) {
            rejectedSessions++;
            throw new TooManyActiveSessionsException(
                    sm.getString("managerBase.createSession.ise"),
                    maxActiveSessions);
        }
        
        // Recycle or create a Session instance
        Session session = createEmptySession();

        // Initialize the properties of the new session and return it
        session.setNew(true);
        session.setValid(true);
        session.setCreationTime(System.currentTimeMillis());
        session.setMaxInactiveInterval(this.maxInactiveInterval);
        String id = sessionId;
        if (id == null) {
            id = generateSessionId();  //此处生成sessionID
        }
        session.setId(id);
        sessionCounter++;

        SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
        synchronized (sessionCreationTiming) {
            sessionCreationTiming.add(timing);
            sessionCreationTiming.poll();
        }
        return (session);

    }
	
	
	
	    public void setId(String id, boolean notify) {

        if ((this.id != null) && (manager != null))
            manager.remove(this);

        this.id = id;

        if (manager != null)
            manager.add(this); //注意此处

        if (notify) {
            tellNew();
        }
    }


在setId时,同时在Manager中将相应的session保存了下来。此处对于session的保存使用的是ConcurrentHashMap,在创建后将其添加到map中,session过期后将其移除。


    /**
     * Add this Session to the set of active Sessions for this Manager.
     *
     * @param session Session to be added
     */
    @Override
    public void add(Session session) {

        sessions.put(session.getIdInternal(), session);
        int size = getActiveSessions();  //此处应该可以使用AtomicInteger替换掉。
        if( size > maxActive ) {
            synchronized(maxActiveUpdateLock) {
                if( size > maxActive ) {
                    maxActive = size;
                }
            }
        }
    }	




而在停止一个应用时,相应的remove session的调用链如下:
	Daemon Thread [http-bio-8090-exec-2] (Suspended (breakpoint at line 733 in ManagerBase))	
	owns: StandardSession  (id=340)	
	owns: StandardManager  (id=326)	
	owns: StandardContext  (id=327)	
	owns: SocketWrapper<E>  (id=341)	
	StandardManager(ManagerBase).remove(Session, boolean) line: 733	
	StandardSession.expire(boolean) line: 840	
	StandardManager.doUnload() line: 463	
	StandardManager.unload() line: 353	
	StandardManager.stopInternal() line: 518	
	StandardManager(LifecycleBase).stop() line: 232	
	StandardContext.stopInternal() line: 5569	
	StandardContext(LifecycleBase).stop() line: 232	
	HTMLManagerServlet(ManagerServlet).stop(PrintWriter, ContextName, StringManager) line: 1306	
	HTMLManagerServlet.stop(ContextName, StringManager) line: 733	
	HTMLManagerServlet.doPost(HttpServletRequest, HttpServletResponse) line: 221	
	HTMLManagerServlet(HttpServlet).service(HttpServletRequest, HttpServletResponse) line: 647	
	HTMLManagerServlet(HttpServlet).service(ServletRequest, ServletResponse) line: 728	
	ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 305	
	ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 210	
	CsrfPreventionFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 213	
	ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 243	
	ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 210	
	SetCharacterEncodingFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 108	
	ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 243	
	ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 210	
	StandardWrapperValve.invoke(Request, Response) line: 222	
	StandardContextValve.invoke(Request, Response) line: 123	
	BasicAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 581	
	StandardHostValve.invoke(Request, Response) line: 171	
	ErrorReportValve.invoke(Request, Response) line: 99	
	AccessLogValve.invoke(Request, Response) line: 936	
	StandardEngineValve.invoke(Request, Response) line: 118	
	CoyoteAdapter.service(Request, Response) line: 408	
	Http11Processor(AbstractHttp11Processor<S>).process(SocketWrapper<S>) line: 1009	
	Http11Protocol$Http11ConnectionHandler(AbstractProtocol$AbstractConnectionHandler<S,P>).process(SocketWrapper<S>, SocketStatus) line: 589	
	JIoEndpoint$SocketProcessor.run() line: 310	
	ThreadPoolExecutor$Worker.runTask(Runnable) line: 895	
	ThreadPoolExecutor$Worker.run() line: 918	
	TaskThread(Thread).run() line: 662	



其中执行remove的代码如下:
	 /**
     * Perform the internal processing required to invalidate this session,
     * without triggering an exception if the session has already expired.
     *
     * @param notify Should we notify listeners about the demise of
     *  this session?
     */
    public void expire(boolean notify) {

        // Check to see if expire is in progress or has previously been called
        if (expiring || !isValid)
            return;

        synchronized (this) {
            // Check again, now we are inside the sync so this code only runs once
            // Double check locking - expiring and isValid need to be volatile
            if (expiring || !isValid)
                return;

            if (manager == null)
                return;
			... //省略部分代码
		}
		... //省略部分代码
			   if (ACTIVITY_CHECK) {
                accessCount.set(0);
            }
            setValid(false);

            // Remove this session from our manager's active sessions
            manager.remove(this, true);
	}

StandardManager中的remove方法:
    /**
     * Remove this Session from the active Sessions for this Manager.
     *
     * @param session   Session to be removed
     * @param update    Should the expiration statistics be updated
     */
    @Override
    public void remove(Session session, boolean update) {
        
        // If the session has expired - as opposed to just being removed from
        // the manager because it is being persisted - update the expired stats
        if (update) {
            long timeNow = System.currentTimeMillis();
            int timeAlive =
                (int) (timeNow - session.getCreationTimeInternal())/1000;
            updateSessionMaxAliveTime(timeAlive);
            expiredSessions.incrementAndGet();
            SessionTiming timing = new SessionTiming(timeNow, timeAlive);
            synchronized (sessionExpirationTiming) {
                sessionExpirationTiming.add(timing);
                sessionExpirationTiming.poll();
            }
        }

        if (session.getIdInternal() != null) {
            sessions.remove(session.getIdInternal());  //注意此处,即为上面保存session的concurrentHashMap
        }
    }