SpringBoot应用系列文章
序
为承接SpringBoot应用之分布式会话这篇,本文主要解析一下SpringSession的原理。
Session解决方案
session复制
session粘合
-
集群session
扩展指定server
利用Servlet容器提供的插件功能,自定义HttpSession的创建和管理策略,并通过配置的方式替换掉默认的策略。不过这种方式有个缺点,就是需要耦合Tomcat/Jetty等Servlet容器的代码。这方面其实早就有开源项目了,例如 memcached-session-manager ,以及 tomcat-redis-session-manager 。暂时都只支持Tomcat6/Tomcat7。设计一个Filter
利用HttpServletRequestWrapper,实现自己的 getSession()方法,接管创建和管理Session数据的工作。spring-session就是通过这样的思路实现的。
SpringSession的几个关键类
SessionRepositoryFilter(
order是Integer.MIN_VALUE + 50
)
SessionRepositoryRequestWrapper与SessionRepositoryResponseWrapper,通过SessionRepository去操纵sessionSessionRepository
CookieHttpSessionStrategy
如何处理Session过期
SpringSession的redis实现,依赖了redis的过期机制。
redis的过期键的删除策略(懒性删除+定期删除
)
<<redis设计与实现>>一书提到过期键的三种删除策略:
定时删除:在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作。
惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。
定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。
redis实际是以懒性删除+定期删除这种策略组合来实现过期键删除的,导致Spring需要采用及时删除的策略(定时轮询
),在过期的时候,访问一下该key,然后及时触发惰性删除
Spring的轮询如何保证时效性
@Scheduled(cron = "0 * * * * *")
//每分钟跑一次,每次清除前一分钟的过期键
public void cleanExpiredSessions() {
long now = System.currentTimeMillis();
long prevMin = roundDownMinute(now);
if (logger.isDebugEnabled()) {
logger.debug("Cleaning up sessions expiring at " + new Date(prevMin));
}
String expirationKey = getExpirationKey(prevMin);
Set < String > sessionsToExpire = expirationRedisOperations.boundSetOps(expirationKey).members();
expirationRedisOperations.delete(expirationKey);
for (String session: sessionsToExpire) {
String sessionKey = getSessionKey(session);
touch(sessionKey);
}
}
这里的touch操作就是访问该key,然后触发redis删除。
/**
* 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
*/
private void touch(String key) {
sessionRedisOperations.hasKey(key);
}
主动删除session
public void onDelete(ExpiringSession session) {
long toExpire = roundUpToNextMinute(expiresInMillis(session));
String expireKey = getExpirationKey(toExpire);
expirationRedisOperations.boundSetOps(expireKey).remove(session.getId());
}
延长session过期时间
public void onExpirationUpdated(Long originalExpirationTimeInMilli, ExpiringSession session) {
if (originalExpirationTimeInMilli != null) {
long originalRoundedUp = roundUpToNextMinute(originalExpirationTimeInMilli);
String expireKey = getExpirationKey(originalRoundedUp);
expirationRedisOperations.boundSetOps(expireKey).remove(session.getId());
}
long toExpire = roundUpToNextMinute(expiresInMillis(session));
String expireKey = getExpirationKey(toExpire);
BoundSetOperations < String,
String > expireOperations = expirationRedisOperations.boundSetOps(expireKey);
expireOperations.add(session.getId());
long sessionExpireInSeconds = session.getMaxInactiveIntervalInSeconds();
String sessionKey = getSessionKey(session.getId());
expireOperations.expire(sessionExpireInSeconds + 60, TimeUnit.SECONDS);
sessionRedisOperations.boundHashOps(sessionKey).expire(sessionExpireInSeconds, TimeUnit.SECONDS);
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。