一、引言:当内存"爆仓"时,Redis如何抉择?
作为一款基于内存的高性能数据库,Redis 的速度令人惊叹。但内存是有限且昂贵的资源。当你的业务数据持续增长,最终触碰到 Redis 配置的 maxmemory 上限时,会发生什么?
是直接拒绝所有新写入请求?还是智能地删除一些"不那么重要"的旧数据来为新数据腾出空间?
这就是 Redis 内存淘汰策略(Eviction Policy) 要解决的核心问题。它定义了在内存不足时,Redis 应该如何做出取舍,以保证服务的持续可用性。理解并正确配置这些策略,是每一位 Redis 使用者的必修课。
💡 核心价值 :
本文将带你全面了解 Redis 的 8 大内存淘汰策略,深入其工作原理,并提供清晰的选型指南,助你避免线上 OOM 事故!
二、前置知识:maxmemory 与淘汰触发时机
在讨论策略之前,必须明确两个前提:
-
maxmemory配置 :这是 Redis 内存使用的上限。你可以通过CONFIG SET maxmemory <bytes>动态设置,或在redis.conf中永久配置bash# 设置最大内存为 2GB maxmemory 2gb -
触发时机 :只有当 Redis 的已用内存达到或超过
maxmemory时,才会根据配置的淘汰策略来决定如何处理新的写命令(如SET,LPUSH等)。读命令(如GET)不受影响。
三、八大内存淘汰策略详解
Redis 提供了 8 种不同的淘汰策略,可通过 maxmemory-policy 参数进行配置。它们大致可分为三大类:
第一类:不淘汰任何数据 (noeviction)
- 策略名 :
noeviction - 行为 : 这是 Redis 的默认策略 。当内存达到上限时,拒绝所有会增加内存占用的写命令 ,并向客户端返回一个错误信息:
(error) OOM command not allowed when used memory > 'maxmemory'。 - 适用场景 : 适用于那些不能容忍任何数据丢失的场景,或者你希望由应用程序层来处理内存不足的情况。但在生产环境中,这通常不是一个好选择,因为它会导致服务不可用。
第二类:仅从设置了过期时间(TTL)的 Key 中淘汰
这类策略只会在那些已经设置了过期时间的 Key 中进行筛选和淘汰,不会动那些永不过期的 Key。
-
volatile-lru(Least Recently Used - 最近最少使用)- 行为 : 从设置了 TTL 的 Key 中,优先淘汰最近最久未被访问的 Key。
- 原理: 利用近似 LRU 算法(见下文"实现细节")。
- 适用场景: 经典的缓存场景,其中大部分数据都有明确的过期时间,且访问模式符合"热点数据常被访问"的局部性原理。
-
volatile-lfu(Least Frequently Used - 最不经常使用)- 行为 : 从设置了 TTL 的 Key 中,优先淘汰访问频率最低的 Key。
- 原理: 利用近似 LFU 算法(见下文"实现细节")。
- 适用场景: 数据访问频率差异巨大,存在大量"一次性"或"低频"访问的数据。相比 LRU,更能识别出真正的冷数据。
-
volatile-random- 行为 : 从设置了 TTL 的 Key 中,随机选择一个 Key 进行淘汰。
- 适用场景: 当你认为所有带 TTL 的 Key 价值相当,或者无法预测访问模式时,可以作为一种简单粗暴的选择。
-
volatile-ttl- 行为 : 从设置了 TTL 的 Key 中,优先淘汰剩余存活时间(TTL)最短的 Key。
- 适用场景: 你希望尽快清理掉那些"即将过期"的数据,为新数据腾出空间。
第三类:从所有 Key 中淘汰(无论是否设置了 TTL)
当你的 Redis 实例中混合存储了有 TTL 和无 TTL 的 Key,或者所有 Key 都没有设置 TTL 时,就需要使用这类策略。
-
allkeys-lru- 行为 : 从所有 Key 中,优先淘汰最近最久未被访问的 Key。
- 适用场景 : 这是最常用的通用缓存策略。无论 Key 是否有 TTL,都将其视为缓存项,用 LRU 原则进行管理。
-
allkeys-lfu- 行为 : 从所有 Key 中,优先淘汰访问频率最低的 Key。
- 适用场景: 通用缓存场景,但数据访问模式更倾向于区分高频和低频,而非近期访问。
-
allkeys-random- 行为 : 从所有 Key 中,随机选择一个 Key 进行淘汰。
- 适用场景: 所有 Key 被认为具有同等价值,或者作为性能测试时的基准策略。
四、核心算法揭秘:LRU 与 LFU 的实现
Redis 并未使用传统的、精确的 LRU/LFU 算法,因为它们需要额外的数据结构(如链表、计数器),会带来巨大的内存和 CPU 开销。Redis 采用的是高效的近似算法。
4.1 近似 LRU (Approximated LRU)
- 原理 : Redis 在每个
RedisObject结构体中用 24 位(3字节)来记录该对象最后一次被访问的时间戳(秒级精度)。 - 淘汰过程 : 当需要淘汰时,Redis 会随机采样一小部分 Key(默认 5 个),然后比较它们的 LRU 时间戳,淘汰其中最旧的一个。这个过程会重复,直到释放出足够的内存。
- 优点: 内存开销极小(每个 Key 只需 3 字节),性能极高。
- 缺点 : 是一种概率性的近似,并非全局最优。
4.2 近似 LFU (Approximated LFU) - Redis 4.0+
- 原理 : 同样利用
RedisObject中的 24 位字段,但将其拆分为两部分:- 高 16 位: 记录最近一次递减计数器的时间(分钟级)。
- 低 8 位: 一个 8 位的访问计数器。
- 计数器更新 : 采用对数计数器 和概率性递增机制。访问越频繁,计数器增长越慢,防止其过快饱和。同时,计数器会随着时间缓慢衰减,以反映数据热度的变化。
- 优点: 能有效区分高频和低频数据,且能适应数据访问模式随时间变化的情况。
- 缺点: 配置相对复杂,需要理解其衰减逻辑。
五、如何选择合适的淘汰策略?
| 场景 | 推荐策略 | 理由 |
|---|---|---|
| 纯缓存,所有数据都有 TTL | volatile-lru 或 volatile-lfu |
只清理缓存数据,保护非缓存数据(如果有的话)。LFU 适合访问频率差异大的场景。 |
| 纯缓存,数据无 TTL 或混合 TTL | allkeys-lru (首选) 或 allkeys-lfu |
将所有数据都视为可淘汰的缓存项。LRU 简单高效,是通用首选。 |
| 不能接受任何数据丢失 | noeviction |
由应用层处理内存不足,但可能导致服务中断。 |
| 无法预测访问模式 | allkeys-random 或 volatile-random |
作为兜底方案,简单直接。 |
| 希望尽快清理快过期的数据 | volatile-ttl |
专注于 TTL 维度进行淘汰。 |
📌 黄金法则 :对于绝大多数缓存场景,
allkeys-lru是最安全、最通用的选择。
六、配置与验证
6.1 配置方法
bash
# 临时生效
127.0.0.1:6379> CONFIG SET maxmemory-policy allkeys-lru
OK
# 永久生效 (修改 redis.conf)
maxmemory-policy allkeys-lru
6.2 查看当前配置
bash
127.0.0.1:6379> CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "allkeys-lru"
6.3 监控淘汰情况
通过 INFO 命令可以监控淘汰的详细信息:
bash
127.0.0.1:6379> INFO memory
# Memory
...
evicted_keys:12345 # 自启动以来被淘汰的Key总数
...
七、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!