javaee中session的验证_基于SpringSession 和 Redis实现的分布式Session实战

1b7b401881d51c0bde3c428210be4e7a.png

分布式Session-前言

在Web项目开发中,会话管理是一个很重要的部分,用于存储与用户相关的数据。通常是由符合session规范的容器来负责存储管理,也就是一旦容器关闭,重启会导致会话失效。因此打造一个高可用性的系统,必须将session管理从容器中独立出来。

共享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容器的代码。

这方面其实早就有开源项目了,例如memcached-session-manager,以及tomcat-redis-session-manager。暂时都只支持Tomcat6/Tomcat7。

而这实现方案有很多种,下面简单介绍下:

第一种:分布式Session实现方案

是使用容器扩展来实现,大家比较容易接受的是通过容器插件来实现,比如基于Tomcat的tomcat-redis-session-manager,基于Jetty的jetty-session-redis等等。

好处是对项目来说是透明的,无需改动代码。不过前者目前还不支持Tomcat 8,或者说不太完善。

个人觉得由于过于依赖容器,一旦容器升级或者更换意味着又得从新来过。并且代码不在项目中,对开发者来说维护也是个问题。

第二种:分布式Session实现方案

是自己写一套会话管理的工具类,包括Session管理和Cookie管理,在需要使用会话的时候都从自己的工具类中获取,而工具类后端存储可以放到Redis中。

很显然这个方案灵活性最大,但开发需要一些额外的时间。并且系统中存在两套Session方案,很容易弄错而导致取不到数据。

第三种:分布式Session实现方案【本文即是这种方式】

是使用框架的会话管理工具,也就是本文要说的spring-session,可以理解是替换了Servlet那一套会话管理,既不依赖容器,又不需要改动代码,并且是用了spring-data-redis那一套连接池,可以说是最完美的解决方案。

当然,前提是项目要使用Spring Framework才行。

环境准备

redis安装版本:redis-3.2.5.tar.gz

JDK版本:1.7+

Spring版本:4.1+

Spring-session版本:1.2.1.RELEASE

Maven依赖


<properties>
  
  <spring.version>4.1.5.RELEASEspring.version>
  
  <jedis.version>2.8.1jedis.version>
  
  <spring-session-data-redis.version>1.2.1.RELEASEspring-session-data-redis.version>
properties>

<dependency>
  <groupId>org.springframework.sessiongroupId>
  <artifactId>spring-session-data-redisartifactId>
  <version>${spring-session-data-redis.version}version>
dependency>

<dependency>
  <groupId>redis.clientsgroupId>
  <artifactId>jedisartifactId>
  <version>${jedis.version}version>
dependency>

web.xml中配置Session过滤器【springSessionRepositoryFilter】

xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"id="WebApp_ID" version="3.0">
    <display-name>CloudPaymentdisplay-name>
    
    <welcome-file-list>
        <welcome-file>index.htmlwelcome-file>
        <welcome-file>index.htmwelcome-file>
        <welcome-file>index.jspwelcome-file>
        <welcome-file>default.htmlwelcome-file>
        <welcome-file>default.htmwelcome-file>
        <welcome-file>default.jspwelcome-file>
    welcome-file-list>
    
    
    <context-param>
        <param-name>log4jConfigLocationparam-name>
        <param-value>classpath:/config/log4j.xmlparam-value>
    context-param>
    
    <context-param>
        <param-name>log4jRefreshIntervalparam-name>
        <param-value>10000param-value>
    context-param>
    
    <context-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>
            classpath:spring/applicationContext-*.xml
        param-value>
    context-param>
    
    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListenerlistener-class>
    listener>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
    listener>
    
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListenerlistener-class>
    listener>
    
    <servlet>
        <servlet-name>SpringMVCservlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
        <init-param>
            <param-name>contextConfigLocationparam-name>
            <param-value>classpath:spring/applicationContext-springMVC.xmlparam-value>
        init-param>
        <load-on-startup>1load-on-startup>
    servlet>
    <servlet-mapping>
        <servlet-name>SpringMVCservlet-name>
        <url-pattern>*.actionurl-pattern>
        <url-pattern>*.dourl-pattern>
    servlet-mapping>
    
    
    <filter>
        <filter-name>springSessionRepositoryFilterfilter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
    filter>
    <filter-mapping>
        <filter-name>springSessionRepositoryFilterfilter-name>
        <url-pattern>/*url-pattern>
        <dispatcher>REQUESTdispatcher>
        <dispatcher>ERRORdispatcher>
    filter-mapping>
    
    <filter>
        <filter-name>encodingFilterfilter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
        <init-param>
            <param-name>encodingparam-name>
            <param-value>UTF-8param-value>
        init-param>
        <init-param>
            <param-name>forceEncodingparam-name>
            <param-value>trueparam-value>
        init-param>
    filter>
    <filter-mapping>
        <filter-name>encodingFilterfilter-name>
        <url-pattern>/*url-pattern>
    filter-mapping>
web-app>

编写applicationContext-jedis.xml,并配置RedisHttpSessionConfiguration

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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
    <context:property-placeholder location="classpath:config/redis.properties" ignore-unresolvable="true"/>
    
    
    
    <context:component-scan base-package="com.newcapec.cloudpay.dao"/>
    
    
    
    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        
        <property name="maxInactiveIntervalInSeconds" value="${redis.session.maxInactiveIntervalInSeconds}"/>
    bean>
    
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        
        <property name="maxTotal" value="${redis.maxTotal}"/>
        
        <property name="maxIdle" value="${redis.maxIdle}"/>
        
        <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/>
        
        <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
        
        <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/>
        
        <property name="softMinEvictableIdleTimeMillis" value="${redis.softMinEvictableIdleTimeMillis}"/>
        
        <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
        
        <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
        
        <property name="testWhileIdle" value="${redis.testWhileIdle}"/>
        
        <property name="blockWhenExhausted" value="${redis.blockWhenExhausted}"/>
    bean>
    
    <bean id="jedisConnectionFactory"class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.host}"/>
        <property name="port" value="${redis.port}"/>
        <property name="password" value="${redis.password}"/>
        <property name="timeout" value="${redis.timeout}"/>
        <property name="usePool" value="${redis.usePool}"/>
        <property name="poolConfig" ref="jedisPoolConfig"/>
    bean>
    
    <bean id="stringRedisSerializer"class="org.springframework.data.redis.serializer.StringRedisSerializer">
    bean>
    
    
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
        <property name="keySerializer">
            <beanclass="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        property>
        <property name="valueSerializer">
            <beanclass="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
        property>
        <property name="hashKeySerializer">
            <beanclass="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        property>
        <property name="hashValueSerializer">
            <beanclass="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
        property>
    bean>
    
    
    
    <bean id="redisClient" class="redis.clients.jedis.JedisPool">
        <constructor-arg index="0" ref="jedisPoolConfig"/>
        <constructor-arg index="1" value="${redis.host}"/>
        <constructor-arg index="2" value="${redis.port}" type="int"/>
        <constructor-arg index="3" value="${redis.timeout}"type="int"/>
        <constructor-arg index="4" value="${redis.password}"/>
    bean>
beans>

redis相关配置信息

##############################【Redis-配置】【BGN】###################
#++++++++++++【本地】【BGN】++++++++++
#++++++++新支付平台开发++++++++++
## redis【本地--主机地址-新支付平台开发】
redis.host = 192.168.112.XXX
## redis【本地--端口号-新支付平台开发】
redis.port = 6379
# redis【本地--登录密码-新支付平台开发】
redis.password = XXX
#++++++++++++【本地】【END】++++++++++
## redis【超时时间】
redis.timeout=100000
## redis【是否使用连接池】
redis.usePool=true
## redis【最大连接数】
redis.maxTotal=300
## redis【最大空闲连接数】
redis.maxIdle=10
## redis【每次释放连接的最大数目】
redis.numTestsPerEvictionRun=1024
## redis【释放连接的扫描间隔(毫秒)】
redis.timeBetweenEvictionRunsMillis=30000
## redis【连接最小空闲时间】
redis.minEvictableIdleTimeMillis=1800000
## redis【 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放】
redis.softMinEvictableIdleTimeMillis=10000
## redis【获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1】
redis.maxWaitMillis=1000
## redis【在获取连接的时候检查有效性, 默认false】
redis.testOnBorrow=true
## redis【在空闲时检查有效性, 默认false】
redis.testWhileIdle=true
## redis【连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true】
redis.blockWhenExhausted=true
##############################【Redis-配置】【END】###################
##############################【Redis-Spring-Session】【BGN】###################
## 超时时间,7200秒,项目中没有使用拦截器,尽量延长session时间,降低由session超时引起的异常
redis.session.maxInactiveIntervalInSeconds=7200
##############################【Redis-Spring-Session】【END】###################

验证:

第一种方式:

使用redis-cli就可以查看到session key了,且浏览器Cookie中的jsessionid已经替换为session。

127.0.0.1:6379> KEYS *
    1) "spring:session:expirations:1440922740000"
    2) "spring:session:sessions:35b48cb4-62f8-440c-afac-9c7e3cfe98d3"

第二种方式:

通过 redis-desktop-manager【下载地址:https://redisdesktop.com/download】查看

997892ffbed76de49ab638aa497cb626.png

异常解决:  报错springSessionRepositoryFilter不存在

官网解释:

问题:
org.apache.catalina.core.StandardContext filterStart
严重: Exception starting filter springSessionRepositoryFilter
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSessionRepositoryFilter' is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:698)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1175)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:284)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1060)
at org.springframework.web.filter.DelegatingFilterProxy.initDelegate(DelegatingFilterProxy.java:326)
at org.springframework.web.filter.DelegatingFilterProxy.initFilterBean(DelegatingFilterProxy.java:235)
at org.springframework.web.filter.GenericFilterBean.init(GenericFilterBean.java:199

解决:

上图中说明了是步骤1创建的springSessionRepositoryFilter。

仔细检查web.xml。确保springSessionRepositoryFilter先于启动其他flter创建了即可。

注意

spring-session要求Redis Server版本不低于2.8

原文链接:https://blog.csdn.net/fly910905/article/details/78466763

70c339d1a91d8cc062582d9fed172b4b.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值