谈谈 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 使用随机采样法:
-
每次需要淘汰时,从数据库中随机抽取
maxmemory-samples(默认 5)个 Key -
从这些样本中选出最近最久未使用的 Key 进行淘汰
-
通过调整采样数量,可以在精度和性能之间权衡:
maxmemory-samples 10 # 提高采样数,淘汰更精确,但CPU开销更大
LFU 实现机制
Redis 的 LFU 不是简单的访问计数,而是使用概率计数器:
- 访问计数:每次 Key 被访问时,计数器可能增加
- 计数衰减:计数器会随时间衰减,避免旧的历史访问影响当前决策
- 新 Key 保护:新加入的 Key 有一个基础计数值,避免立即被淘汰
五、 监控与运维实践
关键监控指标
used_memory:已使用内存used_memory_peak:内存使用峰值maxmemory:配置的最大内存evicted_keys:被淘汰的 Key 数量(重要!)
告警设置
# 当内存使用率超过 80% 时告警
used_memory / maxmemory > 0.8
# 当淘汰键数持续增长时告警
evicted_keys 持续增加
故障排查
如果 evicted_keys 持续快速增长,说明:
- 内存严重不足,需要扩容
- 或者淘汰策略选择不当,大量热点数据被淘汰
六、 与过期策略的关系
需要明确区分:
- 过期策略:处理设置了 TTL 且已到期 的 Key(何时删除)
- 内存淘汰:处理内存达到上限时的情况(删除什么)
两者协同工作,但解决的问题不同。
总结
面试官,Redis 的内存淘汰机制是一个精心设计的系统,其核心价值在于:
- 提供了多种选择:8 种策略覆盖了从"绝对保护"到"智能淘汰"的各种场景,让我们可以根据业务特性进行精准匹配。
- 基于智能算法:通过近似的 LRU/LFU 算法,在保证性能的同时实现了较好的淘汰效果。
- 生产实践关键:
- 必须设置
maxmemory,这是启用淘汰机制的前提 - 首选
allkeys-lru作为缓存的通用策略 - 监控
evicted_keys是发现内存问题的关键指标
- 必须设置
我的核心建议是:明确 Redis 在架构中的定位。如果作为缓存,就大胆使用淘汰策略来保证服务的可用性;如果作为存储,就使用 noeviction 并确保有完善的内存监控和扩容机制。这种清晰的边界划分,是构建稳定分布式系统的基石。
