首页 / Redis 入门教程 / 17. 内存淘汰机制 Eviction

Redis 入门教程

17. 内存淘汰机制 Eviction

本教程共 40 篇 · 第 17 篇 · 更新于 2026-08-02

内存淘汰maxmemoryLRULFU缓存策略eviction-policy

17. 内存淘汰机制 Eviction

当 Redis 被当作缓存使用时,它本质上是在一块有限的内存里存放“可能被再次访问的数据副本”。一旦写入的数据量逼近内存上限,Redis 必须决定:哪些键可以被丢弃。这个“主动丢弃”的过程,就是内存淘汰(Eviction)。

理解淘汰机制非常关键:它直接决定了缓存在高负载下会丢失哪些数据、命中率会怎样变化。配置得当,缓存可以长期保持高命中率;配置不当,则可能在流量高峰时把“最该留着”的热数据淘汰掉。

本节目标

  • 掌握 maxmemory 的含义,以及它如何与 maxmemory-policy 配合触发淘汰。
  • 区分 noevictionallkeys-*volatile-* 三大类共九种淘汰策略。
  • 理解 LRU、LFU、LRM 三种近似算法的差异与适用场景。
  • 学会用 INFO 命令观察 evicted_keyskeyspace_hits 来评判策略是否合理。
  • 了解 32 位系统与 64 位系统在 maxmemory 默认值上的区别。

内存上限:maxmemory

Redis 通过 maxmemory 配置项来约束数据集能够使用的最大内存。它可以在 redis.conf 中静态设置,也可以运行时用 CONFIG SET 动态调整:

# redis.conf 中设置 100MB 上限
maxmemory 100mb
127.0.0.1:6379> CONFIG SET maxmemory 100mb
OK

maxmemory 设为 0 表示不做限制(这是 64 位系统的默认行为)。而在 32 位系统上,Redis 会隐式施加 3GB 的上限——因为 32 位进程的地址空间本就有限。

每当客户端执行一条会“新增数据”的命令(比如 SETLPUSHHSET),Redis 都会先检查当前内存用量:如果超过了 maxmemory,就按既定策略淘汰键,直到用量回落到上限之内。需要注意,单条命令若一次性写入大量数据(例如把一个大集合的交集结果存进新键),可能让内存临时远超上限,之后再慢慢回落。

提示:如果你启用了主从复制或 AOF 持久化,Redis 会预留一块内存作为“复制/落盘缓冲区”,这块内存不计入 maxmemory 的比较范围。否则淘汰动作自身产生的新写入会再次撑大缓冲区,形成淘汰死循环。因此在生产环境给 maxmemory 留一点余量(例如物理内存的 3/4)是稳妥做法。

九种淘汰策略

maxmemory-policy 决定“到底删谁”。Redis 8.x 提供九种策略,可分为三大类:

不淘汰类

  • noeviction:不删除任何键。当内存超限时,所有会写入新数据的命令都会返回错误(读命令不受影响)。这是 maxmemory 有值时的默认策略。

allkeys 类(在所有键中挑)

  • allkeys-lru:淘汰最近最少使用的键。
  • allkeys-lfu:淘汰最不经常使用的键。
  • allkeys-lru 之外,Redis 8.6 起新增了 allkeys-lrm:淘汰最久未被修改的键(只读不写不会刷新它的“修改时间”)。
  • allkeys-random:随机淘汰。

volatile 类(只在带过期时间的键中挑)

  • volatile-lruvolatile-lfuvolatile-lrm:分别用 LRU / LFU / LRM 在“设置了 TTL 的键”里挑。
  • volatile-random:在带 TTL 的键里随机淘汰。
  • volatile-ttl:优先淘汰剩余 TTL 最短的键。

volatile-* 策略有个重要特征:如果数据库里没有任何键设置了过期时间,它们会退化成 noeviction 行为——因为根本无键可挑。

下面用 CONFIG SET 实时切换策略,再用 INFO 观察效果:

127.0.0.1:6379> CONFIG SET maxmemory-policy allkeys-lru
OK
127.0.0.1:6379> CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "allkeys-lru"

给一批键设置不同的过期时间,然后用 volatile-ttl 观察短 TTL 键先被清掉:

127.0.0.1:6379> SET session:a value EX 10
OK
127.0.0.1:6379> SET session:b value EX 3600
OK
127.0.0.1:6379> CONFIG SET maxmemory-policy volatile-ttl
OK

LRU、LFU、LRM:三种“近似”算法

