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 ...
随机推荐
- Java学习之Java接口回调理解
Java接口回调 在Java学习中有个比较重要的知识点,就是今天我们要讲的接口回调.接口回调的理解如果解释起来会比较抽象,我一般喜欢用一个或几个经典的例子来帮助加深理解. 举例:老板分派给员工做事,员 ...
- SlopeOne
相信大家对如下的Category都很熟悉,很多网站都有类似如下的功能,“商品推荐”,"猜你喜欢“,在实体店中我们有导购来为我们服务,在网络上 我们需要同样的一种替代物,如果简简单单的在数据库 ...
- HTML5+CSS3点击指定按钮显示某些内容效果
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 【数据库】MySql分割字符串
上论坛时看到一个骨骼清奇的分割字符串算法. DROP TABLE IF EXISTS Tmp_AreaCode; CREATE TABLE Tmp_AreaCode( string ) )ENGINE ...
- ELK对Tomcat日志双管齐下-告警触发/Kibana日志展示
今天我们来聊一聊Tomcat,相信大家并不陌生,tomcat是一个免费开源的web应用服务器,属于轻量级的应用程序,在小型生产环境和并发不是很高的场景下被普遍使用,同时也是开发测试JSP程序的首选.也 ...
- C# 错误集锦
①字段重复 js → qs 仔细 ② 代码臃肿 通过判断 资产类型zc_type来判断模块的显隐 实际在其中嵌入 <%=zc_type == "2"?"" ...
- NEO GUI 多方签名使用
众所周至,NEOGUI是一个开发者演示用钱包,使用体验是非常的不友好的. 今天本来打算使用多方签名账户,发现和想象的不一样,请教了小伙伴也不行.遂调试了一下原因,发现踩进坑里了. 把这个问题记 ...
- 如果往错误的NEO地址转账会发生什么
昨天聊天有人用NEO往错误地址转账丢钱了 我的第一反应是这不可能 Neo使用的地址带有验证功能 最下面ARPP-.G6ce这一串是个base58编码 把他解开就是17-.151f7b5f这一串 红 ...
- Java HotSpot(TM) 64-Bit Server VM warning
Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00000007e4200000, 467140608, 0) ...
- NOIP-Cantor表
题目描述 现代数学的著名证明之一是Georg Cantor证明了有理数是可枚举的.他是用下面这一张表来证明这一命题的: 我们以Z字形给上表的每一项编号.第一项是1/1,然后是1/2,2/1,3/1,2 ...