谈谈 Redis 的过期策略?
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
谈谈 Redis 的过期策略?
面试考察点
-
基础掌握度:面试官不仅仅是想知道 Redis 有过期策略,更是想知道你是否清楚 Redis 采用的是 惰性删除 + 定期删除 的组合方案,而不是定时删除,以及为什么要这样设计。
-
原理理解深度:考察你是否了解过期字典的内部结构,定期删除的采样逻辑(随机抽取、循环检测),以及这些设计背后的性能与内存权衡。
-
知识关联能力:能否自然过渡到 "过期策略解决不了内存问题时怎么办" → 内存淘汰策略(
maxmemory-policy),展示完整的知识体系。
核心答案
Redis 采用 惰性删除 + 定期删除 两种策略配合使用:
| 策略 | 触发时机 | 行为 | 特点 |
|---|---|---|---|
| 惰性删除 | 访问 Key 时 | 发现过期才删除 | 对 CPU 友好,但可能有垃圾数据残留 |
| 定期删除 | 每隔一段时间 | 随机抽查一批 Key,删除过期的 | 折中方案,平衡 CPU 和内存 |
一句话结论:Redis 不使用定时删除(到期立即删),因为那会消耗大量 CPU 资源。而是用 "惰性 + 定期" 的组合策略,在 CPU 和内存之间取一个平衡。如果过期策略清理不过来,内存还是满了,就会触发 内存淘汰策略。
深度解析
一、三种过期策略的理论对比
在讲 Redis 的方案之前,先了解一下理论上过期策略有三种:
上图展示了三种过期策略的理论对比:
- 定时删除:每个 Key 设置一个定时器,到期立即删除。内存最优,但 CPU 开销太大,Redis 没有采用。
- 惰性删除:只在访问时检查。CPU 最友好,但过期不访问的 Key 会一直占内存,Redis 采用。
- 定期删除:每隔一段时间抽查。是 CPU 和内存的折中方案,Redis 采用。
Redis 选择 惰性 + 定期 的组合策略,既不会消耗过多 CPU,又能及时清理大部分过期数据。
二、过期字典:Redis 如何记录 Key 的过期时间?
上图展示了 Redis 存储过期时间的内部结构:
- Redis 在每个数据库(
redisDb)中维护了两个字典:dict(主字典,存储所有 Key-Value 数据)和expires(过期字典,存储设置了过期时间的 Key 及其过期时间戳)。 - 过期字典的 key 指向主字典中的同一个 Key 对象(不是副本),所以不会额外占用太多内存。
- 当检查一个 Key 是否过期时,只需在
expires字典中查找对应的时间戳,与当前时间比较即可。
三、惰性删除的工作流程
上图展示了惰性删除的判断流程:
- 核心思路:每次访问 Key 时,顺便检查一下是否过期。如果过期了,就删除并返回空;没过期就正常返回数据。
- 优点:对 CPU 极度友好,只有在业务需要访问时才执行删除操作,不会浪费 CPU 资源。
- 缺点:如果有些 Key 过期了但 一直没有被访问,它们就会一直占用内存,成为 "垃圾数据"。
四、定期删除的工作流程
定期删除是 Redis 主动清理过期数据的机制,它的执行逻辑比惰性删除复杂得多。
上图展示了定期删除的完整流程,核心逻辑:
- 随机抽取:每 100ms 从过期字典中随机抽取 20 个 Key,检查是否过期。
- 自适应循环:如果这 20 个 Key 中过期的比例超过 25%,说明当前过期 Key 很多,就再随机抽 20 个继续清理,直到过期比例降到 25% 以下,或者执行时间超过上限(默认 25ms)。
- 为什么限制 25ms:定期删除是在 Redis 主线程中执行的,如果清理时间过长会阻塞正常请求,所以必须严格限制单次执行时长。
定期删除的局限性:
- 由于是 随机抽样,总会有一些过期 Key 被遗漏,不会全扫一遍。
- 如果过期 Key 数量非常多,而随机抽取的概率有限,仍然会有大量过期 Key 残留在内存中。
五、过期策略 + 内存淘汰策略 = 完整方案
"惰性 + 定期" 只能清理 过期了的 Key。如果有些 Key 没有设置过期时间,或者过期策略清理速度跟不上新 Key 的写入速度,内存还是会满。这时就需要 内存淘汰策略 出场了。
# redis.conf 配置最大内存和淘汰策略
maxmemory 4gb
maxmemory-policy allkeys-lru
Redis 8 种内存淘汰策略:
| 策略 | 行为 | 适用场景 |
|---|---|---|
noeviction | 不淘汰,写入直接报错(默认) | 数据不能丢失 |
allkeys-lru | 从所有 Key 中淘汰最久未使用的 | 通用缓存(推荐) |
allkeys-lfu | 从所有 Key 中淘汰使用频率最低的(Redis 4.0+) | 热点数据明显的缓存 |
allkeys-random | 从所有 Key 中随机淘汰 | 无访问热点 |
volatile-lru | 从设置了过期的 Key 中淘汰最久未使用的 | 混合使用(部分持久化) |
volatile-lfu | 从设置了过期的 Key 中淘汰使用频率最低的 | 混合使用 |
volatile-random | 从设置了过期的 Key 中随机淘汰 | 混合使用 |
volatile-ttl | 从设置了过期的 Key 中淘汰剩余时间最短的 | 业务有明确 TTL 需求 |
生产环境推荐:allkeys-lru(通用缓存)或 allkeys-lfu(热点明显的缓存)。
面试高频追问
-
追问一:为什么 Redis 不用定时删除?
定时删除需要为每个设置了过期时间的 Key 维护一个定时器,如果同时有几十万个 Key 设置了过期时间,就会有几十万个定时器同时运行,严重消耗 CPU 资源,影响 Redis 处理正常请求的性能。Redis 作为高性能缓存,把 CPU 留给业务处理更重要。
-
追问二:LRU 和 LFU 淘汰策略的区别?
- LRU(Least Recently Used):淘汰最久没被访问的 Key,基于 "最近访问的数据大概率还会被访问" 的假设。Redis 的 LRU 是近似算法,随机采样 N 个 Key(默认 5 个),淘汰其中最久未访问的。
- LFU(Least Frequently Used):淘汰访问频率最低的 Key,基于 "访问频率高的数据更有价值" 的假设。Redis 4.0 引入,更适合 热点数据明显 的场景(比如 20% 的数据占 80% 的访问量)。
-
追问三:如何查看 Redis 当前内存使用情况?
# 查看内存使用详情 INFO memory # 关键指标: # used_memory:已用内存 # maxmemory:最大内存限制 # maxmemory_policy:当前淘汰策略
常见面试变体
- 变体一:"Redis 的 Key 过期了是怎么被删除的?"
- 变体二:"Redis 过期策略和内存淘汰策略的区别?"
- 变体三:"Redis 内存满了怎么办?"
- 变体四:"Redis 的 LRU 算法了解吗?是精确的还是近似的?"
记忆口诀
过期策略:惰性删除(访问才删)+ 定期删除(随机抽查,过期超 25% 继续抽)。
为什么不单独用一种:惰性删不干净(残留垃圾),定期删不精确(随机抽样),两者互补。
内存满了:过期策略搞不定 → 内存淘汰策略接手 → allkeys-lru 最常用。
总结
Redis 过期策略采用 惰性删除 + 定期删除 的组合方案。惰性删除在访问时才检查并删除过期 Key,对 CPU 友好;定期删除每 100ms 随机抽样一批 Key 清理过期的,防止大量过期数据残留。两者互补,在 CPU 和内存之间取得平衡。如果过期策略仍无法释放足够内存,就会触发 内存淘汰策略(推荐 allkeys-lru 或 allkeys-lfu)。