SpringSession原理解析

SpringBoot应用系列文章java

为承接SpringBoot应用之分布式会话这篇,本文主要解析一下SpringSession的原理。redis

Session解决方案

  • session复制算法

  • session粘合spring

  • 集群session数据库

    • 扩展指定server
      利用Servlet容器提供的插件功能,自定义HttpSession的建立和管理策略,并经过配置的方式替换掉默认的策略。不过这种方式有个缺点,就是须要耦合Tomcat/Jetty等Servlet容器的代码。这方面其实早就有开源项目了,例如 memcached-session-manager ,以及 tomcat-redis-session-manager 。暂时都只支持Tomcat6/Tomcat7。segmentfault

    • 设计一个Filter
      利用HttpServletRequestWrapper,实现本身的 getSession()方法,接管建立和管理Session数据的工做。spring-session就是经过这样的思路实现的。tomcat

SpringSession的几个关键类

  • SessionRepositoryFilter(order是Integer.MIN_VALUE + 50)
    SessionRepositoryRequestWrapper与SessionRepositoryResponseWrapper,经过SessionRepository去操纵sessionsession

  • SessionRepository

  • 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);
}

参考

相关文章
相关标签/搜索