1. 程式人生 > >tomcat8 nginx 叢集 tomcat-redis-session-manager 使用注意事項

tomcat8 nginx 叢集 tomcat-redis-session-manager 使用注意事項

最近有個專案需要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會話不會過期。