反应式 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'
并将 annotation 添加到配置类中:@EnableRedisIndexedWebSession
@Configuration
@EnableRedisIndexedWebSession
public class SessionConfig {
// ...
}
就是这样。您的应用程序现在具有反应式 Redis 支持的索引 Web 会话支持。 现在您已经配置了应用程序,您可能希望开始自定义内容:
-
我想为 Spring Session 使用的键指定不同的命名空间。
-
我想知道 Spring Session 是如何清理过期的会话的。
-
我想更改会话清理的频率。
-
我想控制清理任务。
-
我想监听会话事件。
使用 JSON 序列化 Session
默认情况下,Spring Session Data Redis 使用 Java 序列化来序列化会话属性。
有时可能会出现问题,尤其是当您有多个应用程序使用同一个 Redis 实例但具有同一类的不同版本时。
您可以提供一个 Bean 来自定义如何将会话序列化为 Redis。
Spring Data Redis 提供了使用 Jackson 的 序列化和反序列化对象的 。RedisSerializer
GenericJackson2JsonRedisSerializer
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 模块的自定义。
如果您不需要 Spring Security Jackson 模块,则可以注入应用程序的 bean 并按如下方式使用它:ObjectMapper
ObjectMapper
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(ObjectMapper objectMapper) {
return new GenericJackson2JsonRedisSerializer(objectMapper);
}
bean 名称必须如此,这样它就不会与 Spring Data Redis 使用的其他 bean 冲突。
如果提供了不同的名称,则 Spring Session 不会选择它。 |
指定不同的命名空间
多个应用程序使用同一个 Redis 实例,或者希望将会话数据与 Redis 中存储的其他数据分开,这种情况并不少见。
出于这个原因, Spring Session 如果需要,使用 (默认为 )来保持 session 数据分离。namespace
spring:session
您可以通过在 annotation 中设置属性来指定:namespace
redisNamespace
@EnableRedisIndexedWebSession
@Configuration
@EnableRedisIndexedWebSession(redisNamespace = "spring:session:myapplication")
public class SessionConfig {
// ...
}
了解 Spring Session 如何清理过期的会话
Spring Session 依赖于 Redis Keyspace Events 来清理过期的会话。
更具体地说,它侦听发送到 and 通道的事件,并根据销毁的密钥解析会话 ID。__keyevent@*__:expired
__keyevent@*__:del
例如,假设我们有一个 id 为 的会话,并且该会话设置为 30 分钟后过期。
当达到过期时间时,Redis 将向通道发出一个事件,其中包含 message (该消息)已过期的 key。
然后,Spring Session 将从 key 中解析会话 id () 并从 Redis 中删除所有相关的 session key。1234
__keyevent@*__:expired
spring:session:sessions:expires:1234
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 事件
默认情况下, Spring Session 尝试将 Redis 配置为使用 this 发送密钥空间事件,而这反过来可能会将 configuration 属性设置为。
但是,如果 Redis 实例已得到适当保护,则此策略将不起作用。
在这种情况下,应该在外部配置 Redis 实例,并公开 Bean 类型的 Bean 以禁用自动配置。ConfigureNotifyKeyspaceEventsReactiveAction
notify-keyspace-events
Egx
ConfigureReactiveRedisAction.NO_OP
@Bean
public ConfigureReactiveRedisAction configureReactiveRedisAction() {
return ConfigureReactiveRedisAction.NO_OP;
}
更改会话清理的频率
根据应用程序的需要,您可能希望更改会话清理的频率。
为此,您可以公开一个 bean 并设置该属性:ReactiveSessionRepositoryCustomizer<ReactiveRedisIndexedSessionRepository>
cleanupInterval
@Bean
public ReactiveSessionRepositoryCustomizer<ReactiveRedisIndexedSessionRepository> reactiveSessionRepositoryCustomizer() {
return (sessionRepository) -> sessionRepository.setCleanupInterval(Duration.ofSeconds(30));
}
您还可以设置 invoke 以禁用清理任务。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();
}
}
侦听会话事件
通常,对会话事件做出反应是有价值的,例如,您可能希望根据会话生命周期进行某种处理。
您可以将应用程序配置为侦听 和 事件。
有几种方法可以在 Spring 中监听应用程序事件,对于这个例子,我们将使用 Comments。SessionCreatedEvent
SessionDeletedEvent
SessionExpiredEvent
@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
}
}