Reactive Redis 索引配置
若要開始使用 Redis 索引 Web Session 支援,您需要將以下相依性新增至您的專案
-
Maven
-
Gradle
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
implementation 'org.springframework.session:spring-session-data-redis'
並將 @EnableRedisIndexedWebSession
註解新增至配置類別
@Configuration
@EnableRedisIndexedWebSession
public class SessionConfig {
// ...
}
就這樣。您的應用程式現在具有反應式 Redis 後端的索引 Web Session 支援。既然您已配置好應用程式,您可能想要開始自訂一些項目
-
我想要為 Spring Session 使用的索引鍵指定不同的命名空間。
-
我想要變更 Session 清理的頻率。
-
我想要掌控清理任務。
-
我想要監聽 Session 事件。
使用 JSON 序列化 Session
預設情況下,Spring Session Data Redis 使用 Java 序列化來序列化 Session 屬性。有時這可能會造成問題,尤其是在您有多個應用程式使用相同的 Redis 執行個體,但具有相同類別的不同版本時。您可以提供 RedisSerializer
Bean 來客製化 Session 如何序列化至 Redis。Spring Data Redis 提供了 GenericJackson2JsonRedisSerializer
,它使用 Jackson 的 ObjectMapper
來序列化和還原序列化物件。
@Configuration
public class SessionConfig implements BeanClassLoaderAware {
private ClassLoader loader;
/**
* Note that the bean name for this bean is intentionally
* {@code springSessionDefaultRedisSerializer}. It must be named this way to override
* the default {@link RedisSerializer} used by Spring Session.
*/
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer(objectMapper());
}
/**
* Customized {@link ObjectMapper} to add mix-in for class that doesn't have default
* constructors
* @return the {@link ObjectMapper} to use
*/
private ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModules(SecurityJackson2Modules.getModules(this.loader));
return mapper;
}
/*
* @see
* org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang
* .ClassLoader)
*/
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.loader = classLoader;
}
}
上述程式碼片段使用了 Spring Security,因此我們正在建立一個自訂的 ObjectMapper
,它使用 Spring Security 的 Jackson 模組。如果您不需要 Spring Security Jackson 模組,您可以注入您應用程式的 ObjectMapper
Bean 並像這樣使用它
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(ObjectMapper objectMapper) {
return new GenericJackson2JsonRedisSerializer(objectMapper);
}
|
指定不同的命名空間
有多個應用程式使用相同的 Redis 執行個體,或者想要將 Session 資料與儲存在 Redis 中的其他資料分開是很常見的。因此,Spring Session 使用 namespace
(預設為 spring:session
) 在需要時保持 Session 資料分隔。
您可以透過在 @EnableRedisIndexedWebSession
註解中設定 redisNamespace
屬性來指定 namespace
@Configuration
@EnableRedisIndexedWebSession(redisNamespace = "spring:session:myapplication")
public class SessionConfig {
// ...
}
了解 Spring Session 如何清除過期的 Session
Spring Session 依賴 Redis Keyspace Events 來清除過期的 Session。更具體地說,它監聽發送到 __keyevent@*__:expired
和 __keyevent@*__:del
通道的事件,並根據已銷毀的索引鍵解析 Session ID。
舉例來說,假設我們有一個 Session ID 為 1234
的 Session,並且該 Session 設定為在 30 分鐘後過期。當達到過期時間時,Redis 將會發送一個事件到 __keyevent@*__:expired
通道,訊息為 spring:session:sessions:expires:1234
,這是過期的索引鍵。然後 Spring Session 將會從索引鍵解析 Session ID (1234
),並從 Redis 刪除所有相關的 Session 索引鍵。
完全依賴 Redis 過期的一個問題是,如果索引鍵未被存取,Redis 無法保證何時會觸發過期事件。如需更多詳細資訊,請參閱 Redis 文件中的 Redis 如何使索引鍵過期。為了規避過期事件不保證發生的事實,我們可以確保在預期索引鍵過期時存取每個索引鍵。這表示如果索引鍵上的 TTL 過期,當我們嘗試存取索引鍵時,Redis 將會移除索引鍵並觸發過期事件。因此,每個 Session 過期也會透過將 Session ID 儲存在一個依過期時間排序的排序集中來追蹤。這允許背景任務存取可能過期的 Session,以確保 Redis 過期事件以更具確定性的方式觸發。例如
ZADD spring:session:sessions:expirations "1.702402961162E12" "648377f7-c76f-4f45-b847-c0268bb48381"
我們不會明確地刪除索引鍵,因為在某些情況下,可能會發生競爭條件,錯誤地將索引鍵識別為已過期,但實際上並未過期。除了使用分散式鎖定 (這會嚴重影響我們的效能) 之外,沒有辦法確保過期對應的一致性。透過簡單地存取索引鍵,我們確保只有在該索引鍵上的 TTL 過期時才會移除該索引鍵。
預設情況下,Spring Session 每 60 秒將會檢索最多 100 個過期的 Session。如果您想要配置清理任務的執行頻率,請參閱變更 Session 清理的頻率章節。
配置 Redis 以發送 Keyspace 事件
預設情況下,Spring Session 嘗試使用 ConfigureNotifyKeyspaceEventsReactiveAction
配置 Redis 以發送 Keyspace 事件,這反過來可能會將 notify-keyspace-events
配置屬性設定為 Egx
。但是,如果 Redis 執行個體已正確保護,則此策略將無法運作。在這種情況下,Redis 執行個體應在外部配置,並且應公開類型為 ConfigureReactiveRedisAction.NO_OP
的 Bean 以停用自動配置。
@Bean
public ConfigureReactiveRedisAction configureReactiveRedisAction() {
return ConfigureReactiveRedisAction.NO_OP;
}
變更 Session 清理的頻率
根據您的應用程式需求,您可能想要變更 Session 清理的頻率。若要執行此操作,您可以公開 ReactiveSessionRepositoryCustomizer<ReactiveRedisIndexedSessionRepository>
Bean 並設定 cleanupInterval
屬性
@Bean
public ReactiveSessionRepositoryCustomizer<ReactiveRedisIndexedSessionRepository> reactiveSessionRepositoryCustomizer() {
return (sessionRepository) -> sessionRepository.setCleanupInterval(Duration.ofSeconds(30));
}
您也可以設定調用 disableCleanupTask()
來停用清理任務。
@Bean
public ReactiveSessionRepositoryCustomizer<ReactiveRedisIndexedSessionRepository> reactiveSessionRepositoryCustomizer() {
return (sessionRepository) -> sessionRepository.disableCleanupTask();
}
掌控清理任務
有時,預設的清理任務可能不足以滿足您應用程式的需求。您可能想要採用不同的策略來清理過期的 Session。由於您知道Session ID 儲存在索引鍵 spring:session:sessions:expirations
下的排序集中,並依其過期時間排序,您可以停用預設的清理任務並提供您自己的策略。例如
@Component
public class SessionEvicter {
private ReactiveRedisOperations<String, String> redisOperations;
@Scheduled
public Mono<Void> cleanup() {
Instant now = Instant.now();
Instant oneMinuteAgo = now.minus(Duration.ofMinutes(1));
Range<Double> range = Range.closed((double) oneMinuteAgo.toEpochMilli(), (double) now.toEpochMilli());
Limit limit = Limit.limit().count(1000);
return this.redisOperations.opsForZSet().reverseRangeByScore("spring:session:sessions:expirations", range, limit)
// do something with the session ids
.then();
}
}
監聽 Session 事件
通常,對 Session 事件做出反應很有價值,例如,您可能想要根據 Session 生命週期執行某種處理。
您可以配置您的應用程式以監聽 SessionCreatedEvent
、SessionDeletedEvent
和 SessionExpiredEvent
事件。在 Spring 中,有幾種監聽應用程式事件的方法,在此範例中,我們將使用 @EventListener
註解。
@Component
public class SessionEventListener {
@EventListener
public Mono<Void> processSessionCreatedEvent(SessionCreatedEvent event) {
// do the necessary work
}
@EventListener
public Mono<Void> processSessionDeletedEvent(SessionDeletedEvent event) {
// do the necessary work
}
@EventListener
public Mono<Void> processSessionExpiredEvent(SessionExpiredEvent event) {
// do the necessary work
}
}