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 ...
随机推荐
- H2 Database Engine
http://www.h2database.com/html/main.html H2 Database Engine Welcome to H2, the Java SQL database. Th ...
- JAVA 数组元素的反转
package Code411;/*数组元素的反转本来[1,2,3,4]反转后[4,3,2,1]1.对称位置的元素交换2.对称位子需要两个索引3.int temp =a:a=b;b=temp;4.什么 ...
- jsp 部署
部署一个Servlet 3.0应用程序是一件轻而易举的 事.通过Servlet注解类型,对于不太复杂的应用程序, 可以部署没有描述符的Servlet/JSP应用程序.尽管如 此,在需要更加精细配置的情 ...
- 【Linux】-- Linux上java运行环境的配置(JDK+TOMCAT)
1.JDK安装 安装之前首先要查询软件是否存可以直接使用yum安装 yum search java | grep open 选择需要的版本安装 注意:*星号代表下载该版本的所有文件,不能少. 验证是否 ...
- webpack的devServer配置错误
首先声明,之前vue项目没有报错,做react项目这样配置就报错了. 一.问题描述 [HMR] Hot Module Replacement is disabled. 二.问题分析 不太理解控制台为什 ...
- forEach循环
一.语法 var myArr=['camille','2020','vas','en','France']; // 1.只输出元素,传一个参数 myArr.forEach(function (ele) ...
- npm那些事儿
npm,Node Package Manager,是node.js的模块依赖管理工具,安装nodejs时,一般会附带npm包管理工具. 一.npm相关1.npm的用途 能解决NodeJS代码部署上的很 ...
- ARC 103
目录 官方题解 C 官方题解 C 这道题教会了我怎样正确统计众数和第二众数........... 我之前的方法是错的 #include <bits/stdc++.h> using name ...
- 洛谷.5284.[十二省联考2019]字符串问题(后缀自动机 拓扑 DP)
LOJ BZOJ 洛谷 对这题无话可说,确实比较...裸... 像dls说的拿拓扑和parent树一套就能出出来了... 另外表示BZOJ Rank1 tql... 暴力的话,由每个\(A_i\)向它 ...
- BZOJ.4942.[NOI2017]整数(分块)
BZOJ 洛谷 UOJ 可能是退役之前最后一个BZOJ rank1了? 参考这里. 如果没有减法,对一个二进制数暴力进位,均摊复杂度是\(O(1)\)的(要进\(O(n)\)次位就至少需要\(O(n) ...