|
背景:
公司的一个web应用,提交给测试部门做压力测试(由于不是我负责的,不清楚如何做的压力测试,以及测试指标),结果没压多久,就出现OutOfMemory.
接手协助原因查找,通过监控工具,发现StandardSession(org.apache.catalina.session.StandardSession)对象不断增长,毫无疑问,肯定是在不断创建Session对象.
备注:一般做压力测试,每次请求都不会指定JESSESIONID值,导致Web容器认为每次请求都是新的请求,于是创建Session对象.
同事负责代码Review,发现应用没有任何一个地方存放Session内容.困惑之...
问题:Tomcat容器何时创建Session对象?
想当然认为,只有动态存放Session内容的时候,才会创建Session对象.但是事实真得如此吗?
先看Servlet协议描述:
请看:
getSession(booleancreate)方法:
javax.servlet.http.HttpServletRequest.getSession(booleancreate)
ReturnsthecurrentHttpSessionassociatedwiththisrequestor,ififthereisnocurrentsessionandcreateistrue,returnsanewsession.
IfcreateisfalseandtherequesthasnovalidHttpSession,thismethodreturnsnull.
Tomakesurethesessionisproperlymaintained,youmustcallthismethodbeforetheresponseiscommitted.
简单地说:当create变量为true时,如果当前Session不存在,创建一个新的Session并且返回.
getSession()方法:
javax.servlet.http.HttpSessiongetSession();
Returnsthecurrentsessionassociatedwiththisrequest,oriftherequestdoesnothaveasession,createsone.
简单的说:当当前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)方法,请看:
protectedSessiondoGetSession(booleancreate){
//Therecannotbeasessionifnocontexthasbeenassignedyet
if(context==null)
return(null);
//Returnthecurrentsessionifitexistsandisvalid
if((session!=null)&&!session.isValid())
session=null;
if(session!=null)
return(session);
//Returntherequestedsessionifitexistsandisvalid
Managermanager=null;
if(context!=null)
manager=context.getManager();
if(manager==null)
return(null);//Sessionsarenotsupported
if(requestedSessionId!=null){
try{
session=manager.findSession(requestedSessionId);
}catch(IOExceptione){
session=null;
}
if((session!=null)&&!session.isValid())
session=null;
if(session!=null){
session.access();
return(session);
}
}
//Createanewsessionifrequestedandtheresponseisnotcommitted
if(!create)
return(null);
if((context!=null)&&(response!=null)&&
context.getCookies()&&
response.getResponse().isCommitted()){
thrownewIllegalStateException
(sm.getString("coyoteRequest.sessionCreateCommitted"));
}
//Attempttoreusesessionidifonewassubmittedinacookie
//DonotreusethesessionidifitisfromaURL,topreventpossible
//phishingattacks
if(connector.getEmptySessionPath()
&&isRequestedSessionIdFromCookie()){
session=manager.createSession(getRequestedSessionId());
}else{
session=manager.createSession(null);
}
//Creatinganewsessioncookiebasedonthatsession
if((session!=null)&&(getContext()!=null)
&&getContext().getCookies()){
Cookiecookie=newCookie(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方法:
publicvoidprocessExpires(){
longtimeNow=System.currentTimeMillis();
Sessionsessions[]=findSessions();
intexpireHere=0;
if(log.isDebugEnabled())
log.debug("Startexpiresessions"+getName()+"at"+timeNow+"sessioncount"+sessions.length);
for(inti=0;i<sessions.length;i++){
if(sessions!=null&&!sessions.isValid()){
expireHere++;
}
}
longtimeEnd=System.currentTimeMillis();
if(log.isDebugEnabled())
log.debug("Endexpiresessions"+getName()+"processingTime"+(timeEnd-timeNow)+"expiredsessions:"+expireHere);
processingTime+=(timeEnd-timeNow);
}
其中,Session.isValid()方法会做Session的清除工作.
在org.apache.catalina.core.ContainerBase中,会启动一个后台线程,跑一些后台任务,Session过期任务是其中之一:
protectedvoidthreadStart(){
if(thread!=null)
return;
if(backgroundProcessorDelay<=0)
return;
threadDone=false;
StringthreadName="ContainerBackgroundProcessor["+toString()+"]";
thread=newThread(newContainerBackgroundProcessor(),threadName);
thread.setDaemon(true);
thread.start();
}
准确地讲,除非你的应用完全不需要保存状态(无状态应用),不然地话,只要有一个新的连接过来,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方案都能有效解决文中所描述的问题. |
|