Redis过期Key不会立即删除,因其采用惰性删除(访问时检查并删)与定期删除(每秒10次抽检,每次最多25轮、耗时≤1ms)组合策略,放弃定时删除以避免单线程模型下定时器带来的高CPU开销和设计复杂性。

Redis过期Key不会被立即删除,是因为它压根没采用“定时器一到就删”的实时策略。这种设计不是疏漏,而是权衡——用可控的延迟换CPU和内存的稳定性。
惰性删除:访问时才触发,不访问就“装死”
惰性删除的核心逻辑藏在每个读命令里:GET、HGET、EXISTS 等访问操作执行前,Redis会先调用 expireIfNeeded 检查键是否过期。只有这时才会真正删除。
- 如果键已过期,
DEL操作同步发生,客户端收到nil或空响应,就像从来没设过这个键 - 如果键没过期,照常返回值,不产生额外开销
- 键长期没人碰?那它就一直留在内存里,直到下一次被访问或被定期删除抽中
这导致一个典型现象:监控看到内存持续上涨,但 INFO memory 显示 used_memory_peak 很高,而 expired_keys 计数增长缓慢——很多过期键根本没被触达。
定期删除:每秒最多10轮抽查,靠概率清内存
定期删除由 activeExpireCycle 函数驱动,默认每100ms执行一次(对应配置项 hz 10),但它不是全量扫描,而是“抽样+重试”模式:
- 每次随机从过期键字典(
expiresdict)中选20个键检查 - 若其中超过25%已过期,立刻再抽20个,最多重复16轮
- 单次执行有严格时间限制(通常不超过1ms),避免阻塞主线程
-
hz值调高(比如设为100)会让清理更积极,但CPU占用明显上升,尤其在过期键密集时
这意味着:即使一个key在00:00:00过期,它可能要等到00:00:02甚至更晚才被抽中清理——不是不删,是“还没轮到”。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
为什么不用定时删除?代价太高
定时删除听起来最理想:给每个key配个定时器,到期秒删。但Redis没选它,原因很实际:
- 数千万key就得维护数千万定时器,红黑树或时间轮的插入/删除开销巨大
- 大量短TTL键(如验证码、临时token)会导致定时器高频创建销毁,引发频繁内存分配和锁竞争
- 定时器回调需跨线程安全调度,在单线程事件循环模型里反而破坏简洁性
所以Redis干脆放弃“精确准时”,转而用惰性+定期组合——既避免定时器的工程负担,又防止内存无限堆积。
内存淘汰是兜底,不是替代方案
当 maxmemory 触发时,noeviction 以外的淘汰策略(如 allkeys-lru)可能顺手删掉过期键,但这只是副作用。关键点在于:
- 淘汰策略只在内存不足时启动,不能依赖它日常清理过期键
- 像
volatile-lfu这类策略,根本不会碰没设TTL的键,对“漏掉”的过期键无能为力 - 如果业务误设了超长TTL(比如7天),又极少访问,这些键可能卡在内存里好几天
真正容易被忽略的是:定期删除的抽样机制依赖过期键在独立字典中的分布密度。如果过期键占比长期低于5%,抽中率会显著下降——这时候光调 hz 没用,得结合业务节奏主动 SCAN + EXPIRE 修正或拆分TTL粒度。

















