tomcat8 nginx 集群 tomcat-redis-session-manager 使用注意事项

甄阿苏
2023-12-01

最近有个项目需要tomcat集群,使用的方案是:
1)nginx做tomcat负载均衡;
2)tomcatA和tomcatB做应用集群;
3)tomcatA和tomcatB session统一存放到redis;
4)数据库使用阿里云RDS高可用数据库(带主备功能,读写分离)
关于session统一存放到redis,本来是想单独写个组件的,后来发现网上有现成的,我看网上很多tomcat集群session共享用的都是那个组件tomcat-redis-session-manager。既然已经有轮子啦,就没必要重新造,拿来用好即可。

1、项目搭建问题

我用的是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>  

2、不支持tomcat8处理

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的提示,如:

  • getMaxInactiveInterval
  • setDistributable等
    不影响代码工作,因此可以暂且忽略。

源码修改注意事项:
因为源码打包jar后,要发布到tomcat\lib目录下,不是应用的WEB-INF\lib目录下,因此修改源码时尽量不要使用第三方jar,包括项目的公共jar,否则必须把涉及的第三方jar也复制到tomcat\lib下,不然tomcat启动时会报class not found 错。

3、项目发布

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目录下。

  • tomcat-redis-session-manager.jar
  • commons-pool2-2.3.jar
  • jedis-2.7.3.jar

4、tomcat-redis-session-manager 工作机制

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个关键事情:

  • 根据判断session有没发生变化,有则序列化到redis;
  • 更新redis的expire,过期时间,只要访问过,保证session会话不会过期。
 类似资料: