spring-redis-session 自定义 key 和过期时间

对于分布式应用来说,最开始遇到的问题就是 session 的存储了,解决方案大致有如下几种

  • 使用 spring-session 它可以把 session 存储到你想存储的位置,如 redis,mysql 等
  • 使用 JWTs ,它使用算法来验证 token 的合法性,是否过期,并且 token 无法被伪造,信息也是无法被篡改的

本文内容主要说 spring-session 使用 redis 来存储 session ,实现原理,修改过期时间,自定义 key 等

spring-session 对于内部系统来说还是可以的,使用方便,但如果用户量上来了的话,会使 redis 有很大的 session 存储开销,不太划算。

使用

使用起来比较简单,简单说一下,引包,配置,加注解 。如下面三步,就配置好了使用 redis-session

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>
spring.redis.host=localhost
# 其它 超时,端口,库,连接池,集群,就自己去找了
@EnableRedisHttpSession(maxInactiveIntervalInSeconds= 1800)

测试:因为是在 getSession 的时候才会创建 Session ,所以我们必须在接口中调用一次才能看到效果

@GetMapping("/sessionId")
public String sessionId(){
  HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  HttpSession session = request.getSession();
  session.setAttribute("user","sanri");
  return session.getId();
}

它的存储结果如下

hash spring:session:sessions:e3d4d84f-cc9f-44d5-9199-463cd9de8272
string spring:session:sessions:expires:e3d4d84f-cc9f-44d5-9199-463cd9de8272
set spring:session:expirations:1577615340000
第一个 hash 结构存储了 session 的一些基本信息和用户设置的一些属性信息

creationTime 创建时间

lastAccessedTime 最后访问时间

maxInactiveInterval 过期时长,默认是 30 分钟,这里保存的秒值

sessionAttr:user 这是我通过 session.setAttribute 设置进去的属性

第二个 string 结构,它没有值,只有一个 ttl 信息,标识这组 key 还能活多久,可以用 ttl 查看

第三个 set 结构,保存了所以需要过期的 key

实现原理

说明:这个实现没多少难度,我就照着源码念一遍了,就是一个过滤器的应用而已。

首先从网上了解到,它是使用过滤器来实现把 session 存储到 redis 的,然后每次请求都是从 redis 拿到 session 的,所以目标就是看它的过滤器是哪个,是怎么存储的,又是怎么获取的。

我们可以从它唯一的入口 @EnableRedisHttpSession 进入查看,它引入了一个 RedisHttpSessionConfiguration 开启了一个定时器,继承自 SpringHttpSessionConfiguration ,可以留意到 RedisHttpSessionConfiguration 创建一个 Bean RedisOperationsSessionRepository repository 是仓库的意思,所以它就是核心类了,用于存储 session ;那过滤器在哪呢,查看SpringHttpSessionConfiguration 它属于 spring-session-core 包,这是一个 spring 用来管理 session 的包,是一个抽象的概念,具体的实现由 spring-session-data-redis 来完成 ,那过滤器肯定在这里创建的,果然可以看到它创建一个 SessionRepositoryFilter 的过滤器,下面分别看过滤器和存储。

SessionRepositoryFilter

过滤器一定是有 doFilter 方法,查看 doFilter 方法,spring 使用 OncePerRequestFilter 把 doFilter 包装了一层,最终是调用 doFilterInternal 来实现的,查看 doFilterInternal 方法

实现方式为使用了包装者设计把 request 和 response 响应进行了包装,我们一般拿 session 一般是从 request.getSession() ,所以包装的 request 肯定要重写 getSession ,所以可以看 getSession 方法来看是如何从 redis 获取 session ;

前面都是已经存在 session 的判断相关,关键信息在这里

S session = SessionRepositoryFilter.this.sessionRepository.createSession();

这里的 sessionRepository 就是我们用来存取 session 的 RedisOperationsSessionRepository 查看 createSession 方法

RedisOperationsSessionRepository

// 这里保存了在 redis 中 hash 结构能看到的数据
RedisSession redisSession = new RedisSession();

this(new MapSession());
this.delta.put(CREATION_TIME_ATTR, getCreationTime().toEpochMilli());
this.delta.put(MAX_INACTIVE_ATTR, (int) getMaxInactiveInterval().getSeconds());
this.delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime().toEpochMilli());
this.isNew = true;
this.flushImmediateIfNecessary();

在 flushImmediateIfNecessary 方法中,如果 redisFlushMode 是 IMMEDIATE 模式,则会立即保存 session 进 redis ,但默认配置的是 ON_SAVE ,那是在哪里保存进 redis 的呢,我们回到最开始的过滤器 doFilterInternal 方法中,在 finally 中有一句

wrappedRequest.commitSession();

就是在这里将 session 存储进 redis 的 ,我们跟进去看看,核心语句为这句

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

进入 saveDelta ,在这里进行了 hash 结构的设置

getSessionBoundHashOperations(sessionId).putAll(this.delta);

