1. 程式人生 > >Spring Session工作原理

Spring Session工作原理

本文首發於 vivo網際網路技術 微信公眾號 https://mp.weixin.qq.com/s/KCOFv0nRuymkX79-RZi9eg
作者:張正林

HTTP協議本身是無狀態的,為了儲存會話資訊,瀏覽器Cookie通過SessionID標識會話請求,伺服器以SessionID為key來儲存會話資訊。在單例項應用中,可以考慮應用程序自身儲存,隨著應用體量的增長,需要橫向擴容,多例項session共享問題隨之而來。

Spring Session就是為了解決多程序session共享的問題,本文將介紹怎麼使用Spring Session,以及Spring Session工作原理。

1、引入背景

應用部署在Tomcat時,session是由Tomcat記憶體維護,如果應用部署多個例項,session就不能共享。Spring Session就是解決為了解決分散式場景中的session共享問題。

2、使用方法

Spring Session支援儲存在Hazelcast 、Redis、MongoDB、關係型資料庫,本文主要討論session儲存在Redis。

web.xml配置:

<!-- spring session -->
  <filter>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

Spring 主要配置:

<!--建立了一個RedisConnectionFactory,它將Spring會話連線到Redis伺服器-->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <!--配置Redis連線池 ,可以不配置,使用預設就行!-->
        p:poolConfig-ref="jedisPoolConfig"
    </bean>
 
    <!--建立一個Spring Bean的名稱springSessionRepositoryFilter實現過濾器-->
    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <!--預設session時效30分鐘-->
        <property name="maxInactiveIntervalInSeconds" value="60" />
    </bean>

3、工作流程

Tomcat web.xml解析步驟:

contextInitialized(ServletContextEvent arg0); // Listener
init(FilterConfig filterConfig); // Filter
init(ServletConfig config); // Servlet

初始化順序:Listener > Filter > Servlet。

1) 通過 Tomcat 的 listener 把SessionRepositoryFilter載入到Spring容器中。

上一小節Spring配置檔案裡面聲明瞭RedisHttpSessionConfiguration,正是在其父類SpringHttpSessionConfiguration中生成了SessionRepositoryFilter:

@Bean
public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(
        SessionRepository<S> sessionRepository) {
    ......
    return sessionRepositoryFilter;
}

RedisHttpSessionConfiguration類繼承關係

2) filter初始化

web.xml裡面配置的filter是DelegatingFilterProxy。

DelegatingFilterProxy類繼承關係

DelegatingFilterProxy初始化入口在其父類GenericFilterBean中:

public final void init(FilterConfig filterConfig) throws ServletException {
        ......
        // Let subclasses do whatever initialization they like.
        initFilterBean();
        ......
    }

DelegatingFilterProxy去Spring容器取第1步初始化好的springSessionRepositoryFilter:

protected void initFilterBean() throws ServletException {
        synchronized (this.delegateMonitor) {
            if (this.delegate == null) {
                // If no target bean name specified, use filter name.
                if (this.targetBeanName == null) {
                    //targetBeanName 為springSessionRepositoryFilter
                    this.targetBeanName = getFilterName();
                }
                WebApplicationContext wac = findWebApplicationContext();
                if (wac != null) {
                    this.delegate = initDelegate(wac);
                }
            }
        }
    }

至此 sessionRepositoryFilter 初始化完成,DelegatingFilterProxy 實際代理了SessionRepositoryFilter。

SessionRepositoryFilter 工作核心流程:

protected void doFilterInternal(HttpServletRequest request,
           HttpServletResponse response, FilterChain filterChain)
           throws ServletException, IOException {
       request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
       //包裝了HttpServletRequest,覆寫了HttpServletRequest中 getSession(boolean create)方法
       SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
               request, response, this.servletContext);
             ......
       try {
           filterChain.doFilter(strategyRequest, strategyResponse);
       }
       finally {
           //保證session持久化
           wrappedRequest.commitSession();
       }
   }

4、快取機制

每一個session,Redis實際快取的資料如下:

spring:session:sessions:1b8b2340-da25-4ca6-864c-4af28f033327
spring:session:sessions:expires:1b8b2340-da25-4ca6-864c-4af28f033327
spring:session:expirations:1557389100000

spring:session:sessions為hash結構,儲存Spring Session的主要內容:

hgetall spring:session:sessions:1b8b2340-da25-4ca6-864c-4af28f033327
 1) "creationTime"
 2) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01j\x9b\x83\x9d\xfd"
 3) "maxInactiveInterval"
 4) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\a\b"
 5) "lastAccessedTime"
 6) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01j\x9b\x83\x9d\xfd"

