一,tomcat 如何支持session
首先来看下$catalina.home/conf/context.xml
<Context> <!-- Default set of monitored resources --> <WatchedResource>WEB-INF/web.xml</WatchedResource> <!-- Uncomment this to disable session persistence across Tomcat restarts --> <!-- <Manager pathname="" /> --> <!-- Uncomment this to enable Comet connection tacking (provides events on session expiration as well as webapp lifecycle) --> <!-- <Valve className="org.apache.catalina.valves.CometConnectionManagerValve" /> --> <Manager className="org.apache.catalina.session.PersistentManager" checkInterval="1" maxIdleBackup="2"> <Store className="org.apache.catalina.session.JDBCStore" driverName="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/session?user=root&password=hello" sessionTable="session" sessionIdCol="session_id" sessionDataCol="session_data" sessionValidCol="sessionValid" sessionMaxInactiveCol="maxInactive" sessionLastAccessedCol="lastAccess" sessionAppCol="appName" checkInterval="1" debug="99" /> </Manager> </Context>
众所周知 HTTP协议是说HTTP是无状态的,而我们常常使得程序有状态无非一种办法,把相同会话(即一组应当有状态的请求)都赋予一个统一的标识即JSESSIONID。
如果每次请求过来都带有JSESSIONID的话,那么这些请求都可以看成是同一个会话下的,那么会话中的状态就可以再服务端内存或者任何地方进行保存和关联。
1,那么这个JSESSIONID,首先是服务端生成的一个唯一标示(TOMCAT内部实现会帮我们自动生成)
2,其次这个JSESSIONID会被传给客户端。传给客户端有两种方式:
A,写到客户端Cookie里去。B,给客户端进行URL 的rewriter。
所以客户端拿到这个JSESSIONID之后再提交给服务端的时候,服务端就能知道:哦,原来你就是上次来请求过我的那个人;我记得你在我这边喝了一杯咖啡还没付钱呢。
那么JSESSIONID从服务端写到客户端有两个步骤,A和B,服务端是如何选择用哪种方式的呢。这里就看上面贴出来的XML配置
如果 <Context cookies="false"> 那么是禁用了cookie的,表示用url rewriter的方式
否则默认都是用cookie的方式保存JSESSIONID的。
================ 上面讲了 服务器如何生成JSESSIONID,并且把Cookie保存到客户端 ===========
================ 下面讲服务器如何保存JSESSIONID,即会话状态 ===========
服务器保存会话有两种方式:
1,标准方式,就保存到内存里。系统一旦shutdown,全部会话忘了。你欠我一杯咖啡也忘了。
2,持久化保存,使得系统重启可恢复
如我上面贴出来的配置
<Manager className="org.apache.catalina.session.PersistentManager" checkInterval="1" maxIdleBackup="2"> <Store className="org.apache.catalina.session.JDBCStore" driverName="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/session?user=root&password=hello" sessionTable="session" sessionIdCol="session_id" sessionDataCol="session_data" sessionValidCol="sessionValid" sessionMaxInactiveCol="maxInactive" sessionLastAccessedCol="lastAccess" sessionAppCol="appName" checkInterval="1" debug="99" /> </Manager>
当系统被正常关闭的时候,内存中的session会被保存到数据库里。
如图:
当然这里得先建好数据库。
类似:
![]() | ![]() | ![]() |
![]() | create table tomcat_sessions ( session_id varchar(100) not null primary key, valid_session char(1) not null, max_inactive int not null, last_access bigint not null, app_name varchar(255), session_data mediumblob, KEY kapp_name(app_name) ); | ![]() |
![]() | ![]() | ![]() |
其中 maxIdleBackup 代表这个session 空闲了多少秒就会被保存到数据库里。
详细配置参见官网:
http://tomcat.apache.org/tomcat-4.1-doc/config/manager.html
========================= 邪恶的分割线 ======================
二,tomcat 如何产生 一个新的session
MangerBase.java
/**
* 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
*/
public Session createSession(String sessionId) {
// 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);
if (sessionId == null) {
sessionId = generateSessionId();
}
session.setId(sessionId);
sessionCounter++;
return (session);
}
真正生成JSESSIONID的实现:
/**
* Generate and return a new session identifier.
*/
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();
} while (sessions.containsKey(result));
return (result);
}
当然上面那个MangerBase是个抽象类,他有各种子类:
比如:DeltaManager.java实现了分布式集群中session的拷贝。
/**
* create new session with check maxActiveSessions and send session creation
* to other cluster nodes.
*
* @param distribute
* @return The session
*/
public Session createSession(String sessionId, boolean distribute) {
if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) {
rejectedSessions++;
throw new IllegalStateException(sm.getString("deltaManager.createSession.ise"));
}
DeltaSession session = (DeltaSession) super.createSession(sessionId) ;
if (distribute) {
sendCreateSession(session.getId(), session);//拷贝到集群其他机器
}
if (log.isDebugEnabled())
log.debug(sm.getString("deltaManager.createSession.newSession",session.getId(), new Integer(sessions.size())));
return (session);
}
当然我们可以有我们自己的实现把session放到 memcached或者数据库里来解决集群问题。