volatile-lru不保护热点数据,仅对带过期时间的key按最近最少使用原则驱逐,无法识别真实热度;需通过懒刷新、ttl动态延长、访问标记及内存隔离等手段主动保障热点key。

volatile-lru 本身不保护热点数据,它只决定“删谁”
volatile-lru 策略仅对设置了过期时间(EXPIRE 或 SETEX)的 key 生效,且在内存不足时,按“最近最少使用”逻辑驱逐——但这里的“使用”指被读/写访问,而非业务意义上的“热点”。一个高频读但刚被访问过的 key,在 LRU 队列里位置靠前;而一个低频但长期未被触碰的过期 key,反而更可能被淘汰。所以它无法感知真实热度,更不会“避开”热点。
常见错误现象:volatile-lru 配置下,缓存命中率突然下跌,监控显示大量 evicted_keys 增长,但业务日志里对应 key 的 GET 请求其实非常密集。
- 根本原因:LRU 是基于访问时间戳的线性队列,不是热度计数器;一次突发访问就把 key 拉到队尾,下次 GC 可能就轮不到它,但下一轮流量高峰来临时它已不在了
- 不要指望靠调大
maxmemory或降低淘汰频率来“缓解”——只要存在内存压力,LRU 就会按规则清,不讲业务优先级 - 该策略适合场景:缓存层作为纯“暂存+自动过期”用途,且所有 key 的生命周期和访问模式相对均匀(如 session、临时 token)
手动设置过期时间必须配合访问刷新,否则等于没设
单纯用 SET key value EX 3600 设置固定 TTL,对热点 key 来说就是埋雷:一旦过期时间一到,key 立刻消失,不管它是否正在被高频访问。真正有效的做法是“懒刷新”——每次成功读取后,重置过期时间。
- 推荐组合:
GET+EXPIRE(非事务)或EVAL脚本原子执行 - 示例脚本(防止并发重置):
EVAL "if redis.call('GET', KEYS[1]) then redis.call('EXPIRE', KEYS[1], ARGV[1]) return 1 else return 0 end" 1 my:hot:key 7200 - 注意
EXPIRE返回值:返回1表示成功设置(key 存在且未过期),0表示 key 不存在或已过期——需据此判断是否要回源重建 - 避免在写操作中盲目
EXPIRE:比如SET后立刻EXPIRE,会导致写放大;应只在读路径中对确认存在的热点 key 刷新
真正防误删的三层补丁:TTL 延长 + 访问标记 + 内存隔离
单靠策略或单次刷新不够稳定。生产中建议叠加三类控制:
-
TTL 动态延长:对访问频次 > 100qps 的 key,每次刷新时把
EXPIRE时间从 3600 秒逐步加到 7200 秒,上限封顶(比如 28800),避免无限续命拖垮内存 -
访问标记机制:用
INCR hot_flag:my:hot:key统计短期访问次数,配合定时任务扫描hot_flag:*,对高分 key 主动PEXPIRE延长毫秒级 TTL,绕过 LRU 队列干扰 -
内存硬隔离:为明确的热点 key 集合(如商品 ID 白名单)单独部署一个小 Redis 实例,配置
noeviction,彻底禁用淘汰;代价是运维成本上升,但关键路径零风险
volatile-lru 和手动过期混用时最易踩的坑
很多人以为“开了 volatile-lru + 所有 key 都设了 EXPIRE”就万事大吉,结果线上出问题才意识到细节反直觉:
-
EXPIRE对已过期 key 无效,且不报错;若读取时 key 刚过期被清理,EXPIRE返回0,但业务代码没判断,后续就直接空缓存穿透 -
volatile-lru下,未设置过期时间的 key 永远不会被淘汰,但也不会被 LRU 管理——如果误存了大量永久 key,会悄悄吃光内存,触发 OOM killer - Redis 7.0+ 引入
LFU模式(volatile-lfu),比 LRU 更适配热点场景,但 LFU 计数有衰减,默认 1 分钟无访问就降权,仍需配合刷新,不能替代业务层热度识别
真正关键的不是策略选型,而是让 Redis 知道“哪些 key 绝对不能丢”——这只能靠业务代码在读写路径中显式表达,而不是寄希望于淘汰算法自动理解。











