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
一点小推广
创作不易,希望可以支持下我的开源软件,及我的小工具,欢迎来 gitee 点星,fork ,提 bug 。
Excel 通用导入导出,支持 Excel 公式
博客地址:https://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poi
使用模板代码 ,从数据库生成代码 ,及一些项目中经常可以用到的小工具
博客地址:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/sanri/sanri-tools-maven
spring-redis-session 自定义 key 和过期时间的更多相关文章
- redis中的key设置过期时间
EXPIRE key seconds 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除. 在 Redis 中,带有生存时间的 key 被称为『易失的 ...
- spring cache之自定义keys的过期时间
spring @cacheable注解默认不支持方法级别的缓存失效时间,只能通过配置来配置全局的失效时间 如果需要实现对方法级别的缓存支持失效时间机制,有一种比较简单的方法,spring配置文件如下: ...
- Python操作Redis之设置key的过期时间
对于一个已经存在的key,我们可以设置其过期时间,到了那个时间后,当你再去访问时,key就不存在了 有两种方式可以设置过期时间,一种是指定key从当前时间开始算起还能存活多久,时间单位有两个,一个是秒 ...
- Redis-设置Key的过期时间及相关策略
Redis-设置Key的过期时间及相关策略 1.设置key的过期时间 1.1expire key second:设置key的过期时间(秒) 1.2ttl key:查看key的有效期 1.3persis ...
- redis key的过期时间
设置redis key的生存过期时间 Redis 有四个不同的命令可以用于设置键的生存时间(键可以存在多久)或过期时间(键什么时候会被删除) : EXPlRE 命令用于将键key 的生存时间设置为tt ...
- Redis五种数据类型-设置key的过期时间
1.redis命令客户端 [root@localhost bin]# ./redis-cli 127.0.0.1:6379> #是否运行着 127.0.0.1:6379> ping PON ...
- Redis设置和更新Key的过期时间
EXPIRE key seconds 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除. 在 Redis 中,带有生存时间的 key 被称为『易失的』(volati ...
- redis 下key的过期时间详解 :expire
memcached 和 redis 的set命令都有expire参数,可以设置key的过期时间.但是redis是一个可以对数据持久化的key-value database,它的key过期策略还是和me ...
- redis hash结构如何设置过期时间
Redis中有个设置时间过期的功能,即通过setex或者expire实现,目前redis没有提供hsetex()这样的方法,redis中过期时间只针对顶级key类型,对于hash类型是不支持的,这个时 ...
随机推荐
- F4NNIU 的 Docker 学习资料整理
F4NNIU 的 Docker 学习资料整理 Docker 介绍 以下来自 Wikipedia Docker是一个开放源代码软件项目,让应用程序部署在软件货柜下的工作可以自动化进行,借此在Linux操 ...
- python 列表对象的增减
- 5.0.1版本的react-router-dom路由传参以及路由表的配置和接收页面的接受
//第一种 通过问号传参 //发送 this.props.history.push("/detail?id="+item.downurl) //路由表配置 <Route pa ...
- 同一个C语言工程不同C文件之间的函数互相调用问题
定义一个function.h文件来声明这些函数: //#ifndef __FUNCTION_H__//#define __FUNCTION_H__ int fun(int,int); int ...
- es6 默认参数、rest参数、扩展运算符
1.默认值 现在可以在定义函数的时候指定参数的默认值了,而不用像以前那样通过逻辑或操作符来达到目的了. function sayHello(name){ //传统的指定默认参数的方式 var name ...
- @hdu - 5503@ EarthCup
目录 @description@ @solution@ @accepted code@ @details@ @description@ n 个队伍两两之间比赛,保证没有平局. 现在给出 n 个队伍分别 ...
- poj 1092 Farmland (Geometry)
1092 -- Farmland 怎么最近做几何题都这么蛋疼,提交C++过不了交G++就过了.据我估计,原因是用了atan2这个函数,或者是其他一些函数造成了精度的影响.不管怎样,这题最后还是过了~ ...
- codeforces2B.The least round way 题解 动态规划/模拟
题目出处:http://codeforces.com/problemset/problem/2/B 题目描述 给你一个 \(n \times n\) 的二维数组,它包含的元素都是非负整数.你需要寻找一 ...
- vue tab栏缓存解决跳转页面后返回的状态保持
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8& ...
- iptables端口映射
见上节透明代理设置 #iptables -t nat -A PREROUTING -i eth0 -p tcp -s 192.168.62.0/24 --dport 80 -j REDIRECT -- ...