一、引言:缓存的"保质期"管理
在 Redis 中,我们经常为数据设置一个"保质期"(TTL, Time To Live),例如:
bash
SET session:12345 "user_data" EX 1800 # 30分钟后过期
这能有效防止内存被无用数据无限占用。但随之而来的问题是:当一个 Key 到了它的"保质期",Redis 是如何知道并清理它的呢?
如果立即删除,会消耗大量 CPU;如果永不删除,又会造成内存泄漏。这是一个经典的CPU 资源与内存资源之间的权衡问题。
Redis 给出的答案是:不采用单一策略,而是将两种互补的策略------惰性删除与定期删除------结合起来,形成一套高效且平衡的过期 Key 处理机制。
💡 核心价值 :
理解这套机制,不仅能让你设计出更高效的缓存方案,更能回答面试中关于 Redis 内存管理的经典问题!
二、为什么不用"定时删除"?
在讨论 Redis 的方案之前,我们先看看一个看似最直接的方案为何被弃用。
2.1 定时删除(Timed Deletion)方案
- 原理:为每个设置了过期时间的 Key 创建一个定时器(Timer)。一旦时间到,定时器立刻触发,将该 Key 删除。
- 优点 :对内存最友好,过期 Key 能被立即释放。
- 致命缺点 :CPU 开销巨大!想象一下,如果有百万甚至千万个 Key 同时设置了过期时间,那么就需要维护同等数量的定时器。这不仅会消耗大量内存来存储定时器本身,其频繁的上下文切换和回调执行会严重拖慢 Redis 主线程的响应速度,违背了 Redis 追求极致性能的初衷。
因此,Redis 没有采用这种"理想化"的定时删除策略。
三、Redis 的双剑合璧:惰性删除 + 定期删除
为了在 CPU 和内存之间取得最佳平衡,Redis 巧妙地结合了两种策略。
3.1 策略一:惰性删除(Lazy Expiration)
原理
这是一种"被动式 "或"按需式 "的删除策略。Redis 并不会主动去关心一个 Key 是否过期,而是在每次客户端尝试访问这个 Key 时 ,才去检查它是否已经过期。如果过期,则立即删除,并向客户端返回空值(如 nil)。
你可以把它想象成超市里顾客结账时,收银员才检查商品是否过期。
优点
- CPU 友好:只有在真正需要访问数据时才进行检查,避免了不必要的计算开销。
- 实现简单:逻辑清晰,易于集成到命令执行流程中。
缺点
- 内存不友好 :如果一个过期的 Key 永远不再被访问 ,那么它将一直占用着宝贵的内存,直到 Redis 重启或触发其他内存回收机制。这在某些场景下可能导致内存泄漏。
3.2 策略二:定期删除(Periodic Expiration)
原理
这是一种"主动式 "的删除策略,用于弥补惰性删除的不足。Redis 的主线程会周期性地(默认每秒 10 次,即每 100 毫秒一次)随机抽查一部分设置了过期时间的 Key,并将其中已过期的 Key 删除。
具体流程如下(以默认的 SLOW 模式为例):
- 随机采样 :从 Redis 的"过期字典"(一个专门存放所有带 TTL 的 Key 的哈希表)中,随机选取 20 个 Key。
- 检查与删除:遍历这 20 个 Key,删除所有已过期的 Key。
- 动态调整 :如果在这 20 个 Key 中,过期 Key 的比例超过 25% (即 5 个以上),则说明过期 Key 的密度很高,于是 Redis 会重复步骤 1,继续采样和删除,直到某次采样中过期比例低于 25%,或者单次操作耗时接近 25 毫秒(为了避免长时间阻塞主线程)。
📌 注意 :Redis 6.0+ 引入了
FAST模式,它会在事件循环的 beforeSleep 阶段执行,每次只花费 1 毫秒,频率更高但每次处理量更小。
优点
- 内存友好:即使某些 Key 永远不被访问,也能通过后台的定期扫描被逐渐清理掉,有效防止了内存泄漏。
- 可控的 CPU 开销:通过限制每次操作的时间和采样数量,将 CPU 消耗控制在一个可接受的范围内。
缺点
- 无法保证实时性:过期 Key 不会被立即删除,存在一定的延迟。
- 仍有遗漏可能:由于是随机采样,不能保证在短时间内清理掉所有过期 Key。
四、协同工作:一张图看懂整体流程
+---------------------+
| Client Request |
| (e.g., GET mykey) |
+----------+----------+
|
v
+---------------------+
| Main Thread |
| |
| 1. Check if 'mykey' |<---+
| has expired? | |
| | |
| YES --> Delete it | | 3. Periodic Task
| & return nil| | (every 100ms)
| | |
| NO --> Return value| |
+----------+----------+ |
| |
| v
| +------------------+
+----->| Randomly sample |
| 20 keys from |
| the expire dict |
| |
| Delete expired |
| ones |
+------------------+
- 路径1(蓝色) :代表惰性删除。由客户端请求触发。
- 路径2(绿色) :代表定期删除。由 Redis 内部的定时任务触发。
这两种策略相互配合,共同构成了 Redis 健壮的过期 Key 回收体系。
五、配置与调优
虽然 Redis 的默认策略已经非常优秀,但在极端场景下,我们也可以对其进行微调。
hz配置项 :控制 Redis 执行后台任务(包括定期删除)的频率。默认值为10,表示每秒 10 次。将其调高(如100)可以让定期删除更积极,更快地回收内存,但会增加 CPU 负担。反之亦然。active-expire-effort(Redis 7.0+):直接控制定期删除的 CPU 努力程度,取值 1-10,默认为 1。值越大,每次定期删除任务会尝试做更多工作。
六、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!