大家好,我是晚安code。
Redis 报错:OOM command not allowed when used memory > 'maxmemory',业务写不进去,人一脸懵。这种错十有八九是 Redis 内存淘汰策略默认的 noeviction 在作妖——内存满了,一个 key 都不删,写命令直接拒收。先声明,我下面讲的线上场景都是示例场景,但排障流程都是真话。
这篇把 Redis 内存淘汰策略一次讲透:什么时候触发、8 种策略怎么选、和缓存击穿什么关系、底层到底怎么删。点个收藏,别等 OOM 了再回来翻。
一、什么时候触发内存淘汰
Redis 的内存淘汰不是后台定时清理,而是被写命令「逼」出来的——内存一旦越过 maxmemory 红线,下一个写操作得先腾地方再放行。(2026 年 8 月整理,基于 Redis 4.0+,6.x/7.x 都适用)
内存淘汰(Eviction):Redis 内存达到 maxmemory 上限后,主动删除一部分 key 来释放空间的机制。你可以把它理解成「缓存满了就得扔点东西,扔谁由策略说了算」。
触发时机很关键:只有写命令会触发。每次执行 SET、LPUSH、INCR 这类会涨内存的命令时,Redis 检查 used_memory 是否超过 maxmemory,超了就调用淘汰逻辑,循环删 key 直到降到线以下,才放行这个写命令。GET 这类读命令不会触发。maxmemory 默认是 0(64 位下不限制),你配了上限,淘汰策略才开始上班。
整个触发链路长这样(图 1):写命令 → 检查内存 → 超限按策略淘汰 → 还没降下来就继续淘汰。

配了 noeviction 却不知道它存在的同学,最容易在 redis-cli 里撞见这句话:
(error) OOM command not allowed when used memory > 'maxmemory'
这时候 DEL 还能删(删了才腾得出空间),但任何会涨内存的写命令全被拒。业务表现就是:缓存写不进去,新热点数据全部 miss。
我见过有人在生产环境 debug 了一个下午,最后才发现是默认的 noeviction 策略在「装死」——内存满了宁可拒绝写入也不删数据。

二、8 种策略:先看两个维度
选淘汰策略,永远先问自己两个问题:Redis 里存的东西能丢吗?能丢的话,按什么规律丢?
maxmemory-policy:redis.conf 里决定超限时删哪个 key 的策略参数,默认值是 noeviction——一个都不删,写命令直接报错。
表面看有 8 种策略,其实是从两个维度组合出来的:
- 淘汰范围:
volatile-*只从「设了过期时间(TTL)」的 key 里挑;allkeys-*从所有 key 里挑。 - 淘汰算法:LRU 看最近多久没被访问,LFU 看被访问的频率,Random 纯随机,TTL 看谁剩的命最短。
范围 × 算法一组合,8 种策略全在这张表里了:
| 策略 | 淘汰范围 | 按什么淘汰 | 一句话适用 |
|---|---|---|---|
| noeviction | 不淘汰 | — | 数据不能丢,宁可报错也不删 |
| allkeys-lru | 所有 key | 最近最少用 | 通用缓存,保住热点(最常用) |
| allkeys-lfu | 所有 key | 最不常用 | 访问冷热分明,怕偶发访问骗过 LRU |
| allkeys-random | 所有 key | 随机 | 访问完全无规律,几乎不用 |
| volatile-lru | 有 TTL 的 key | 最近最少用 | 缓存+存储混合,业务数据不设 TTL |
| volatile-lfu | 有 TTL 的 key | 最不常用 | 同上,但更看重真实热度 |
| volatile-random | 有 TTL 的 key | 随机 | 几乎不用 |
| volatile-ttl | 有 TTL 的 key | 最快过期 | 优先清掉马上要过期的临时数据 |
绝大多数业务,配置一行就够:
maxmemory 512mb
maxmemory-policy allkeys-lru # 通用缓存场景,最常用组合
这里有个新手容易懵的地方:两个维度不是「非此即彼」,而是组合拳。看下面这张图,左边挑「删哪些」,右边挑「按什么删」,搭在一起才是完整的策略。

三、每种策略适合什么场景(举例子)
判断用 volatile 还是 allkeys,关键就看一件事:你的 Redis 是不是「身兼两职」——既当缓存,又当存储。
说几个我配过、也见过别人配过的典型场景:
1)纯缓存、访问有热点 —— allkeys-lru。 电商首页的热销榜,数据全在缓存里,流量集中在少数爆款。内存一紧,LRU 优先淘汰没人看的冷门商品,把爆款留在内存里,命中率最稳。这是我用得最多的一档。
2)缓存 + 存储混合 —— volatile-lru。 Redis 里既有用户会话 session(设了 30 分钟 TTL),又有不想丢的用户表缓存(不设 TTL)。用 volatile-lru 只动 session,业务数据一个都不碰。注意有个坑:session 也得真设 TTL,否则 volatile 系列「无米可炊」,直接退化成 noeviction——这是我一朋友踩过的第二个翻车现场,排障排到怀疑人生才发现是自己没设 TTL。

