Redis 内存不够时,很多人第一反应是“加机器”或“把过期时间设短一点”。这两个方向有时有用,但真正落到 Redis 配置上,先要看两个问题:有没有设置 maxmemory,以及内存到上限后用什么 maxmemory-policy。
如果这两个问题没想清楚,线上表现会很奇怪:有的写入突然报错,有的热点缓存被清掉,有的设置了 TTL 的键不断消失,还有的实例明明内存很紧张,却没有淘汰任何键。
maxmemory 先定义内存边界
maxmemory 用来限制 Redis 数据集可以使用的最大内存。可以在 redis.conf 里写:
maxmemory 2gb
也可以运行时调整:
CONFIG SET maxmemory 2gb
这个值不是越贴近机器内存越好。Redis 还需要为连接、复制、AOF、客户端缓冲区、系统开销留空间。尤其是开启主从复制或 AOF 时,不要把 maxmemory 直接打满机器可用内存,否则淘汰本身产生的写命令也可能继续挤占缓冲区。
实践里更稳的做法是先留出安全余量,再观察 INFO memory。如果实例里有复制或持久化,重点看 mem_not_counted_for_evict,它能帮助判断还有多少内存没有计入淘汰判断。
noeviction 是默认保护,不是缓存策略
maxmemory-policy 的默认值通常是 noeviction。它的含义很直接:达到内存上限后,不主动删除键,继续写入会返回错误,读已有键仍然可以正常读。
这对“不能丢数据”的 Redis 用法比较安全,比如把 Redis 当作状态存储、分布式锁辅助、队列临时状态或一些不能随便丢的业务数据。它宁愿让写入失败,也不会偷偷删数据。
但如果 Redis 明确是缓存,noeviction 往往不是好选择。缓存的价值在于可以重建,内存满了后继续报错,反而会把压力传回业务系统。
allkeys 和 volatile 的区别
淘汰策略大体先分两类:allkeys-* 和 volatile-*。
allkeys-* 会在所有键里选淘汰对象,不管这个键有没有 TTL。它适合 Redis 实例只做缓存,或者实例里的键都可以被重建。
volatile-* 只会在设置了过期时间的键里选择淘汰对象。没有 TTL 的键不会被淘汰。如果实例里同时放了缓存键和少量不应该被删除的持久键,volatile-* 会更保守。
这里有个坑:如果你选了 volatile-lru、volatile-lfu、volatile-random 或 volatile-ttl,但实例里几乎没有键设置 TTL,那么内存满时它们可能找不到可淘汰对象,表现会接近 noeviction。
所以选 volatile-* 的前提是:缓存键必须真的设置了过期时间。
LRU、LFU、random 和 TTL 怎么理解
常见策略可以这样记:
allkeys-lru 所有键里淘汰最近最少使用的键
volatile-lru 有 TTL 的键里淘汰最近最少使用的键
allkeys-lfu 所有键里淘汰访问频率低的键
volatile-lfu 有 TTL 的键里淘汰访问频率低的键
allkeys-random 所有键里随机淘汰
volatile-random 有 TTL 的键里随机淘汰
volatile-ttl 有 TTL 的键里淘汰剩余时间最短的键
noeviction 不淘汰,写入超限时报错
LRU 关注“最近有没有用过”。如果你的访问符合常见的二八分布,少量热点键承载多数请求,allkeys-lru 通常是一个稳妥起点。
LFU 关注“使用频率高不高”。如果有些键不是刚刚被访问过,但长期看确实很常用,LFU 可能比 LRU 更适合。
random 适合访问分布比较平均、没有明显热点的场景。它简单,但对热点保护能力弱。
volatile-ttl 适合你已经能通过 TTL 表达业务优先级的场景。比如越不重要的数据 TTL 越短,让 Redis 在内存紧张时优先清掉快过期的数据。
Redis 的 LRU 不是精确 LRU
需要注意,Redis 不会维护一个绝对精确的全局 LRU 链表。精确 LRU 成本太高,所以 Redis 采用近似算法:随机采样一批键,再从候选里选更适合淘汰的键。
采样数量由 maxmemory-samples 控制:
maxmemory-samples 5
采样越多,淘汰结果越接近理论 LRU,但 CPU 开销也会增加。默认值通常已经能满足大多数缓存场景。除非你能通过命中率和淘汰指标证明需要调整,否则不要一上来就改很激进。
选择策略时先看业务语义
我更建议按下面顺序判断:
- Redis 里有没有不能丢的键?
- 这些不能丢的键能不能拆到单独实例?
- 缓存键是否都有 TTL?
- 访问模式是热点明显,还是比较平均?
- 内存满时你更接受“写失败”,还是“缓存被淘汰后回源重建”?
如果整个实例就是缓存,优先考虑 allkeys-lru 或 allkeys-lfu。
如果实例里混了少量持久键,又暂时没法拆实例,可以考虑 volatile-lru 或 volatile-lfu,但必须保证缓存键都设置 TTL。
如果数据不能被 Redis 自动删除,就保持 noeviction,同时让业务能识别写入失败,并通过扩容、清理或拆分实例解决内存问题。
线上要看哪些指标
调淘汰策略不能只看配置,还要看运行数据。常用检查项包括:
INFO memory
INFO stats
CONFIG GET maxmemory
CONFIG GET maxmemory-policy
重点观察:
used_memory:当前内存使用。used_memory_dataset:数据集占用。evicted_keys:被淘汰的键数量。expired_keys:自然过期的键数量。keyspace_hits和keyspace_misses:缓存命中与未命中。
如果 evicted_keys 持续上涨,同时命中率下降,可能说明内存太小或策略不合适。如果 expired_keys 很高,可能是 TTL 设得过短,缓存还没发挥价值就自然过期了。
小结
Redis 内存淘汰策略不是背几个名字,而是先确定实例定位:
- 只做缓存:优先考虑
allkeys-lru或allkeys-lfu。 - 缓存和持久键混用:尽量拆实例;暂时拆不了时,再考虑
volatile-*。 - 数据不能丢:用
noeviction,让写入失败显式暴露。 - 策略调整后:继续看命中率、淘汰数、过期数和内存曲线。
内存满不是 Redis 自己能“魔法解决”的问题。maxmemory-policy 只是告诉 Redis 在边界到来时怎么取舍,真正的稳定性还要靠实例拆分、TTL 设计、监控和容量规划一起兜住。