spring:session:sessions:expires 為 string 結構,儲存一個空值。

spring:session:expirations為set結構,儲存1557389100000 時間點過期的spring:session:sessions:expires鍵值:

smembers spring:session:expirations:1557389100000
1) "\xac\xed\x00\x05t\x00,expires:1b8b2340-da25-4ca6-864c-4af28f033327"

RedisSessionExpirationPolicy,三個鍵值生成流程:

public void onExpirationUpdated(Long originalExpirationTimeInMilli,
            ExpiringSession session) {
        String keyToExpire = "expires:" + session.getId();
        long toExpire = roundUpToNextMinute(expiresInMillis(session));
                ......
        //把spring:session:sessions:expires加入到spring:session:expirations開頭的key裡面
        String expireKey = getExpirationKey(toExpire);
        BoundSetOperations<Object, Object> expireOperations = this.redis
                .boundSetOps(expireKey);
        expireOperations.add(keyToExpire);
 
        long fiveMinutesAfterExpires = sessionExpireInSeconds
                + TimeUnit.MINUTES.toSeconds(5);
        //spring:session:expirations開頭的key過期時間為xml配置的時間後五分鐘
        expireOperations.expire(fiveMinutesAfterExpires, TimeUnit.SECONDS);
        if (sessionExpireInSeconds == 0) {
            this.redis.delete(sessionKey);
        }
        else {
            //spring:session:sessions:expires開頭的key過期時間為xml配置的時間
            this.redis.boundValueOps(sessionKey).append("");
            this.redis.boundValueOps(sessionKey).expire(sessionExpireInSeconds,
                    TimeUnit.SECONDS);
        }
        //spring:session:sessions開頭的key過期時間為xml配置的時間後五分鐘
        this.redis.boundHashOps(getSessionKey(session.getId()))
                .expire(fiveMinutesAfterExpires, TimeUnit.SECONDS);
    }

Redis過期鍵有三種刪除策略,分別是定時刪除,惰性刪除,定期刪除。

  1. 定時刪除:通過維護一個定時器,過期馬上刪除,是最有效的,但是也是最浪費cpu時間的。
  2. 惰性刪除:程式在取出鍵時才判斷它是否過期,過期才刪除,這個方法對cpu時間友好,對記憶體不友好。
  3. 定期刪除:每隔一定時間執行一次刪除過期鍵的操作,並限制每次刪除操作的執行時長和頻率,是一種折中。

Redis採用了惰性刪除和定期刪除的策略。由此可見依賴 Redis 的過期策略實時刪除過期key是不可靠的。

另外一方面,業務可能會在Spring Session過期後做業務邏輯處理,同時需要session裡面的資訊,如果只有一個 spring:session:sessions鍵值,那麼Redis刪除就刪除了,業務沒法獲取session資訊。

spring:session:expirations鍵中儲存了spring:session:sessions:expires鍵,而spring:session:sessions:expires鍵過期五分鐘早於spring:session:expirations鍵和spring:session:sessions鍵(實際Spring Session對於過期事件處理訂閱的spring:session:sessions:expires鍵,下一節會具體講),這樣在訂閱到過期事件時還能獲取spring:session:sessions鍵值。

如果通過Redis本身清理機制未及時清除spring:session:sessions:expires,可以通過Spring Session提供的定時任務兜底,保證spring:session:sessions:expires清除。

RedisSessionExpirationPolicy,session清理定時任務

