真实面经题目 · 原创解析
Redis 如何删除和淘汰过期数据?
Redis 过期数据的处理分两层:先通过惰性删除和定期删除清理已经到期的 key;当内存仍然超过 maxmemory 时,再按淘汰策略驱逐候选数据。面试回答要区分“过期删除”和“内存淘汰”,再说明为什么 Redis 采用抽样和渐进式清理。
真实面经题目 · 原创解析
Redis 过期数据的处理分两层:先通过惰性删除和定期删除清理已经到期的 key;当内存仍然超过 maxmemory 时,再按淘汰策略驱逐候选数据。面试回答要区分“过期删除”和“内存淘汰”,再说明为什么 Redis 采用抽样和渐进式清理。
Redis 给设置了 TTL 的 key 在过期字典中记录绝对过期时间。删除过期数据主要有两种方式:惰性删除和定期删除。惰性删除是在访问 key 时检查是否过期,过期就删除,优点是不浪费 CPU,缺点是冷数据可能长期占内存。定期删除是后台周期性从过期字典中抽样检查,发现过期比例较高会继续扫描一段时间,避免一次性全量扫描导致阻塞。除此之外,当 Redis 配置了 maxmemory 且内存达到上限时,会触发内存淘汰策略,比如 allkeys-lru、volatile-lru、allkeys-lfu、volatile-ttl、noeviction 等。过期删除解决的是 TTL 到期问题,淘汰策略解决的是内存不足问题,两者不是一回事。
Redis 不会把过期时间塞进 value 本身,而是在数据库对象里维护一张过期字典,key 指向实际键对象,value 是毫秒级的绝对过期时间戳。这样普通 key 和带 TTL 的 key 可以共用同一套读写结构,只有需要判断过期时才查过期字典。TTL、EXPIRE、PEXPIRE 等命令本质上都是在维护这张过期信息表。
惰性删除发生在客户端访问某个 key 时,Redis 会先判断这个 key 是否设置了过期时间以及是否已经到期。如果到期,就把 key 从主字典和过期字典中删除,然后对外表现得像 key 不存在。它的优点是只处理被访问的数据,CPU 成本很低;缺点是如果大量过期 key 后续没人访问,它们会继续占用内存,不能单独依赖这种方式。
定期删除不是全量遍历所有带 TTL 的 key,而是在事件循环中按一定频率从过期字典抽样检查。若样本中过期比例较高,说明库里可能堆积了较多过期数据,就继续扩大清理;若比例较低,就尽快让出执行时间。这个设计在内存回收和单线程响应延迟之间做平衡,避免为了清理过期键而长时间阻塞正常请求。
过期删除只处理已经达到 TTL 的 key,不管 Redis 当前内存是否紧张;内存淘汰则是在 maxmemory 限制下,内存不足时按照策略选择 key 驱逐。一个 key 即使没有过期,也可能因为 allkeys-lru 或 allkeys-lfu 被淘汰;一个 key 即使已经过期,也可能在被访问或定期扫描前暂时留在内存中。面试中必须把这两条链路分开讲。
过期 key 被删除时,不只是内存结构移除这么简单,还可能影响持久化、复制和通知机制。主节点会把删除行为传播给从节点,AOF 中会记录等价的删除命令,避免从节点或重启后再次看到已过期数据。开启键空间通知时,也可能产生过期事件。理解这些副作用,能说明你知道过期删除不只是一个本地内存回收动作。
因为 Redis 核心命令执行主要依赖单线程事件循环,全量扫描会让正常请求长时间等待。抽样加时间限制可以把清理成本摊平,牺牲一点过期键的即时释放,换取整体延迟稳定。
不一定。到期只表示逻辑上不可再返回给客户端,实际删除可能发生在下一次访问、定期扫描或内存淘汰前的检查中。因此过期 key 可能短时间仍占内存。
通常由主节点决定过期删除并把删除命令传播给从节点,以保证复制一致性。从节点读到过期数据时也会有相应判断,但最终的数据变更应以主节点传播为准。
volatile-lru 只在设置了过期时间的 key 中按近似 LRU 淘汰,allkeys-lru 会在所有 key 中选择候选对象。前者适合只希望临时数据被淘汰的场景,后者更像通用缓存。