• Spring Session加Redis(山东数漫江湖)


    redis-spring-boot

    session是一个非常常见的概念。session的作用是为了辅助http协议,因为http是本身是一个无状态协议。为了记录用户的状态,session机制就应运而生了。同时session也是一个非常老的概念了,使用session的方法也是多种多样。就Java来说,servlet的标准本身是包含session的,Tomcat会把session的信息存储在服务器内存里,Request提供了获取session的方法。

    然而,前文所述的session机制其实是有不少缺点的。首先就是session数据没有一定的持久化机制,而且难以实现应用服务器的水平扩展。在负载均衡器 + 应用服务器集群的架构中,session共享是一个基本的要求。在Spring生态圈里,也有Spring session完成session存储和共享的功能。Spring Session支持把session信息以各种形式存储,比如数据库或者Redis。个人认为,Redis比RDBMS更加适合session数据的存储。首先,session数据都是有时效性的,Redis的数据超时机制可以很好的完成session信息的清除;此外,Redis的数据访问速度更快,对于时效性较强的session数据,会有比较好的加速效果。

    由于注解的存在,在Spring项目中增加Spring Session Redis是非常的简单的:

    1. 增加Spring Session以及Spring Data Redis的依赖

        compile 'org.springframework.session:spring-session:1.3.0.RELEASE'
    
        compile 'org.springframework.boot:spring-boot-starter-data-redis'

    2. 增加Spring Session Redis的配置

    @EnableRedisHttpSession 
    public class HttpSessionConfig {
        @Bean
        public RedisConnectionFactory connectionFactory() {
            JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
            connectionFactory.setPort(6379);
            connectionFactory.setHostName("localhost");
            return connectionFactory;
        }
    }

    然后就完成了,实在是太简单了。在浏览器中进行请求,然后可以发现Redis中出现如下的内容:

    image

    先不解释Redis中存储的数据的具体含义,我们先来研究一下,@EnableRedisHttpSession 的注解到底帮我们做了什么。所以,看源码走起咯。。。

    @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
    @Target({ java.lang.annotation.ElementType.TYPE })
    @Documented
    @Import(RedisHttpSessionConfiguration.class)
    @Configuration
    public @interface EnableRedisHttpSession {
        /* */
    }

    这个注解包含了一个重要的@Import注解,@Import注解支持导入普通的java类,并将其声明成一个bean。所以使用了@EnableRedisSession之后,就相当于同时声明了一个RedisHttpSessionConfiguration的bean。然后看看,这个类是如何定义的:

    @Configuration
    @EnableScheduling
    public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
            implements EmbeddedValueResolverAware, ImportAware {
        @Bean
        public RedisTemplate<Object, Object> sessionRedisTemplate(
                RedisConnectionFactory connectionFactory) {
            RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
            template.setKeySerializer(new StringRedisSerializer());
            template.setHashKeySerializer(new StringRedisSerializer());
            if (this.defaultRedisSerializer != null) {
                template.setDefaultSerializer(this.defaultRedisSerializer);
            }
            template.setConnectionFactory(connectionFactory);
            return template;
        }
    
        @Bean
        public RedisOperationsSessionRepository sessionRepository(
                @Qualifier("sessionRedisTemplate") RedisOperations<Object, Object> sessionRedisTemplate,
                ApplicationEventPublisher applicationEventPublisher) {
            RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(
                    sessionRedisTemplate);
            sessionRepository.setApplicationEventPublisher(applicationEventPublisher);
            sessionRepository
                    .setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
            if (this.defaultRedisSerializer != null) {
                sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
            }
    
            String redisNamespace = getRedisNamespace();
            if (StringUtils.hasText(redisNamespace)) {
                sessionRepository.setRedisKeyNamespace(redisNamespace);
            }
    
            sessionRepository.setRedisFlushMode(this.redisFlushMode);
            return sessionRepository;
        }
    
    }

    RedisHttpSessionConfiguration中声明了几个重要的bean,上面的代码片段其实就是实现了Redis相关的配置。其中的依赖关系为RedisOperationsSessionRepository =>sessionRedisTemplate => RedisConnectionFactory。同时RedisHttpSessionConfiguration继承于SpringHttpSessionConfiguration。而SpringHttpSessionConfiguration本身会申明一个springSessionRepositoryFilter,这个filter作用于全局,session的保存和失效都是在这里完成的。对于这个filter的具体实现,这里先不作分析,因为它本身还是通过调用RedisOperationsSessionRepository来操作Redis实现session的保存的。

    RedisOperationsSessionRepository实现代码其实还挺多的,但是逻辑却都不复杂,所以没有必要贴很多代码。就看看Session保存的函数吧!

        public void save(RedisSession session) {
            session.saveDelta();
            if (session.isNew()) {
                String sessionCreatedKey = getSessionCreatedChannel(session.getId());
                this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
                session.setNew(false);
            }
        }

    并没有太多东西,sessionRedisOperations.convertAndSend()完成了session数据的保存。那么Redis中的session数据是如何存储的呢?对于一个session数据,会在Redis存两个key/value,其中一个存储实际的session数据,另外一个用于辅助session过期的处理。此外还有一个总体的数据,用于记录过期的session。下面是一个典型的存储形式:

     * HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr2:attrName someAttrValue2
     * EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
     * APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
     * EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
     * SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
     * EXPIRE spring:session:expirations1439245080000 2100

    第一条的存储命令就是实际的session数据,这个数据在Redis是以HashSet的形式存储的,可以在里面存储各种数据。第三条存储命令就是辅助session过期的数据,它在expire时间会比实际数据久一点,用于区别一个session是超时还是不存在。第五条存储命令就是整体的session过期记录,它是一个集合。这个整体expirations集合的意义在于Redis的超时是不精确的,可能会出现一个session已经过期(辅助session数据也已经过期)了,但是它仍然存在于Redis中。这是因为Redis的数据超时清除任务是低优先级的。有了整体的session过期记录,就可以解决这种问题。当Redis真正清除数据时(会有事件通知),再把session从expirations集合中删除掉。

  • 相关阅读:
    函数与递归、第五章习题
    第四章习题continue、break
    程序异常处理
    第一章习题
    第三章习题.split('')
    文本进度条
    字符串处理函数、Unicode
    字符串处理.<方法>()
    springbatch
    卸载gitlab
  • 原文地址:https://www.cnblogs.com/kkdn/p/8931927.html
Copyright © 2020-2023  润新知