Redis 并不维护一个精确的“全局访问顺序链表”,因为那会带来额外的内存与计算开销。它采用的是近似算法:每次需要淘汰时,随机采样一小批键,再从中挑出“最该淘汰”的那个。

LRU(Least Recently Used):记录“最后一次被访问的时间”。读和写都会刷新这个时间戳。如果你认为一小部分热数据会被反复访问、其余几乎不碰(符合帕累托法则),allkeys-lru 是最稳妥的默认选择。

LFU(Least Frequently Used):从 Redis 4.0 起提供。它统计“访问频率”而非“最近一次时间”,用一种叫 Morris 计数器的概率结构,仅用几个 bit 就能估算访问频次,并带衰减机制——一个很久没被访问的键,其计数会随时间下降。适合“有些键长期高频、有些键偶尔来一下”的场景。allkeys-lfu 往往比 LRU 的命中率更高。

LRM(Least Recently Modified):Redis 8.6 新增。和 LRU 的唯一区别是——只有写操作才刷新时间戳,读操作不刷新。于是它能在“读多写少”的负载中,精准淘汰那些“内容很久没更新过”的键,而不管它被读过多少次。

采样精度由 maxmemory-samples 控制(默认 5,调大到 10 更接近理论 LRU,但更费 CPU):

127.0.0.1:6379> CONFIG SET maxmemory-samples 10
OK

LFU 还可调两个参数:lfu-log-factor 10(计数饱和速度)与 lfu-decay-time 1(每分钟衰减一次)。一般使用默认值即可。

用 INFO 评估策略是否选对

淘汰是否生效、策略是否合适,都可以通过 INFO stats 量化:

127.0.0.1:6379> INFO stats
# Stats
keyspace_hits:9801
keyspace_misses:199
evicted_keys:42
expired_keys:310

命中率可粗略计算为:

keyspace_hits / (keyspace_hits + keyspace_misses) * 100

如果命中率明显低于预期,往往说明策略选错了:此时看 evicted_keys,若它很高,说明“该留的键被频繁淘汰”,换成 allkeys-lru 通常能改善;若 evicted_keys 很低但 expired_keys 很高,则可能是 TTL 设得太短,键在“本该被访问”之前就自己过期了。

另外,INFO memory 里的 mem_not_counted_for_evict 告诉你复制/落盘缓冲区占了多少内存,正好用来反推该给 maxmemory 留多少余量。

小结

  • maxmemory 设上限,maxmemory-policy 决定淘汰谁;64 位默认不设限,32 位隐式 3GB。
  • noeviction 是默认策略,超限后写命令报错;纯缓存场景几乎都要改成 LRU/LFU 类。
  • allkeys-* 在全体键中挑,volatile-* 只在带 TTL 的键中挑;后者无 TTL 键时退化为不淘汰。
  • LRU 看“最近访问”、LFU 看“访问频率”、LRM(8.6+)只看“最近修改”,按访问模式对号入座。
  • 一切以 INFOevicted_keys 与命中率为准绳来调优,别凭感觉。

常见误区

  • 误区一:以为默认就不淘汰。maxmemory 设了上限却没有改策略时,默认是 noeviction:内存满了之后写命令直接报错,而不是默默删键。纯缓存场景务必显式改成 allkeys-lruallkeys-lfu
  • 误区二:以为 volatile-* 是万能的。 只在带 TTL 的键里挑,如果库里根本没键设置过期时间,它会退化成 noeviction,照样写报错。想让全部键都参与淘汰,用 allkeys-* 系列。
  • 误区三:把 LRU 当成精确算法。 Redis 用的是近似 LRU——随机采样一小批键再挑最久未访问的,并非维护全局严格顺序。绝大多数业务下命中率与真实 LRU 几乎无差别,但若想更逼近,可调大 maxmemory-samples(代价是更多 CPU)。
  • 误区四:以为设了 maxmemory 就万事大吉。 复制缓冲区和 AOF 重写缓冲不计入 maxmemory,若内存被这两块吃满,仍可能 OOM。给 maxmemory 留点余量(如物理内存 3/4),并用 INFO memorymem_not_counted_for_evict 观察缓冲占用。

提示:本教程统一基于 Redis 8.x(最新稳定版)行文。补充说明:官方 redis.io 下载页另标 8.8 为 “Latest stable”,而 GitHub 上的最新发布 tag 为 8.10.0,二者同属 8.x 大版本,对命令与淘汰策略的讲述没有影响。