1. 引言
很多场景下,我们需要在 redis 的 key 过期时触发一些业务逻辑,比如订单超时自动关闭、缓存失效后回源、限流窗口重置等。那么,redis 能不能监听 key 的过期时间呢?
答案是:可以。redis 从 2.8.0 版本开始支持 keyspace notifications(键空间通知),通过订阅 __keyevent@<db>__:expired 频道,就能在 key 过期时收到通知。
2. 核心原理
2.1 键空间通知机制
redis 的键空间通知(keyspace notifications)是一种发布/订阅(pub/sub)机制。当某个事件发生时(如 key 被修改、删除、过期),redis 会向对应的频道推送一条消息。
与 key 过期相关的事件有两类:
__keyspace@<db>__:<key>:键空间通知,频道名包含具体的 key 名。__keyevent@<db>__:expired:键事件通知,频道名只包含事件类型,消息内容为过期的 key 名。
实际开发中,我们通常订阅 __keyevent@<db>__:expired 频道,因为不需要为每个 key 单独订阅。
2.2 过期事件的触发时机
这里有一个非常重要的细节:redis 并不会在 key 到达 ttl 的那一刻立即推送过期事件。
redis 删除过期 key 有两种方式:
- 惰性删除:当客户端访问一个已过期的 key 时,redis 才将其删除。
- 定期删除:redis 每隔一段时间(默认 100ms)随机抽取一部分设置了过期时间的 key 进行检查,发现过期则删除。
只有 key 真正被删除时,才会触发过期事件。因此,过期事件的通知可能会有延迟,延迟时间取决于定期删除的执行周期和 key 的采样情况。
3. 开启过期事件监听
3.1 修改 redis 配置
默认情况下,redis 的键空间通知是关闭的。需要修改 redis.conf 配置文件:
notify-keyspace-events ex
其中:
e:表示开启键事件通知(keyevent)。x:表示过期事件(expired)。
如果不想修改配置文件,也可以在运行时通过命令动态开启:
redis-cli config set notify-keyspace-events ex
注意:config set 是运行时生效,但重启后会失效。如需持久化,仍需修改配置文件。
3.2 验证是否开启
可以通过 config get notify-keyspace-events 查看当前配置:
redis-cli config get notify-keyspace-events
输出结果中包含 ex 即表示已开启。
4. 代码实战
4.1 使用 redis 命令行验证
先通过命令行直观感受一下过期事件:
# 终端 1:订阅过期事件 redis-cli --csv psubscribe '__keyevent@0__:expired' # 终端 2:设置一个 5 秒过期的 key redis-cli set order:1001 "pending" ex 5
等待 5 秒后,终端 1 会收到类似如下的消息:
"pmessage","__keyevent@0__:expired","__keyevent@0__:expired","order:1001"
4.2 java(spring boot)实现
在 spring boot 中,可以通过 redismessagelistenercontainer 来监听过期事件。
首先,注册一个监听器:
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.patterntopic;
import org.springframework.data.redis.listener.redismessagelistenercontainer;
import org.springframework.data.redis.listener.adapter.messagelisteneradapter;
@configuration
public class rediskeyexpiredconfig {
@bean
public redismessagelistenercontainer redismessagelistenercontainer(
redisconnectionfactory connectionfactory,
messagelisteneradapter expiredlisteneradapter) {
redismessagelistenercontainer container = new redismessagelistenercontainer();
container.setconnectionfactory(connectionfactory);
// 订阅 db0 的过期事件
container.addmessagelistener(expiredlisteneradapter, new patterntopic("__keyevent@0__:expired"));
return container;
}
@bean
public messagelisteneradapter expiredlisteneradapter(rediskeyexpiredlistener listener) {
return new messagelisteneradapter(listener);
}
}然后,实现具体的监听逻辑:
import org.springframework.data.redis.connection.message;
import org.springframework.data.redis.connection.messagelistener;
import org.springframework.stereotype.component;
@component
public class rediskeyexpiredlistener implements messagelistener {
@override
public void onmessage(message message, byte[] pattern) {
// 获取过期的 key 名称
string expiredkey = new string(message.getbody());
system.out.println("key 已过期:" + expiredkey);
// 在这里编写业务逻辑,例如:
// - 订单超时自动关闭
// - 缓存失效后回源数据库
// - 发送通知等
if (expiredkey.startswith("order:")) {
handleordertimeout(expiredkey);
}
}
private void handleordertimeout(string orderkey) {
// 处理订单超时逻辑
system.out.println("订单超时:" + orderkey);
}
}4.3 python 实现
使用 redis-py 的 pub/sub 功能:
import redis
r = redis.redis(host="localhost", port=6379, db=0)
pubsub = r.pubsub()
pubsub.psubscribe("__keyevent@0__:expired")
print("开始监听 redis 过期事件...")
for message in pubsub.listen():
if message["type"] == "pmessage":
expired_key = message["data"].decode("utf-8")
print(f"key 已过期: {expired_key}")
# 在这里处理业务逻辑5. 注意事项与局限
5.1 事件可能丢失
redis 的 pub/sub 是即发即弃(fire-and-forget)模式。如果客户端在事件推送时处于断线状态,该事件就会丢失,不会重发。
对于不能容忍丢失的场景,建议改用 redis stream 或结合 redisson 的延迟队列 来实现可靠的消息投递。
5.2 过期时间不精确
如前文所述,过期事件的实际触发时间会晚于 ttl 设定的时间,存在一定的延迟。对于需要精确到秒级甚至毫秒级的定时任务,不建议依赖此机制。
5.3 订阅所有 key 的性能开销
如果 redis 中大量 key 频繁过期,事件通知会产生较大的网络和 cpu 开销。建议:
- 只订阅需要的数据库(如
__keyevent@0__:expired)。 - 通过 key 前缀在业务侧过滤,而不是为每个业务分别订阅。
5.4 集群模式下的差异
在 redis cluster 模式下,key 分散在不同的节点上,过期事件也会由对应节点推送。客户端需要订阅所有节点的过期事件,或者通过 proxy 层统一处理,否则可能漏掉部分事件。
6. 替代方案对比
| 方案 | 实时性 | 可靠性 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| keyspace notifications | 有延迟(秒级) | 低(可能丢失) | 低 | 非关键业务、允许延迟 |
| redis stream + 消费者组 | 较高 | 高(可持久化) | 中 | 需要可靠投递的业务 |
| redisson 延迟队列 | 较高 | 高 | 中 | 订单超时、定时任务 |
| 外部消息队列(如 rabbitmq、kafka) | 高 | 高 | 高 | 大规模分布式系统 |
7. 总结
redis 确实可以监听 key 的过期时间,核心机制是键空间通知。通过订阅 __keyevent@<db>__:expired 频道,我们可以在 key 过期时收到通知并触发业务逻辑。
但需要注意,这种方案存在事件丢失和触发延迟两个固有限制。对于订单超时这类对可靠性要求较高的场景,建议结合 redis stream 或延迟队列等方案,做到万无一失。
以上就是redis过期事件监听原理与实战指南的详细内容,更多关于redis过期事件监听的资料请关注代码网其它相关文章!
发表评论