tomcat架构分析 (Session管理)

[b][size=medium]出处:[url=http://gearever.iteye.com]http://gearever.iteye.com[/url][/size][/b]

Session管理是JavaEE容器比较重要的一部分,在app中也经常会用到。在开发app时,我们只是获取一个session,然后向session中存取数据,然后再销毁session。那么如何产生session,以及session池如何维护及管理,这些并没有在app涉及到。这些工作都是由容器来完成的。
Tomcat中主要由每个context容器内的一个Manager对象来管理session。对于这个manager对象的实现,可以根据tomcat提供的接口或基类来自己定制,同时,tomcat也提供了标准实现。
在tomcat架构分析(容器类)中已经介绍过,在每个context对象,即web app都具有一个独立的manager对象。通过server.xml可以配置定制化的manager,也可以不配置。不管怎样,在生成context对象时,都会生成一个manager对象。缺省的是StandardManager类,其类路径为:
[quote]org.apache.catalina.session.StandardManager[/quote]
Session对象也可以定制化实现,其主要实现标准servlet的session接口:
[quote]javax.servlet.http.HttpSession[/quote]
Tomcat也提供了标准的session实现:
[quote]org.apache.catalina.session.StandardSession[/quote]
本文主要就是结合消息流程介绍这两个类的实现,及session机制。
Session方面牵涉的东西还是蛮多的,例如HA,session复制是其中重要部分等,不过本篇主要从功能方面介绍session管理,有时间再说说扩展。
Session管理主要涉及到这几个方面:

[list]
[*]创建session
[*]注销session
[*]持久化及启动加载session
[/list]

[size=large][b]创建session[/b][/size]
在具体说明session的创建过程之前,先看一下BS访问模型吧,这样理解直观一点。
[img]http://img13.poco.cn/mypoco/myphoto/20120601/12/4649467720120601120943026.jpg[/img]
[list=1]
[*]browser发送Http request;
[*]tomcat内核Http11Processor会从HTTP request中解析出“jsessionid”(具体的解析过程为先从request的URL中解析,这是为了有的浏览器把cookie功能禁止后,将URL重写考虑的,如果解析不出来,再从cookie中解析相应的jsessionid),解析完后封装成一个request对象(当然还有其他的http header);
[*]servlet中获取session,其过程是根据刚才解析得到的jsessionid(如果有的话),从session池(session maps)中获取相应的session对象;这个地方有个逻辑,就是如果jsessionid为空的话(或者没有其对应的session对象,或者有session对象,但此对象已经过期超时),可以选择创建一个session,或者不创建;
[*]如果创建新session,则将session放入session池中,同时将与其相对应的jsessionid写入cookie通过Http response header的方式发送给browser,然后重复第一步。
[/list]
以上是session的获取及创建过程。在servlet中获取session,通常是调用request的getSession方法。这个方法需要传入一个boolean参数,这个参数就是实现刚才说的,当jsessionid为空或从session池中获取不到相应的session对象时,选择创建一个新的session还是不创建。
看一下核心代码逻辑;

protected Session doGetSession(boolean create) {

……
// 先获取所在context的manager对象
Manager manager = null;
if (context != null)
manager = context.getManager();
if (manager == null)
return (null); // Sessions are not supported

//这个requestedSessionId就是从Http request中解析出来的
if (requestedSessionId != null) {
try {
//manager管理的session池中找相应的session对象
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
//判断session是否为空及是否过期超时
if ((session != null) && !session.isValid())
session = null;
if (session != null) {
//session对象有效,记录此次访问时间
session.access();
return (session);
}
}

// 如果参数是false,则不创建新session对象了,直接退出了
if (!create)
return (null);
if ((context != null) && (response != null) &&
context.getCookies() &&
response.getResponse().isCommitted()) {
throw new IllegalStateException
(sm.getString("coyoteRequest.sessionCreateCommitted"));
}

// 开始创建新session对象
if (connector.getEmptySessionPath()
&& isRequestedSessionIdFromCookie()) {
session = manager.createSession(getRequestedSessionId());
} else {
session = manager.createSession(null);
}

// 将新session的jsessionid写入cookie,传给browser
if ((session != null) && (getContext() != null)
&& getContext().getCookies()) {
Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME,
session.getIdInternal());
configureSessionCookie(cookie);
response.addCookieInternal(cookie);
}
//记录session最新访问时间
if (session != null) {
session.access();
return (session);
} else {
return (null);
}
}


尽管不能贴出所有代码,但是上述的核心逻辑还是很清晰的。从中也可以看出,我们经常在servlet中这两种调用方式的不同;
新创建session
[quote]request.getSession(); 或者request.getSession(true);[/quote]
不创建session
[quote]request.getSession(false);[/quote]
接下来,看一下StandardManager的createSession方法,了解一下session的创建过程;

public Session createSession(String sessionId) {
//这是个session数量控制逻辑,超过上限则抛异常退出
if ((maxActiveSessions >= 0) &&
(sessions.size() >= maxActiveSessions)) {
rejectedSessions++;
throw new IllegalStateException
(sm.getString("standardManager.createSession.ise"));
}
return (super.createSession(sessionId));
}

这个最大支持session数量maxActiveSessions是可以配置的,先不管这个安全控制逻辑,看其主逻辑,即调用其基类的createSession方法;

public Session createSession(String sessionId) {

// 创建一个新的StandardSession对象
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);
if (sessionId == null) {
//设置jsessionid
sessionId = generateSessionId();
}
session.setId(sessionId);
sessionCounter++;
return (session);
}

关键是jsessionid的产生过程,接着看generateSessionId方法;

