Redis 过期删除与内存淘汰机制深度解析
在 Redis 的内存管理体系中,过期删除机制负责清理已设置过期时间的无效键 ,内存淘汰机制则在 Redis 内存达到阈值时主动释放内存空间,二者协同工作,确保 Redis 在有限内存下高效、稳定运行,是 Redis 作为缓存中间件的核心设计之一。本文将从实现原理、工作流程、核心参数、使用场景等维度,全面解析这两大机制,并梳理二者的关联与区别。
一、Redis 过期删除机制
Redis 并未采用单一的过期键清理方式,而是结合惰性删除 和定期删除 的组合策略,在 CPU 资源占用和内存回收效率之间做了最优平衡。在此之前,先了解 Redis 存储过期键的核心结构 ------过期字典(expires dict) :当通过EXPIRE key seconds、SET key value EX seconds等命令为键设置过期时间时,Redis 会将键和其对应的绝对时间戳(如 2026-01-30 12:00:00)存入过期字典,该字典基于哈希表实现,可实现 O (1) 时间复杂度的过期时间查询,是过期删除机制的基础。
1. 惰性删除(Lazy Deletion):访问时的 "最后把关"
核心思想 :不主动扫描和删除过期键,仅当客户端读写访问某个键 时,才检查其是否过期,若过期则立即清理,属于被动清理策略。
工作流程
- 客户端执行
GET、HSET、DEL等任意读写命令操作某个键; - Redis 先查询过期字典,判断该键是否存在过期时间且当前时间已超过该时间戳;
- 若过期:立即将键从数据库字典 和过期字典 中移除(Redis 4.0 + 可配置异步删除),返回
nil或对应空结果; - 若未过期 / 无过期时间:正常执行客户端命令,返回对应结果。
核心优缺点
- ✅ CPU 友好:仅在必要时执行过期检查,避免无意义的 CPU 消耗,实现逻辑简单;
- ❌ 内存占用风险:若大量过期键长期未被访问,会一直占用内存,造成 "内存泄漏",这也是其无法单独使用的核心原因。
底层实现
由 Redis 源码db.c中的expireIfNeeded函数实现,所有键的访问 / 修改操作前都会调用该函数,是过期键清理的 "最后一道防线"。
2. 定期删除(Periodic Deletion):主动的 "抽样巡逻"
核心思想 :Redis 通过后台定时任务周期性地随机抽样 过期键并清理,属于主动清理策略,专门解决惰性删除的内存积压问题。
工作流程
Redis 的服务器核心定时任务serverCron会执行定期删除逻辑(由activeExpireCycle函数实现),默认流程如下:
- 触发频率:由配置
hz控制,默认每秒执行 10 次(每 100ms 一次); - 随机抽样:每次从过期字典中随机抽取20 个键进行过期检查;
- 批量删除:删除抽样中已过期的键,若过期键占比超过 25% ,则重复抽样删除流程;
- 时长限制:为避免阻塞主线程,单次定期删除的最大执行时间不超过 25ms,超时则立即终止,等待下一次执行。
简单来说,过期键占比越高,Redis 的主动清理力度越大,但始终控制在不影响正常请求的范围内。
核心优缺点
- ✅ 主动释放内存:能清理长期未被访问的过期键,避免内存无限积压;
- ✅ 可控性强:可通过配置调整触发频率,适配不同业务的负载;
- ❌ 清理不彻底:由于随机抽样和时长限制,无法保证所有过期键都被及时清理,仍会有少量过期键残留。
3. 组合策略的协同逻辑
惰性删除和定期删除并非独立工作,而是互补配合:
- 定期删除:负责主动巡逻,清理大部分 "僵尸过期键"(长期未访问),控制内存整体占用;
- 惰性删除:负责最后把关,确保任何被访问的键都是 "干净的",避免客户端获取到过期数据。
可以用一个通俗的比喻理解:定期删除像 "保洁阿姨定时抽查打扫",惰性删除像 "主人用东西前发现脏了立刻清理",二者结合既保证了环境整洁,又不会浪费过多精力。
4. 过期删除的核心配置
可通过redis.conf或CONFIG SET命令调整,适配业务场景:
| 配置项 | 默认值 | 作用 |
|---|---|---|
hz |
10 | 定时任务执行频率,值越大定期删除越频繁,CPU 占用略高 |
lazyfree-lazy-expire |
no | Redis 4.0+,是否异步删除过期键,开启可避免大键删除阻塞主线程 |
maxmemory-samples |
5 | 抽样估算过期键分布的样本数,不影响定期删除的 20 个键抽样规则 |
5. 特殊场景:持久化与主从的过期键处理
过期删除的逻辑在持久化和主从架构中存在特殊处理,避免数据不一致:
- RDB 持久化:生成 RDB 文件时,会过滤过期键,不写入文件;主库加载 RDB 时过滤过期键,从库加载时则全部载入(由主库同步删除指令);
- AOF 持久化 :过期键被删除时,会向 AOF 文件写入
DEL命令,保证 AOF 重写后无过期键; - 主从架构 :过期键的删除操作仅在主库 执行,从库不主动处理过期键,而是通过主库的
DEL同步指令实现过期清理,保证主从数据一致。
二、Redis 内存淘汰机制
当 Redis 的已使用内存达到配置的maxmemory阈值 时,过期删除机制已无法满足内存释放需求(比如大部分键未设置过期时间),此时会触发内存淘汰机制------ 主动淘汰部分键值对,释放内存以保证后续写操作正常执行,这是 Redis 内存管理的 "最后一道屏障"。
1. 核心触发条件
- Redis 已使用内存 ≥
maxmemory(可通过CONFIG SET maxmemory 4GB设置,单位支持 b/k/m/g); - 后续有新的写操作(
SET、LPUSH等),无空闲内存可用; - 若未配置
maxmemory(默认无限制),则永远不会触发内存淘汰(Linux 环境下可能因物理内存不足被 OOM 杀死)。
2. 8 种核心淘汰策略
Redis 提供 8 种淘汰策略,分为针对过期键 和针对所有键 两大类,可通过maxmemory-policy配置,Redis 4.0 + 新增 LFU 相关策略,覆盖更多业务场景。
基础策略(Redis 2.8+)
| 策略名 | 淘汰范围 | 淘汰规则 | 适用场景 |
|---|---|---|---|
noeviction |
无 | 不淘汰任何键,写操作返回 OOM 错误 | 核心数据存储,不允许丢失数据 |
volatile-lru |
仅设置过期时间的键 | 淘汰最近最少使用的键 | 混合存储(核心键无过期,缓存键有过期) |
allkeys-lru |
所有键 | 淘汰最近最少使用的键 | 纯缓存场景,优先保留热点数据 |
volatile-random |
仅设置过期时间的键 | 随机淘汰键 | 过期键数量大,无明显热点 |
allkeys-random |
所有键 | 随机淘汰键 | 数据访问均匀,无热点区分 |
volatile-ttl |
仅设置过期时间的键 | 淘汰TTL 最短(即将过期)的键 | 需精准控制过期键的淘汰顺序 |
新增策略(Redis 4.0+)
| 策略名 | 淘汰范围 | 淘汰规则 | 适用场景 |
|---|---|---|---|
volatile-lfu |
仅设置过期时间的键 | 淘汰最不经常使用的键 | 热点数据随时间变化,LRU 效果不佳 |
allkeys-lfu |
所有键 | 淘汰最不经常使用的键 | 纯缓存,访问频率是核心热点指标 |
3. 核心算法:LRU 与 LFU 的实现
Redis 并未实现严格的 LRU/LFU 算法 (会占用大量内存存储访问记录),而是采用近似 LRU/LFU ,通过随机抽样 实现,兼顾性能和效果,抽样数由maxmemory-samples(默认 5)控制,值越大越接近严格算法,CPU 占用略高。
- LRU(Least Recently Used) :最近最少使用,核心是 "淘汰最后一次访问时间最久的键";
- LFU(Least Frequently Used) :最不经常使用,核心是 "淘汰访问频率最低的键",Redis 通过 8bit 存储访问频次的衰减值,解决 LRU 在 "突发访问后长期闲置" 场景的淘汰偏差问题。
4. 内存淘汰的执行流程
当触发内存淘汰时,Redis 的执行逻辑如下:
- 根据配置的淘汰策略,从指定范围(过期键 / 所有键)中随机抽样
maxmemory-samples个键; - 按照 LRU/LFU/TTL 等规则,从抽样结果中选择 "最该淘汰" 的键;
- 删除该键并释放内存,若内存仍超过
maxmemory,重复步骤 1-3; - 若淘汰后内存低于阈值,或无符合条件的键可淘汰(如
volatile-*策略下无过期键),则停止淘汰,若为noeviction策略则返回写错误。
三、过期删除与内存淘汰的核心区别
很多开发者会混淆这两大机制,核心区别在于触发条件、清理对象、设计目标的不同,具体对比如下:
| 对比维度 | 过期删除机制 | 内存淘汰机制 |
|---|---|---|
| 触发条件 | 键过期 +(访问 / 定期抽样),与内存使用量无关 | Redis 已用内存 ≥ maxmemory,仅写操作触发 |
| 清理对象 | 仅设置了过期时间且已过期的键 | 按策略划分:可是过期键,也可是所有键 |
| 设计目标 | 清理过期的无效数据,保证数据有效性 | 释放内存,避免 OOM,保证 Redis 写可用性 |
| 执行时机 | 访问时实时执行 / 后台定时执行 | 内存达到阈值时,写操作时触发 |
| 是否可关闭 | 不可关闭(惰性删除是基础逻辑) | 可通过maxmemory-policy noeviction变相关闭 |
核心关联:内存淘汰机制可视为过期删除机制的 "补充和升级"------ 当过期删除无法满足内存释放需求时,由内存淘汰机制接手,二者共同构成 Redis 完整的内存回收体系。
四、生产环境的最佳实践
1. 配置建议
根据业务场景选择合适的配置,核心推荐如下:
纯缓存场景(无核心数据,允许丢失)
maxmemory:根据服务器内存设置,建议预留 20% 以上物理内存(如 16G 服务器设置 12G);maxmemory-policy:推荐allkeys-lfu(访问频率更贴合热点),次之allkeys-lru;hz:调整为 20-50(提高定期删除频率,减少过期键积压);- 开启
lazyfree-lazy-expire yes(异步删除过期键,避免阻塞)。
混合存储场景(核心数据 + 缓存数据)
- 核心数据:不设置过期时间,保证数据不被淘汰;
- 缓存数据:统一设置过期时间;
maxmemory-policy:推荐volatile-lfu/volatile-lru(仅淘汰缓存键,保护核心数据);maxmemory:合理评估核心数据内存占用,预留足够空间。
核心数据存储场景(不允许数据丢失)
maxmemory:不设置或设置足够大的值;maxmemory-policy:noeviction(拒绝写操作,避免数据被淘汰);- 依赖过期删除机制清理过期数据,配合
hz=20提高清理效率。
2. 性能优化要点
- 避免大量键同时过期 :若批量设置过期时间(如整点过期),会导致定期删除集中执行,造成 CPU 峰值,建议增加随机过期时间 (如
EX 3600 + random(0, 600)); - 大键单独处理:对 Hash、List 等大键,避免直接设置过期时间,可拆分为小键,防止删除时阻塞主线程;
- 监控核心指标 :通过
INFO memory查看expired_keys(已过期删除键数)、evicted_keys(已淘汰键数),通过INFO stats查看内存使用趋势,及时调整配置; - 开启主动碎片整理 :设置
activedefrag yes,清理内存碎片,提高内存利用率。
3. 常见问题排查
- Redis 内存占用过高但无大键 :大概率是过期键积压,检查
hz是否过小,可提高至 20-50,同时检查expired_keys指标; - 出现大量 evicted_keys :说明
maxmemory设置过小,或淘汰策略不合适,需增大内存或调整策略; - 主线程阻塞 :检查是否有大键过期 / 被淘汰,开启异步删除(
lazyfree相关配置),拆分大键。
五、总结
Redis 的过期删除和内存淘汰机制是其高性能的核心设计之一,充分体现了 Redis 在性能与资源之间的平衡思想:
- 过期删除通过惰性 + 定期的组合策略,在保证 CPU 效率的同时,尽可能清理过期数据,保证数据有效性;
- 内存淘汰机制在内存达到阈值时,通过近似 LRU/LFU 等策略释放内存,保证 Redis 的写可用性;
- 二者分工明确、协同工作,过期删除负责 "数据有效性",内存淘汰负责 "内存可用性"。
在生产环境中,无需过度纠结底层实现,核心是根据业务场景选择合适的配置,并通过监控指标持续优化,让 Redis 的内存管理能力适配业务的实际需求。