SpringSession會話管理之Redis與JDBC存儲實(shí)現(xiàn)方式
引言
在分布式應(yīng)用架構(gòu)中,會話管理是一個(gè)關(guān)鍵挑戰(zhàn)。傳統(tǒng)的容器內(nèi)存儲會話的方法在多個(gè)服務(wù)實(shí)例間難以共享,導(dǎo)致用戶在不同服務(wù)器間切換時(shí)會話丟失。
Spring Session提供了一種創(chuàng)建和管理用戶會話的標(biāo)準(zhǔn)方法,使會話可以脫離應(yīng)用服務(wù)器的限制,實(shí)現(xiàn)跨多個(gè)服務(wù)實(shí)例的會話共享。
一、Spring Session基本概念
Spring Session是Spring生態(tài)系統(tǒng)中的一個(gè)項(xiàng)目,它提供了一套API和實(shí)現(xiàn)來管理用戶會話。其核心優(yōu)勢在于能夠?qū)挻鎯膽?yīng)用服務(wù)器剝離,放置到外部存儲系統(tǒng)中,從而實(shí)現(xiàn)會話的跨應(yīng)用共享和持久化。
Spring Session的主要特性包括:
- 透明集成:對現(xiàn)有應(yīng)用代碼幾乎無侵入性
- 多種存儲方式:支持Redis、JDBC、MongoDB等多種后端存儲
- 安全支持:與Spring Security無縫集成
- REST API支持:提供了基于請求頭的會話傳遞機(jī)制,適用于RESTful應(yīng)用
- WebSocket支持:保證WebSocket通信中會話的連續(xù)性
要在項(xiàng)目中使用Spring Session,首先需要添加相應(yīng)的依賴。以Spring Boot項(xiàng)目為例,根據(jù)存儲類型選擇相應(yīng)的依賴:
<!-- 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> <!-- 或者JDBC存儲 --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
二、Redis實(shí)現(xiàn)會話存儲
Redis是一個(gè)高性能的鍵值存儲系統(tǒng),非常適合用于存儲會話數(shù)據(jù)。
使用Redis存儲會話具有低延遲、高可用性和水平擴(kuò)展能力,是分布式系統(tǒng)中會話管理的理想選擇。
2.1 配置Redis會話存儲
在Spring Boot應(yīng)用中,配置Redis會話存儲非常簡單:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @Configuration @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) // 會話過期時(shí)間:30分鐘 public class RedisSessionConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); return template; } }
@EnableRedisHttpSession
注解自動配置了一個(gè)RedisIndexedSessionRepository
,它負(fù)責(zé)會話的創(chuàng)建、保存、刪除和過期處理。
在application.properties或application.yml中配置Redis連接信息:
# Redis服務(wù)器連接配置 spring.redis.host=localhost spring.redis.port=6379 # 如果需要密碼認(rèn)證 spring.redis.password=your-password # Spring Session配置 spring.session.store-type=redis spring.session.redis.namespace=spring:session
2.2 Redis會話示例
Redis會話存儲的工作機(jī)制是透明的,開發(fā)者可以像使用普通HttpSession一樣使用它:
import javax.servlet.http.HttpSession; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class SessionController { @GetMapping("/session/set") public String setSessionAttribute(HttpSession session) { session.setAttribute("username", "john.doe"); session.setAttribute("role", "admin"); return "Session attributes set successfully"; } @GetMapping("/session/get") public String getSessionAttribute(HttpSession session) { String username = (String) session.getAttribute("username"); String role = (String) session.getAttribute("role"); return "Username: " + username + ", Role: " + role; } }
在后臺,Spring Session攔截了HttpSession的操作,將會話數(shù)據(jù)存儲在Redis中。Redis存儲會話的數(shù)據(jù)結(jié)構(gòu)如下:
spring:session:sessions:<sessionId>
- 存儲會話本身spring:session:sessions:expires:<sessionId>
- 存儲會話的過期信息spring:session:expirations:<expireTime>
- 按過期時(shí)間索引的會話列表
三、JDBC實(shí)現(xiàn)會話存儲
對于已經(jīng)使用關(guān)系型數(shù)據(jù)庫的應(yīng)用,JDBC會話存儲是一個(gè)不錯(cuò)的選擇。它利用現(xiàn)有的數(shù)據(jù)庫基礎(chǔ)設(shè)施,簡化了系統(tǒng)架構(gòu)和運(yùn)維復(fù)雜度。
3.1 配置JDBC會話存儲
在Spring Boot應(yīng)用中,配置JDBC會話存儲同樣簡單:
import org.springframework.context.annotation.Configuration; import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession; @Configuration @EnableJdbcHttpSession(maxInactiveIntervalInSeconds = 1800, tableName = "SPRING_SESSION") public class JdbcSessionConfig { // JDBC會話存儲不需要額外的Bean定義 }
@EnableJdbcHttpSession
注解配置了一個(gè)JdbcIndexedSessionRepository
,它使用JDBC來存儲和檢索會話數(shù)據(jù)。
在application.properties中配置數(shù)據(jù)源和會話存儲類型:
# 數(shù)據(jù)源配置 spring.datasource.url=jdbc:mysql://localhost:3306/session_db spring.datasource.username=root spring.datasource.password=password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # Spring Session配置 spring.session.store-type=jdbc spring.session.jdbc.initialize-schema=always
Spring Session會自動創(chuàng)建所需的數(shù)據(jù)庫表。默認(rèn)情況下,會創(chuàng)建以下表:
SPRING_SESSION
- 存儲會話元數(shù)據(jù)SPRING_SESSION_ATTRIBUTES
- 存儲會話屬性
3.2 JDBC會話表結(jié)構(gòu)
JDBC會話存儲使用的表結(jié)構(gòu)如下:
CREATE TABLE SPRING_SESSION ( PRIMARY_ID CHAR(36) NOT NULL, SESSION_ID CHAR(36) NOT NULL, CREATION_TIME BIGINT NOT NULL, LAST_ACCESS_TIME BIGINT NOT NULL, MAX_INACTIVE_INTERVAL INT NOT NULL, EXPIRY_TIME BIGINT NOT NULL, PRINCIPAL_NAME VARCHAR(100), CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID) ); CREATE TABLE SPRING_SESSION_ATTRIBUTES ( SESSION_PRIMARY_ID CHAR(36) NOT NULL, ATTRIBUTE_NAME VARCHAR(200) NOT NULL, ATTRIBUTE_BYTES BLOB NOT NULL, CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME), CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE );
這些表存儲了會話ID、創(chuàng)建時(shí)間、最后訪問時(shí)間、過期時(shí)間以及所有會話屬性。
四、高級特性與最佳實(shí)踐
4.1 自定義會話序列化
默認(rèn)情況下,Spring Session使用JDK序列化來存儲會話屬性。為了提高性能和兼容性,可以自定義序列化機(jī)制:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; @Configuration @EnableRedisHttpSession public class RedisSessionConfig { @Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { return new GenericJackson2JsonRedisSerializer(); } }
對于JDBC存儲,可以實(shí)現(xiàn)SessionConverter
接口來自定義序列化:
@Configuration @EnableJdbcHttpSession public class JdbcSessionConfig { @Bean public SessionConverter sessionConverter() { return new JacksonSessionConverter(); } }
4.2 會話事件監(jiān)聽
Spring Session提供了一系列事件,允許開發(fā)者在會話生命周期的不同階段執(zhí)行自定義邏輯:
import org.springframework.context.event.EventListener; import org.springframework.session.events.*; import org.springframework.stereotype.Component; @Component public class SessionEventListener { @EventListener public void onCreation(SessionCreatedEvent event) { System.out.println("Session created: " + event.getSessionId()); } @EventListener public void onExpiration(SessionExpiredEvent event) { System.out.println("Session expired: " + event.getSessionId()); } @EventListener public void onDeletion(SessionDeletedEvent event) { System.out.println("Session deleted: " + event.getSessionId()); } }
4.3 多瀏覽器會話支持
Spring Session支持在同一用戶的不同瀏覽器或設(shè)備上維護(hù)獨(dú)立的會話:
import org.springframework.context.annotation.Bean; import org.springframework.session.web.http.CookieSerializer; import org.springframework.session.web.http.DefaultCookieSerializer; @Configuration public class SessionConfig { @Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer serializer = new DefaultCookieSerializer(); serializer.setCookieName("SESSION"); serializer.setCookiePath("/"); serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); return serializer; } }
五、性能與安全考慮
在生產(chǎn)環(huán)境中使用Spring Session時(shí),需要考慮以下性能和安全因素:
- Redis持久化:配置Redis的持久化策略(AOF或RDB),確保在Redis服務(wù)重啟時(shí)不會丟失會話數(shù)據(jù)。
- 連接池管理:為Redis或數(shù)據(jù)庫配置適當(dāng)?shù)倪B接池,避免連接資源耗盡。
- 會話清理:定期清理過期的會話數(shù)據(jù),特別是使用JDBC存儲時(shí)。
- 會話超時(shí):根據(jù)應(yīng)用安全需求設(shè)置合理的會話超時(shí)時(shí)間。
- Cookie安全:配置會話Cookie的安全屬性:
@Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer serializer = new DefaultCookieSerializer(); serializer.setUseSecureCookie(true); // 僅在HTTPS連接中傳輸 serializer.setUseHttpOnlyCookie(true); // 防止JavaScript訪問 serializer.setSameSite("Strict"); // 防止CSRF攻擊 return serializer; }
總結(jié)
Spring Session為分布式應(yīng)用提供了一個(gè)強(qiáng)大而靈活的會話管理解決方案。通過將會話數(shù)據(jù)從應(yīng)用服務(wù)器轉(zhuǎn)移到Redis或關(guān)系型數(shù)據(jù)庫等外部存儲中,實(shí)現(xiàn)了會話的跨服務(wù)實(shí)例共享,為構(gòu)建可擴(kuò)展的分布式系統(tǒng)奠定了基礎(chǔ)。
Redis存儲方案提供了高性能和高可用性,適合對響應(yīng)時(shí)間要求較高的應(yīng)用;而JDBC存儲方案則充分利用了現(xiàn)有的數(shù)據(jù)庫基礎(chǔ)設(shè)施,適合已經(jīng)大量使用關(guān)系型數(shù)據(jù)庫的項(xiàng)目。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
基于Springboot+Netty實(shí)現(xiàn)rpc的方法 附demo
這篇文章主要介紹了基于Springboot+Netty實(shí)現(xiàn)rpc功能,在父項(xiàng)目中引入相關(guān)依賴結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02深入了解Java8中的時(shí)區(qū)日期時(shí)間
Java?在?java.time?包中也提供了幾個(gè)類用于處理需要關(guān)注時(shí)區(qū)的日期時(shí)間?API,本文將通過簡單的示例講講它們的用法,需要的可以參考一下2023-04-04Spring Security+Spring Data Jpa如何進(jìn)行安全管理
這篇文章主要介紹了Spring Security+Spring Data Jpa如何進(jìn)行安全管理,幫助大家更好的理解和學(xué)習(xí)Spring Security框架,感興趣的朋友可以了解下2020-09-09SpringDataJPA之Specification復(fù)雜查詢實(shí)戰(zhàn)
這篇文章主要介紹了SpringDataJPA之Specification復(fù)雜查詢實(shí)戰(zhàn),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11JUC并發(fā)編程LinkedBlockingQueue隊(duì)列深入分析源碼
LinkedBlockingQueue 是一個(gè)可選有界阻塞隊(duì)列,這篇文章主要為大家詳細(xì)介紹了Java中LinkedBlockingQueue的實(shí)現(xiàn)原理與適用場景,感興趣的可以了解一下2023-04-04