天天看點

Spring - Spring Session Redis 共享

前言

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 - Spring Session Redis 共享

spring session在spring架構中的實作原理,其實就是在請求request上通過DelegatingFilterProxy代理過濾器封裝了一層,将原來存儲在容器緩存的session變成存儲在redis的session,是以在web.xml中的此filter必須得是在所有filter的前面。具體的web容器加載過程,如下圖所示:

Spring - Spring Session Redis 共享

準備工作

這裡的準備工作有幾點,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/​​

Spring - Spring Session Redis 共享

(2)打開第二個标簽頁

Spring - Spring Session Redis 共享

(3)待30秒過後,重新整理第二個标簽頁,發現tomcat1已經為null,說明上面的spring session的過期時間設定是有效的

Spring - Spring Session Redis 共享