前言
對(duì)于分布式使用Nginx+Tomcat實(shí)現(xiàn)負(fù)載均衡菲饼,最常用的均衡算法有IP_Hash吵护、輪訓(xùn)、根據(jù)權(quán)重达布、隨機(jī)等团甲。不管對(duì)于哪一種負(fù)載均衡算法,由于Nginx對(duì)不同的請(qǐng)求分發(fā)到某一個(gè)Tomcat黍聂,Tomcat在運(yùn)行的時(shí)候分別是不同的容器里躺苦,因此會(huì)出現(xiàn)session不同步或者丟失的問題。
實(shí)際上實(shí)現(xiàn)Session共享的方案很多产还,其中一種常用的就是使用Tomcat匹厘、Jetty等服務(wù)器提供的Session共享功能,將Session的內(nèi)容統(tǒng)一存儲(chǔ)在一個(gè)數(shù)據(jù)庫(kù)(如MySQL)或緩存(如Redis)中脐区。
在以前寫的一篇文章中:
使用Redis存儲(chǔ)Nginx+Tomcat負(fù)載均衡集群的Session:http://blog.csdn.net/xlgen157387/article/details/52024139
這一篇文章中已經(jīng)學(xué)習(xí)了一下集乔,如何使用 tomcat-redis-session-manager 開源項(xiàng)目解決分布式session跨域的問題,他的主要思想是利用Servlet容器提供的插件功能坡椒,自定義HttpSession的創(chuàng)建和管理策略扰路,并通過配置的方式替換掉默認(rèn)的策略。tomcat-redis-session-manager重寫了Tomcat的org.apache.catalina.session.ManagerBase里邊的具體寫的操作倔叼, 將tomcat的session存儲(chǔ)位置指向了Redis:
RedisSessionManager繼承了org.apache.catalina.session.ManagerBase并重寫了add汗唱、findSession、createEmptySession丈攒、remove等方法哩罪,并將對(duì)session的增刪改查操作指向了對(duì)Redis數(shù)據(jù)存儲(chǔ)的操作授霸。
有興趣可參考一篇Tomcat中session的管理機(jī)制:http://www.cnblogs.com/interdrp/p/4935614.html
不過使用過tomcat-redis-session-manager 的都應(yīng)該知道,配置相對(duì)還是有一點(diǎn)繁瑣的际插,需要人為的去修改Tomcat的配置碘耳,需要耦合Tomcat等Servlet容器的代碼,并且對(duì)于分布式Redis集群的管理并不是很好框弛,與之相對(duì)的個(gè)人認(rèn)為比較好的一個(gè)框架Spring Session可以真正對(duì)用戶透明的去管理分布式Session辛辨。
Spring Session不依賴于Servlet容器,而是Web應(yīng)用代碼層面的實(shí)現(xiàn)瑟枫,直接在已有項(xiàng)目基礎(chǔ)上加入spring Session框架來(lái)實(shí)現(xiàn)Session統(tǒng)一存儲(chǔ)在Redis中斗搞。如果你的Web應(yīng)用是基于Spring框架開發(fā)的,只需要對(duì)現(xiàn)有項(xiàng)目進(jìn)行少量配置慷妙,即可將一個(gè)單機(jī)版的Web應(yīng)用改為一個(gè)分布式應(yīng)用僻焚,由于不基于Servlet容器,所以可以隨意將項(xiàng)目移植到其他容器膝擂。
Spring Session使用
官方地址:http://projects.spring.io/spring-session/
官方文檔地址:http://docs.spring.io/spring-session/docs/1.3.0.RELEASE/reference/html5/
Spring Session提供了一套創(chuàng)建和管理Servlet HttpSession的方案虑啤。Spring Session提供了集群Session(Clustered Sessions)功能,默認(rèn)采用外置的Redis來(lái)存儲(chǔ)Session數(shù)據(jù)架馋,以此來(lái)解決Session共享的問題咐旧。
一、特性
Spring Session提供以下特性:
API和用于管理用戶會(huì)話的實(shí)現(xiàn)绩蜻;
HttpSession - 允許以應(yīng)用程序容器(即Tomcat)中性的方式替換HttpSession;
Clustered Sessions - Spring Session讓支持集群會(huì)話變得不那么繁瑣室埋,并且不和應(yīng)用程序容器金習(xí)性綁定到办绝。
Multiple Browser Sessions - Spring會(huì)話支持在單個(gè)瀏覽器實(shí)例中管理多個(gè)用戶的會(huì)話。
RESTful APIs - Spring Session允許在headers 中提供會(huì)話ID以使用RESTful API姚淆。
二孕蝉、基于XML配置方式的Spring Session案例實(shí)現(xiàn)
基于SSM框架的一個(gè)小案例,Git OS項(xiàng)目代碼地址:http://git.oschina.net/xuliugen/spring-session-demo
項(xiàng)目展示:
(1)基本環(huán)境需求
進(jìn)行使用Spring Session的話腌逢,首先的是已經(jīng)安裝好的有一個(gè) Redis服務(wù)器降淮!
(2)添加項(xiàng)目依賴(最基本的依賴使用)
<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>
1
2
3
4
5
6
7
8
9
10
11
(3)添加Spring配置文件
添加了必要的依賴之后,我們需要?jiǎng)?chuàng)建相應(yīng)的Spring配置搏讶。Spring配置是要?jiǎng)?chuàng)建一個(gè)Servlet過濾器佳鳖,它用Spring Session支持的HttpSession實(shí)現(xiàn)來(lái)替換容器本身HttpSession實(shí)現(xiàn)。這一步也是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"/>
1
2
3
4
5
上述代碼注釋:
LettuceConnectionFactory實(shí)例是配置Redis的ConnectionFactory系吩。
注意:
<bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
查看源代碼可以看到,默認(rèn)的Redis鏈接配置為:
因此妒蔚,如果有自己的Redis配置穿挨,請(qǐng)修改月弛,例如下邊的配置:
<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>
(5)關(guān)于Error creating bean with name ‘enableRedisKeyspaceNotificationsInitializer’錯(cuò)誤的處理:
添加如下配置讓Spring Session不再執(zhí)行config命令
<util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>
1
如果不添加的話,會(huì)報(bào)如下錯(cuò)誤:
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并將其轉(zhuǎn)換為過濾器科盛。對(duì)于調(diào)用DelegatingFilterProxy的每個(gè)請(qǐng)求帽衙,也將調(diào)用springSessionRepositoryFilter。
(6)Spring MVC controller代碼用于測(cè)試:
@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);
}
}
(7)測(cè)試
訪問鏈接:http://localhost:8080/spring/session/setSession.do?name=xuiliugen&value=123456
使用工具查看Redis內(nèi)容:
可以發(fā)現(xiàn)已經(jīng)有值了贞绵!并且有expirations厉萝,可以看到箭頭指向的位置,是失效的時(shí)間記錄值但壮!
(8)到此冀泻,Spring Session的使用已經(jīng)完成!其他具體的細(xì)節(jié)請(qǐng)參考:http://git.oschina.net/xuliugen/spring-session-demo 項(xiàng)目源代碼蜡饵。
總結(jié)
對(duì)于分布式環(huán)境Session跨域共享的問題弹渔,不管是使用開源的框架還是使用自己開發(fā)的框架,都需要明白的一個(gè)問題是:在Tomcat容器中創(chuàng)建Session是一個(gè)很耗費(fèi)內(nèi)存的事情溯祸。因此肢专,我們?cè)谧约簩戭愃瓶蚣艿臅r(shí)候,我們一定要注意的是焦辅,并不是Tomcat為我們創(chuàng)建好了Session之后博杖,我們首先獲取Session然后再上傳到Redis等進(jìn)行存儲(chǔ),而是直接有我們自己創(chuàng)建Session筷登,這一點(diǎn)是至關(guān)重要的剃根!