Reactive 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 {
// ...
}
就這樣。你的應用程式現在擁有了基於 Reactive Redis 的索引式 Web 會話支援。現在你的應用程式已經配置好了,你可能想要開始自定義一些東西
使用 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,因此我們建立了一個使用 Spring Security 的 Jackson 模組的自定義 `ObjectMapper`。如果你不需要 Spring Security 的 Jackson 模組,你可以注入你應用程式的 `ObjectMapper` bean 並像這樣使用它
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(ObjectMapper objectMapper) {
return new GenericJackson2JsonRedisSerializer(objectMapper);
}
`RedisSerializer` bean 的名稱必須是 `springSessionDefaultRedisSerializer`,這樣它才不會與 Spring Data Redis 使用的其他 `RedisSerializer` bean 衝突。如果提供了不同的名稱,Spring Session 將不會選取它。 |
指定不同的名稱空間
多個應用程式使用同一個 Redis 例項,或者希望將會話資料與其他儲存在 Redis 中的資料分開,這並不少見。因此,Spring Session 使用 `namespace`(預設為 `spring:session`)來在需要時將會話資料分開。
你可以透過在 `@EnableRedisIndexedWebSession` 註解中設定 `redisNamespace` 屬性來指定 `namespace`
@Configuration
@EnableRedisIndexedWebSession(redisNamespace = "spring:session:myapplication")
public class SessionConfig {
// ...
}
理解 Spring Session 如何清理過期會話
Spring Session 依賴於 Redis Keyspace Events 來清理過期會話。更具體地說,它監聽傳送到 `__keyevent@*__:expired` 和 `__keyevent@*__:del` 通道的事件,並根據被銷燬的鍵解析出會話 ID。
例如,假設我們有一個會話,其 ID 為 `1234`,並且該會話設定為 30 分鐘後過期。當達到過期時間時,Redis 會向 `__keyevent@*__:expired` 通道傳送一個事件,訊息為 `spring:session:sessions:expires:1234`,這是已過期的鍵。然後 Spring Session 將從該鍵解析出會話 ID(`1234`),並從 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 傳送 Keyspace Events
預設情況下,Spring Session 嘗試使用 `ConfigureNotifyKeyspaceEventsReactiveAction` 配置 Redis 傳送 keyspace events,這可能會將 `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
}
}