3)访问冷热分明、怕偶发访问骗算法 —— allkeys-lfu。 精准投放位的数据,平时几乎没人碰,但每个月固定被大规模拉一次。LRU 看到它「最近没被访问」就随手删了;LFU 记得它历史访问得多,会手下留情。
4)和上游强同步的临时缓存 —— volatile-ttl。 这些 key 设了很短的 TTL,反正时间一到也要重拉,那内存紧张时就先删这些「命不久矣」的,性价比最高。
5)绝不能丢的数据 —— noeviction。 分布式锁、消息队列这类,宁可报错也别让锁悄悄消失——锁丢了比报错可怕多了。
四、淘汰策略和缓存击穿是什么关系
淘汰策略不会直接制造缓存击穿,但选错了策略,等于亲手把还没过期的热点 key 推到了刀口上。
缓存击穿(Cache Breakdown):单个热点 key 过期或被清掉的瞬间,大量并发请求同时未命中缓存、全部打到数据库。可以理解成独木桥桥板塌了,人群全挤向桥头。
教科书里的击穿,说的是「热点 key 到点过期」。但淘汰会把风险面扩大:allkeys-lru 在内存压力下,会把还没过期、只是最近访问频率降下来的热点 key 也当冷数据清掉。 缓存从有变无,一堆请求瞬间回源数据库,效果和击穿一模一样——而且更隐蔽,因为 key 压根没过期,你排查半天都想不到是淘汰干的。
看这张时序图(图 2),key 消失瞬间发生了什么:

我见过的真实翻车(示例场景):大促前运营预热了一批商品数据,内存被别的冷数据占满,预热数据被 allkeys-lru 当「冷数据」清了。秒杀一开始,所有请求直接打穿数据库,监控里 evicted_keys 冲上一个台阶——那批 key 明明一个都没过期。


那怎么防?三板斧:热点数据不设过期时间(物理永不过期,也永远不会被 volatile 系列淘汰);重建时加互斥锁(只放一个线程去查库,其他人等待);逻辑过期 + 异步重建(value 里记个逻辑过期时间,过期了先返回旧值再偷偷刷新)。前两招治标,第三招治本。
监控是最后的哨兵:INFO stats 里的 evicted_keys 一旦飙升,别犹豫,先看是不是策略把热点清走了。
五、底层原理:近似 LRU/LFU 与淘汰池
Redis 用的从来不是教科书里的标准 LRU,而是一种「随机采样 + 淘汰池」的近似方案,拿一点点精度换常数级内存开销。
LRU(Least Recently Used):按「最近多久没被访问」淘汰的算法,最久没被用的先删,是淘汰家族里最常用的选手。
标准 LRU 得给每个 key 维护一条双向链表,走到哪排到哪,内存开销太大,Redis 不想付这笔账。它改成:每个 key 的对象头里塞一个 24 bit 字段,记最近一次访问时间(俗称 LRU 时钟)。要淘汰时,随机抽 maxmemory-samples 个 key(默认 5 个),比谁最久没被访问,删最旧的那个。这就是近似 LRU——像老师抽查作业,不会逐本看完全班,随机抽几本判个大概。
Redis 3.0 又加了张「小抄」:淘汰池。抽中的候选先丢进一个容量 16 的池子里,跨轮次保留,池里永远留着一批「最该淘汰」的候选。这样精度蹭蹭往上涨,接近理论 LRU,内存开销还是常数。看下图,抽查出来的候选进池子排队,队尾最该删。

LFU 则换了套思路。LFU(Least Frequently Used):按「最近一段时间被访问的次数」淘汰的算法,访问得越少越先被删。它治的是 LRU 的软肋:一个 key 只是「刚刚」被访问了一次,就会被当成刚翻红的明星保护起来,而它可能一年到头就这一次。
Redis 4.0 起复用那 24 bit 字段:高 16 位记「上次衰减时间」,低 8 位当访问计数器。8 位最多记到 255,所以访问次数不是每次 +1,而是按概率递增——越到后面越难涨,类似 Morris 计数器,省内存又够用。计数器还会随时间衰减(lfu-decay-time,默认 1 分钟降一次),避免一个数据十年前火过就一直占着坑。想微调采样精度,用 maxmemory-samples;想调 LFU 敏感度,动 lfu-log-factor 和 lfu-decay-time。
六、两个高频疑问一次说清
这两个问题几乎每次讲淘汰都有人问,我一次性说清。
可能有人会问:volatile- 系列,如果我的 key 全都没设过期时间会怎样?*
会退化成 noeviction 的行为——想淘汰却发现「无米可炊」,内存满了照样报 OOM。所以用 volatile 系列之前,先确认你手里有足够多设了 TTL 的临时数据。
可能有人会问:内存淘汰和过期键删除,是一回事吗?
不是。过期删除是 key 到点自己消失(惰性删除 + 每秒抽样的定期删除);内存淘汰是内存超限时被迫删,跟 key 过没过期没关系。两套机制、两个触发源,面试和排查都容易混,记住它们各管各的就行。
说白了,Redis 内存淘汰策略没有银弹,只有「合适」:纯缓存就 allkeys-lru,身兼存储就用 volatile 系列守住业务数据,绝不能丢的就 noeviction 别硬塞。配完盯住两个指标——evicted_keys 和命中率,比背策略名字有用得多。
缓存击穿的完整防御(互斥锁重建、逻辑过期、永不过期)值得单独写一篇,想看的评论区喊一声。
我是晚安code,持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊:你在生产环境用的是哪套 Redis 内存淘汰策略,遇到过热点 key 被 LRU 误杀吗?

2007

被折叠的 条评论
为什么被折叠?



