stone2083

Tomcat(6.0.14) Session创建机制简介

背景:
公司的一个web应用,提交给测试部门做压力测试(由于不是我负责的,不清楚如何做的压力测试,以及测试指标),结果没压多久,就出现OutOfMemory.
接手协助原因查找,通过监控工具,发现StandardSession(org.apache.catalina.session.StandardSession)对象不断增长,毫无疑问,肯定是在不断创建Session对象.
备注:一般做压力测试,每次请求都不会指定JESSESIONID值,导致Web容器认为每次请求都是新的请求,于是创建Session对象.
同事负责代码Review,发现应用没有任何一个地方存放Session内容.困惑之...

问题:Tomcat容器何时创建Session对象?
想当然认为,只有动态存放Session内容的时候,才会创建Session对象.但是事实真得如此吗?

先看Servlet协议描述:
请看:
getSession(boolean create)方法:
javax.servlet.http.HttpServletRequest.getSession(boolean create)

Returns the current HttpSession associated with this request or, if if there is no current session and create is true, returns a new session. 

If create is false and the request has no valid HttpSession, this method returns null. 

To make sure the session is properly maintained, you must call this method before the response is committed.

简单地说:当create变量为true时,如果当前Session不存在,创建一个新的Session并且返回.

getSession()方法:
javax.servlet.http.HttpSession getSession();

Returns the current session associated with this request, or if the request does not have a session, creates one.
简单的说:当当前Session不存在,创建并且返回.


所以说,协议规定,在调用getSession方法的时候,就会创建Session对象.



既然协议这么定了,我们再来看看Tomcat是如何实现的:(下面的描述,是基于Tomcat6.0.14版本源码)
先看一张简单的类图:


ApplicationContext:Servlet规范中ServletContext的实现
StandardContext:Tomcat定义的Context默认实现.维护了一份SessionManager对象,管理Session对象.所有的Session对象都存放在Manager定义的Map<String,Session>容器中.
StanardManager:标准的Session管理,将Session存放在内容,Web容器关闭的时候,持久化到本地文件
PersistentManager:持久化实现的Session管理,默认有两种实现方式:
--持久化到本地文件
--持久化到数据库

了解了大概的概念后,回头再来看看org.apache.catalina.connector.Request.getSession()是如何实现的.
最终调用的是doGetSession(boolean create)方法,请看:
protected Session doGetSession(boolean create) {

        
// There cannot be a session if no context has been assigned yet
        if (context == null)
            
return (null);

        
// Return the current session if it exists and is valid
        if ((session != null&& !session.isValid())
            session 
= null;
        
if (session != null)
            
return (session);

        
// Return the requested session if it exists and is valid
        Manager manager = null;
        
if (context != null)
            manager 
= context.getManager();
        
if (manager == null)
            
return (null);      // Sessions are not supported
        if (requestedSessionId != null) {
            
try {
                session 
= manager.findSession(requestedSessionId);
            } 
catch (IOException e) {
                session 
= null;
            }
            
if ((session != null&& !session.isValid())
                session 
= null;
            
if (session != null) {
                session.access();
                
return (session);
            }
        }

        
// Create a new session if requested and the response is not committed
        if (!create)
            
return (null);
        
if ((context != null&& (response != null&&
            context.getCookies() 
&&
            response.getResponse().isCommitted()) {
            
throw new IllegalStateException
              (sm.getString(
"coyoteRequest.sessionCreateCommitted"));
        }

        
// Attempt to reuse session id if one was submitted in a cookie
        
// Do not reuse the session id if it is from a URL, to prevent possible
        
// phishing attacks
        if (connector.getEmptySessionPath() 
                
&& isRequestedSessionIdFromCookie()) {
            session 
= manager.createSession(getRequestedSessionId());
        } 
else {
            session 
= manager.createSession(null);
        }

        
// Creating a new session cookie based on that session
        if ((session != null&& (getContext() != null)
               
&& getContext().getCookies()) {
            Cookie cookie 
= new Cookie(Globals.SESSION_COOKIE_NAME,
                                       session.getIdInternal());
            configureSessionCookie(cookie);
            response.addCookieInternal(cookie, context.getUseHttpOnly());
        }

        
if (session != null) {
            session.access();
            
return (session);
        } 
else {
            
return (null);
        }

    }


至此,简单地描述了Tomcat Session创建的机制,有兴趣的同学要深入了解,不妨看看Tomcat源码实现.



补充说明,顺便提一下Session的过期策略.
过期方法在:
org.apache.catalina.session.ManagerBase(StandardManager基类) processExpires方法:
public void processExpires() {

        
long timeNow = System.currentTimeMillis();
        Session sessions[] 
= findSessions();
        
int expireHere = 0 ;
        
        
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.isValid()方法会做Session的清除工作.


在org.apache.catalina.core.ContainerBase中,会启动一个后台线程,跑一些后台任务,Session过期任务是其中之一:
protected void threadStart() {

        
if (thread != null)
            
return;
        
if (backgroundProcessorDelay <= 0)
            
return;

        threadDone 
= false;
        String threadName 
= "ContainerBackgroundProcessor[" + toString() + "]";
        thread 
= new Thread(new ContainerBackgroundProcessor(), threadName);
        thread.setDaemon(
true);
        thread.start();

    }


OVER :)

posted on 2010-02-26 16:12 stone2083 阅读(7027) 评论(4)  编辑  收藏 所属分类: java

Feedback

# re: Tomcat(6.0.14) Session创建机制简介 2010-03-05 17:21 小菜花

也就是说session对象过多是没法规避的?  回复  更多评论   

# re: Tomcat(6.0.14) Session创建机制简介 2010-03-07 15:30 stone2083

@小菜花
准确地讲,除非你的应用完全不需要保存状态(无状态应用),不然地话,只要有一个新的连接过来,web容器都需要创建Session概念,维护状态信息.
但是Session是什么?Session仅仅是一个概念:"Provides a way to identify a user across more than one page request or visit to a Web site and to store information about that user."--简单地讲,保存用户状态信息.
所以说,我们完全可以根据应用的需求,定制Session的实现:
a. Session保存到JVM内容中--Tomcat默认的实现
b. Session保存到Cookie中--Cookie-Based Session
c. Session保存到本地文件--Tomcat提供的非默认实现之一
d. Session保存到Cache Store中--比如常见的Memcached
e. Session保存到数据库中--比如保存到mysql数据库session表,中间对于活跃的Session 缓存到cached中.
......
那么,假如一个应用有大量一次性不同用户的请求(仅仅是一次性的,比如上述文章描述的场景),那么选择c,d,e方案都能有效解决文中所描述的问题.  回复  更多评论   

# re: Tomcat(6.0.14) Session创建机制简介 2010-03-07 18:27 小菜花

@stone2083
恩,之前我也遇到这个问题,也是性能测试的时候发现内存下不来,后来发现是session对象过多,我们在测试的时候有传递sessionid给服务器,后来没找到为啥还是有过多session的原因,后来不了了之了,因为线上基本上不会出现这种情况  回复  更多评论   

# re: Tomcat(6.0.14) Session创建机制简介 2010-06-11 20:23 stone2083

@小菜花
理论上来说,传递jsessionid,只要value是一致的,服务端不会创建多份session.
可以在测试环境下通过debug 或者 监控工具,跟踪下创建多份session的情况.  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航: