1. 程式人生 > 其它 >Java常用包系列--SpringSession(原理)

Java常用包系列--SpringSession(原理)

技術標籤:專案(後端)

其他網址

分散式Session系列--綜述_分散式_feiying0canglang的部落格-CSDN部落格

第十七節 SpringBoot通過Redis實現Session共享_大宇的部落格,歡迎訪問-CSDN部落格
springboot整合redis和springsession做session伺服器_請叫我猿叔叔的部落格-CSDN部落格
SpringBoot 2 整合 Spring Session 最簡操作_ljk126wy的部落格-CSDN部落格
spring boot + redis 實現session共享分析 - 簡書

簡介

簡介

Spring-Session原理:設計一個Filter,利用HttpServletRequestWrapper,實現自己的 getSession()方法,接管建立和管理Session資料的工作。

Redis儲存Session不僅僅可以用Spring-Session,還可以用Shiro引入。Shiro+Redis用法見:Shiro應用系列--快取_feiying0canglang的部落格-CSDN部落格

請求攔截

Spring Session 是通過SessionRepositoryFilter過濾器進行攔截,然後通過SessionRepositoryRequestWrapper繼承HttpServletRequestWrapper進行管理Session。

存放策略

Spring Session 為我們提供了3中存放的策略而每種策略提供對應的註解啟動。分別為:

(1)NoSql形式的MongoDb:@EnableMongoHttpSession

(2)持久化形式的JDBC:@EnableJdbcHttpSession
(3)快取形式的Redis:@EnableRedisHttpSession

Spring Session:共享Session過程

1.過濾請求

Spring-Session自動注入springSessionRepositoryFilter過濾器,每次請求都過濾。其本質是:對每個請求的request進行一次封裝。request.getSession()的時候,實際上拿到是Spring封裝後的session,這個session儲存在Redis中。

2.應用獲取session

應用通過 getSession(boolean create) 方法來獲取 session 資料,引數 create 表示 session 不存在時是否建立新的 session 。 getSession 方法首先從請求的 “.CURRENT_SESSION” 屬性來獲取 currentSession 。

3.若session不存在

沒有 currentSession ,則從 request 取出 sessionId ,然後讀取 spring:session:sessions:[sessionId] 的值,同時根據 lastAccessedTime 和 MaxInactiveIntervalInSeconds 來判斷這個 session 是否過期。如果 request 中沒有 sessionId ,說明該使用者是第一次訪問,會根據不同的實現,如 RedisSession ,MongoExpiringSession ,GemFireSession 等來建立一個新的 session ,新增到請求中。​

從 request 取 sessionId 依賴具體的 HttpSessionStrategy 的實現,Spring Session 給了兩個預設的實現 CookieHttpSessionStrategy 和 HeaderHttpSessionStrategy ,即從 cookie 和 header 中取出 sessionId 。

整體流程

Spring-Session會自動注入springSessionRepositoryFilter過濾器,每一次的請求都由他來過濾,其本質是:對每一個請求的request進行了一次封裝。request.getSession()的時候,實際上拿到是Spring封裝後的session,這個session儲存在Redis資料庫中。

應用通過 getSession(boolean create) 方法來獲取 session 資料,引數 create 表示 session 不存在時是否建立新的 session 。 getSession 方法首先從請求的 “.CURRENT_SESSION” 屬性來獲取 currentSession ,沒有 currentSession ,則從 request 取出 sessionId ,然後讀取 spring:session:sessions:[sessionId] 的值,同時根據 lastAccessedTime 和 MaxInactiveIntervalInSeconds 來判斷這個 session 是否過期。

如果 request 中沒有 sessionId ,說明該使用者是第一次訪問,會根據不同的實現,如 RedisSession ,MongoExpiringSession ,GemFireSession 等來建立一個新的 session,新增到請求中 。​

從 request 取 sessionId 依賴具體的 HttpSessionStrategy 的實現,Spring Session 給了兩個預設的實現 CookieHttpSessionStrategy 和 HeaderHttpSessionStrategy ,即從 cookie 和 header 中取出 sessionId 。

Spring-Session在Redis中的儲存結構

spring:session是預設的Redis HttpSession字首(Redis中,常用’:’分割)。如上圖,每個session都建立3組資料:

(1)session本身的一些屬性(spring:session:sessions:9ab7456f-4882-40c1-b2c5-929f98395234)

hash結構,儲存:creationTime、maxInactiveInterval、、lastAccessedTime、session資訊(實體類的序列化資料)。

(2)會過期的key(spring:session:sessions:expires:9ab7456f-4882-40c1-b2c5-929f98395234)

string結構,value為空。

(3)以時間為key儲存在該時間點過期的sessionId列表(spring:session:expirations:1599909240000:)

set結構,儲存在這個時間過期的sessionId

session儲存結構總結

session在儲存時分為三個結構:

  1. session本身的一些屬性儲存
  2. 會過期的key
  3. 以時間為key儲存在該時間點需要過期的sessionId列表

為什麼需要三個儲存結構?

先說明第2個結構是用來幹嘛的,第二儲存一般設定成session的過期時間如30分鐘或者15分鐘,同時session的客戶端會註冊一個redis的key過期事件的監聽,一旦有key過期客戶端有會事件響應和處理。

在處理事件時可能會需要該session的資訊,這時候第1個結構就有用了,因此第1個結構的過期時間會比第二儲存過期時間多1-3min,這就是為什麼需要把屬性儲存和過期分開的原因。

那第3個結構的用處呢?對`Redis`比較熟悉的同學一定會知道其中的奧祕,因為`Redis`的key過期方式是定期隨機測試是否過期和獲取時測試是否過期(也稱懶刪除),由於定期隨機測試Task的優先順序是比較低的,所以即便這個key已經過期但是沒有測試到所以不會觸發key過期的事件。所以,第3個結構的用處的意義在於,儲存了什麼時間點會過期的session,這樣可以去主動請求來觸發懶刪除,以此觸發過期事件。

原始碼分析

其他網址

spring boot + redis 實現session共享分析 - 簡書
SpringSession系列-整合SpringBoot - 掘金

Spring-Session的Key

Spring-Session是使用預設的UUID來作為Key值來將Session儲存到Redis容器中的。

相關程式碼

// ---------- RedisSession的建構函式
RedisSession() {
    // 關鍵就是下面這句了, RedisSession以一個MapSession作為backup.
    // 另外JdbcSession也是以一個MapSession作為backup.
    this(new MapSession());
    this.delta.put(CREATION_TIME_ATTR, getCreationTime());
    this.delta.put(MAX_INACTIVE_ATTR, getMaxInactiveIntervalInSeconds());
    this.delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime());
    this.isNew = true;
    flushImmediateIfNecessary();
}

// ---------- MapSession的建構函式
public MapSession() {
    // 這裡的UUID.randomUUID().toString()就是預設的Redis中的鍵值; 
    // 當然其實Spring-Session將Session存入Redis時會給這個值附加一些前後綴. 不過與我們本次關注的重點關係不大. 就不再深入講解了.
    this(UUID.randomUUID().toString());
}

public MapSession(String id) {
    // 所以上面生成的uuid最終是被儲存在mapSession的id欄位裡.
    this.id = id;
}