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

參考

相關文章
相關標籤/搜索