起因
由于在部署项目的时候发现,每次部署都要往tomcat的lib加 `jedis.jar,redis-session.jar,commons-pool.jar,而且只能使用tomcat7x的版本来部署。于是想了解一下怎么回事,了解一下tomcat和redis-session的源码。
关于session管理器
tomcat的session通过session管理器产生。内置的session管理器类图结构如下:
可以看到,tomcat内置的有主要三种session管理器。
StandardManager :默认的session管理器,单机并且重启之后即session即丢失
PersistentManager:提供了session的持久化功能,可以实现重启tomcat之后还保持session。在内部维持了一个Store,决定将session持久化到文件还是数据库。单机。简单配置如下:
<Context docBase="ecm" path="/ecm">
<Manager className="org.apache.catalina.session.PersistentManager" debug="0" saveOnRestart="true" maxActiveSessions="-1" minIdleSwap="-1" maxIdleSwap="5" maxIdleBackup="3" >
<Store className="org.apache.catalina.session.FileStore" directory="/home/sessions/"/>
</Manager>
</Context>
<!--
className:Session的管理类,tomcat将会通过定义的接口来应用该类生成的对象。
debug:Session管理器的跟踪级别。
saveOnRestart:(true/false)配置服务重启工程中对session的处理,若为true,则关闭前把有效的session保存,启动后重新载入
maxActiveSession:活动状态Session的最大数,为-1时则不限制,否则Session Manager将会把超出的Session对象转移到Session Store中。
minIdleSwap:Session不活动的最短时间,超过该时间,Session Manager 可能会把该Session对象转移到Session Store中,单位为秒。
maxidleSwap:Session不活动的最长时间,超过该时间,Session Manager 将会把该Session对象转移到Session Store中,该Session将不在内存中。
maxidleBackup: Session不活动的最长时间,超过该时间,Session Manager 将会把该Session对象备份到Session Store中,但该Session对象依然存在内存中。
当按照我们上面的配置启动Tomcat后,服务器会根据maxIdleBackup的时间,以秒为单位,进行空闲Session的持久化。在配置的目录中,会生成以sessionId为文件名.session的文件
-->
其中Store可换为如下:
<Store calssName="org.apache.catalina.JDBCStore" driverName="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost/session?usename=xxx&password=xxx"
sessionTable="session" sessionIdCol="session_id" sessionDataCol="session_data"
sessionValidCol="sessionValid" sessionMaxInactiveCol="maxInactive"
sessionLastAccessedCol="lastAccess" sessionAppCol="app" checkInterval="60" debug="99" />
session表如下
create table sessions
(
id varchar(100) not null primary key,
valid char(1) not null,
maxinactive int not null,
lastaccess bigint,
app varchar(100),
data mediumblob
);
-- http://svn.apache.org/repos/asf/tomcat/archive/tc4.1.x/trunk/container/catalina/docs/JDBCStore-howto.html
ClusterManagerBase:支持cluster功能的session管理器,即支持session复制功能以支持多个tomcat之间同步session。
为什么说session基于cookies
看源码就很明白了,org.apache.catalina.connector.Request类下
protected Session doGetSession(boolean create) {
....
Manager 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);
}
}
...
}
而manager.findSession(id)
为如下。
protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
public Session findSession(String id) throws IOException {
if (id == null) {
return null;
}
return sessions.get(id);
}
其中requestedSessionId
就是从cookie里获取来的。
protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {
//如果禁用了cookies,则直接结束(后续步骤通过URL获取)
Context context = (Context) request.getMappingData().context;
if (context != null && !context.getServletContext()
.getEffectiveSessionTrackingModes().contains(
SessionTrackingMode.COOKIE)) {
return;
}
Cookies serverCookies = req.getCookies();
int count = serverCookies.getCookieCount();
if (count <= 0) {
return;
}
String sessionCookieName = SessionConfig.getSessionCookieName(context);
//默认的sessionCookieName为 "JSESSIONID"
for (int i = 0; i < count; i++) {
ServerCookie scookie = serverCookies.getCookie(i);
if (scookie.getName().equals(sessionCookieName)) {
// Override anything requested in the URL
if (!request.isRequestedSessionIdFromCookie()) {
// Accept only the first session id cookie
convertMB(scookie.getValue());
request.setRequestedSessionId
(scookie.getValue().toString());
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false);
if (log.isDebugEnabled()) {
log.debug(" Requested cookie session id is " +
request.getRequestedSessionId());
}
} else {
if (!request.isRequestedSessionIdValid()) {
// Replace the session id until one is valid
convertMB(scookie.getValue());
request.setRequestedSessionId
(scookie.getValue().toString());
}
}
}
}
}
可以很明显的知道session是基于cookie的,即服务器的session是根据客户端带过来的cookie来识别的。
回到正题:回到我们原来的题目上,在使用nginx等做tomcat集群时,如何实现session的共享。
- Tomcat容器本身支持的session复制功能(web容器一般会有),如上。这个缺点是,消耗资源,复制同步session需要占用网络,并且每个tomcat都维持了很多session,配置也比较麻烦。一般适用于比较少,比较小的情况。并且 个人认为,应该开启异步复制,这样可以尽量使session复制不影响到应用,同时负载启用会话粘滞功能,防止会话丢失。
- 实现一个session管理器。该session管理器使用关系DB如mysql,非关系DB如redis或mongo等来来统一存储和查找session信息。我们现在项目配置的redis-session-manager就是这种。主要要在findSession需要在db里查找,session创建时存储到db,当然,还有其它很多需要处理。详情可以查看 https://github.com/jcoleman/tomcat-redis-session-manager 以及https://github.com/simplicityitself/Mongo-Tomcat-Sessions (mongo),https://github.com/magro/memcached-session-manager/(memcached)。这个缺点是和特定web容器甚至版本耦合在一起,应用要换一个容器或者版本就有问题了。
- 通过装饰或代理增强request和session,在filter进行拦截替换。使得
request.getSession
方法能够到mysql,redis等地方写入和读取session。猜测spring-session是这么操作的。这种方式的不依赖于特定的web容器,适应性很好。现在应该大部分使用这种方式。可以看看 https://docs.spring.io/spring-session/docs/2.1.0.M2/reference/html5/#httpsession
p.s 关于怎么调试tomcat源码,可以查看:
https://blog.csdn.net/xiongyouqiang/article/details/78941077
https://github.com/apache/tomcat/tree/7.0.94