1. 程式人生 > >使用Spring Session和Redis解決分布式Session跨域共享問題

使用Spring Session和Redis解決分布式Session跨域共享問題

默認 mark value des nested project pty 錯誤 lte

前言

對於分布式使用Nginx+Tomcat實現負載均衡,最常用的均衡算法有IP_Hash、輪訓、根據權重、隨機等。不管對於哪一種負載均衡算法,由於Nginx對不同的請求分發到某一個Tomcat,Tomcat在運行的時候分別是不同的容器裏,因此會出現session不同步或者丟失的問題。

文末分享了我一部分私人收藏 有興趣的可以收藏看一下的 都是架構師進階的內容

實際上實現Session共享的方案很多,其中一種常用的就是使用Tomcat、Jetty等服務器提供的Session共享功能,將Session的內容統一存儲在一個數據庫(如MySQL)或緩存(如Redis)中。

 如何使用 tomcat-redis-session-manager 開源項目解決分布式session跨域的問題,他的主要思想是利用Servlet容器提供的插件功能,自定義HttpSession的創建和管理策略,並通過配置的方式替換掉默認的策略。tomcat-redis-session-manager重寫了Tomcat的org.apache.catalina.session.ManagerBase裏邊的具體寫的操作, 將tomcat的session存儲位置指向了Redis:

https://img-blog.csdn.net/20170226114651267?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGxnZW4xNTczODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

RedisSessionManager繼承了org.apache.catalina.session.ManagerBase並重寫了add、findSession、createEmptySession、remove等方法,並將對session的增刪改查操作指向了對Redis數據存儲的操作。

有興趣可參考一篇Tomcat中session的管理機制:http://www.cnblogs.com/interdrp/p/4935614.html

不過使用過tomcat-redis-session-manager 的都應該知道,配置相對還是有一點繁瑣的,需要人為的去修改Tomcat的配置,需要耦合Tomcat等Servlet容器的代碼,並且對於分布式Redis集群的管理並不是很好,與之相對的個人認為比較好的一個框架Spring Session可以真正對用戶透明的去管理分布式Session。

Spring Session不依賴於Servlet容器,而是Web應用代碼層面的實現,直接在已有項目基礎上加入spring Session框架來實現Session統一存儲在Redis中。如果你的Web應用是基於Spring框架開發的,只需要對現有項目進行少量配置,即可將一個單機版的Web應用改為一個分布式應用,由於不基於Servlet容器,所以可以隨意將項目移植到其他容器。

Spring Session使用
官方地址:http://projects.spring.io/spring-session/

官方文檔地址:http://docs.spring.io/spring-session/docs/1.3.0.RELEASE/reference/html5/

Spring Session提供了一套創建和管理Servlet HttpSession的方案。Spring Session提供了集群Session(Clustered Sessions)功能,默認采用外置的Redis來存儲Session數據,以此來解決Session共享的問題。

一、特性

Spring Session提供以下特性:

API和用於管理用戶會話的實現;
HttpSession - 允許以應用程序容器(即Tomcat)中性的方式替換HttpSession;
Clustered Sessions - Spring Session讓支持集群會話變得不那麽繁瑣,並且不和應用程序容器金習性綁定到。
Multiple Browser Sessions - Spring會話支持在單個瀏覽器實例中管理多個用戶的會話。
RESTful APIs - Spring Session允許在headers 中提供會話ID以使用RESTful API。

二、基於XML配置方式的Spring Session案例實現

基於SSM框架的一個小案例,Git OS項目代碼地址:http://git.oschina.net/xuliugen/spring-session-demo
https://img-blog.csdn.net/20170226141717129?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGxnZW4xNTczODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

項目展示:
https://img-blog.csdn.net/20170226142056838?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGxnZW4xNTczODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
(1)基本環境需求

進行使用Spring Session的話,首先的是已經安裝好的有一個 Redis服務器!

(2)添加項目依賴(最基本的依賴使用)

<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.3.0.RELEASE</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>biz.paluch.redis</groupId>
<artifactId>lettuce</artifactId>
<version>3.5.0.Final</version>
</dependency>
(3)添加Spring配置文件

