在 Spring Boot 中监听 Redis Key 过期事件
在 Redis 数据库中,可以通过 EXPIRE
、EXPIREAT
、EXPIRETIME
命令来设置 Key 的有效时间,当一个 Key 过期后会自动从数据库中删除,释放空间。
得益于于这个特性,我们可以很轻松地实现诸多类似于 “Session” 管理、数据缓存等功能。它们都有一个共同点就是,数据不会永久保存!
在有些场景中,我们可能希望在某些 Key 过期的时候获取到通知,进行一些业务处理。或者是干脆用于 “定时通知/任务” 功能,例如:下单 30 分钟后未支付,则取消订单。那么可以在用户下单的时候使用订单号作为 key 设置到 Redis 数据库中,并且设置过期时间为 30 分钟。当超时后,我们可以在 “key 过期通知” 中获取到 key 也就是订单号,判断用户是否已经支付从而是否取消订单。
注意: Redis 的 Key 过期通知功能本质上是通过 发布/订阅 功能实现的。也就是说,它不能保证通知消息的交付,当 Key 过期时如果服务器停机、重启中则该通知消息会永久丢失。
本文将会带你学习如何在 Spring Boot 应用中使用 Spring Data Redis 监听 Redis Key 过期事件。
本文所使用的软件版本:
- Spring Boot:
3.1.3
- Redis:
7.0.5
整合 Spring Data Redis
得益于 Spring Boot 对 Redis 开箱即用的支持,只需要 2 步,就可以快速地在 Spring Boot 中整合、使用 Redis。
你可以通过 start.springboot.io 快速创建示例应用。
添加 spring-boot-starter-data-redis 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置 application.yaml
spring:
data:
redis:
database: 0
host: localhost
port: 6379
password:
在配置文件中指定 redis 服务的主机、端口、密码(如果有)以及要使用的数据库。
更详细的配置信息请参考 Spring Boot 中文文档。
监听 Redis 过期 Key 通知
配置 RedisMessageListenerContainer
在 cn.springdoc.demo.config
包下创建 RedisConfiguration
,用于定义 RedisMessageListenerContainer
Bean。
package cn.springdoc.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
@Configuration
public class RedisConfiguration {
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory factory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
// container.setTaskExecutor(null); // 设置用于执行监听器方法的 Executor
// container.setErrorHandler(null); // 设置监听器方法执行过程中出现异常的处理器
// container.addMessageListener(null, null); // 手动设置监听器 & 监听的 topic 表达式
return container;
}
}
如上所述,Redis 过期 Key 通知本质上是使用 Redis 发布订阅(Pub/Sub) 实现,所以必须先在 Application Context 中定义 RedisMessageListenerContainer
bean。
其唯一必须的设置项是 RedisConnectionFactory
,得益于自动配置,factory
可以直接从方法注入。其他设置项非必须,你可以从 Java Doc 了解更多。
继承 KeyExpirationEventMessageListener
Spring Data Redis 专门提供了一个监听 Redis Key 过期事件的监听器:KeyExpirationEventMessageListener
。
我们只需要自定义监听器类,继承它,并且覆写 doHandleMessage(Message message)
方法即可。
在 cn.springdoc.demo.listener
包下创建 KeyExpireListener
类,如下:
package cn.springdoc.demo.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
@Component
public class KeyExpireListener extends KeyExpirationEventMessageListener {
final static Logger logger = LoggerFactory.getLogger(KeyExpireListener.class);
// 通过构造函数注入 RedisMessageListenerContainer 给 KeyExpirationEventMessageListener
public KeyExpireListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void doHandleMessage(Message message) {
// 过期的 key
byte[] body = message.getBody();
// 消息通道
byte[] channel = message.getChannel();
logger.info("message = {}, channel = {}", new String(body), new String(channel));
}
}
doHandleMessage
就是处理 Redis Key 过期通知事件的方法。其中 Message
表示通知消息,只有 2 属性,分别表示消息正文(在这里就是过期的 Key 名称)以及来自于哪个 channel。如下:
public interface Message extends Serializable {
byte[] getBody();
byte[] getChannel();
}
注意: 在 Redis Key 过期事件中,只能获取到已过期的 Key 的名称,不能获取到值。
测试
首先启动 Spring Boot 应用,确定程序正常启动。然后在 Redis 客户端执行如下命令:
SET hello "springdoc.cn" EX 10
上述命令,往 Redis 服务器设置了一个 Key 名称为 hello
、值为 springdoc.cn
的 string 数据,并且通过 EX
参数指定它过期时间为 10 秒。
然后,观察程序控制台,耐心等待 10 秒后,我们会看到如下日志信息:
... [enerContainer-1] c.s.demo.listener.KeyExpireListener : message = hello, channel = __keyevent@0__:expired
如你所见,我们成功监听到了 hello
这个 key 的过期事件。