Redis 数据驱逐策略 · maxmemory 与近似 LRU / LFU

作者:杨帅      发布日期:2026.08.05

Redis · 内存管理

数据驱逐

当内存写满,Redis 如何决定把谁请出去

Redis 把数据放在内存里,内存是有上限的。写到 maxmemory 边界时,要么拒绝新写入,要么按策略逐出一些旧数据腾地方。有意思的是:为了快,Redis 并不精确地找"最该删的那个"——它靠随机采样做近似。

▲ 内存触顶 → 采样 → 逐出最冷的 keymaxmemory · approximated eviction
内存使用 maxmemory ⚠ 超过上限,触发驱逐 随机采样 5 个 key(数字 = 空闲时长) k1闲 2s k2闲 9s k3闲 1s k4闲 4s k5闲 12s EVICTED k6闲 3s k7闲 7s k8闲 5s ↑ 样本里空闲最久 → 逐出 只在 5 个样本里比,不扫描全部 key —— 用一点点精度,换巨大的速度
热:最近访问过 冷:空闲很久 被逐出
01 · 边界发生了什么

内存写满时会发生什么

Redis 用 maxmemory 给数据集划一条内存上限。每执行一条会占用更多内存的命令,Redis 都会检查用量;一旦超过上限,就按驱逐策略删掉一些 key,把用量压回线下。

所以实际运行时,内存用量是反复轻微越界又被拉回的锯齿状:越过上限 → 逐出 → 回到线下 → 再写入 → 再越界。策略决定的,就是每次"该逐出谁"。

打个比方

一个满员的停车场。新车要进来,保安得先请走一辆。请走谁,取决于规则:最久没动的那辆(LRU)?来得最少的那辆(LFU)?还是干脆随机挑一辆?——不同的驱逐策略,就是不同的"请离场"规则。

!

先有上限,才有驱逐。 没设 maxmemory(或设为 0),就没有边界、也不会驱逐;在 Redis Cloud 里,这个上限就是你的数据库内存大小

02 · 策略全景

八种驱逐策略

策略由两个维度组合而成:作用范围allkeys- 所有 key / volatile- 只限设了过期时间的 key)× 挑选规则(LRU 最久未用 / LFU 最少使用 / random 随机 / ttl 最快过期)。再加一个"什么都不删"的 noeviction

allkeys- · 所有 keyvolatile- · 仅带 TTL 的 key
LRU
最久未用
allkeys-lru
在全部 key 里逐出最久没被访问的。纯缓存最常用的默认。
volatile-lru
只在带过期时间的 key 里按 LRU 逐出。混合持久数据 + 缓存时用。
LFU
最少使用
allkeys-lfu
在全部 key 里逐出访问频率最低的。存在明显热点时更优。
volatile-lfu
只在带过期时间的 key 里按 LFU 逐出。
Random
随机
allkeys-random
随机挑一个逐出。访问大致均匀、无冷热之分时可用。
volatile-random
在带过期时间的 key 里随机逐出。
TTL
最快过期
TTL 维度只对带过期时间的 key 有意义。
volatile-ttl
优先逐出剩余存活时间最短的 key。适合限流、短时缓存。
不驱逐
noeviction
不逐出任何 key。内存满后,需要额外内存的写命令直接报错,只读命令照常。若开了副本,此限制作用于主库。适合"数据一条都不能丢"的场景。
!

volatile-* 的陷阱:如果没有任何 key 设置了过期时间,volatile-*退化成 noeviction——内存满了就开始报错,而不是驱逐。用它就要确保该被缓存的 key 都带上了 TTL。

03 · 近似算法

近似 LRU:不精确,但够用

真正的 LRU 需要为所有 key 维护一条按访问时间排序的链表,每次访问都要移动节点——在百万级 key 上,这个开销大到无法接受。

Redis 的做法是近似:每个对象头里存一个 24 位的 LRU 时钟(秒级精度),访问时更新。要驱逐时,随机采样 maxmemory-samples 个 key(默认 5),只在这几个样本里逐出访问时间最旧的那个。从 Redis 3.0 起,还会维护一个候选池,把历次采样中的好候选保留下来,让结果更逼近真 LRU。

