谈谈 Redis 的内存淘汰策略?

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

Redis 使用的内存达到配置的最大限制 (maxmemory) 时,内存淘汰策略就决定了 Redis 应该如何选择数据来删除,以便为新写入的数据腾出空间。

一、 核心概念与配置

在深入策略之前,必须先理解 redis.conf 配置文件中两个核心配置项:

# 1. 设置最大内存限制(必须设置)
maxmemory 2gb

# 2. 设置内存淘汰策略
maxmemory-policy noeviction

如果不设置 maxmemory,在 64 位系统上 Redis 会一直使用内存直到被操作系统 OOM Killer 强制杀死,这是生产环境的灾难。

二、 八种内存淘汰策略详解

Redis 提供了 8 种策略,可以分为三大类:

第一类:不淘汰策略

1. noeviction(默认策略)

  • 行为:当内存达到限制时,所有会引起内存增加的新写入命令都会报错(如 SET, LPUSH 等),而读命令(如 GET)正常执行。
  • 错误信息(error) OOM command not allowed when used memory > 'maxmemory'
  • 适用场景
    • 数据绝对不能丢失的场景
    • 作为存储系统而非缓存的 Redis
    • 需要业务层自己处理写入失败的情况

第二类:对所有 Key 进行淘汰

这些策略会从整个数据集中选择要删除的 Key。

2. allkeys-lru(最常用策略)

  • 行为:从 所有 Key 中,淘汰 最近最少使用 的 Key。
  • 算法:使用近似的 LRU 算法,通过随机采样来避免全量扫描的性能开销。
  • 适用场景:Redis 作为缓存的通用场景。我们希望保留热点数据,淘汰长时间未被访问的冷数据。
  • 示例:如果用户最近访问了商品 A、B、C,但很久没访问商品 D,那么当内存不足时,D 会被优先淘汰。

3. allkeys-lfu(Redis 4.0+)

  • 行为:从 所有 Key 中,淘汰 最不经常使用 的 Key。
  • 与 LRU 的区别
    • LRU:关注 最近访问时间,淘汰最久未访问的
    • LFU:关注 访问频率,淘汰访问次数最少的
  • 适用场景:有明显访问热点的业务。比如某些热门商品被反复查看,而有些商品几乎无人问津。

4. allkeys-random

  • 行为:从 所有 Key 中,随机选择并淘汰。
  • 适用场景
    • 所有 Key 被访问的概率大致相等
    • 没有明显的访问模式或热点
    • 简单粗暴,在特定场景下效果不错

第三类:仅对设置了过期时间的 Key 进行淘汰

这些策略只从设置了 TTL 的 Key 中选择要删除的目标。

5. volatile-lru

  • 行为:从设置了过期时间的 Key 中,淘汰最近最少使用的。
  • 适用场景:Redis 中同时存在缓存数据(有 TTL)和 持久数据(无 TTL)。我们希望只淘汰缓存数据,保护核心的持久数据。

6. volatile-lfu(Redis 4.0+)

  • 行为:从设置了过期时间的 Key 中,淘汰最不经常使用的。
  • 适用场景:在缓存数据中进一步区分热点,保留频繁访问的缓存。

7. volatile-random

  • 行为:从设置了过期时间的 Key 中,随机淘汰。
  • 适用场景:缓存数据的访问模式比较随机。

8. volatile-ttl

  • 行为:从设置了过期时间的 Key 中,淘汰剩余生存时间最短的。
  • 适用场景:希望尽快清理即将过期的缓存,为更"新鲜"的数据腾出空间。比如会话(Session)缓存。

三、 生产环境选型指南

作为架构师,我的选择基于 Redis 在系统中的角色:

Redis 角色推荐策略理由
纯缓存allkeys-lru通用选择,自动保留热点数据,淘汰冷数据
缓存(有明显热点)allkeys-lfu更好地保留高频访问数据,淘汰低频数据
缓存 + 数据库volatile-lru保护永久数据(不设 TTL),只淘汰缓存数据(设 TTL)
关键数据存储noeviction数据绝对不能丢失,通过监控和扩容解决内存问题
访问模式随机allkeys-random简单有效,在无热点场景下表现良好

配置示例:

# 作为缓存服务器
maxmemory 16gb
maxmemory-policy allkeys-lru

# 作为混合存储(缓存+持久化)
maxmemory 8gb
maxmemory-policy volatile-lru

四、 底层算法深度解析

LRU 近似算法

传统的 LRU 需要维护一个所有 Key 的访问顺序链表,内存和性能开销巨大。Redis 使用随机采样法:

  1. 每次需要淘汰时,从数据库中随机抽取 maxmemory-samples(默认 5)个 Key

  2. 从这些样本中选出最近最久未使用的 Key 进行淘汰

  3. 通过调整采样数量,可以在精度和性能之间权衡:

    maxmemory-samples 10  # 提高采样数,淘汰更精确,但CPU开销更大
    

LFU 实现机制

Redis 的 LFU 不是简单的访问计数,而是使用概率计数器:

  1. 访问计数:每次 Key 被访问时,计数器可能增加
  2. 计数衰减:计数器会随时间衰减,避免旧的历史访问影响当前决策
  3. 新 Key 保护:新加入的 Key 有一个基础计数值,避免立即被淘汰

五、 监控与运维实践

关键监控指标

  • used_memory:已使用内存
  • used_memory_peak:内存使用峰值
  • maxmemory:配置的最大内存
  • evicted_keys:被淘汰的 Key 数量(重要!)

告警设置

# 当内存使用率超过 80% 时告警
used_memory / maxmemory > 0.8

# 当淘汰键数持续增长时告警
evicted_keys 持续增加

故障排查

如果 evicted_keys 持续快速增长,说明:

  1. 内存严重不足,需要扩容
  2. 或者淘汰策略选择不当,大量热点数据被淘汰

六、 与过期策略的关系

需要明确区分:

  • 过期策略:处理设置了 TTL 且已到期 的 Key(何时删除)
  • 内存淘汰:处理内存达到上限时的情况(删除什么)

两者协同工作,但解决的问题不同。

总结

面试官,Redis 的内存淘汰机制是一个精心设计的系统,其核心价值在于:

  1. 提供了多种选择:8 种策略覆盖了从"绝对保护"到"智能淘汰"的各种场景,让我们可以根据业务特性进行精准匹配。
  2. 基于智能算法:通过近似的 LRU/LFU 算法,在保证性能的同时实现了较好的淘汰效果。
  3. 生产实践关键
    • 必须设置 maxmemory,这是启用淘汰机制的前提
    • 首选 allkeys-lru 作为缓存的通用策略
    • 监控 evicted_keys 是发现内存问题的关键指标

我的核心建议是:明确 Redis 在架构中的定位。如果作为缓存,就大胆使用淘汰策略来保证服务的可用性;如果作为存储,就使用 noeviction 并确保有完善的内存监控和扩容机制。这种清晰的边界划分,是构建稳定分布式系统的基石。