Spring Session + Redis實現分布式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
首先要想辦法給云服務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>
來自:http://xxgblog.com/2016/09/29/spring-session-redis/