spring-session 2.0 实现细节
一、 前置知识
1. redis 在键实际过期之后不一定会被删除,可能会继续存留
2. 具有过期时间的 key 有两种方式来保证过期
一是这个键在过期的时候被访问了
二是后台运行一个定时任务自己删除过期的 key
划重点:这启发我们在 key 到期后只需要访问一下 key 就可以确保 redis 删除该过期键
二、三种类型的键
192.168.1.251:> type spring:session:sessions:804f5333-e5dc-48c8-a3d3-86e832f41045
hash 192.168.1.251:> hgetall spring:session:sessions:804f5333-e5dc-48c8-a3d3-86e832f41045
) "lastAccessedTime"
) ""
) "sessionAttr:_SESSION_CACHE_PREFIX_"
) "{\"@class\":\"com.reals.session.SessionInfo\",\"mainBindId\":1,\"bindIds\":null,\"phone\":null,\"loginMode\":null,\"openId\":\"o6kAJ4z4LvyPao\",\"platform\":\"Miniprogram\",\"sid\":\"804f5333-e5dc-48c8-a3d3-86e832f41045\",\"validSeconds\":2678400,\"session_key\":\"bBhW9tWg==\"}"
) "maxInactiveInterval"
) ""
) "creationTime"
) "" 192.168.1.251:> type spring:session:expirations:
set
192.168.1.251:>
192.168.1.251:> smembers spring:session:expirations:
) "\"expires:804f5333-e5dc-48c8-a3d3-86e832f41045\"" 92.168.1.251:> type spring:session:sessions:expires:804f5333-e5dc-48c8-a3d3-86e832f41045
string
192.168.1.251:> get spring:session:sessions:expires:804f5333-e5dc-48c8-a3d3-86e832f41045
""
A型键(Hash):spring:session:sessions:2ce8e358-3c23-4233-af40-a338deb0691f
B型键(Set):spring:session:expirations:1550627520000
C型键(String):spring:session:sessions:expires:2ce8e358-3c23-4233-af40-a338deb0691f
A/B类型的键ttl比C的长5分钟
三、运行机制
1. 定时任务每分钟查找spring:session:expirations:{timestamp}的值
RedisSessionExpirationPolicy.cleanExpiredSessions
public void cleanExpiredSessions() {
long now = System.currentTimeMillis();
long prevMin = roundDownMinute(now);
//看到是set操作,是B型键
String expirationKey = getExpirationKey(prevMin);
Set<Object> sessionsToExpire = this.redis.boundSetOps(expirationKey).members();
this.redis.delete(expirationKey);
//B型键有三种类型的值,如下示例
for (Object session : sessionsToExpire) {
String sessionKey = getSessionKey((String) session);
touch(sessionKey);
}
}
参考github issue并发导致的问题
Cleanup in RedisOperationsSessionRepository can cause session to be deleted incorrectly
/**
* By trying to access the session we only trigger a deletion if it the TTL is
* expired. This is done to handle
* https://github.com/spring-projects/spring-session/issues/93
*
* @param key the key
*/
private void touch(String key) {
this.redis.hasKey(key);
}
2. B类型键的值
# . 已过期,已被删除的键。
# . 已过期,但是还没来得及被 redis 清除的 key。在 key 到期后只需要访问一下 key 就可以确保 redis 删除该过期键
# . 并发问题导致的多余数据,实际上并未过期。
192.168.0.200:[]> smembers spring:session:expirations:
) "\"86719669-9214-4dfa-952d-e4a956a201c2\""
192.168.0.200:[]>
192.168.0.200:[]> smembers spring:session:expirations:
# RedisSessionExpirationPolicy.onExpirationUpdated 在这里加了下面这种类型的值
) "\"expires:00e801a5-30dd-4e12-8398-ac9b9336e3b1\""
3. RedisSessionExpirationPolicy.onExpirationUpdated
public void onExpirationUpdated(Long originalExpirationTimeInMilli, Session session) {
String keyToExpire = "expires:" + session.getId();
long toExpire = roundUpToNextMinute(expiresInMillis(session));
//删除B型键的旧值
if (originalExpirationTimeInMilli != null) {
long originalRoundedUp = roundUpToNextMinute(originalExpirationTimeInMilli);
if (toExpire != originalRoundedUp) {
String expireKey = getExpirationKey(originalRoundedUp);
this.redis.boundSetOps(expireKey).remove(keyToExpire);
}
}
long sessionExpireInSeconds = session.getMaxInactiveInterval().getSeconds();
//C型键spring:session:sessions:expires:2ce8e358-3c23-4233-af40-a338deb0691f
String sessionKey = getSessionKey(keyToExpire);
if (sessionExpireInSeconds < 0) {
this.redis.boundValueOps(sessionKey).append("");
this.redis.boundValueOps(sessionKey).persist();
this.redis.boundHashOps(getSessionKey(session.getId())).persist();
return;
}
//B型键spring:session:expirations:1550627520000
String expireKey = getExpirationKey(toExpire);
BoundSetOperations<Object, Object> expireOperations = this.redis
.boundSetOps(expireKey);
expireOperations.add(keyToExpire);
long fiveMinutesAfterExpires = sessionExpireInSeconds
+ TimeUnit.MINUTES.toSeconds(5);
//A、B型键的过期时间加多5分钟
expireOperations.expire(fiveMinutesAfterExpires, TimeUnit.SECONDS);
if (sessionExpireInSeconds == 0) {
this.redis.delete(sessionKey);
}
else {
this.redis.boundValueOps(sessionKey).append("");
this.redis.boundValueOps(sessionKey).expire(sessionExpireInSeconds,
TimeUnit.SECONDS);
}
this.redis.boundHashOps(getSessionKey(session.getId()))
.expire(fiveMinutesAfterExpires, TimeUnit.SECONDS);
}
You will note that the expiration that is set is 5 minutes after the session
actually expires. This is necessary so that the value of the session can be
accessed when the session expires. An expiration is set on the session itself
five minutes after it actually expires to ensure it is cleaned up, but only
after we perform any necessary processing.
4.删除String类型键spring:session:sessions:expires触发键空间通知
public void onMessage(Message message, byte[] pattern) {
byte[] messageChannel = message.getChannel();
byte[] messageBody = message.getBody();
String channel = new String(messageChannel);
if (channel.startsWith(getSessionCreatedChannelPrefix())) {
// TODO: is this thread safe?
Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer
.deserialize(message.getBody());
handleCreated(loaded, channel);
return;
}
String body = new String(messageBody);
//C型键spring:session:sessions:expires才继续执行
if (!body.startsWith(getExpiredKeyPrefix())) {
return;
}
boolean isDeleted = channel.endsWith(":del");
if (isDeleted || channel.endsWith(":expired")) {
int beginIndex = body.lastIndexOf(":") + ;
int endIndex = body.length();
String sessionId = body.substring(beginIndex, endIndex);
RedisSession session = getSession(sessionId, true);
if (session == null) {
logger.warn("Unable to publish SessionDestroyedEvent for session "
+ sessionId);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Publishing SessionDestroyedEvent for session " + sessionId);
}
cleanupPrincipalIndex(session);
if (isDeleted) {
handleDeleted(session);
}
else {
handleExpired(session);
}
}
}
spring-session 2.0 实现细节的更多相关文章
- 详细介绍Spring Boot 2.0的那些新特性与增强
以Java 8 为基准 Spring Boot 2.0 要求Java 版本必须8以上, Java 6 和 7 不再支持. 内嵌容器包结构调整 为了支持reactive使用场景,内嵌的容器包结构被重构了 ...
- Spring Boot 2.0 新特性和发展方向
以Java 8 为基准 Spring Boot 2.0 要求Java 版本必须8以上, Java 6 和 7 不再支持. 内嵌容器包结构调整 为了支持reactive使用场景,内嵌的容器包结构被重构了 ...
- 【2.0新特性】Spring Boot 2.0新特性
以Java 8 为基准 Spring Boot 2.0 要求Java 版本必须8以上, Java 6 和 7 不再支持. 内嵌容器包结构调整 为了支持reactive使用场景,内嵌的容器包结构被重构了 ...
- 通过Spring Session实现新一代的Session管理
长期以来,session管理就是企业级Java中的一部分,以致于我们潜意识就认为它是已经解决的问题,在最近的记忆中,我们没有看到这个领域有很大的革新. 但是,现代的趋势是微服务以及可水平扩展的原生云应 ...
- Spring Boot实践——Spring Boot 2.0 新特性和发展方向
出自:https://mp.weixin.qq.com/s/EWmuzsgHueHcSB0WH-3AQw 以Java 8 为基准 Spring Boot 2.0 要求Java 版本必须8以上, Jav ...
- 转:通过Spring Session实现新一代的Session管理
长期以来,session管理就是企业级Java中的一部分,以致于我们潜意识就认为它是已经解决的问题,在最近的记忆中,我们没有看到这个领域有很大的革新. 但是,现代的趋势是微服务以及可水平扩展的原生云应 ...
- 通过 Spring Session 实现新一代的 Session 管理
长期以来,session 管理就是企业级 Java 中的一部分,以致于我们潜意识就认为它是已经解决的问题,在最近的记忆中,我们没有看到这个领域有很大的革新. 但是,现代的趋势是微服务以及可水平扩展的原 ...
- Spring Boot 3.0.0 发布第一个里程碑版本M1,你的 Java 升到17 了吗?
2022年1月20日,Spring官方发布了Spring Boot 3.0.0的第一个里程碑版本M1. 下面一起来来看看Spring Boot 3.0.0 M1版本都有哪些重大变化: Java基线从 ...
- Spring 官宣发布 Spring Boot 3.0 第一个里程碑 M1,从 Java 8 提升到 Java 17!
Spring官方于2022年1月20日发布Spring Boot 3.0.0-M1版本,预示开启了Spring Boot 3.0的里程碑,相信这是通往下一代Spring框架的激动人心的旅程. 接下来一 ...
- spring对bean的管理细节
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.spr ...
随机推荐
- jqGrid合并单元格
两个参数 /**合并单元格:合并指定 gridName表格的NoName 列,合并的标准是参考CellName+CellNameTwo列内单元格的值. * gridName :表格名称 * NoNam ...
- javascript数组去重 String字符串去掉两端空格 javascript Array二分法排序 比较 javascript 求和
通过原形添加方法: ==================数组去重(对象去重法)======================= Array.prototype.unique=function(){ va ...
- ==、Equals 、ReferenceEquals它们的异同
链接 string foobar = "foobar"; string foobar2 = new StringBuilder().Append("foo"). ...
- Javascript我学之三函数的参数
本文是金旭亮老师网易云课堂的课程笔记,记录下来,以供备忘 函数的参数 对于参数值,JavaScript不会进行类型检查,任何类型的值都可以被传递给参数. ...
- php页面传值的方法(转)
原文链接:https://www.cnblogs.com/suvllian/p/5582540.html PHP页面间传值的几种方法 方法一:require_once //Page a: < ...
- symfony-安装,使用与创建应用程序以及创建第一个hello world界面
说明:由于学校里面要用到symfony3进行开发,并且之前对php和mysql有了一定的学习,所以这里进行对symfony2.3版本的学习,目前的版本已经到了symfony4了,但是本人之后要用到的是 ...
- Hadoop开启后jps显示只有jps
之前在用Mapreduce写代码时,在DFS Location下的会报一个error,大体的意思就是与主机名相关的错误 然后我就觉得可能时Hadoop开启时出了错误,然后我就重启了Hadoop,jps ...
- 自己总结的C#编码规范--7.文档下载 & 总结
今天终于把这一系列的编码规范写完了,这个编码规范算上前面阅读相关书籍,前前后后总共花了一个月的时间,也算是个人的呕心沥血之作了. 本来也没打算把这个系列写的这么长,但是在写的过程中自己搜了相关的网上资 ...
- S0.2 灰度图
目录 灰度图定义 灰度图优点 RGB转灰度算法(OpenCV3) 量化 算法公式 OpenCV自带函数实现 综合比较 灰度图定义 对于单色(灰度)图像而言,每个像素的亮度用一个数值来表示,通常数值范围 ...
- XVIII Open Cup named after E.V. Pankratiev. Grand Prix of Khamovniki
A. Ability Draft 记忆化搜索. #include<stdio.h> #include<iostream> #include<string.h> #i ...