用 Spring Session on Redis 来管理 HttpSession

本贴最后更新于 2979 天前,其中的信息可能已经物是人非

传统 HttpSession 有什么缺陷?

传统的 HttpSession 是存储在实现了 Servlet 标准的容器中的,其实现逻辑完全由容器自身完成,session 的整个生命周期,只谈产生和销毁,第一次发出请求由容器产生,过了有效时间或者是容器重启或者关闭失效,表面上看这样视乎是没有问题的,但是随着用户数的增多,软件架构发生变化,这样的 session 方案视乎已经不能完全满足需求

  • 用户数的增多会导致容器内部存储 session 数量增大,从而在 jvm 中产生大量的数据,GC 回收效率降低
  • 多 node 服务架构,粘性 session 问题,比如负载均衡之后节点(nginx 反向代理,LVS 负载等) session 共享

从上面的 2 个方面来看传统的 HttpSession 已经逐渐的不能胜任大型站点架构方案,我们需要找出新的 session 方案。

Session 方案

  • Tomcat Session on Redis or Memcached
  • Jetty Session on Redis
  • Spring Session

上面的几种方案,或者是 N 种方案,都是基于容器实现的,对容器的依赖度比较高。在之前的 XXX 软件中曾经使用第一种方案,但是这种方案是完全针对某一特定容器,跨容器部署的时候就显得力不从心,而且维护起来十分麻烦,曾把单 node 的 Redis 改到 redis-cluster 上用了很多时间,中间也出了很多问题,所以不推荐针对某一容器的这种 session 方案。推荐 Spring Session 的原因是 Spring Session 是完全实现了 Servlet 标准的 Session 方案,并不关心到底是使用什么容器。

参考文档

Minimum Requirements

  • Java 5+
  • If you are running in a Servlet Container (not required), Servlet 2.5+
  • If you are using other Spring libraries (not required), the minimum required version is Spring 3.2.14. While we re-run all unit tests against Spring 3.2.x, we recommend using the latest Spring 4.x version when possible.
  • @EnableRedisHttpSession requires Redis 2.8+. This is necessary to support Session Expiration

Setup

  • 添加 Maven 依赖
    pom.xml
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session</artifactId>
    <version>1.2.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
    <version>1.2.1.RELEASE</version>
</dependency>
  • 添加 springSessionRepositoryFilter
    由于 spring-data-redis 用到了 spring 框架,所以需要加载 spring 配置文件并且添加 spring-web 依赖
    pom.xml
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>4.2.5.RELEASE</version>
</dependency>

web.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring-session-redis-single.xml</param-value>
</context-param>
 <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
<!-- 注springSessionRepositoryFilter必须放在最前面 -->
<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>

  • 编写 /WEB-INF/spring-session-redis-single.xml
    下面是 spring 官方给出的示例配置,并没有发现从哪里配置 Redis 的相关信息
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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.2.xsd
          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
          http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
          http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
    <context:annotation-config/>
    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
    <bean class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"/>  
</beans>

经过研究得到如下配置

<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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.2.xsd
          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
          http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
          http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">   
    <context:annotation-config/>
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="30"/>
        <property name="maxIdle" value="10"/>
        <property name="minIdle" value="1"/>
        <property name="maxWaitMillis" value="30000"/>
        <property name="testOnBorrow" value="true"/>
        <property name="testOnReturn" value="false"/>
        <property name="testWhileIdle" value="false"/>
    </bean>
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
	<property name="hostName" value="127.0.0.1" />
	<property name="port" value="6379" />
	<property name="password" value="" />
	<property name="timeout" value="3000" />
	<property name="poolConfig" ref="jedisPoolConfig" />
	<property name="usePool" value="true" />
    </bean>
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
	<property name="connectionFactory" ref="jedisConnectionFactory"/>
    </bean>
    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
	<property name="maxInactiveIntervalInSeconds" value="1800" />
    </bean>
</beans>

深入研究

  • Session 的跨域问题, cookiepath 和 domain
  • 修改 Session 存在 cookie 中名字,怕与其他程序有影响
  • org.springframework.session.web.http.SessionRepositoryFilter
  • org.springframework.session.web.http.CookieHttpSessionStrategy
  • org.springframework.session.web.http.DefaultCookieSerializer
    翻阅 spring-session 的源代码,DefaultCookieSerializer 的 writeCookieValue 方法,getCookiePath 方法,getDomainName 方法可以得知 spring-session 中没有对跨域问题进行处理,但是他允许我们 IOC 属性来修改实现逻辑
<bean class="org.springframework.session.web.http.DefaultCookieSerializer">
    <!-- 我去掉了这个地方的value值,请根据自己需要填写 -->
	<property name="cookiePath" value="" />
    <property name="domainName" value="" />
    <property name="cookieName" value="" />
</bean>
  • 如何支持 Redis Cluster (没有经过测试,由于 Azure 的 Redis 没有看到提供 Redis Cluster 的方式,顾暂时不考虑,后期可做深入研究)

  • org.springframework.data.redis.connection.jedis.JedisConnectionFactory

  • org.springframework.data.redis.connection.RedisClusterConfiguration

  • org.springframework.data.redis.connection.RedisNode

