Redis不立刻删除过期数据,是因为定时删除会导致CPU过载、内存与调度开销剧增;它采用惰性删除(访问时检查并删)与定期删除(每100ms抽查、单次不超过25ms)结合的策略,在响应延迟与内存占用间动态权衡。

Redis不直接删除所有过期Key,是因为“到期即删”在高并发场景下会瞬间压垮CPU——它用惰性+定期双策略,本质是在保响应延迟和省内存之间做实时权衡。
为什么定时删除(到期立刻删)被彻底弃用
定时删除听起来最干脆:给key设EXPIRE时,顺手挂个定时器,到点就调dbDelete()。但问题在于,Redis每秒可能处理数万次写入,若每个带过期时间的key都注册一个定时器,系统要维护海量定时事件,触发时又集中调用删除逻辑,主线程会频繁被抢占。
- 大量定时器本身占用内存(每个timer对象至少几十字节),且红黑树或时间轮调度开销不可忽视
- 高峰期多个key同时过期,删除动作扎堆,
evictExpiredKeys()可能连续执行数十毫秒,阻塞后续命令 - 从节点同步压力陡增:主节点批量广播
DEL事件,从节点重放时同样卡顿
expireIfNeeded()怎么做到“访问才查”,又不拖慢读操作
惰性删除靠的是所有数据库访问命令(GET、HGET、EXISTS等)执行前必调的expireIfNeeded()函数。它不做扫描,只做两件事:
- 检查该
key是否在过期字典db->expires中存在对应条目 - 若存在,比对当前
mstime()与存储的过期时间戳——纯整数比较,O(1)
只要没过期,函数0纳秒返回;一旦过期,才走dbDelete()路径。所以99%的正常读请求完全感知不到开销。但代价是:那些永远没人GET的过期key,就真的一动不动躺在内存里。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
定期删除如何用25ms守住CPU底线
定期删除不是“每秒扫一遍全库”,而是由activeExpireCycle()驱动的自适应抽查:
- 默认每100ms触发一次(由
hz配置控制,默认10,即每秒10次) - 每次先抽20个
key(ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP),删掉其中过期的 - 如果这20个里超5个(25%)已过期,说明过期密度高,就继续抽、再删,直到比例≤25%或累计耗时≥25ms
这个25ms硬限制很关键——哪怕内存里堆积了10万个过期key,单次清理也绝不会让主线程卡顿。但反过来,如果业务写入节奏快、过期key生成速度远超抽查清理速度,内存水位就可能缓慢爬升。
真正容易被忽略的实战陷阱
很多人以为开了maxmemory + allkeys-lru就能高枕无忧,却忽略了:内存淘汰机制只在写入时触发,而activeExpireCycle()清理的是“已过期但未被淘汰”的key。如果某批key既过期又没被访问,还恰好躲过了定期抽查(比如刚过期就被新key挤出采样池),它们就会卡在内存里,直到下一次SCAN或手动DEBUG POPULATE触发全量扫描——这种延迟释放,在长连接+低频访问的IoT类场景里特别隐蔽。

















