谈谈 Redis 的内存淘汰策略?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

Redis 的内存淘汰策略?Redis 的内存淘汰策略?

面试考察点

  1. 基础掌握度:面试官不仅仅是想知道有哪些淘汰策略,更是想知道你是否能说清楚 8 种策略各自的淘汰范围(allkeys vs volatile)和淘汰算法(lru vs lfu vs random vs ttl),以及生产环境怎么选。

  2. 原理理解深度:考察你是否知道 Redis 的 LRU 不是精确的 LRU,而是近似算法(随机采样 + 淘汰最旧),以及 Redis 4.0 引入的 LFU 是如何基于计数器实现的。

  3. 知识体系完整性:能否区分 "过期策略"(惰性 + 定期,针对有过期时间的 Key)和 "淘汰策略"(内存满了怎么办,针对所有 Key),以及它们的触发时机不同。

核心答案

Redis 提供了 8 种 内存淘汰策略,当内存使用达到 maxmemory 上限时触发:

策略淘汰范围淘汰算法适用场景
noeviction不淘汰直接拒绝写入数据不能丢失(默认)
allkeys-lru所有 Key最近最少使用通用缓存(推荐)
allkeys-lfu所有 Key最不经常使用(4.0+)热点明显的缓存
allkeys-random所有 Key随机淘汰无明显访问热点
volatile-lru仅过期 Key最近最少使用部分数据可丢
volatile-lfu仅过期 Key最不经常使用(4.0+)部分数据可丢
volatile-random仅过期 Key随机淘汰部分数据可丢
volatile-ttl仅过期 Key剩余时间最短业务有明确 TTL 需求

一句话结论:生产环境做缓存用 allkeys-lru,热点明显用 allkeys-lfu;做存储(数据不能丢)用 noeviction 并做好容量规划。

深度解析

一、过期策略 vs 淘汰策略:别搞混了

很多人分不清这两个概念,面试时先说清楚区别是一个加分项。

上图清晰地展示了两者的区别:

  • 过期策略是 "Key 到期了怎么处理",只管设置了过期时间的 Key。
  • 淘汰策略是 "内存满了怎么办",在过期策略清理后内存仍然不够时触发,负责主动踢出一些 Key 来给新数据腾空间。

二、8 种策略的分类体系

上图按分类维度整理了 8 种策略。理解的关键在于两个维度:

  • 淘汰范围allkeys-* 从所有 Key 中选(包括没设过期的),volatile-* 只从设了过期时间的 Key 中选。
  • 选择算法lru(最近最少使用)、lfu(最不经常使用)、random(随机)、ttl(剩余存活时间最短)。

三、LRU 算法:Redis 是近似的,不是精确的

标准的 LRU 算法需要一个 双向链表,每次访问都把数据移到链表头部,淘汰时直接删链表尾部。但 Redis 没有用这种方式,因为它需要额外的数据结构,且每次访问都要更新链表,开销大。

上图展示了 Redis 近似 LRU 的核心思路:

  • 随机采样:每次需要淘汰时,随机抽取 N 个 Key(默认 5 个),从中淘汰最后一次访问时间最早的那个。
  • 不是全局扫描:不会遍历所有 Key,所以效率很高,但结果不够精确。
  • 采样数可调:通过 maxmemory-samples 配置,值越大越接近真实 LRU,但 CPU 开销也越大。官方测试表明,采样数设为 10 时,效果已经非常接近真实 LRU。

四、LFU 算法:Redis 4.0 引入的 "热点感知"

LFU(Least Frequently Used)基于 "访问频率" 而非 "最近访问时间" 来决定淘汰谁,更适合 热点数据明显 的场景。

上图展示了 LFU 的计数器机制:

  • 8 bit 计数器:范围 0~255,记录 Key 的 "逻辑访问频率"。不是每次访问都 +1,而是概率递增 —— 计数器越大,再增长的概率越低。这样新的热点 Key 有机会追赶老的。
  • 16 bit 衰减时间:记录最后一次衰减的时间。每隔 lfu-decay-time 分钟(默认 1 分钟),计数器减 1。这样长期不访问的 Key 的计数器会逐渐衰减,避免 "曾经热门但现在没人用" 的数据占着位置。

LRU vs LFU 对比

维度LRULFU
淘汰依据最近访问时间访问频率
适合场景访问模式均匀热点数据明显(20% 数据占 80% 流量)
优点实现简单,效果稳定更精准识别热点
缺点偶尔访问的冷数据可能顶掉热数据需要调参(衰减时间、计数器增长率)
配置参数maxmemory-sampleslfu-decay-time + lfu-log-factor

五、生产环境如何选型?

上图是一个简单的选型决策树,核心逻辑:

  • 纯缓存场景:用 allkeys-* 系列,因为缓存数据丢了可以重建。推荐 allkeys-lru(通用)或 allkeys-lfu(热点明显)。
  • 混合使用场景:有些 Key 是缓存(可丢),有些是持久化数据(不能丢)。给缓存 Key 设置过期时间,用 volatile-lru,只淘汰设了过期的 Key。
  • 纯存储场景:用 noeviction,但必须做好容量监控和提前扩容。

配置示例

# redis.conf

# 设置最大内存(建议设为系统内存的 60%~70%)
maxmemory 4gb

# 设置淘汰策略
maxmemory-policy allkeys-lru

# LRU 采样数(越大越精确,但越耗 CPU)
maxmemory-samples 10

# LFU 相关参数(仅使用 LFU 时需要配置)
# 衰减时间(分钟),默认 1
lfu-decay-time 1
# 计数器增长因子,默认 10(越小增长越快)
lfu-log-factor 10

面试高频追问

  1. 追问一:volatile-* 策略有什么风险?

    如果所有 Key 都没有设置过期时间,volatile-* 策略就退化成了 noeviction —— 找不到可淘汰的 Key,写入直接报错。所以如果你选了 volatile-*,一定要确保需要被淘汰的 Key 都设置了过期时间,否则不如直接用 allkeys-*

  2. 追问二:Redis 的 LRU 和 Java LinkedHashMap 的 LRU 有什么区别?

    Java 的 LinkedHashMap 可以通过重写 removeEldestEntry() 实现精确 LRU(每次访问都更新链表顺序)。Redis 为了高性能,用的是 近似 LRU(随机采样 + 淘汰最旧),不是全局排序,牺牲了一点精度换来更高的性能。

  3. 追问三:如何监控 Redis 的淘汰情况?

    # 查看淘汰相关指标
    INFO stats | grep evicted
    # evicted_keys:已淘汰的 Key 总数
    
    # 查看当前内存和淘汰策略
    INFO memory | grep maxmemory
    # maxmemory:最大内存限制
    # maxmemory_policy:当前淘汰策略
    

常见面试变体

  • 变体一:"Redis 内存满了怎么办?"
  • 变体二:"Redis 的 LRU 算法是精确的吗?"
  • 变体三:"LRU 和 LFU 的区别?生产环境怎么选?"
  • 变体四:"allkeys-lruvolatile-lru 的区别?"

记忆口诀

8 种策略分两类allkeys-*(全局选)和 volatile-*(过期里选)。

4 种算法:LRU(最久没用)、LFU(用得最少)、Random(随机)、TTL(快过期)。

生产首选:通用缓存 allkeys-lru,热点缓存 allkeys-lfu,不能丢数据 noeviction

关键区别:过期策略 = "到期了怎么删",淘汰策略 = "内存满了踢谁"。

总结

Redis 内存淘汰策略共有 8 种,核心按 淘汰范围allkeys vs volatile)和 选择算法lru / lfu / random / ttl)两个维度划分。生产环境做缓存推荐 allkeys-lru(通用)或 allkeys-lfu(热点明显),做存储用 noeviction。Redis 的 LRU 是近似算法(随机采样),LFU 基于 8 bit 计数器 + 衰减机制,两者都在精度和性能之间取得了平衡。