protected synchronized String generateSessionId() {

byte random[] = new byte[16];
String jvmRoute = getJvmRoute();
String result = null;

// Render the result as a String of hexadecimal digits
StringBuffer buffer = new StringBuffer();
do {
int resultLenBytes = 0;
if (result != null) {
buffer = new StringBuffer();
duplicates++;
}

while (resultLenBytes < this.sessionIdLength) {
getRandomBytes(random);
random = getDigest().digest(random);
for (int j = 0;
j < random.length && resultLenBytes < this.sessionIdLength;
j++) {
byte b1 = (byte) ((random[j] & 0xf0) >> 4);
byte b2 = (byte) (random[j] & 0x0f);
if (b1 < 10)
buffer.append((char) ('0' + b1));
else
buffer.append((char) ('A' + (b1 - 10)));
if (b2 < 10)
buffer.append((char) ('0' + b2));
else
buffer.append((char) ('A' + (b2 - 10)));
resultLenBytes++;
}
}
if (jvmRoute != null) {
buffer.append('.').append(jvmRoute);
}
result = buffer.toString();
//注意这个do…while结构
} while (sessions.containsKey(result));
return (result);
}

这里主要说明的不是生成jsessionid的算法了,而是这个[color=red]do…while结构[/color]。把这个逻辑抽象出来,可以看出;
[img]http://img13.poco.cn/mypoco/myphoto/20120601/12/4649467720120601121008097.jpg[/img]
如图所示,创建jsessionid的方式是由tomcat内置的加密算法算出一个随机的jsessionid,如果此jsessionid已经存在,则重新计算一个新的,直到确保现在计算的jsessionid唯一。
好了,至此一个session就这么创建了,像上面所说的,返回时是将jsessionid以HTTP response的header:“Set-cookie”发给客户端。
[size=large][b]注销session[/b][/size]
[list]
[*]主动注销
[*]超时注销
[/list]
Session创建完之后,不会一直存在,或是主动注销,或是超时清除。即是出于安全考虑也是为了节省内存空间等。例如,常见场景:用户登出系统时,会主动触发注销操作。
[size=medium][u][b]主动注销[/b][/u][/size]
主动注销时,是调用标准的servlet接口:
[quote]session.invalidate();[/quote]
看一下tomcat提供的标准session实现(StandardSession)

public void invalidate() {
if (!isValidInternal())
throw new IllegalStateException
(sm.getString("standardSession.invalidate.ise"));
// 明显的注销方法
expire();
}

Expire方法的逻辑稍后再说,先看看超时注销,因为它们调用的是同一个expire方法。
[size=medium][u][b]超时注销[/b][/u][/size]
Tomcat定义了一个最大空闲超时时间,也就是说当session没有被操作超过这个最大空闲时间时间时,再次操作这个session,这个session就会触发expire。
这个方法封装在StandardSession中的isValid()方法内,这个方法在获取这个request请求对应的session对象时调用,可以参看上面说的创建session环节。也就是说,获取session的逻辑是,先从manager控制的session池中获取对应jsessionid的session对象,如果获取到,就再判断是否超时,如果超时,就expire这个session了。
看一下tomcat提供的标准session实现(StandardSession)

public boolean isValid() {
……
//这就是判断距离上次访问是否超时的过程
if (maxInactiveInterval >= 0) {
long timeNow = System.currentTimeMillis();
int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
if (timeIdle >= maxInactiveInterval) {
expire(true);
}
}
return (this.isValid);
}

[size=medium][u][b]Expire方法[/b][/u][/size]
是时候来看看expire方法了。

public void expire(boolean notify) {

synchronized (this) {
......
//设立标志位
setValid(false);

//计算一些统计值,例如此manager下所有session平均存活时间等
long timeNow = System.currentTimeMillis();
int timeAlive = (int) ((timeNow - creationTime)/1000);
synchronized (manager) {
if (timeAlive > manager.getSessionMaxAliveTime()) {
manager.setSessionMaxAliveTime(timeAlive);
}
int numExpired = manager.getExpiredSessions();
numExpired++;
manager.setExpiredSessions(numExpired);
int average = manager.getSessionAverageAliveTime();
average = ((average * (numExpired-1)) + timeAlive)/numExpired;
manager.setSessionAverageAliveTime(average);
}

// 将此session从manager对象的session池中删除
manager.remove(this);
......
}
}

不需要解释,已经很清晰了。
这个超时时间是可以配置的,缺省在tomcat的全局web.xml下配置,也可在各个app下的web.xml自行定义;

<session-config>
<session-timeout>30</session-timeout>
</session-config>

单位是分钟。
[size=large][b]Session持久化及启动初始化[/b][/size]
这个功能主要是,当tomcat执行安全退出时(通过执行shutdown脚本),会将session持久化到本地文件,通常在tomcat的部署目录下有个session.ser文件。当启动tomcat时,会从这个文件读入session,并添加到manager的session池中去。
这样,当tomcat正常重启时, session没有丢失,对于用户而言,体会不到重启,不影响用户体验。
看一下概念图吧,觉得不是重要实现逻辑,代码就不说了。
[img]http://img13.poco.cn/mypoco/myphoto/20120601/12/464946772012060112103202.jpg[/img]
[size=large][b]总结[/b][/size]
由此可以看出,session的管理是容器层做的事情,应用层一般不会参与session的管理,也就是说,如果在应用层获取到相应的session,已经是由tomcat提供的,因此如果过多的依赖session机制来进行一些操作,例如访问控制,安全登录等就不是十分的安全,因为如果有人能得到正在使用的jsessionid,则就可以侵入系统。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值