リアクティブ Redis インデックス付き構成
Redis インデックス付き Web セッションサポートの使用を開始するには、次の依存関係をプロジェクトに追加する必要があります。
そして、@EnableRedisIndexedWebSession
アノテーションを構成クラスに追加します。
@Configuration
@EnableRedisIndexedWebSession
public class SessionConfig {
// ...
}
これで完了です。アプリケーションにリアクティブ Redis でサポートされるンデックス付き Web セッションのサポートが追加されました。アプリケーションの設定が完了したら、カスタマイズを開始できます。
Spring Session で使用されるキーに別の名前空間を指定したいと考えています。
セッションのクリーンアップの頻度を変更したいと考えています。
クリーンアップタスクを制御したいと考えています。
セッションイベントを聞きたい。
JSON を使用したセッションの直列化
デフォルトでは、Spring Session Data Redis は Java 直列化を使用してセッション属性を直列化します。特に、同じ Redis インスタンスを使用しているが、同じクラスの異なるバージョンを持つ複数のアプリケーションがある場合、問題が発生することがあります。RedisSerializer
Bean を提供して、セッションを Redis に直列化する方法をカスタマイズできます。Spring Data Redis は、Jackson の ObjectMapper
を使用してオブジェクトを直列化および逆直列化する GenericJackson2JsonRedisSerializer
を提供します。
@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);
}
|
別の名前空間の指定
複数のアプリケーションが同じ Redis インスタンスを使用することや、セッションデータを Redis に保存されている他のデータから分離しておきたいことは、珍しいことではありません。そのため、Spring Session は、必要に応じて namespace
(デフォルトは spring:session
) を使用してセッションデータを分離します。
namespace
を指定するには、@EnableRedisIndexedWebSession
アノテーションの redisNamespace
プロパティを設定します。
@Configuration
@EnableRedisIndexedWebSession(redisNamespace = "spring:session:myapplication")
public class SessionConfig {
// ...
}
Spring Session が期限切れのセッションをクリーンアップする方法を理解する
Spring Session は、期限切れのセッションをクリーンアップするために Redis キースペースイベント (英語) に依存します。具体的には、__keyevent@*__:expired
および __keyevent@*__:del
チャネルに発行されたイベントをリッスンし、破棄されたキーに基づいてセッション ID を解決します。
例として、ID 1234
のセッションがあり、そのセッションが 30 分で期限切れになるように設定されていると想像してみましょう。有効期限に達すると、Redis は有効期限が切れたキーであるメッセージ spring:session:sessions:expires:1234
を含むイベントを __keyevent@*__:expired
チャネルに送信します。次に、Spring Session はキーからセッション ID (1234
) を解決し、関連するすべてのセッションキーを Redis から削除します。
Redis の有効期限にのみ依存する場合の問題の 1 つは、キーがアクセスされていない場合に、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
}
}