最近有个项目需要tomcat集群,使用的方案是:
1)nginx做tomcat负载均衡;
2)tomcatA和tomcatB做应用集群;
3)tomcatA和tomcatB session统一存放到redis;
4)数据库使用阿里云RDS高可用数据库(带主备功能,读写分离)
关于session统一存放到redis,本来是想单独写个组件的,后来发现网上有现成的,我看网上很多tomcat集群session共享用的都是那个组件tomcat-redis-session-manager。既然已经有轮子啦,就没必要重新造,拿来用好即可。
我用的是eclipse+maven,从github下载源码后,配置maven项目即可,几个关键依赖如下,pom.xml如下:
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.0.36</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.3</version>
</dependency>
</dependencies>
tomcat-redis-session-manager组件已经很久没更新,支持tomcat6、tomcat7,在tomcat8上有一些小问题,需要修改RedisSessionManager.java源码,改动如下:
// 原代码
private void initializeSerializer() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
log.info("Attempting to use serializer :" + serializationStrategyClass);
serializer = (Serializer) Class.forName(serializationStrategyClass).newInstance();
Loader loader = null;
if (getContainer() != null) {
loader = getContainer().getLoader();
}
ClassLoader classLoader = null;
if (loader != null) {
classLoader = loader.getClassLoader();
}
serializer.setClassLoader(classLoader);
}
// 改动后
private void initializeSerializer() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
log.info("Attempting to use serializer :" + serializationStrategyClass);
serializer = (Serializer) Class.forName(serializationStrategyClass).newInstance();
Loader loader = null;
/*
* if (getContainer() != null) { loader = getContainer().getLoader(); }
*/
Context context = this.getContext();
if (context != null) {
loader = context.getLoader();
}
ClassLoader classLoader = null;
if (loader != null) {
classLoader = loader.getClassLoader();
}
serializer.setClassLoader(classLoader);
}
项目代码可能会有很多方法 is deprecated的提示,如:
源码修改注意事项:
因为源码打包jar后,要发布到tomcat\lib目录下,不是应用的WEB-INF\lib目录下,因此修改源码时尽量不要使用第三方jar,包括项目的公共jar,否则必须把涉及的第三方jar也复制到tomcat\lib下,不然tomcat启动时会报class not found 错。
1)配置wen应用的context,可以配置到tomcat里,也可以配置到当前应用,以当前应用为例:
在应用的WebContent\META-INF目录下,新建context.xml,内容大致如下:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
<Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
host="localhost" <!-- 可选: 默认 "localhost" -->
port="6379" <!-- 可选: 默认 "6379" -->
database="0" <!-- 可选: 默认 "0" -->
password="" <!-- 可选: 默认 "" -->
maxInactiveInterval="60" <!-- 可选: 默认 "60" (in seconds) -->
sessionPersistPolicies="PERSIST_POLICY_1,PERSIST_POLICY_2,.." <!-- 可选 -->
sentinelMaster="SentinelMasterName" <!-- 可选,redis集群主机 -->
sentinels="sentinel-host-1:port,sentinel-host-2:port,.." <!-- 可选,集群机器队列 --> />
</Context>
RedisSessionHandlerValve,RedisSessionManager class path视具体情况而定,别写错了,否则tomcat启动会报Class Not Found错。
2)jar包发布
项目编译后,连同另外2个jar,复制到tomcat\lib目录下,切记不是应用的WEB-INF\lib目录下。
1)request请求开始
2)调用request.getSession()时,RedisSessionManager会先findSession(id),找到返回session,没找到创建一个session,createSession,并序列化到redis;
3)session.setAttribute(),判断set的值与老值(类型、内容)是否一致,不一致,序列化session到redis;
问题:set的是一个对象,比如loginUser(某个属性,绑定的email修改),老值和新值指向同一地址,这种情况RedisSession.setAttribute,并不会序列化session到redis,但是在afterRequest中会修正。
4)session.removeAttribute(),会序列化session到redis
5)reqeust结束,会回调RedisSessionManager.afterRequest,做2个关键事情: