面试知识库
极高 进阶

Redis过期与淘汰策略#

一句话答案#

过期策略:惰性删除(访问时检查)+ 定期删除(随机抽样);淘汰策略 8 种:LRU/LFU/TTL/Random × volatile/allkeys。

核心要点

Redis 采用两种策略结合:惰性删除 + 定期删除

1. 惰性删除(Lazy Expiration)

  • 不主动删除过期 key,只在访问时检查是否过期
  • 过期了 → 删除并返回 null
  • 优点:对 CPU 负担小(按需检查)
  • 缺点:如果 key 一直不被访问,即使过期也占着内存(内存泄漏风险)

2. 定期删除(Periodic Expiration)

  • Redis 每隔一段时间(默认 100ms,由 hz 配置)随机抽取一批设置了过期时间的 key
  • 检查并删除其中已过期的 key
  • 如果抽取的 key 中过期比例超过 25%,重复执行直到比例降下来或超时
  • 优点:可以主动回收过期内存
  • 缺点:随机抽样,不保证所有过期 key 都及时删除

两种策略为什么结合:

  • 只有惰性删除 → 内存持续增长(不被访问的过期 key 积压)
  • 只有定期删除 → CPU 开销大,且抽样有遗漏
  • 两者结合:定期删除兜底回收内存,惰性删除保证访问时一定返回正确结果

淘汰算法内部机制:近似 LRU 与 LFU(深挖)

近似 LRU——为什么不用真链表。 标准 LRU 要维护一条双向链表,每次访问把节点移到头部,链表本身要额外指针、移动开销大。Redis 改成:每个对象头里塞一个 24 bit 的”访问时钟戳”(秒级 LRU clock),访问时只更新这个戳,不动任何链表。淘汰时不是全局找最旧,而是 随机采样 N 个 key(maxmemory-samples,默认 5),从样本里淘汰戳最旧的那个,并维护一个候选池逐步逼近真 LRU。

为什么 24bit 时钟戳:① 省内存,每个对象只多 24bit,不用维护链表节点
                    ② 避免移动链表的 CPU 开销,访问只是写一个时间戳
代价:采样近似,样本里可能没包含真正最久未用的 key;samples 调大→更准但更慢
24bit 秒级时钟约 194 天溢出回绕,对淘汰判断无实质影响
plaintext

LFU——基于访问频率,靠两个关键机制工作。 LRU 的缺点:偶尔被扫一次的冷数据会”假装很热”挤掉真热点。LFU 改为按访问频率淘汰,对象头里那 24bit 拆成 16bit 上次访问时间 + 8bit 频率计数器(counter)。8bit 只能到 255,直接 +1 早就溢出,所以有两个精巧设计:

对数概率递增(counter 越大越难涨)。 每次访问不是无脑 +1,而是按概率递增:counter 越大,+1 的概率越低(由 lfu-log-factor 控制,默认 10)。这样计数器是”对数刻度”,几次访问就能爬到几十,但要到 255 需要海量访问——既防溢出,又让热度有上限、不同热度档位区分得开。

P(递增) = 1 / (counter × lfu-log-factor + 1)
  counter 小 → 概率接近 1,几乎每次都涨;counter 大 → 很难再涨
  factor 越大,counter 饱和得越慢,能区分更高频的 key
plaintext

时间衰减(counter 随时间递减)。 只升不降会导致”历史热点永远是高分”——它早就没人访问了却赖着不被淘汰,新晋热点反而进不来。lfu-decay-time(默认 1 分钟)规定:key 每隔这么久没被访问,counter 就衰减一档。这样老热点会逐渐降温、让位给新热点。

衰减量 = 距上次访问的分钟数 / lfu-decay-time
没有衰减 → 启动初期偶发的高频 key 会霸占内存永不淘汰
plaintext

一句话区分:近似 LRU 看”多久没访问”(时间),LFU 看”访问多频繁”(次数+衰减)。LFU 适合有稳定热点、且偶发扫描多的场景(如商品详情缓存);二者都用对象头那 24bit、都靠采样淘汰,不维护全局链表。

面试回答(2分钟版)

Redis过期key删除采用惰性删除加定期删除结合。惰性删除是访问时才检查是否过期,CPU开销小但不被访问的过期key会一直占内存。定期删除是默认每100ms随机抽样一批有过期时间的key检查,过期比例超25%就持续执行。两者结合兼顾CPU和内存。当内存达到maxmemory上限时触发淘汰策略,共8种:noeviction拒绝写入、volatile-lru/allkeys-lru按LRU淘汰、volatile-lfu/allkeys-lfu按LFU淘汰、volatile-ttl按最快过期淘汰、volatile-random/allkeys-random随机淘汰。生产环境推荐allkeys-lru。Redis的LRU是近似实现,默认随机采样5个key取最久未访问的淘汰,通过maxmemory-samples调整采样数平衡精度和性能。

追问与易错

追问方向:

  • “惰性删除和定期删除各自的问题?”→ 惰性删除不主动清理,不被访问的过期 key 持续占内存造成泄漏;定期删除是随机抽样,CPU 开销随过期 key 比例增大,且无法保证所有过期 key 及时清理
  • “LRU 和 LFU 怎么选?”→ LRU 淘汰最久未访问的 key,实现简单但可能误淘汰偶尔访问的热数据;LFU 基于访问频率淘汰,更精确但需要额外计数空间;Redis 4.0+ 支持 LFU,热点数据场景推荐 LFU
  • “如果不设过期时间,内存满了怎么办?”→ 触发 maxmemory-policy 淘汰策略;默认 noeviction 直接拒绝新写入返回错误;生产环境通常设置 allkeys-lru 或 allkeys-lfu,在所有 key 中按策略淘汰

易错点:

  • ❌ “过期的 key 立即被删除”——惰性+定期,可能已过期但还在内存中
  • ❌ 混淆 volatile 和 allkeys 前缀——volatile 只淘汰设了过期时间的 key