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會話不會過期。