▲ 真 LRU vs 采样近似sample = 5
整个键空间(可能上百万) 比较 5 个样本的空闲时间 sample = [ 3s, 6s, 2s, 14s, 5s ] → 逐出 14s(最旧) 每对象 24-bit LRU 时钟 · 候选池跨轮次逼近真 LRU maxmemory-samples:默认 5,调到 10 更准(更耗 CPU)
CONFIG SET maxmemory 2gb
CONFIG SET maxmemory-policy allkeys-lru
CONFIG SET maxmemory-samples 10 # 采样数:5→10 更接近真 LRU,代价是更多 CPU
04 · 另一种视角

LFU:看频率,而不是看新近

LRU 关心"多久没用过",但有些数据访问间隔长、却一直重要(比如每天跑一次的热门榜单)。LFU(Redis 4.0 起)改看访问频率:用得越频繁,越该留下。

它同样是近似的:每个对象用约 8 位的概率计数器(Morris counter)估算访问频率。两个关键旋钮——lfu-log-factor 让计数对数增长(越高涨得越慢,避免热 key 瞬间顶满);lfu-decay-time 让计数随时间衰减(曾经很热、现在冷了的 key 也能被逐出,适应访问模式的变化)。

▲ 频率计数器:访问时对数增长,静默时衰减morris counter
255 0 访问频率计数(8-bit) ↑ 访问脉冲:计数对数增长(涨幅递减) 无访问:随 decay-time 衰减 → 可被逐出

直觉:LRU 会误删"访问间隔长但重要"的数据;LFU 用频率+衰减,把真正的热点长期留在内存里,同时给"过气热点"一个退场机制。

05 · 如何选择

怎么选一个策略

没有万能策略,按数据的性质和访问模式来定。一个简单的判断顺序:

数据一条都不能被删?纯数据库、非缓存
noeviction
有些 key 必须常驻、只想删缓存部分?给缓存 key 设 TTL
volatile-*
纯缓存,任何 key 都可删,且存在明显冷热?
allkeys-lfu
纯缓存,最近用过≈将来会用(新近性有效)?
allkeys-lru
限流器 / 短时 key,想先清最快过期的?
volatile-ttl
访问大致均匀、无冷热之分?
*-random

allkeys-lru 是没有特别理由时的稳妥默认——因为大多数访问符合二八分布,最近用过的确实更可能被再次用到。

06 · 在 Redis Cloud 里

配置与替代方案

1

在数据库级别设置

驱逐策略是每个数据库独立的设置。编辑数据库详情,修改 Data eviction policy 即可。多数数据库的默认是 volatile-lruActive-Active 数据库默认是 noeviction

2

监控驱逐是否健康

INFO 观察 evicted_keys命中率。命中率持续下滑,往往意味着策略选错、或内存该扩容了。

# 关注这些指标 evicted_keys # 已逐出的 key 数(持续飙升 = 内存吃紧) keyspace_hits / misses # 命中率下降 = 策略或容量需要调整
3

不想驱逐?用分层存储

如果既想装下更多数据、又不想驱逐,Redis Cloud 提供 Auto Tiering(Pro)和 Redis Flex(Essentials):把数据横跨 RAM + 闪存(SSD),热数据留在 RAM、冷数据放到闪存,用更低成本扩容而不牺牲太多性能。

07 · 常见的坑

关于驱逐的几个真相

×
volatile-* 却不设 TTL没有 key 带过期时间时,它退化成 noeviction,内存满就报错。用它务必给缓存 key 加 TTL。
×
把 Redis 当数据库却开了 allkeys唯一副本的数据可能被悄悄逐出。关键数据要么 noeviction,要么另做持久化 + 备份。
×
期待"精确"的 LRU/LFU都是采样近似,不是全局精确排序。要更准就调高 maxmemory-samples,但别指望零误差。
×
noeviction 下写入报错却没预案内存满后写命令会失败,应用要能优雅处理错误 / 触发扩容,而不是直接崩。
×
热点被 LRU 误删访问间隔长但重要的数据,用 LRU 容易被清掉。有明显冷热就换 LFU。
×
持续大量驱逐还硬撑evicted_keys 长期高企是扩容信号——加内存或用分层存储,而不是靠驱逐硬扛。
收尾 · 一句话

用一点点精度,换巨大的速度

到了 maxmemory 边界,Redis 靠随机采样做近似 LRU / LFU,逐出样本里最该走的那个。选对范围(allkeys / volatile)和规则(LRU / LFU / random / ttl),再用 evicted_keys 和命中率验证——就能在有限内存里维持高命中。

maxmemory 触顶 → 采样 → 逐出最冷 · allkeys-lru 稳妥默认 · 有冷热选 LFU · 不能丢用 noeviction