Redis 的过期删除和内存淘汰策略是怎样的?
一句话回答
过期删除用惰性删除 + 定期删除:访问 key 时检查是否过期,过期就删掉;后台再定期分批检查设置了过期时间的 key,删掉已经过期的。两者结合仍然会有过期 key 残留,所以内存达到 maxmemory 时,还要靠内存淘汰策略腾出空间,一共 8 种,默认的 noeviction 会直接拒绝写入。Redis 的 LRU 和 LFU 都是采样近似实现,纯缓存场景一般用 allkeys-lru 或 allkeys-lfu。
详细解析
过期删除
设置了过期时间的 key,Redis 会把它的过期时间戳单独记在一个过期字典里。
- 惰性删除:每次访问 key 时先检查是否过期,过期就删除并返回不存在。对 CPU 最友好,但一直没人访问的过期 key 会一直占着内存
- 定期删除:后台定时任务每秒执行若干次(由
hz控制,默认 10),每次从带过期时间的 key 中检查一批,删除已过期的;如果这一批里过期的比例比较高,就接着检查下一批。每次执行都有时间上限,避免长时间占用主线程。挑选方式在 Redis 6.0 有变化:之前是随机抽样,之后改为用游标逐批扫描过期字典
为什么不在到期的那一刻删除:要给每个 key 维护定时器,key 多时 CPU 开销太大;只靠惰性删除又会浪费内存。两者结合是在 CPU 和内存之间折中。
主从复制时,从节点不会主动删除过期 key,而是等主节点删除后同步过来的删除命令;不过读取从节点上已经过期的 key 时,从节点会返回不存在。
内存淘汰策略
使用的内存超过 maxmemory 后,Redis 在处理命令前按 maxmemory-policy 淘汰 key,直到内存降到限制以下:
| 策略 | 淘汰范围 | 淘汰规则 |
|---|---|---|
| noeviction(默认) | 不淘汰 | 写命令直接返回 OOM 错误,读命令正常 |
| allkeys-lru | 所有 key | 最近最少使用 |
| volatile-lru | 设置了过期时间的 key | 最近最少使用 |
| allkeys-lfu | 所有 key | 使用频率最低 |
| volatile-lfu | 设置了过期时间的 key | 使用频率最低 |
| allkeys-random | 所有 key | 随机 |
| volatile-random | 设置了过期时间的 key | 随机 |
| volatile-ttl | 设置了过期时间的 key | 剩余存活时间最短的优先 |
volatile 系列只在带过期时间的 key 中挑选,如果没有这样的 key,效果和 noeviction 一样,写入会被拒绝。
近似 LRU
标准的 LRU 要维护一个按访问时间排序的链表,每次访问都要移动节点,对 Redis 来说内存和 CPU 开销都太大。Redis 的做法是:
- 每个对象里有一个 24 位的字段,记录最近一次被访问的时间
- 需要淘汰时,随机采样几个 key(
maxmemory-samples,默认 5),放进一个按空闲时间排序的候选池(大小为 16) - 淘汰候选池里空闲时间最长的 key
采样数越大,结果越接近真正的 LRU,CPU 开销也越大,调到 10 时已经很接近了。
LFU 适合什么场景
LRU 只看最近一次访问时间,有一个问题:某个批处理任务把大量冷数据各访问一遍,它们都成了"最近使用"的,真正的热点数据反而被挤了出去。
LFU(Redis 4.0 引入)复用同一个 24 位字段:高 16 位记录上次衰减的时间(分钟),低 8 位是一个对数计数器。访问次数越多,计数器增长得越慢,最大到 255,所以 8 位就能区分很大范围的访问频率;一段时间不访问,计数器会衰减(lfu-decay-time,默认每分钟),过去的热点会慢慢冷却。新 key 的计数器初始值是 5,避免刚写入就被淘汰。
访问频率差异明显、热点稳定的场景(如商品详情、热门内容)适合 LFU;访问模式变化快、更看重"最近"的场景用 LRU。
缓存场景的建议
- 一定要设置
maxmemory,并为 fork 时的写时复制、客户端缓冲区留出余量,不要设成机器的全部内存 - 纯缓存:
allkeys-lru或allkeys-lfu - 缓存和不能丢的数据混在一个实例里:用
volatile-lru,并保证缓存 key 都设置了过期时间;更好的做法是拆成两个实例 - 当数据库或队列用、数据不能丢:
noeviction,并对内存使用率设置告警
代码示例
127.0.0.1:6379> CONFIG SET maxmemory 4gb
OK
127.0.0.1:6379> CONFIG SET maxmemory-policy allkeys-lfu
OK
127.0.0.1:6379> OBJECT FREQ product:1001
(integer) 12
OBJECT FREQ 返回 key 的 LFU 计数器,只有在 LFU 策略下才能用;LRU 策略下可以用 OBJECT IDLETIME 查看空闲了多少秒。INFO stats 中的 expired_keys 和 evicted_keys 分别是过期删除和被淘汰的 key 数,evicted_keys 持续增长说明内存不够用了。
面试官可能追问
大量 key 在同一时间过期会有什么问题?
定期删除在主线程里执行,过期 key 很多时,它会在时间上限内尽量多删一些,这段时间的请求延迟会升高;同时这些 key 对应的请求会一起打到数据库,也就是缓存雪崩,见 缓存穿透、击穿、雪崩。解决办法是过期时间加随机值,把过期时间打散。
内存满了写入报 OOM,怎么排查?
用 INFO memory 看 used_memory 和 maxmemory,看 mem_fragmentation_ratio 判断内存碎片多不多;用 redis-cli --bigkeys 或 MEMORY USAGE 找出占内存大的 key(见 大 Key 和热 Key);还要看客户端的输出缓冲区(CLIENT LIST 中的 omem),比如消费太慢的订阅者会让缓冲区堆积。最后确认淘汰策略是不是 noeviction、缓存 key 有没有设置过期时间。
过期 key 在 RDB 和 AOF 中是怎么处理的?
生成 RDB 时,已经过期的 key 不会写进文件;主节点加载 RDB 时也会跳过过期的 key。AOF 中,key 因过期被删除时会追加一条删除命令,同样的命令也会发给从节点;重写 AOF 时,已过期的 key 不会被写入。
易错点
- key 到期后不会立刻被删除,过期 key 可能在内存里残留一段时间
- 默认策略是 noeviction,不配置的话内存满了只会报错,不会自动淘汰
- volatile 系列策略不会淘汰没有设置过期时间的 key
maxmemory不是整个进程的内存上限,内存碎片等因素会让进程实际占用的内存更高
AI 模拟面试官
用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮
这道题你掌握了吗?
选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。
学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。