Spring Session + Redis 使用

1. Session Cookie

由於Http協議是無狀態的協議,爲了能夠記住請求的狀態,於是引入了Session和Cookie的機制。

Session是存在於服務器端的,在單體式應用中,它是由Tomcat管理的,存在於Tomcat的內存中。

當我們爲了解決分佈式場景中的Session共享問題時,引入了Redis,其共享內存,以及支持key自動過期的特性,非常契合Session的特性,我們在企業開發中最常用的也就是這種模式。但是隻要你願意,也可以選擇存儲在JDBC,Mongo中,這些,Spring都提供了默認的實現,在大多數情況下,我們只需要引入配置即可。

Cookie則是存在於客戶端,更方便理解的說法,可以說存在於瀏覽器。Http協議允許從服務器返回Response時攜帶一些Cookie,並且同一個域下對Cookie的數量有所限制,之前說過Session的持久化依賴於服務端的策略,而Cookie的持久化則是依賴於本地文件。雖然說Cookie並不常用,但是有一類特殊的Cookie卻是我們需要額外關注的,那便是與Session相關的sessionId,他是真正維繫客戶端和服務端的橋樑。

當服務端往Session中保存一些數據時,Response中自動添加了一個Cookie:JSESSIONID:xxxx,再後續的請求中,瀏覽器也是自動的帶上了這個Cookie,服務端根據Cookie中的JSESSIONID取到了對應的Session。

客戶端服務端是通過JSESSIONID進行交互的,並且,添加和攜帶key爲JSESSIONID的Cookie都是Tomcat和瀏覽器自動幫助我們完成的。

不同瀏覽器,訪問是隔離的,甚至重新打開同一個瀏覽器,JSESSIONID也是不同的。但是存放在客戶端的Cookie還是存在安全問題的,可修改Cookie“騙”過了服務器。

Spring Sessoin使用第三方倉儲來實現集羣Session管理,也就是常說的分佈式Session容器,替換應用容器(如Tomcat的Session容器)。倉儲的實現,Spring Session提供了三個實現(redis,mongodb,jdbc),其中Redis使我們最常用的。

2. Spring Session + Redis

添加依賴

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--spring2.0集成redis所需common-pool2-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.4.2</version>
</dependency>

添加Redis配置

spring:
  redis:
    # Redis默認情況下有16個分片,這裏配置具體使用的分片,默認是0
    database: 0
    host: localhost
    port: 6379
    # 連接密碼(默認爲空)
    password:
    # 連接超時時間(毫秒)
    timeout: 10000ms
    lettuce:
      pool:
        # 連接池最大連接數(使用負值表示沒有限制) 默認 8
        max-active: 8
        # 連接池最大阻塞等待時間(使用負值表示沒有限制) 默認 -1
        max-wait: -1
        # 連接池中的最大空閒連接 默認 8
        max-idle: 8
        # 連接池中的最小空閒連接 默認 0
        min-idle: 0

添加註解@EnableRedisHttpSession,可自定義Cookie的名稱,默認爲SESSION

@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {

    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
        defaultCookieSerializer.setCookiePath("/");
        defaultCookieSerializer.setCookieName("SESSION_TEST");
        return defaultCookieSerializer;
    }

}

Spring Session的核心流程是在SessionRepositoryFilter的doFilterInternal方法中。

SessionRepositoryFilter的核心操作是用來修改包裝請求和響應,負責包裝切換HttpSession至Spring Session的請求和響應。每個HttpRequest進入,都會被該Filter包裝成切換Session的請求很響應對象

3. Redis中的Session

redis中key
spring:session是默認的Redis HttpSession前綴。

類型 數據結構 TTL
A spring:session:sessions:bcfc0d30-290b-40c5-bdb9-8303c9449c7d Hash 35分鐘
B spring:session:expirations:1586512320000 Set 30分鐘
C spring:session:sessions:expires:bcfc0d30-290b-40c5-bdb9-8303c9449c7d String 30分鐘

A類型鍵
將內存中的Session信息序列化到了Redis中。

創建時間creationTime;最後訪問時間lastAccessedTime;最大間隔maxInactiveInterval;若有數據存在Session中,也是存在該Hash中,

B類型鍵
可當作一個桶,存放着這一分鐘應當過期的 Session 的 key,具體值是C類型鍵
時間計算:lastAccessedTime向上取整 + maxInactiveInterval * 1000

後臺有一個定時任務去“刪除”過期的 key,來補償 Redis 到期未刪除的 key。
具體操作就是取得當前時間的時間戳作爲 key,去 redis 中定位到 spring:session:expirations:{當前時間戳} ,這個 set 裏面存放的便是所有過期的 key 了。

每次 Session 的續簽,需要將舊桶中的數據移除,放到新桶中。前後相隔一分鐘訪問,會發現桶發生了變化。
當衆多用戶活躍時,桶的增刪和以及 Set 中數據的增刪都是很頻繁的,並且對應 key 的 ttl 時間也會被更新。

如果只有A和B兩個鍵可能存在併發問題,前後兩分鐘都進行續簽,可能導致後續籤的Session放在了前一個續簽桶裏,導致Session提前失效。

並且在Spring Session 中 A 類型鍵的過期時間是 35 分鐘,這意味着即便 Session 已經過期,我們還是可以在 Redis 中有 5 分鐘間隔來操作過期的 Session。於此同時,Spring Session 引入了 C 類型鍵來作爲 Session 的引用。

C類型鍵
具體作用便是在自身過期後觸發 Redis 的 keyspace notifications,keyspace notifications 只會告訴我們哪個鍵過期了,不會告訴我們內容是什麼。關鍵就在於如果 Session 過期後監聽器可能想要訪問 Session 的具體內容,然而自身都過期了,還怎麼獲取內容。

解耦 Session 的存儲和過期,並且使得 server 獲取到過期通知後可以訪問到 Session 真實的值。
對於用戶來說,C 類型鍵過期後,意味着登錄失效,而對於服務端而言,真正的過期其實是 A 類型鍵過期,這中間會有 5 分鐘的誤差。

總結
Spring Session 使用這B和C類型鍵來維持Session過期,Redis清除過期key的行爲是一個異步行爲且是一個低優先級的行爲,可能會導致session不被清除。於是引入了專門的expiresKey,來專門負責Session的清除.

Spring Session 是爲了嚴謹而設計了這一套方案,但引入了定時器和很多輔助的鍵值對,無疑對內存消耗和 cpu 消耗都是一種浪費。如果在生產環境大量使用 Spring Session,最好權衡下相關問題。

參考:
從零開始的Spring Session(一)
從零開始的Spring Session(二)
從零開始的Spring Session(三)
從Spring-Session源碼看Session機制的實現細節
Redis鍵空間通知
spring-session(一)揭祕
spring-session(一)揭祕續篇
Spring Session 官方文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章