前言
Web開發中session一直都是做分布式叢集應用時需要解決的一個難題,前面寫了tomcat伺服器叢集的文章,那麼叢集中怎麼實作session共享呢?
讓我們回顧一下,Tomcat叢集搭建(APACHE+MOD_JK+TOMCAT配置)的session共享是直接通過tomcat自帶的複制功能,即通路其中一台tomcat伺服器就會在其他配置好的tomcat伺服器上各複制一份session,這樣的session共享可能會存在一定的延遲,同時若并發量一大的話也會有網絡風暴的風險;而Tomcat叢集搭建(nginx+tomcat+redis)的session共享是通過第三方redis來存儲session來實作的,這種方式以前比較流行,但是這種也是需要依賴于tomcat(需要修改tomcat的context.xml的配置),而且現在官方也沒去更新tomcat-redis-session-manager的jar包了(仍然停留在支援tomcat7上,一些自己修改的jar包除外)。
本文我要說的session共享,就是獨立于servlet容器、session存儲在第三方存儲容器redis的spring session。它不用擔心并發量大時的網絡風暴;不用擔心換容器運作時,又需要各種各樣的配置;不用擔心servlet容器(即這裡的tomcat)停止服務了,session就不存在了,使用者又得重新登陸的麻煩。因為spring session的實作是在項目的配置,以及代碼中的。
在開始之前,讓我們先了解一下spring session實作session共享,以及在spring架構中實作的原理。
spring session通過redis來實作多個伺服器間的session共享的原理,其實就是将session獨立出來不依賴原來的web容器,而是存放到與web容器沒有耦合關系的redis容器中,這樣即使是任何一台web容器挂了,也不會影響到其中的session,如下圖所示:
spring session在spring架構中的實作原理,其實就是在請求request上通過DelegatingFilterProxy代理過濾器封裝了一層,将原來存儲在容器緩存的session變成存儲在redis的session,是以在web.xml中的此filter必須得是在所有filter的前面。具體的web容器加載過程,如下圖所示:
準備工作
這裡的準備工作有幾點,redis存儲容器、spring架構的jar包(需要在spring架構的環境下)和spring session的相關jar包。其中redis存儲容器,我就不多說了,網上有很多redis的安裝教程。
spring架構的jar包
若你的項目原本就有spring架構的,忽略此準備工作。
commons-logging選擇1.2版本:http://commons.apache.org/proper/commons-logging/download_logging.cgi
spring-aop選擇5.0.8版本:http://www.mvnrepository.com/artifact/org.springframework/spring-aop
spring-beans選擇5.0.8版本:http://mvnrepository.com/artifact/org.springframework/spring-beans
spring-context選擇5.0.8版本:http://mvnrepository.com/artifact/org.springframework/spring-context
spring-core選擇5.0.8版本:http://mvnrepository.com/artifact/org.springframework/spring-core
spring-expression選擇5.0.8版本:http://mvnrepository.com/artifact/org.springframework/spring-expression
spring-web選擇5.0.8版本:http://mvnrepository.com/artifact/org.springframework/spring-web
spring-webmvc選擇5.0.8版本:http://www.mvnrepository.com/artifact/org.springframework/spring-webmvc
spring-tx選擇5.0.8版本:http://mvnrepository.com/artifact/org.springframework/spring-tx
spring session的相關jar包
commons-pool2選擇2.6.0版本:http://commons.apache.org/proper/commons-pool/download_pool.cgi
jedis選擇2.9.0版本:http://central.maven.org/maven2/redis/clients/jedis/
spring-data-commons選擇2.0.9版本:https://repo.spring.io/libs-release/org/springframework/data/spring-data-commons/
spring-data-redis選擇2.0.9版本:https://repo.spring.io/libs-release/org/springframework/data/spring-data-redis/
spring-session-core選擇2.0.5版本:http://mvnrepository.com/artifact/org.springframework.session/spring-session-core
spring-session-data-redis選擇2.0.5版本:https://repo.spring.io/libs-release/org/springframework/session/spring-session-data-redis/
Ps:這裡用的spring都是比較高版本的,最好用本文的相應版本,否則會出現一些版本不合的奇葩錯誤。
具體步驟
建立項目
(1)在eclipse中建立一個web項目,命名為SpringSession。
(2)将上面下載下傳的jar包全放到項目中WEB-INF/lib目錄下。
spring mvc的配置
(1)在源碼的src目錄下建立config放置配置檔案的包,以及建立com.sky.springsession放置java源碼的包。
(2)在config包下建立applicationContext.xml檔案,放置如下代碼
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.sky.springsession"/>
<context:annotation-config/>
</beans>
spring session的配置
在config包下建立spring-session.xml檔案,放置如下代碼
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="10"/><!-- 最大空閑連接配接數, 預設8個 -->
<property name="maxTotal" value="20"/><!-- 最大連接配接數, 預設8個 -->
<property name="blockWhenExhausted" value="true"/><!-- 連接配接耗盡時是否阻塞, false報異常,ture阻塞直到逾時, 預設true -->
<property name="maxWaitMillis" value="1000"/><!-- 擷取連接配接時的最大等待毫秒數(如果設定為阻塞時BlockWhenExhausted),如果逾時就抛異常, 小于零:阻塞不确定的時間, 預設-1 -->
<property name="testOnBorrow" value="true"/><!-- 在擷取連接配接的時候檢查有效性, 預設false -->
</bean>
<!-- redis連接配接配置,依次為主機ip,端口,密碼,是否使用池,連接配接池配置引用 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="192.168.17.132" p:port="6379" p:password="123456" p:usePool="true" p:pool-config-ref="jedisPoolConfig">
</bean>
<!-- 配置spring-session -->
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<!-- session過期時間,機關是秒 -->
<property name="maxInactiveIntervalInSeconds" value="30"></property>
</bean>
</beans>
Ps:注意,用了spring session後,在web.xml設定的session過期時間是無效。因為此session已經不是原來的存儲在容器緩存内的session了,而是被封裝多了一層後存儲在redis的session。
web.xml的配置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 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>
</filter-mapping>
<!-- springmvc配置 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 通過初始化參數,指定xml檔案的位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Ps:配置至此,就能夠實作spring session通過存儲到redis的session共享了。
建立jsp測試檔案
(1)在WebContent目錄下建立index.jsp,放置如下代碼
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.util.Date" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>spring session</title>
</head>
<body>
<%
System.out.println(new Date()+"=============tomcat1=================");
session.setAttribute("tomcat1", "I am tomcat1");
%>
<h1>hello world!</h1><br>
<p><%=session.getId()%>======<%=new Date()%></p>
<p>tomcat1======<%=session.getAttribute("tomcat1")%></p>
<p>tomcat2======<%=session.getAttribute("tomcat2")%></p>
</body>
</html>
(2)在WebContent目錄下建立result.jsp,放置如下代碼
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.util.Date" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>spring session</title>
</head>
<body>
<h1>result tomcat1</h1><br>
<p><%=session.getId()%>======<%=new Date()%></p>
<p>tomcat1======<%=session.getAttribute("tomcat1")%></p>
<p>tomcat2======<%=session.getAttribute("tomcat2")%></p>
</body>
</html>
開始測試
(1)打開浏覽器,輸入url:http://localhost:8080/SpringSession/
(2)打開第二個标簽頁
(3)待30秒過後,重新整理第二個标簽頁,發現tomcat1已經為null,說明上面的spring session的過期時間設定是有效的