public void cleanExpiredSessions() {
        long now = System.currentTimeMillis();
        long prevMin = roundDownMinute(now);
        ......
        //獲取到spring:session:expirations鍵
        String expirationKey = getExpirationKey(prevMin);
        // 取出當前這一分鐘應當過期的 session
        Set<Object> sessionsToExpire = this.redis.boundSetOps(expirationKey).members();
        // 注意:這裡刪除的是spring:session:expirations鍵,不是刪除 session 本身!
        this.redis.delete(expirationKey);
        for (Object session : sessionsToExpire) {
            String sessionKey = getSessionKey((String) session);
            //遍歷一下spring:session:sessions:expires鍵
            touch(sessionKey);
        }
    }
 
    /**
     * 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) {
        //並不是直接刪除 key,而只是訪問 key,通過惰性刪除確保spring:session:sessions:expires鍵實時刪除,
        // 同時也保證多執行緒併發續簽的場景下,key移動到不同spring:session:expirations鍵裡面時,
        //以spring:session:sessions:expires鍵實際ttl時間為準
        this.redis.hasKey(key);
    }

5、事件訂閱

預設至少訂閱鍵空間通知gxE事件(http://redisdoc.com/topic/notification.html)。

ConfigureNotifyKeyspaceEventsAction,開啟鍵空間通知:

public void configure(RedisConnection connection) {
       String notifyOptions = getNotifyOptions(connection);
       String customizedNotifyOptions = notifyOptions;
       if (!customizedNotifyOptions.contains("E")) {
           customizedNotifyOptions += "E";
       }
       boolean A = customizedNotifyOptions.contains("A");
       if (!(A || customizedNotifyOptions.contains("g"))) {
           customizedNotifyOptions += "g";
       }
       if (!(A || customizedNotifyOptions.contains("x"))) {
           customizedNotifyOptions += "x";
       }
       if (!notifyOptions.equals(customizedNotifyOptions)) {
           connection.setConfig(CONFIG_NOTIFY_KEYSPACE_EVENTS, customizedNotifyOptions);
       }
   }

RedisHttpSessionConfiguration,註冊監聽事件:

@Bean
 public RedisMessageListenerContainer redisMessageListenerContainer(
         RedisConnectionFactory connectionFactory,
         RedisOperationsSessionRepository messageListener) {
             ......
     //psubscribe del和expired事件
     container.addMessageListener(messageListener,
             Arrays.asList(new PatternTopic("__keyevent@*:del"),
                     new PatternTopic("__keyevent@*:expired")));
     //psubscribe created事件
     container.addMessageListener(messageListener, Arrays.asList(new PatternTopic(
             messageListener.getSessionCreatedChannelPrefix() + "*")));
     return container;
 }

RedisOperationsSessionRepository,事件處理:

public void onMessage(Message message, byte[] pattern) {
      ......
      if (channel.startsWith(getSessionCreatedChannelPrefix())) {
          ...
          //處理spring:session created事件
          handleCreated(loaded, channel);
          return;
      }
 
      //非spring:session:sessions:expires事件不做處理
      String body = new String(messageBody);
      if (!body.startsWith(getExpiredKeyPrefix())) {
          return;
      }
 
      boolean isDeleted = channel.endsWith(":del");
      if (isDeleted || channel.endsWith(":expired")) {
          ......
          if (isDeleted) {
              //處理spring:session:sessions:expires del事件
              handleDeleted(sessionId, session);
          }
          else {
              //處理spring:session:sessions:expires expired事件
              handleExpired(sessionId, session);
          }
          ......
          return;
      }
  }

事件訂閱樣例:

@Component
public class SessionExpiredListener implements ApplicationListener<SessionExpiredEvent> {
    @Override
    public void onApplicationEvent(SessionExpiredEvent event) {
        ......
    }
}

6、總結

Spring Session給我們提供了很好的分散式環境下資源共享問題解決思路,其基於Servlet 規範實現,業務使用時只需要簡單配置就可以實現session共享,做到與業務低耦合,這都是以後我們專案開發中可以借籤的設計理念。

更多內容敬請關注 vivo 網際網路技術 微信公眾號

相關推薦

Spring Session工作原理

本文首發於 vivo網際網路技術 微信公眾號 https://mp.weixin.qq.com/s/KCOFv0nRuymk

Spring工作原理

屬性文件 價值 訪問 實現 討論 文件的 jdb 完成 面向對象 一、什麽是Spring (1)、Spring真正的精華是它的Ioc模式實現的BeanFactory和AOP,它自己在這個基礎上延伸的功能有些畫蛇添足。 (2)、 Spring它是一個開源的項目,而且目前非

spring mvc 工作原理

bubuko src mage 工作原理 img pri 圖片 技術分享 ring spring mvc 工作原理

詳細簡述Spring MVC 工作原理

Spring MVC 工作原理: 簡單理解:客戶端傳送請求----前端控制器接受客戶端請求DispatcherServlet----找到處理器對映HandlerMapping----找到處理器Handler----處理器返回一個模型檢視ModelAndView----檢視解

描述Cookie和Session的作用,區別和各自的應用範圍,Session工作原理

Session用於儲存每個使用者的專用資訊. 每個客戶端使用者訪問時,伺服器都為每個使用者分配一個唯一的會話ID(Session ID) . 她的生存期是使用者持續請求時間再加上一段時間(一般是20分鐘左右).Session中的資訊儲存在Web伺服器內容中,儲存

Spring PropertySourcesPlaceholderConfigurer工作原理

前言 Spring提供配置解析功能,就是這種: <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"

spring的核心機制(spring工作原理

spring的核心機制有兩點(個人理解,如有錯誤,歡迎各位大神糾正告訴我):1.IOC:控制反轉物件,程式本身不會建立或維護物件,而是把它交給spring去管理和維護。依賴注入和控制反轉含義相同,當某個Java例項需要另外一個java例項時,傳統的方法是由呼叫者來建立被呼叫者

Spring MVC工作原理 及註解說明

SpringMVC框架介紹     1) Spring MVC屬於SpringFrameWork的後續產品,已經融合在Spring Web Flow裡面。 Spring 框架提供了構建 Web 應用程式的全功能 MVC 模組。使用 Spring 可插入的 MVC 架構,

spring mvc工作原理解析

springMVC各個元件間工作路線圖: 各個元件解析: 1.前端控制器(DispacterServlet):由spring提供,不需要程式設計師編寫,配置在web.xml檔案中,主要用於接受前臺的請求、請求查詢controller以及向前臺作出響應,它是前臺請求進入

框架源碼系列八:Spring源碼學習之Spring核心工作原理(很重要)

ted pos avi Edito 重要 explicit mon alt 構造函數 目錄:一、搞清楚ApplicationContext實例化Bean的過程二、搞清楚這個過程中涉及的核心類三、搞清楚IOC容器提供的擴展點有哪些,學會擴展四、學會IOC容器這裏使用的設計模式

session rsyns 的工作原理

session rsyns 的工作原理session的工作原理 1.session實現與工作原理瀏覽器和服務器采用http無狀態的通訊,為了保持客戶端的狀態,使用session來達到這個目的。然而服務端是怎麽樣標示不同的客戶端或用戶呢?這裏我們可以使用生活中的一個例子,假如你參加一個晚會,認識了很多人,你會采

session工作原理

銷毀 這樣的 機制 過期 登陸 處理 art 客戶端瀏覽器 生成 一直在使用session存儲數據,一直沒有好好總結一下session的使用方式以及其工作原理,今天在這裏做一下梳理。這裏的介紹主要是基於php語言,其他的語言操作可能會有差別,但基本的原理不變。 1.在p

spring工作原理

模塊 反射機制 spa oca .class 關聯 sso 三種方式 += 對spring原理以前寫過類似的博客,地址:點擊打開鏈接。 但是經過一些時間後,盡管天天用著spring,但一提到原理方面,就遺忘了呢?就記得AOP和IOC,

spring學習9 Spring工作原理及其作用

支持 上下 構建 並且 tro 配置管理 模塊 operate 有助於 1.springmvc請所有的請求都提交給DispatcherServlet,它會委托應用系統的其他模塊負責負責對請求進行真正的處理工作。   2.DispatcherServlet查詢一個或多個Han

Spring Boot 揭秘與實戰 源碼分析 - 工作原理剖析

pro rop 功能 row commons 擴展 onf 公眾 ica 文章目錄 1. EnableAutoConfiguration 幫助我們做了什麽 2. 配置參數類 – FreeMarkerProperties 3. 自動配置類 – FreeMarkerAuto

Spring 工作原理

源代碼 工作 讀取 nbsp 動態 解析 object str loader spring框架有兩個重要的功能是IOC和AOP IOC(依賴註入):將對象的創建和依賴關系交給容器進行管理,簡化了開發過程。 AOP(面向切面):spring通過預編譯方式和運行期動態代理的方式

Spring高級話題[email protected]***註解的工作原理

sso metadata bool logs tcl task ota -c ann 出自:http://blog.csdn.net/qq_26525215 @EnableAspectJAutoProxy @EnableAspectJAutoProxy註解 激活Aspe

[Java]Servlet工作原理之二:Session與Cookie

工作 一段時間 .cn cookie font ava logs mage ont (未完待續) 一、Cookie Cookie 用於記錄用戶在一段時間內的行為,它有兩個版本:Version 0 和 Version 1,分別對應兩種響應頭 Set-Cookie 和 Set-

007-Spring Boot @Enable*註解的工作原理

sync express override factor run ext soft navi itself 一、@Enable* 啟用某個特性的註解 1、EnableConfigurationProperties 回顧屬性裝配 application.properti

Spring學習之旅(四)Spring工作原理再探

容器 mxml 實現 span ssp express 16px 部分 做了 上篇博文對Spring的工作原理做了個大概的介紹,想看的同學請出門左轉。今天詳細說幾點。 (一)Spring IoC容器及其實例化與使用 Spring IoC容器負責Bean的實例化、配置和組裝工