redis键过期通知仅推送键名,需配置notify-keyspace-events ex并重启服务;监听器只获key,查value需额外操作;必须手动注册patterntopic、保证幂等,并配兜底定时任务。

不能直接监听到过期 value,Redis 的键过期通知只推送键名(key),不带任何值或上下文。这是底层设计决定的,不是 Spring Boot 或配置能绕过的限制。
必须开启 Redis 的 notify-keyspace-events 配置
Redis 默认关闭键空间事件通知,不改配置,Spring Boot 监听器永远收不到消息。
-
notify-keyspace-events必须至少包含Ex:其中E表示启用 keyevent 事件,x表示启用过期事件 - 推荐设为
AEx(兼容性更好),但生产环境建议最小化,仅用Ex - 修改方式:编辑
redis.conf,添加或修改行:notify-keyspace-events Ex - Windows 下若用服务安装,需改带
service后缀的配置文件;Linux 下通常改/etc/redis/redis.conf - 改完必须重启 Redis 进程(
redis-server --service-restart或systemctl restart redis),热加载不生效
KeyExpirationEventMessageListener 只能拿到 key 字符串
Spring Data Redis 提供的 KeyExpirationEventMessageListener 在 onMessage 中收到的 message.getBody() 是原始字节数组,解码后仅为键名(如 "order:12345"),没有对应 value、TTL、创建时间等任何附加信息。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 如果你依赖 value 做业务判断(比如检查订单当前状态是否仍为“待支付”),必须在监听器内重新查一次 Redis:
redisTemplate.opsForValue().get(key) - 注意并发风险:从过期通知到你查 Redis 之间可能有极短窗口,订单状态已被其他线程修改
- 不要在
onMessage中做耗时操作(如远程调用、DB 写入),否则会阻塞整个监听线程池,导致后续过期事件堆积 - 建议把实际业务逻辑投递到异步线程池或消息队列,监听器只做轻量分发
监听器注册必须绑定 __keyevent@N__:expired 主题
Spring Boot 不会自动订阅过期事件,必须显式注册监听器并指定 Topic 模式。
- Topic 名称格式严格为:
__keyevent@{db}__:expired,例如__keyevent@0__:expired表示监听 DB 0 的过期事件 - 如果 Redis 使用非默认 DB(如
database: 1),Topic 必须同步改成__keyevent@1__:expired,否则收不到 -
RedisMessageListenerContainer需手动addMessageListener(),不能只靠@Component注解自动注册 - 使用
PatternTopic而非ChannelTopic,因为事件是按模式匹配发布的
生产环境必须考虑事件丢失和重复问题
Redis 键过期通知是“尽力而为”的 fire-and-forget 机制,不保证 100% 投递,也不保证不重复。
- Redis 重启、主从切换、网络抖动都可能导致事件丢失 —— 所以不能把它当唯一取消依据
- 单个 key 过期可能触发多次通知(尤其在集群或高负载下),监听器必须幂等:比如用
redisTemplate.opsForValue().setIfAbsent("task:done:12345", "1", 1, TimeUnit.MINUTES)做执行标记 - 强烈建议搭配兜底方案:例如每 5 分钟跑一次定时任务扫描
order:前缀 + TTL 剩余 - 如果业务强依赖可靠性(如金融级订单关单),应优先选 Redisson 延迟队列或 RabbitMQ 延迟插件,而非原生过期监听
真正难的从来不是写监听器,而是怎么处理“通知来了但数据已变”“通知没来但时间到了”“通知来了两次”这三类情况。每个 case 都得结合业务容忍度加补偿逻辑,而不是指望一个 onMessage 方法包打全部。










