redis默认关闭键空间事件,需显式配置notify-keyspace-events ex启用过期通知;客户端须订阅__keyevent@n__:expired频道,且仅主节点生成事件。

Redis 的 notify-keyspace-events 默认是关闭的
Redis 启动时默认不发送任何键空间事件,哪怕你写了 EXPIRE 或 SET key val EX 60,过期也不会触发通知。这不是 bug,是设计选择——事件通知有开销,必须显式开启。
实操建议:
- 在
redis.conf中添加notify-keyspace-events Ex(大小写敏感),E表示启用键空间事件,x表示监听过期事件 - 如果用
redis-cli CONFIG SET notify-keyspace-events Ex动态设置,重启后会丢失,务必同步写入配置文件 - 别写成
notify-keyspace-events "Ex"(带引号)——Redis 会静默忽略,且不报错 - 注意:该配置对所有数据库生效,不能只对
db0开启而db1关闭
订阅 __keyevent@N__:expired 才能收到过期消息
开启配置后,Redis 并不会主动推送;客户端必须通过 PUBLISH/SUBSCRIBE 主动监听对应频道。频道名格式固定:__keyevent@<db_id>__:expired</db_id>,其中 <db_id></db_id> 是数据库编号(默认是 0)。
常见错误现象:
- 订阅了
__keyevent@0__:del却等EXPIRE通知——频道名不匹配,收不到 - 用
redis-cli --csv SUBSCRIBE '__keyevent@0__:expired'测试时,忘了加单引号,shell 把@当作特殊字符处理,导致订阅失败 - 应用连接的是
db2(SELECT 2),但监听的是@0频道——过期事件只发到实际发生操作的数据库频道
简单验证命令:
redis-cli SUBSCRIBE '__keyevent@0__:expired'
另起终端执行:
redis-cli SET testkey "val" EX 2
两秒后,第一个终端应输出类似:
1) "message" 2) "__keyevent@0__:expired" 3) "testkey"
EXPIRE 和 PEXPIRE 触发行为不一致
过期通知只在键真正被 Redis 主动删除那一刻发出,不是在 EXPIRE 命令返回时。这意味着:如果键已过期但尚未被惰性或定期删除机制清理,就不会发通知。
关键差异:
-
EXPIRE key 1设置 1 秒后过期,但若期间没访问该 key,且 Redis 还没轮到它做定期清理(默认每 100ms 检查一次,每次最多检查 20 个 key),通知可能延迟数秒甚至更久 -
PEXPIRE key 100也是同样逻辑,毫秒级精度不改变“触发时机取决于实际删除”这一本质 - 使用
DEL或FLUSHDB删除带过期时间的 key,**不会**触发expired事件——它走的是显式删除路径,发的是del事件
所以别指望靠这个做精确定时任务;它适合“某键大概率已失效,需要清理下游状态”的场景,比如 Session 过期后踢用户下线。
监听服务要防断连、重复、漏收
Redis 的 SUBSCRIBE 是无状态的:连接断开后,之前订阅的频道全部丢失,重连需重新 SUBSCRIBE;而且没有 ACK 机制,网络抖动可能导致消息丢失。
实操建议:
- 不要用短连接反复订阅;保持长连接,并实现自动重连 + 重订阅逻辑
- 收到
expired消息后,立刻用EXISTS key确认键是否真的没了——防止收到旧消息或重复消息(比如主从切换时可能重发) - 避免在回调里做耗时操作(如调远程 HTTP 接口),否则阻塞订阅线程,后续事件积压
- 如果业务要求强可靠性,得配合外部存储(如写入 Kafka / DB 记录过期事件)+ 幂等处理,Redis 自身不保证投递
最常被忽略的一点:从节点默认不产生键空间事件,即使开启了 notify-keyspace-events。只有写操作发生在主节点时,事件才由主节点发出。从节点只转发,不生成。










