Redis 的 LRU 淘汰机制不会触发 Pub/Sub 事件,只有键过期事件(需配置 notify-keyspace-events Ex)才能被监听;如果要实现缓存同步失效,应依赖 TTL + keyspace notification,而不是依赖 LRU 淘汰策略。

Redis 本身并没有真正意义上的“本地缓存同步”能力。它的 LRU 淘汰策略只在单个 Redis 实例内部生效,无法把淘汰结果跨进程、跨服务传播出去。如果想依靠 maxmemory-policy allkeys-lru 来触发 Pub/Sub,通知其他节点“这个 key 刚刚被淘汰了”,实际上是不可行的,因为 Redis 不会为内存淘汰事件发布消息。
Redis 的 LRU 淘汰不会触发任何 Pub/Sub 事件
这是一个非常常见的误区。很多人以为只要开启了 allkeys-lru,当 key 被移除时就能通过订阅机制收到通知。但事实并非如此。像 DEL、EXPIRE、EVICT 这类底层行为里,Redis 默认不会针对淘汰动作对外广播。只有在 key 明确进入过期流程,且启用了 keyspace notifications 的情况下,Redis 才会向 __keyevent@ 频道发送消息。需要注意的是,淘汰(eviction)和过期(expiration)并不是一回事,两者的实现机制彼此独立。
EXPIRE/SET ... EX→ 可能触发expired事件(前提是配置了notify-keyspace-events Ex)LRU淘汰 → 全程静默,没有 channel、没有日志、也没有回调通知- 即便使用
redis-cli --stat看到evicted_keys持续增长,也无法得知究竟是哪个 key 被淘汰
想同步“被 LRU 踢掉的 key”,得自己埋点拦截
如果你确实需要让其他服务感知“某个 key 已经从当前 Redis 缓存中消失”,就不能依赖自动淘汰,而应该绕开 LRU 机制,改用显式可控的失效方式:
- 不要依赖自动淘汰,改为使用带 TTL 的
SET key value EX 300主动设置过期时间 - 提前开启 keyspace notifications:执行
CONFIG SET notify-keyspace-events Ex(也可以写入redis.conf) - 订阅
__keyevent@0__:expired频道(注意 db 编号必须一致) - 收到消息后,解析 payload(即 key 名),再通过自定义 channel(如
cache-evict-broadcast)使用PUBLISH转发给其他节点
示例监听逻辑(Python):
import redis
conn = redis.Redis()
pubsub = conn.pubsub()
pubsub.subscribe('__keyevent@0__:expired')
for msg in pubsub.listen():
if msg['type'] == 'message':
key = msg['data'].decode()
# 这里不是淘汰,是过期;但语义上可当作“缓存失效”处理
conn.publish('cache-invalidation', key)
Pub/Sub 同步缓存失效,别碰 LRU,盯紧 TTL + keyspace events
真正可落地的方案不是“同步淘汰”,而是“同步缓存失效”:
- 所有写操作统一采用
SET key value EX N或PEXPIRE,避免把maxmemory当成业务级淘汰方案 - 确保所有 Redis 实例都开启
notify-keyspace-events,至少包含Ex - 每个业务服务启动后订阅
cache-invalidation频道,收到 key 后及时清理本地缓存或二级缓存 - 尽量不要直接使用
DEL删除,因为它不会触发 expired 事件,也无法用于失效通知;更稳妥的方式是通过EXPIRE key 1强制过期兜底
在这种架构下,LRU 相关参数(maxmemory-policy、maxmemory-samples)只适合作为 Redis 内存保护策略存在,不应参与缓存同步业务逻辑。它的职责是“内存兜底”,而不是“同步通知源”。
真正需要重点关注的是 TTL 的精度以及 Pub/Sub 的可靠性:过期事件可能会有几百毫秒延迟,而 Pub/Sub 本身也不保证消息一定送达。如果业务对缓存一致性要求较高,就不能只依赖这一层,还应结合数据库 binlog、延迟双删或业务侧补偿机制一起使用。