最后一行进行了过期时间的设置和把当前 key 加入 set ,读者自行查看

RedisOperationsSessionRepository.this.expirationPolicy
          .onExpirationUpdated(originalExpiration, this);

修改一些参数

实际业务中,可能需要修改一些参数才能达到我们业务的需求,最常见的需求就是修改 session 的过期时间了,在 EnableRedisHttpSession 注解中,已经提供了一些基本的配置如

maxInactiveIntervalInSeconds 最大过期时间,默认 30 分钟
redisNamespace 插入到 redis 的 session 命名空间,默认是 spring:session
cleanupCron 过期 session 清理任务,默认是 1 分钟清理一次
redisFlushMode 刷新方式 ,其实在上面原理的 flushImmediateIfNecessary 方法中有用到,默认是 ON_SAVE
redisNamespace 是一定要修改的,这个不修改会影响别的项目,一般使用我们项目的名称加关键字 session 做 key ,表明这是这个项目的 session 信息。

不过这样的配置明显不够,对于最大过期时间来说,有可能需要加到配置文件中去,而不是写在代码中,但是这里没有提供占位符的功能,回到 RedisOperationsSessionRepository 的创建,最终配置的 maxInactiveIntervalInSeconds 还是要设置到这个 bean 中去的,我们可以把这个 bean 的创建过程覆盖,重写 maxInactiveIntervalInSeconds 的获取过程,就解决了,代码如下

@Autowired
RedisTemplate sessionRedisTemplate;

@Autowired
ApplicationEventPublisher applicationEventPublisher;

@Value("${server.session.timeout}")
private int sessionTimeout = 1800;

@Primary    // 使用 Primary 来覆盖默认的 Bean
@Bean
public RedisOperationsSessionRepository sessionRepository() {
  RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(sessionRedisTemplate);
  // 这里要把原来的属性引用过来,避免出错 ,可以引用原来的类并复制属性 ;像 redisNamespace,redisFlushMode 都要复制过来
  return sessionRepository;
}

还有一个就是 redis 的序列化问题,默认是使用的 jdk 的对象序列化,很容易出现加一个字段或减少一个字段出现不能反序列化,所以序列化方式是需要换的,如果项目中的缓存就已经使用了对象序列化的话,那就面要为其单独写一个 redisTemplate 并设置进去,在构建 RedisOperationsSessionRepository 的时候设置 redisTemplate

还有一个就是生成在 redis 中的 key 值都是 uuid 的形式,根本没办法知道当前这个 key 是哪个用户在哪里登录的,我们其实可以修改它的 key 为 userId_ip_time 的形式,用来表明这个用户什么时间在哪个 ip 有登录过,我是这么玩的(没有在实际中使用过,虽然能改,但可能有坑):

经过前面的源码分析,创建 session 并保存到 redis 的是 RedisOperationsSessionRepository 的 createSession 方法,但是这里写死了 RedisSession 使用空的构造,而且 RedisSession 是 final 的内部类,访问权限为默认,构造的时候 new MapSession 也是默认的,最终那个 id 为使用 UUID ,看起来一点办法都没有,其实在这里创建完 session ,用户不一定是登录成功的状态,我们应该在登录成功才能修改 session 的 key ,好在 RedisOperationsSessionRepository 提供了一个方法 findById ,我们可以在这个上面做文章,先把 RedisSession 查出来,然后用反射得到 MapSession ,然后留意到 MapSession 是可以修改 id 的,它自己也提供了方法 changeSessionId ,我们完全可以在登录成功调用 setId 修改 sessionId ,然后再写回去,这个代码一定要和 RedisSession 在同包 代码如下:

package org.springframework.session.data.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.MapSession;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;

@Component
public class SessionOperation {
  @Autowired
  private RedisOperationsSessionRepository redisOperationsSessionRepository;

  public void loginSuccess(String userId){
    String sessionId = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession().getId();
    RedisOperationsSessionRepository.RedisSession redisSession = redisOperationsSessionRepository.findById(sessionId);
    Field cached = ReflectionUtils.findField(RedisOperationsSessionRepository.RedisSession.class, "cached");
    ReflectionUtils.makeAccessible(cached);
    MapSession mapSession = (MapSession) ReflectionUtils.getField(cached, redisSession);
    mapSession.setId("userId:1");
    redisOperationsSessionRepository.save(redisSession);
  }
}

