分布式Session:基于Spring-Session 和 Redis实现

来源:互联网 发布:ubuntu 新建文档 编辑:程序博客网 时间:2024/06/06 02:04
  1. 前言
    1. 在Web项目开发中,会话管理是一个很重要的部分,用于存储与用户相关的数据。通常是由符合session规范的容器来负责存储管理
    2. 也就是一旦容器关闭,重启会导致会话失效。因此打造一个高可用性的系统,必须将session管理从容器中独立出来
    3. 共享Session问题

      HttpSession是通过Servlet容器创建和管理的,像Tomcat/Jetty都是保存在内存中的。而如果我们把web服务器搭建成分布式的集群,然后利用LVS或Nginx做负载均衡,那么来自同一用户的Http请求将有可能被分发到两个不同的web站点中去。那么问题就来了,如何保证不同的web站点能够共享同一份session数据呢?

      最简单的想法就是把session数据保存到内存以外的一个统一的地方,例如Memcached/Redis等数据库中。那么问题又来了,如何替换掉Servlet容器创建和管理HttpSession的实现呢?
      (1)设计一个Filter,利用HttpServletRequestWrapper,实现自己的 getSession()方法,接管创建和管理Session数据的工作spring-session就是通过这样的思路实现的。
      (2)利用Servlet容器提供的插件功能,自定义HttpSession的创建和管理策略,并通过配置的方式替换掉默认的策略。不过这种方式有个缺点,就是需要耦合Tomcat/Jetty等Servlet容器的代码
    4.          这方面其实早就有开源项目了,例如memcached-session-manager,以及tomcat-redis-session-manager。暂时都只支持Tomcat6/Tomcat7。
    5. 而这实现方案有很多种,下面简单介绍下:
    6. 第一种
    7. 是使用容器扩展来实现,大家比较容易接受的是通过容器插件来实现,比如基于Tomcat的tomcat-redis-session-manager基于Jetty的jetty-session-redis等等。
    8. 好处是对项目来说是透明的,无需改动代码。不过前者目前还不支持Tomcat 8,或者说不太完善。
    9. 个人觉得由于过于依赖容器,一旦容器升级或者更换意味着又得从新来过。
    10. 并且代码不在项目中,对开发者来说维护也是个问题。
    11. 第二种
    12. 是自己写一套会话管理的工具类,包括Session管理和Cookie管理,在需要使用会话的时候都从自己的工具类中获取,而工具类后端存储可以放到Redis中。
    13. 很显然这个方案灵活性最大,但开发需要一些额外的时间。
    14. 并且系统中存在两套Session方案,很容易弄错而导致取不到数据。
    15. 第三种【本文即是这种方式】
    16. 是使用框架的会话管理工具,也就是本文要说的spring-session,可以理解是替换了Servlet那一套会话管理,
    17. 既不依赖容器,又不需要改动代码,并且是用了spring-data-redis那一套连接池,可以说是最完美的解决方案。
    18. 当然,前提是项目要使用Spring Framework才行。

  2. Spring-session官网的特性介绍
    1. Features
    2. Spring Session provides the following features:
    3. API and implementations for managing a user's session
    4. HttpSession - allows replacing the HttpSession in an application container (i.e. Tomcat) neutral way
    5. Clustered Sessions - Spring Session makes it trivial[平常的,平凡的; 不重要的] to support clustered sessions without being tied to an application container specific solution.
    6. Multiple Browser Sessions - Spring Session supports managing multiple users' sessions in a single browser instance (i.e. multiple authenticated accounts similar to Google).
    7. RESTful APIs - Spring Session allows providing session ids in headers to work with RESTful APIs
    8. WebSocket - provides the ability to keep the HttpSession alive when receiving WebSocket messages


  3. 环境准备
    1. redis安装版本:redis-3.2.5.tar.gz
    2. JDK版本:1.7+
    3. Spring版本:4.1+
    4. Spring-session版本:1.2.1.RELEASE

  4.   Maven依赖
    1. <!-- 指定版本号 -->
    2. <properties>
    3. <!-- Spring -->
    4. <spring.version>4.1.5.RELEASE</spring.version>
    5. <!-- JEDIS -->
    6. <jedis.version>2.8.1</jedis.version>
    7. <!-- Spring Session -->
    8. <spring-session-data-redis.version>1.2.1.RELEASE</spring-session-data-redis.version>
    9. </properties>
    10. <!-- 使用Spring Session做分布式会话管理 -->
    11. <dependency>
    12. <groupId>org.springframework.session</groupId>
    13. <artifactId>spring-session-data-redis</artifactId>
    14. <version>${spring-session-data-redis.version}</version>
    15. </dependency>
    16. <!-- JEDIS -->
    17. <dependency>
    18. <groupId>redis.clients</groupId>
    19. <artifactId>jedis</artifactId>
    20. <version>${jedis.version}</version>
    21. </dependency>

  5.   web.xml中配置Session过滤器【springSessionRepositoryFilter
    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3. xmlns="http://java.sun.com/xml/ns/javaee"
    4. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    5. id="WebApp_ID" version="3.0">
    6. <display-name>CloudPayment</display-name>
    7. <!-- 欢迎页面 -->
    8. <welcome-file-list>
    9. <welcome-file>index.html</welcome-file>
    10. <welcome-file>index.htm</welcome-file>
    11. <welcome-file>index.jsp</welcome-file>
    12. <welcome-file>default.html</welcome-file>
    13. <welcome-file>default.htm</welcome-file>
    14. <welcome-file>default.jsp</welcome-file>
    15. </welcome-file-list>
    16. <!-- Log4jConfigListener必须要在Spring的Listener之前 -->
    17. <!-- log4jConfigLocation -->
    18. <context-param>
    19. <param-name>log4jConfigLocation</param-name>
    20. <param-value>classpath:/config/log4j.xml</param-value>
    21. </context-param>
    22. <!-- Spring刷新Log4j配置文件变动的间隔,单位为毫秒 -->
    23. <context-param>
    24. <param-name>log4jRefreshInterval</param-name>
    25. <param-value>10000</param-value>
    26. </context-param>
    27. <!-- 加载spring容器 -->
    28. <context-param>
    29. <param-name>contextConfigLocation</param-name>
    30. <param-value>
    31. classpath:spring/applicationContext-*.xml
    32. </param-value>
    33. </context-param>
    34. <!-- Log4jConfigListener -->
    35. <listener>
    36. <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    37. </listener>
    38. <!-- ContextLoaderListener -->
    39. <listener>
    40. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    41. </listener>
    42. <!-- 在spring的普通类取session和request对象 -->
    43. <listener>
    44. <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    45. </listener>
    46. <!-- Spring MVC配置【*.do】开始 -->
    47. <servlet>
    48. <servlet-name>SpringMVC</servlet-name>
    49. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    50. <init-param>
    51. <param-name>contextConfigLocation</param-name>
    52. <param-value>classpath:spring/applicationContext-springMVC.xml</param-value>
    53. </init-param>
    54. <load-on-startup>1</load-on-startup>
    55. </servlet>
    56. <servlet-mapping>
    57. <servlet-name>SpringMVC</servlet-name>
    58. <url-pattern>*.action</url-pattern>
    59. <url-pattern>*.do</url-pattern>
    60. </servlet-mapping>
    61. <!-- Spring MVC配置【*.do】结束 -->
    62. <!-- 一般情况下Spring Session的过滤器要放在第一位 -->
    63. <filter>
    64. <filter-name>springSessionRepositoryFilter</filter-name>
    65. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    66. </filter>
    67. <filter-mapping>
    68. <filter-name>springSessionRepositoryFilter</filter-name>
    69. <url-pattern>/*</url-pattern>
    70. <dispatcher>REQUEST</dispatcher>
    71. <dispatcher>ERROR</dispatcher>
    72. </filter-mapping>
    73. <!-- Post乱码解决 (要放在最后) -->
    74. <filter>
    75. <filter-name>encodingFilter</filter-name>
    76. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    77. <init-param>
    78. <param-name>encoding</param-name>
    79. <param-value>UTF-8</param-value>
    80. </init-param>
    81. <init-param>
    82. <param-name>forceEncoding</param-name>
    83. <param-value>true</param-value>
    84. </init-param>
    85. </filter>
    86. <filter-mapping>
    87. <filter-name>encodingFilter</filter-name>
    88. <url-pattern>/*</url-pattern>
    89. </filter-mapping>
    90. </web-app>

  6.   编写applicationContext-jedis.xml,并配置RedisHttpSessionConfiguration
    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    6. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    7. <!-- 配置文件 -->
    8. <context:property-placeholder location="classpath:config/redis.properties" ignore-unresolvable="true"/>
    9. <!-- 支持注解 -->
    10. <!-- <context:annotation-config /> -->
    11. <!-- 组件扫描 -->
    12. <context:component-scan base-package="com.newcapec.cloudpay.dao"/>
    13. <!-- 将session放入redis -->
    14. <!-- <context:annotation-config /> -->
    15. <!-- RedisHttpSessionConfiguration -->
    16. <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
    17. <!-- 超时时间,默认是1800秒 -->
    18. <property name="maxInactiveIntervalInSeconds" value="${redis.session.maxInactiveIntervalInSeconds}"/>
    19. </bean>
    20. <!-- 连接池配置 -->
    21. <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
    22. <!-- 最大连接数 -->
    23. <property name="maxTotal" value="${redis.maxTotal}"/>
    24. <!-- 最大空闲连接数 -->
    25. <property name="maxIdle" value="${redis.maxIdle}"/>
    26. <!-- 每次释放连接的最大数目 -->
    27. <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/>
    28. <!-- 释放连接的扫描间隔(毫秒) -->
    29. <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
    30. <!-- 连接最小空闲时间 -->
    31. <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/>
    32. <!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 -->
    33. <property name="softMinEvictableIdleTimeMillis" value="${redis.softMinEvictableIdleTimeMillis}"/>
    34. <!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
    35. <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
    36. <!-- 在获取连接的时候检查有效性, 默认false -->
    37. <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
    38. <!-- 在空闲时检查有效性, 默认false -->
    39. <property name="testWhileIdle" value="${redis.testWhileIdle}"/>
    40. <!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
    41. <property name="blockWhenExhausted" value="${redis.blockWhenExhausted}"/>
    42. </bean>
    43. <!--JedisConnectionFactory -->
    44. <bean id="jedisConnectionFactory"
    45. class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    46. <property name="hostName" value="${redis.host}"/>
    47. <property name="port" value="${redis.port}"/>
    48. <property name="password" value="${redis.password}"/>
    49. <property name="timeout" value="${redis.timeout}"/>
    50. <property name="usePool" value="${redis.usePool}"/>
    51. <property name="poolConfig" ref="jedisPoolConfig"/>
    52. </bean>
    53. <!-- 序列化 -->
    54. <bean id="stringRedisSerializer"
    55. class="org.springframework.data.redis.serializer.StringRedisSerializer">
    56. </bean>
    57. <!-- redisTemplate配置,redisTemplate是对Jedis的对redis操作的扩展,有更多的操作,封装使操作更便捷 -->
    58. <!-- <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
    59. p:connection-factory-ref="jedisConnectionFactory" p:keySerializer-ref="stringRedisSerializer"
    60. p:valueSerializer-ref="stringRedisSerializer" p:hashKeySerializer-ref="stringRedisSerializer"
    61. p:hashValueSerializer-ref="stringRedisSerializer"> </bean> -->
    62. <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    63. <property name="connectionFactory" ref="jedisConnectionFactory"/>
    64. <property name="keySerializer">
    65. <bean
    66. class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    67. </property>
    68. <property name="valueSerializer">
    69. <bean
    70. class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
    71. </property>
    72. <property name="hashKeySerializer">
    73. <bean
    74. class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    75. </property>
    76. <property name="hashValueSerializer">
    77. <bean
    78. class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
    79. </property>
    80. </bean>
    81. <!-- 静态注入 -->
    82. <!-- <bean name="testRedisTemplate" class=""> <property name="redisTemplate"
    83. ref="redisTemplate" /> </bean> -->
    84. <!-- jedis客户端单机版(配置用户名和密码)-->
    85. <bean id="redisClient" class="redis.clients.jedis.JedisPool">
    86. <constructor-arg index="0" ref="jedisPoolConfig"/>
    87. <constructor-arg index="1" value="${redis.host}"/>
    88. <constructor-arg index="2" value="${redis.port}" type="int"/>
    89. <constructor-arg index="3" value="${redis.timeout}"
    90. type="int"/>
    91. <constructor-arg index="4" value="${redis.password}"/>
    92. </bean>
    93. </beans>

  7. redis相关配置信息
    1. ##############################【Redis-配置】【BGN】###################
    2. #++++++++++++【本地】【BGN】++++++++++
    3. #++++++++新支付平台开发++++++++++
    4. ## redis【本地--主机地址-新支付平台开发】
    5. redis.host = 192.168.112.XXX
    6. ## redis【本地--端口号-新支付平台开发】
    7. redis.port = 6379
    8. # redis【本地--登录密码-新支付平台开发】
    9. redis.password = XXX
    10. #++++++++++++【本地】【END】++++++++++
    11. ## redis【超时时间】
    12. redis.timeout=100000
    13. ## redis【是否使用连接池】
    14. redis.usePool=true
    15. ## redis【最大连接数】
    16. redis.maxTotal=300
    17. ## redis【最大空闲连接数】
    18. redis.maxIdle=10
    19. ## redis【每次释放连接的最大数目】
    20. redis.numTestsPerEvictionRun=1024
    21. ## redis【释放连接的扫描间隔(毫秒)】
    22. redis.timeBetweenEvictionRunsMillis=30000
    23. ## redis【连接最小空闲时间】
    24. redis.minEvictableIdleTimeMillis=1800000
    25. ## redis 连接空闲多久后释放, 当空闲时间>该值 空闲连接>最大空闲连接数 时直接释放】
    26. redis.softMinEvictableIdleTimeMillis=10000
    27. ## redis【获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1
    28. redis.maxWaitMillis=1000
    29. ## redis【在获取连接的时候检查有效性, 默认false
    30. redis.testOnBorrow=true
    31. ## redis【在空闲时检查有效性, 默认false
    32. redis.testWhileIdle=true
    33. ## redis【连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
    34. redis.blockWhenExhausted=true
    35. ##############################【Redis-配置】【END】###################
    36. ##############################【Redis-Spring-Session】【BGN】###################
    37. ## 超时时间,7200秒,项目中没有使用拦截器,尽量延长session时间,降低由session超时引起的异常
    38. redis.session.maxInactiveIntervalInSeconds=7200
    39. ##############################【Redis-Spring-Session】【END】###################

  8.    验证:
    1. 第一种方式:
    2. 使用redis-cli就可以查看到session key了,且浏览器Cookie中的jsessionid已经替换为session
      1. 127.0.0.1:6379> KEYS *
      2. 1) "spring:session:expirations:1440922740000"
      3. 2) "spring:session:sessions:35b48cb4-62f8-440c-afac-9c7e3cfe98d3"

    1. 第二种方式:
    2. 通过 redis-desktop-manager【下载地址:https://redisdesktop.com/download】查看
  9.   异常解决:  报错springSessionRepositoryFilter不存在
    1. 问题:
    2. org.apache.catalina.core.StandardContext filterStart
    3. 严重: Exception starting filter springSessionRepositoryFilter
    4. org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSessionRepositoryFilter' is defined
    5. at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:698)
    6. at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1175)
    7. at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:284)
    8. at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    9. at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1060)
    10. at org.springframework.web.filter.DelegatingFilterProxy.initDelegate(DelegatingFilterProxy.java:326)
    11. at org.springframework.web.filter.DelegatingFilterProxy.initFilterBean(DelegatingFilterProxy.java:235)
    12. at org.springframework.web.filter.GenericFilterBean.init(GenericFilterBean.java:199)
    官网解释:
    1.  解决:
    2. 上图中说明了是步骤1创建的springSessionRepositoryFilter
    3. 仔细检查web.xml。确保springSessionRepositoryFilter先于启动其他flter创建了即可。

  10. 注意
    1. spring-session要求Redis Server版本不低于2.8

阅读全文
1 0