添加了必要的依賴之後,我們需要創建相應的Spring配置。Spring配置是要創建一個Servlet過濾器,它用Spring Session支持的HttpSession實現來替換容器本身HttpSession實現。這一步也是Spring Session的核心。

<context:annotation-config/>

<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>

<bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
上述代碼註釋:
https://img-blog.csdn.net/20170226145153620?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGxnZW4xNTczODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

LettuceConnectionFactory實例是配置Redis的ConnectionFactory。

註意:

<bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
查看源代碼可以看到,默認的Redis鏈接配置為:
https://img-blog.csdn.net/20170226150015627?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGxnZW4xNTczODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

因此,如果有自己的Redis配置,請修改,例如下邊的配置:

<bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
<property name="hostName" value="192.168.1.149"/>
<property name="port" value="6379"/>
<property name="password" value="123456"/>
</bean>
(4)關於Error creating bean with name ‘enableRedisKeyspaceNotificationsInitializer’錯誤的處理:

添加如下配置讓Spring Session不再執行config命令

<util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>
如果不添加的話,會報如下錯誤:

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

(5)在web.xml中添加DelegatingFilterProxy

<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>

DelegatingFilterProxy將通過springSessionRepositoryFilter的名稱查找Bean並將其轉換為過濾器。對於調用DelegatingFilterProxy的每個請求,也將調用springSessionRepositoryFilter。

(6)Spring MVC controller代碼用於測試:

@Controller
@RequestMapping(value = "/spring/session", produces = {ConstString.APP_JSON_UTF_8})
public class SpringSessionDemoController {

@RequestMapping(value = "/setSession.do", method = RequestMethod.GET)
public void setSession(HttpServletRequest request, HttpServletResponse response) {
    String name = request.getParameter("name");
    String value = request.getParameter("value");
    request.getSession().setAttribute(name, value);
}

@RequestMapping(value = "/getSession.do", method = RequestMethod.GET)
public void getInterestPro(HttpServletRequest request, HttpServletResponse response) {
    String name = request.getParameter("name");
    System.out.println("------" + request.getSession().getAttribute(name));
}

@RequestMapping(value = "/removeSession.do", method = RequestMethod.GET)
public void removeSession(HttpServletRequest request, HttpServletResponse response) {
    String name = request.getParameter("name");
    request.getSession().removeAttribute(name);
}

}

https://img-blog.csdn.net/20170226152438708?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGxnZW4xNTczODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
(7)測試

訪問鏈接:http://localhost:8080/spring/session/setSession.do?name=xuiliugen&value=123456

使用工具查看Redis內容:
https://img-blog.csdn.net/20170226154301031?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGxnZW4xNTczODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

可以發現已經有值了!並且有expirations,可以看到箭頭指向的位置,是失效的時間記錄值!

(8)到此,Spring Session的使用已經完成!其他具體的細節請參考:http://git.oschina.net/xuliugen/spring-session-demo 項目源代碼。

總結:
對於分布式環境Session跨域共享的問題,不管是使用開源的框架還是使用自己開發的框架,都需要明白的一個問題是:在Tomcat容器中創建Session是一個很耗費內存的事情。因此,我們在自己寫類似框架的時候,我們一定要註意的是,並不是Tomcat為我們創建好了Session之後,我們首先獲取Session然後再上傳到Redis等進行存儲,而是直接有我們自己創建Session,這一點是至關重要的!
額外分享一些自己的收藏:
高可用集群架構技術進階篇手把手教你玩轉Nginx與Docker
鏈接: https://pan.baidu.com/s/1A7Z75fN2UR-cteT8fgY5kQ 密碼: yp8e
走向架構師,你必須了解的Java虛擬機高級特性
鏈接: https://pan.baidu.com/s/1hAPo19keNFHb9ycBctkU2A 密碼: iayw
高並發處理技術老司機帶你玩RabbitMq實現性能倍增
鏈接: https://pan.baidu.com/s/1nZNYXtqkmEAHPm1JKT2iBg 密碼: y3sa
2018.07.09-T5架構師帶你解讀Spring源碼~手寫SpringMVC實戰
鏈接: https://pan.baidu.com/s/1-NRFpVQd0TLyzT1SjskXdg 密碼: 6amk

使用Spring Session和Redis解決分布式Session跨域共享問題