響應式 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 會話。現在您的應用程式已配置,您可能希望開始自定義設定

使用 JSON 序列化會話

預設情況下,Spring Session Data Redis 使用 Java 序列化來序列化會話屬性。有時這可能會有問題,尤其是當您有多個應用程式使用相同的 Redis 例項但具有相同類的不同版本時。您可以提供一個 RedisSerializer bean 來自定義會話如何序列化到 Redis。Spring Data Redis 提供了 GenericJackson2JsonRedisSerializer,它使用 Jackson 的 ObjectMapper 序列化和反序列化物件。

配置 RedisSerializer
@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);
}

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 鍵空間事件 來清理過期會話。更具體地說,它監聽傳送到 __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();
    }

}

監聽會話事件

通常,對會話事件作出反應很有價值,例如,您可能希望根據會話生命週期進行某種處理。

您可以配置應用程式監聽 SessionCreatedEventSessionDeletedEventSessionExpiredEvent 事件。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
    }

}
© . This site is unofficial and not affiliated with VMware.