響應式 Redis 索引配置
要開始使用 Redis 索引 Web 會話支援,您需要向專案中新增以下依賴項
-
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 會話。現在您的應用程式已配置,您可能希望開始自定義設定
-
我想為 Spring Session 使用的鍵指定不同的名稱空間。
-
我想知道 Spring Session 如何清理過期會話。
-
我想更改會話清理的頻率。
-
我想控制清理任務。
-
我想監聽會話事件。
使用 JSON 序列化會話
預設情況下,Spring Session Data Redis 使用 Java 序列化來序列化會話屬性。有時這可能會有問題,尤其是當您有多個應用程式使用相同的 Redis 例項但具有相同類的不同版本時。您可以提供一個 RedisSerializer bean 來自定義會話如何序列化到 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 例項,或者希望將會話資料與 Redis 中儲存的其他資料分開,這並不少見。因此,Spring Session 使用一個 namespace(預設為 spring:session)來在需要時將會話資料分開。
您可以透過在 @EnableRedisIndexedWebSession 註解中設定 redisNamespace 屬性來指定 namespace
@Configuration
@EnableRedisIndexedWebSession(redisNamespace = "spring:session:myapplication")
public class SessionConfig {
// ...
}
瞭解 Spring Session 如何清理過期會話
Spring Session 依賴於 Redis 鍵空間事件 來清理過期會話。更具體地說,它監聽傳送到 __keyevent@*__:expired 和 __keyevent@*__:del 通道的事件,並根據被銷燬的鍵解析會話 ID。
例如,假設我們有一個會話 ID 為 1234,並且該會話設定為在 30 分鐘後過期。當達到過期時間時,Redis 將向 __keyevent@*__:expired 通道傳送一個事件,訊息為 spring:session:sessions:expires:1234,這是過期的鍵。Spring Session 然後將從鍵中解析會話 ID (1234) 並從 Redis 中刪除所有相關的會話鍵。
僅依賴 Redis 過期的一個問題是,如果鍵未被訪問,Redis 不保證何時會觸發過期事件。有關更多詳細資訊,請參閱 Redis 文件中的 Redis 如何使鍵過期。為了規避過期事件不一定發生的事實,我們可以確保在預期過期時訪問每個鍵。這意味著如果鍵上的 TTL 已過期,當我們嘗試訪問該鍵時,Redis 將刪除該鍵並觸發過期事件。因此,每個會話過期也透過將會話 ID 儲存在一個按其過期時間排序的有序集合中來跟蹤。這允許後臺任務訪問可能過期的會話,以確保 Redis 過期事件以更確定的方式觸發。例如
ZADD spring:session:sessions:expirations "1.702402961162E12" "648377f7-c76f-4f45-b847-c0268bb48381"
我們沒有明確刪除鍵,因為在某些情況下可能會出現競爭條件,錯誤地將會話識別為已過期而實際上並非如此。除了使用分散式鎖(這會極大地影響效能)之外,沒有辦法確保過期對映的一致性。透過簡單地訪問鍵,我們可以確保僅當該鍵上的 TTL 過期時才刪除該鍵。
預設情況下,Spring Session 每 60 秒檢索最多 100 個過期會話。如果您想配置清理任務執行的頻率,請參閱更改會話清理頻率部分。
配置 Redis 傳送鍵空間事件
預設情況下,Spring Session 嘗試使用 ConfigureNotifyKeyspaceEventsReactiveAction 配置 Redis 傳送鍵空間事件,這反過來可能會將 notify-keyspace-events 配置屬性設定為 Egx。但是,如果 Redis 例項已正確安全化,此策略將不起作用。在這種情況下,應在外部配置 Redis 例項,並暴露型別為 ConfigureReactiveRedisAction.NO_OP 的 Bean 以停用自動配置。
@Bean
public ConfigureReactiveRedisAction configureReactiveRedisAction() {
return ConfigureReactiveRedisAction.NO_OP;
}
更改會話清理頻率
根據您應用程式的需求,您可能希望更改會話清理的頻率。為此,您可以公開一個 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();
}
控制清理任務
有時,預設的清理任務可能不足以滿足應用程式的需求。您可能希望採用不同的策略來清理過期會話。由於您知道會話 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();
}
}
監聽會話事件
通常,對會話事件作出反應很有價值,例如,您可能希望根據會話生命週期進行某種處理。
您可以配置應用程式監聽 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
}
}