<bean id="clusterNodes1" class="org.springframework.data.redis.connection.RedisNode">
    <!-- 可以采用property给每个属性赋值,但是目前并不知道那个节点是Master Node,所以这个部分交给Srping自己完成 -->
    <constructor-arg index="0" name="host" value="" />
    <constructor-arg index="1" name="port" value="" />
</bean>
<bean id="clusterConfig" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
    <property name="clusterNodes">
        <set>
            <ref bean="clusterNodes1" />
            ...
        </set>
    </property>
</bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <property name="timeout" value="300" />
    <property name="poolConfig" ref="jedisPoolConfig" />
    <property name="password" value="" />
    <property name="usePool" value="true" />
    <property name="clusterConfig" ref="clusterConfig" />
</bean>
  • Spring Session 支持其他的存储方案
  • MongoDB
  • JDBC

Testing Spring Session on Redis

下面的内容更新于 2016 年 10 月 26 日 早 9 时,注意配置文件里面是用的单实例的 Redis,如果用集成环境的 Redis,自己稍加改造就出来了,配置文件上面已经给出了,需要注意的东西我会放在打赏区

  • 下载源代码 http://7bvamo.com1.z0.glb.clouddn.com/spring-session.zip (源代码已经更新)
  • 修改/spring-session/src/main/resources/META-INF/configuration/environments/application.properties 中对于 redis 的配置和相关的 cookie 配置
  • mvn clean package
  • 启动 2 个不同端口的容器
  • 同一个浏览器访问不同端口的 /login 看看什么情况,注意观察 redis 中的变化和浏览器 cookie 部分
  • 换一个浏览器访问任意一个端口的/login 再看看 redis 中的变化
  • 删除掉 redis 中的所有数据, 改变之前访问过的浏览器地址为/login 再看看什么情况
打赏 25 积分后可见
25 积分 • 12 打赏
  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    943 引用 • 1460 回帖 • 3 关注
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    142 引用 • 442 回帖 • 1 关注
  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    286 引用 • 248 回帖 • 44 关注
  • HTTP
    75 引用 • 128 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • Angonger

    我需要的时候再看,目前观望,等待评价

  • 714593351

    必须用 Redis 吗

    1 回复
  • tomaer 1
    作者

    @714593351 可以用别的,你没仔细看,看了就不会这样问我

  • 714593351

    @tomaer 下载你的代码看了,貌似有问题啊,对着官方 sample 试了,挺好用的,配置简单。

    就是我们项目用的还是 spring3.2.6,不支持啊。。。

  • tomaer
    作者

    @714593351 什么问题你说说呗。我在我们的项目中就是这样用的。也许是我的代码中哪里写错了。我不知道

  • 714593351

    @tomaer 我没有修改配置,直接运行,看到 redis 里有内容,但是 session 获取不到保存的内容

  • tomaer
    作者

    @714593351 按我的测试步骤走啊。

  • 714593351

    @tomaer
    redis 配置都一样,没有需要修改的吧

    难道需要修改这一块?




    bean>

  • 714593351

    @tomaer
    redis 配置都一样,没有需要修改的吧

    难道需要修改这一块?




    bean>

  • 714593351

    卧槽,为啥粘不上代码了。。

  • 714593351

    @tomaer
    redis 配置都一样,没有需要修改的吧

    难道需要修改这一块?

    <bean  class="org.springframework.session.web.http.DefaultCookieSerializer">
    <property  name="cookiePath" value="/" />
    <property  name="domainName" value="*.zhishinet.com" />
    <property  name="cookieName" value="zswsessionid" />
    <bean>
    
  • youymi

    楼主,相当于只要这么配置,httpsession 自动启用 redis 的存储,而不使用容器的方式?

  • tomaer
    作者

    @youymi bingo。答对了

  • tomaer
    作者

    @714593351 这个部分你注释掉先

  • 714593351

    @tomaer
    为什么取不出值?
    111.png

  • tomaer
    作者

    @714593351 用 RedisDesktopManager 而且这个值是序列化过的。你觉得你这样能取出来吗

  • 714593351

    @tomaer
    用 jedis 也取不出来。。。也是报这个错,其实我就是想知道 value 是什么

  • tomaer
    作者

    @714593351 好像是进制级别的

  • hp245120020

    get

  • zjhch123

    下面是 spring 官方给出的示例配置, 并没有发现从哪里配置 Redis 的相关信息
    ...
    经过研究得到如下配置

    看到这里的时候笑了…当时研究 Spring Data JPA 的时候对着文档撸也是满满的坑,各种少配置 QAQ

  • stewarddeng

    留部 以后官网,追逐大牛的脚步

  • someone1764

    置顶了吗

    1 回复
  • tomaer
    作者

  • mainlove

    我很好奇的是 它怎么实现一个游览器可以有多个 seesion 的。。 怎么样标示呢?

    1 回复
  • tomaer
    作者

    用 cookie 实现的。

    1 回复
  • mainlove

    官方 有策略说 不用 cookie 能实现 根据 http header 就能实现 不知道它怎么搞的

  • Sue

    同问

请输入回帖内容 ...