Spring Session+Spring Data Redis 解決分散式系統架構中 Session 共享問題

zifangsky發表於2019-03-04

一 簡介

如題所示,在分散式系統架構中需要解決的一個很重要的問題就是——如何保證各個應用節點之間的Session共享。其實有一個很好的解決辦法就是在redis、memcached等元件中獨立儲存所有應用節點的Session,以達到各個應用節點之間的Session共享的目的

Spring Session提供了一種獨立於應用伺服器的方案,這種方案能夠在Servlet規範之內配置可插拔的session資料儲存,不依賴於任何應用伺服器的特定API。這就意味著Spring Session能夠用於實現了servlet規範的所有應用伺服器之中(Tomcat、Jetty、 WebSphere、WebLogic、JBoss等),它能夠非常便利地在所有應用伺服器中以完全相同的方式進行配置。同時我們還可以選擇任意最適應需求的外部session資料儲存方式

Spring Session為企業級Java應用的session管理帶來了革新,使得以下的功能更加容易實現:

  • 編寫可水平擴充套件的原生雲應用
  • 將session所儲存的狀態儲存到特定的外部session儲存中,如Redis或Apache Geode中,它們能夠以獨立於應用伺服器的方式提供高質量的叢集
  • 當使用者使用WebSocket傳送請求的時候,能夠保持HttpSession處於活躍狀態
  • 在非Web請求的處理程式碼中,能夠訪問session資料,比如在JMS訊息的處理程式碼中
  • 支援每個瀏覽器上使用多個session,從而能夠很容易地構建更加豐富的終端使用者體驗
  • 控制session id如何在客戶端和伺服器之間進行交換,這樣的話就能很容易地編寫Restful API,因為它可以從HTTP 頭資訊中獲取session id,而不必再依賴於cookie

需要說明的很重要的一點就是,Spring Session的核心專案並不依賴於Spring框架,所以,我們甚至能夠將其應用於不使用Spring框架的專案中

二 程式碼示例

其實,我們想要在專案中使用Spring Session需要做的工作並不多,只需要做到以下幾步即可:

(1)引入相關jar包:

這裡需要引入Spring Session和Spring Data Redis相關的依賴jar包。可以自行從maven倉庫下載,也可以參考我使用的jar包:down.51cto.com/data/228183…

(2)修改spring-data-redis相關配置:

在專案的spring-data-redis相關配置中新增以下配置:

  <bean id="redisHttpSessionConfiguration"
          class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <!--超時時間,預設1800秒-->
        <property name="maxInactiveIntervalInSeconds" value="1800" />
    </bean>複製程式碼

完整的context_redis_cluster.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:context="http://www.springframework.org/schema/context"
    xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
            http://www.springframework.org/schema/jee 
            http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
            http://www.springframework.org/schema/context 
            http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    <description>spring-data-redis-cluster</description>

    <!-- 配置Cluster -->
    <bean id="redisClusterConfiguration"
        class="org.springframework.data.redis.connection.RedisClusterConfiguration">
        <property name="maxRedirects" value="${redis.cluster.maxRedirects}" />
        <!-- 節點配置 -->
        <property name="clusterNodes">
            <set>
                <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                    <constructor-arg name="host" value="${redis.cluster.host1}" />
                    <constructor-arg name="port" value="${redis.cluster.port1}" />
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                    <constructor-arg name="host" value="${redis.cluster.host2}" />
                    <constructor-arg name="port" value="${redis.cluster.port2}" />
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                    <constructor-arg name="host" value="${redis.cluster.host3}" />
                    <constructor-arg name="port" value="${redis.cluster.port3}" />
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                    <constructor-arg name="host" value="${redis.cluster.host4}" />
                    <constructor-arg name="port" value="${redis.cluster.port4}" />
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                    <constructor-arg name="host" value="${redis.cluster.host5}" />
                    <constructor-arg name="port" value="${redis.cluster.port5}" />
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                    <constructor-arg name="host" value="${redis.cluster.host6}" />
                    <constructor-arg name="port" value="${redis.cluster.port6}" />
                </bean>
            </set>
        </property>
    </bean>

    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="${redis.cluster.jedisPoolConfig.maxTotal}" />
        <property name="maxIdle" value="${redis.cluster.jedisPoolConfig.maxIdle}" />
        <property name="maxWaitMillis"
            value="${redis.cluster.jedisPoolConfig.maxWaitMillis}" />
        <property name="testOnBorrow"
            value="${redis.cluster.jedisPoolConfig.testOnBorrow}" />
    </bean>

    <bean id="jedisConnectionFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <constructor-arg ref="redisClusterConfiguration" />
        <constructor-arg ref="jedisPoolConfig" />
        <property name="password" value="${redis.cluster.jedisConnectionFactory.password}" />
    </bean>

    <bean id="stringRedisSerializer"
        class="org.springframework.data.redis.serializer.StringRedisSerializer" />
    <bean id="jdkSerializer"
        class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />

    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
        p:connection-factory-ref="jedisConnectionFactory">
        <!-- 序列化方法 -->
        <property name="keySerializer" ref="stringRedisSerializer" />
        <property name="hashKeySerializer" ref="stringRedisSerializer" />
        <property name="valueSerializer" ref="jdkSerializer" />
        <property name="hashValueSerializer" ref="jdkSerializer" />
    </bean>

    <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"
        p:connection-factory-ref="jedisConnectionFactory" />

    <bean id="redisHttpSessionConfiguration"
          class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <!--超時時間,預設1800秒-->
        <property name="maxInactiveIntervalInSeconds" value="1800" />
    </bean>
</beans>複製程式碼

注:對應的屬性檔案是:

#Redis Cluster
redis.cluster.maxRedirects=3

redis.cluster.host1=192.168.1.30
redis.cluster.port1=7000

redis.cluster.host2=192.168.1.30
redis.cluster.port2=7001

redis.cluster.host3=192.168.1.30
redis.cluster.port3=7002

redis.cluster.host4=192.168.1.224
redis.cluster.port4=7003

redis.cluster.host5=192.168.1.224
redis.cluster.port5=7004

redis.cluster.host6=192.168.1.224
redis.cluster.port6=7005

#JedisPoolConfig
redis.cluster.jedisPoolConfig.maxTotal=1024
redis.cluster.jedisPoolConfig.maxIdle=20
redis.cluster.jedisPoolConfig.maxWaitMillis=100000
redis.cluster.jedisPoolConfig.testOnBorrow=true

#JedisConnectionFactory
redis.cluster.jedisConnectionFactory.password=admin複製程式碼

(3)修改web.xml:

在web.xml中新增以下filter:

    <!-- 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>複製程式碼

注:需要將這個filter放在第一位,其他的如編碼、shiro等filter需要放在這之後

到此,Spring Session和Spring Data Redis就整合到專案中了

(4)測試:

執行專案後,可以發現生成了一個名為“SESSION”的cookie。接著在我們登入之後,可以通過其cookie值在redis上取得儲存的對應的session物件。如下圖所示:

Spring Session+Spring Data Redis 解決分散式系統架構中 Session 共享問題

從上圖可以發現,登入之後的使用者物件已經儲存到redis叢集中了。接著還可以測試將同樣的專案釋出到其他埠的tomcat上,在同一瀏覽器上訪問需要登入認證之後才能訪問的頁面,看看是否能夠直接訪問。如果能夠直接訪問而不是重定向到登入頁面,則說明已經達到session共享的效果了

參考:

相關文章