redis过期键内存不立即下降是设计使然,因其采用惰性删除(访问时触发expireifneeded)与定期删除(每100ms随机抽样20个键清理)协同机制,两者均存在延迟且不保证全覆盖,主从同步进一步加剧释放滞后。

Redis设置了过期时间,内存却没立刻下降——这不是异常,是设计使然。根本原因在于:Redis不采用“到期即删”的即时删除模型,而是靠expireIfNeeded()(惰性)和activeExpireCycle()(定期)两个异步、非强制、有延迟的机制协同清理,且两者都不保证“秒删”。
为什么GET或TTL命令返回-2之前,内存还在涨
因为惰性删除只在真正访问键时触发检查,而TTL本身就会调用expireIfNeeded();但前提是该键**确实被访问到了**。常见误区:
- 用
SCAN或KEYS *遍历,不会触发任何过期检查——它们只读键名,不查expires字典 - 从节点上执行
TTL,返回值是本地时间与主节点同步来的过期时间戳对比结果,但**不触发删除**,也不通知主节点 - 一个key过期后若长期无人读写,它就一直躺在内存里,
redis-cli连主节点查TTL也可能返回正数(因未被抽中扫描,也未被访问)
activeExpireCycle()每100ms都在删,为什么还是漏
这个函数默认每秒运行10次(由配置项hz控制),但每次只做“尽力而为”的随机采样:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 每次仅从
expires字典中随机抽取20个带TTL的key检查 - 只有其中过期比例 > 25% 才会再抽一轮,且总耗时 capped at 25ms
- 当Redis负载高、主线程忙于处理请求时,该循环可能被跳过或大幅缩减执行轮次
- 如果过期key集中在某个db或slot,而随机采样没覆盖到,它们就会滞留数分钟甚至更久
主从模式下,从节点的内存释放完全被动
从节点自身**不运行expireIfNeeded(),也不执行activeExpireCycle()**:
- 它只重放主节点发来的写命令,包括
DEL——这才是它真正删掉key的唯一时机 - 主节点上靠定期/惰性删掉的key,要等对应
DEL命令通过复制流同步过去,从节点才释放内存 - 网络延迟、主从积压、复制缓冲区满,都会让
DEL晚到;期间从节点内存持续虚高 - 你用
INFO memory看到的used_memory包含这些“僵尸key”,但KEYS *又查不到它们——因为已逻辑删除,只是还没刷出内存页
真正容易被忽略的是:内存释放不是原子动作,它卡在主节点清扫节奏、复制链路稳定性、从节点执行确认三者之间。哪怕hz调到100,也不能保证“全量覆盖”,更无法解决主从同步延迟带来的双端残留。线上观察内存变化,至少要看3–5分钟窗口,而不是盯着EXPIRE执行完那1毫秒。