源码地址: https://gitee.com/sanri/example/tree/master/test-redis-session

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Spring session整合到Redis过程解析

    为何要用Spring-session 在传统单机web应用中,一般使用tomcat/jetty等web容器时,用户的session都是由容器管理.浏览器使用cookie中记录sessionId,容器根据sessionId判断用户是否存在会话session.这里的限制是,session存储在web容器中,被单台服务器容器管理. 但是网站主键演变,分布式应用和集群是趋势(提高性能).此时用户的请求可能被负载分发至不同的服务器,此时传统的web容器管理用户会话session的方式即行不通.除非集群或者

  • 如何使用Spring+redis实现对session的分布式管理

    在Spring中实现分布式 session管理 本文主要是在Spring中实现分布式session,采用redis对session进行持久化管理,这样当应用部署的时候,不需要在Resin.Tomcat等容器里面进行分布式配置,方便加入新的节点服务器进行集群扩容,session不依赖各节点的服务器,可直接从redis获取.下面是功能的核心代码: 一.首先在web.xml里面配置 加入拦截器: <!-- 分布式session start --> <filter> <filter-

  • 学习Spring-Session+Redis实现session共享的方法

    1.添加依赖 <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>1.2.1.RELEASE</version> </dependency> <dependency> <groupId>redis.cl

  • Spring Boot高级教程之使用Redis实现session共享

    Redis是一个缓存消息中间件及具有丰富特性的键值存储系统.Spring Boot为Jedis客户端库和由Spring Data Redis提供的基于Jedis客户端的抽象提供自动配置.spring-boot-starter-redis'Starter POM'为收集依赖提供一种便利的方式. 引入spring-boot-starter-redis,在pom.xml配置文件中增加配置如下(基于之前章节"Spring Boot 构建框架"中的pom.xml文件): <dependen

  • 详解Spring-Boot集成Spring session并存入redis

    spring Session 提供了一套用于管理用户 session 信息的API和实现. Spring Session为企业级Java应用的session管理带来了革新,使得以下的功能更加容易实现: 编写可水平扩展的原生云应用. 将session所保存的状态卸载到特定的外部session存储中,如Redis或Apache Geode中,它们能够以独立于应用服务器的方式提供高质量的集群. 当用户使用WebSocket发送请求的时候,能够保持HttpSession处于活跃状态. 在非Web请求的处

  • 详解springboot中redis的使用和分布式session共享问题

    对于分布式使用Nginx+Tomcat实现负载均衡,最常用的均衡算法有IP_Hash.轮训.根据权重.随机等.不管对于哪一种负载均衡算法,由于Nginx对不同的请求分发到某一个Tomcat,Tomcat在运行的时候分别是不同的容器里,因此会出现session不同步或者丢失的问题. 实际上实现Session共享的方案很多,其中一种常用的就是使用Tomcat.Jetty等服务器提供的Session共享功能,将Session的内容统一存储在一个数据库(如MySQL)或缓存(如Redis)中. 本文旨在

  • Spring Boot项目利用Redis实现session管理实例

    在现代网络服务中,session(会话)不得不说是非常重要也是一定要实现的概念,因此在web后台开发中,对session的管理和维护是必须要实现的组件.这篇文章主要是介绍如何在Spring Boot项目中加入redis来实现对session的存储与管理. 1. 利用Spring Initializr来新建一个spring boot项目 2. 在pom.xml中添加redis和session的相关依赖.项目生成的时候虽然也会自动生成父依赖,但是1.5.3版本的spring boot的redis相关

  • SpringSessionRedis配置及发现的问题讲解

    最近写项目,需要把session放入Redis中,来实现分布式.我本来要用Tomcat部署Redis这种方法,但是依赖于容器了.无意中发现了SpringSession,这可挺不错的,写完了发现不好用,问度娘也没弄明白,最后我写了2个demo一个springMVC的,一个spring整合struts2的,发现SpringSession需要SpringMVC的支持.也就是说我的项目用不了了. 先说说springsession的配置吧: 一.Maven中pom.xml文件中添加(选一种添加上就行):

  • spring boot与redis 实现session共享教程

    如果大家对spring boot不是很了解,大家可以参考下面两篇文章. Spring Boot 快速入门教程 Spring Boot 快速入门指南 这次带来的是spring boot + redis 实现session共享的教程. 在spring boot的文档中,告诉我们添加@EnableRedisHttpSession来开启spring session支持,配置如下: @Configuration @EnableRedisHttpSession public class RedisSessi

  • SpringSession+Redis实现集群会话共享的方法

    WEB应用开发完成后部署到Tomcat或其他容器中供用户访问. 小型应用在一台服务器上安装Tomcat并部署WEB应用. 随着访问量增大, Tomcat的压力会越来越大, 直至崩溃. 为了保证WEB应用的承载能力, 需要对WEB应用进行集群处理. 技术发展到今天, 集群/负载均衡已经变的相对简单了. 下面用通俗的语言给刚入门的同学介绍下这两个概念: 某KFC开业时只有一个点餐窗口(一台Tocmat服务器, 可以节约成本)对外提供点餐服务. 应对日常点餐没有问题, 当饭口或者周末时一个窗口就会排起

  • SpringCloud实现Redis在各个微服务的Session共享问题

    在微服务中,需要我们在各个微服务中共享Session,使用Redis来共享Session是一个很好的解决方法,Redis是运行在内存中,查取速度很快. 1.pom文件中添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency> <depe

随机推荐