叢集session共享處理
通常情況下,Tomcat、Jetty等Servlet容器,會預設將Session儲存在記憶體中。如果是單個伺服器例項的應用,將Session儲存在伺服器記憶體中是一個非常好的方案。但是這種方案有一個缺點,就是不利於擴充套件。
目前越來越多的應用採用分散式部署,用於實現高可用性和負載均衡等。那麼問題來了,如果將同一個應用部署在多個伺服器上通過負載均衡對外提供訪問,如何實現Session共享?
實際上實現Session共享的方案很多,其中一種常用的就是使用Tomcat、Jetty等伺服器提供的Session共享功能,將Session的內容統一儲存在一個數據庫(如MySQL)或快取(如Redis)中。我在以前的一篇部落格中有介紹如何配置Jetty的Session儲存在MySQL或MongoDB中。
本文主要介紹另一種實現Session共享的方案,不依賴於Servlet容器,而是Web應用程式碼層面的實現,直接在已有專案基礎上加入Spring Session框架來實現Session統一儲存在Redis中。如果你的Web應用是基於Spring框架開發的,只需要對現有專案進行少量配置,即可將一個單機版的Web應用改為一個分散式應用,由於不基於Servlet容器,所以可以隨意將專案移植到其他容器。
Maven依賴
在專案中加入Spring Session的相關依賴包,包括Spring Data Redis、Jedis、Apache Commons Pool:
<!-- Jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.3.RELEASE</version>
</dependency>
<!-- Spring Session -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>
<!-- Apache Commons Pool -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version>
</dependency>
配置Filter
在web.xml中加入以下過濾器,注意如果web.xml中有其他過濾器,一般情況下Spring Session的過濾器要放在第一位。
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
Spring配置檔案
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
<bean class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="localhost" />
<property name="password" value="your-password" />
<property name="port" value="6379" />
<property name="database" value="10" />
</bean>
只需要以上簡單的配置,至此為止即已經完成Web應用Session統一儲存在Redis中,可以說是及其簡單。
解決Redis雲服務Unable to configure Redis to keyspace notifications異常
如果是自建伺服器搭建Redis服務,以上已經完成了Spring Session配置,這一節就不用看了。不過很多公司為了穩定性、減少運維成本,會選擇使用Redis雲服務,例如阿里雲資料庫Redis版、騰訊雲端儲存Redis等。使用過程中會出現異常:
Context initialization failed org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'enableRedisKeyspaceNotificationsInitializer' defined in class path resource [org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.class]:
Invocation of init method failed; nested exception is java.lang.IllegalStateException: Unable to configure Redis to keyspace notifications.
See http://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisoperationssessionrepository-sessiondestroyedevent
Caused by: redis.clients.jedis.exceptions.JedisDataException: ERR unknown command config
實際上這種異常發生的原因是,很多Redis雲服務提供商考慮到安全因素,會禁用掉Redis的config命令:
在錯誤提示連結的文件中,可以看到Redis需要以下的配置:
redis-cli config set notify-keyspace-events Egx
文件地址:
http://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisoperationssessionrepository-sessiondestroyedevent
首先要想辦法給雲服務Redis加上這個配置。
部分Redis雲服務提供商可以在對應的管理後臺配置:
如果不能在後臺配置,可以通過工單聯絡售後工程師幫忙配置,例如阿里雲:
完成之後,還需要在Spring配置檔案中加上一個配置,讓Spring Session不再執行config命令:
However, in a secured Redis enviornment the config command is disabled. This means that Spring Session cannot configure Redis Keyspace events for you. To disable the automatic configuration add ConfigureRedisAction.NO_OP as a bean.
配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
<bean class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="localhost" />
<property name="password" value="your-password" />
<property name="port" value="6379" />
<property name="database" value="10" />
</bean>
<!-- 讓Spring Session不再執行config命令 -->
<util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>
</beans>
!
本文為裝載,違權必刪。