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 ...
随机推荐
- C语言学习及应用笔记之五:C语言typedef关键字及其使用
在C语言中有一个typedef关键字,其用来定义用户自定义类型.当然,并不是真的创造了一种数据类型,而是给已有的或者符合型的以及复杂的数据类型取一个我们自己更容易理解的别名.总之,可以使用typede ...
- ARKit1.5 采坑
1.对应的生成的预制体,0.1的大小按照Cube的实际大小进行缩放. Plane和Cube都是0.1的情况下是不一样的大小的.
- 字符串(3)AC自动机
AC自动机真神奇,其实说白了就是在trie树上进行kmp模式匹配,不过刚接触确实有些难度,有些思想确实有些难以理解,所以学习的时候最好亲自手动模拟整个算法的全过程,那我就来写篇blog总结一下. 首先 ...
- 在Pythonfor循环中如何获取循环次数?
在Python的for循环里,循环遍历可以写成: for item in list: print item 它可以遍历列表中的所有元素,但是有什么方法可以知道到目前为止我循环了多少次? 想到的替代方案 ...
- jade模板 注意事项
1. jade模板 语法 doctype html html head body header div 2. 添加内容:直接在标签后边加空格 直接写内容 如下: div 我要写的内容 3. ...
- docker 进阶
docker 常用命令: docker pull hub.c.163.com/library/mysql:latest #这是从网址下载下来mysql镜像 docker run -d -p 88 ...
- 小程序 -- ui布局
Flex布局 相对定位和绝对定位 弹性盒模型 display flex-direction flex-wrap :nowrap(不换行)/ wrap(换行,第一行在上方)/ wrap-reverse ...
- UOJ#110. 【APIO2015】Bali Sculptures 贪心 动态规划
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ110.html 题解 我们发现n=2000 的子任务保证A=1! 分两种情况讨论: $n\leq 100$ ...
- Windows Server 2008 R2 修改远程桌面服务RDP默认端口及相应的防火墙配置
修改以下两个注册表项当中的默认端口3389为自定义端口: [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\Wd ...
- 2018-2019-1 20189201《Linux内核原理与分析》第三周作业
写作业之前,写了时光博物馆参观感受.1978-2018 40年的改革开放历程. 一.C语言中内嵌汇编语言的写法 内嵌汇编的语法如下: asm volatile ( 汇编语句模版: 输出部分: 输入部分 ...