Redis 过期删除策略:为什么实际用惰性删除加定期删除
Redis 过期删除章节,讲清定时删除、惰性删除、定期删除的取舍,以及 Redis 如何用 expireIfNeeded、随机抽样和 25% 阈值删除过期 key。
相关工具
过期 key 不会自动消失得那么简单
在 Redis 里给 key 设置过期时间很常见。比如缓存商品详情 10 分钟、验证码 5 分钟、登录态 7 天、限流计数 1 秒。命令层面看起来很简单,用 `EXPIRE`,或者用 `SET` 命令的 `EX` 选项,都能让 key 在一段时间后过期。
但 key 到期以后,Redis 什么时候真正把它删掉?这个问题比设置过期时间更细。资料 在“Redis 过期删除”这一节说,Redis 需要有相应机制把已过期的键值对删除,而过期删除策略就是处理这件事的。
很多人会误以为时间一到,Redis 会立刻把 key 从内存里移走。事实上,如果每个 key 到期都立刻触发删除,CPU 会被大量定时事件打扰;如果完全不主动删,过期 key 又可能长期占着内存。Redis 最后的选择,是在 CPU 时间和内存浪费之间找一个折中。

Redis 理论上有定时删除、惰性删除、定期删除三种策略,实际使用惰性删除和定期删除配合:访问时检查,同时后台随机抽样清理。
定时删除:最及时,也最吃 CPU
第一种策略是定时删除。相关的描述是:设置 key 的过期时间时,同时创建一个定时事件;当时间到达时,由事件处理器自动执行 key 的删除操作。它的优点很直观,过期 key 会被尽快删除。
如果只看内存,定时删除很漂亮。key 到点就走,不会长期占内存,也不会等客户端下次访问才发现它已经过期。对于希望内存特别干净的系统来说,这种策略很有吸引力。
问题在 CPU。Redis 可能有大量 key 设置了过期时间,如果每个 key 都绑定一个定时事件,到点就触发删除,CPU 会被频繁打断。缓存系统里,过期 key 的数量和到期时间可能非常密集;过于及时的删除,反而可能影响正常读写命令的执行。资料 也明确说,它会占用一部分 CPU 时间,对 CPU 不友好。
惰性删除:访问时再判断
第二种策略是惰性删除。它不主动删除过期 key,而是在每次访问 key 时检查是否过期。如果已经过期,就删除这个 key;如果没有过期,就正常返回。文中说,这种方式使用很少的系统资源,对 CPU 友好。
惰性删除的好处是“不打扰”。没有客户端访问这个 key,Redis 就不用为它专门做事。访问发生时顺便检查一下,发现过期再删掉。对于很多冷数据来说,这种方式很省 CPU。
但它的缺点也正因为懒。如果一个 key 已经过期,却再也没有人访问它,那么它就可能长期留在内存里。这个 key 对业务已经没意义,却还占着空间。Redis 是内存系统,空间浪费堆多了,就会影响可用内存,甚至触发后续内存淘汰。
定期删除:后台随机抽查
第三种策略是定期删除。文中写到,它会每隔一段时间随机从数据库中取出一定数量的 key 检查,并删除其中的过期 key。这种方式减少了删除操作对 CPU 的影响,也减少了内存空间浪费。
它不像定时删除那样盯着每个 key 的到期时间,也不像惰性删除那样完全等访问触发。它更像后台清扫:每隔一会儿看一批 key,把已经过期的清掉。如果发现过期 key 很多,就多扫一会儿;如果过期比例不高,就停下来等下一轮。
定期删除的难点是频率和时长不好把握。扫得太频繁,会占 CPU;扫得太少,内存里过期 key 留得太久。资料 也提到,它的效果没有定时删除和惰性删除各自最擅长的那一面那么极端,而且难以确定删除操作执行的时长和频率。
Redis 的实际选择:惰性删除加定期删除
Redis 没有单独选择定时删除,也没有只靠惰性删除。文中的结论是:Redis 选择“惰性删除 + 定期删除”配合使用,以求在合理使用 CPU 时间和避免内存浪费之间取得平衡。
这个组合很自然。惰性删除负责访问路径:你来读或改某个 key,我就检查它是否已经过期,过期就删。定期删除负责后台路径:即使没人访问某些过期 key,Redis 也会隔一段时间随机抽样,主动清理一批。
两者配合以后,Redis 不需要为每个 key 创建定时事件,也不会完全放任过期 key 堆在内存里。它没有追求理论上最及时的删除,而是选择对线上服务更友好的折中。缓存系统里,稳定的读写吞吐往往比“过期一毫秒后必须立刻删除”更重要。
expireIfNeeded 做访问时检查
惰性删除在 Redis 里依赖 `expireIfNeeded` 函数。资料写到,每次返回或者修改 key 之前,都会调用这个函数检查 key 是否过期。如果过期,就删除该 key,然后给客户端返回 null;如果没有过期,就返回正常的键值。
这意味着过期判断不只发生在读取时。只要操作需要返回或修改这个 key,Redis 都要先确认它是否还有效。否则,一个已经过期的 key 可能被错误读取,甚至被继续修改,语义就乱了。
同时提到 `lazyfree_lazy_expire` 参数,它决定删除过期 key 时是同步删除还是异步删除。同步删除更直接,但大对象删除可能阻塞;异步删除可以把释放内存的工作交给后台,减少主线程压力。这个细节说明,删除本身也可能是一件有成本的事。
定期删除不是全量扫描
很多初学者会把定期删除理解成“Redis 每隔一段时间扫完整个数据库”。这其实不现实。Redis 可能有几百万、几千万个 key,如果每轮都全量扫描,CPU 会被拖垮,正常请求也会受影响。
文中写得很具体:定期删除每隔一段时间随机从数据库取出一定数量的 key 进行检查,并删除其中已过期的 key。默认每隔 10 秒检查一次数据库,对应配置键是 `hz: 10`。随机抽查数量是 20 个 key。
随机抽样的思路是用较小成本估计过期 key 的密度。如果一批样本里过期比例很高,说明当前数据库可能堆了很多过期 key,就继续清理;如果比例不高,就先停下来。这样 Redis 不需要每次都扫全库,也能逐步把过期数据清掉。
25% 阈值决定是否继续清理
资料 给了定期删除的具体流程。第一步,从过期字典中随机抽取 20 个 key。第二步,检查这 20 个 key 是否过期,并删除已经过期的 key。第三步,看本轮检查中过期 key 的数量。
如果本轮过期 key 超过 5 个,也就是超过 20 个样本的 25%,Redis 会继续重复抽查流程。这个判断很朴素:随机抽 20 个里有超过 5 个过期,说明过期 key 密度可能偏高,继续清理是值得的。
如果过期比例小于 25%,Redis 就停止继续删除,等待下一轮检查。这就是 Redis 在 CPU 和内存之间做平衡的地方。过期 key 多时多干点活,过期 key 少时别硬扫。它不是精确地知道数据库里还有多少过期 key,而是用抽样结果做判断。
过期删除和内存淘汰不是一回事
资料 在过期删除后紧接着讲 Redis 内存淘汰,说明两者相关,但不要混为一谈。过期删除处理的是“这个 key 已经过了设置的有效期,应该移除”。内存淘汰处理的是“Redis 内存达到 maxmemory,需要释放空间”。
一个 key 可以没有过期时间,但在内存不足时被淘汰;一个 key 可以已经过期,但还没被惰性或定期删除清掉。前者是容量压力驱动,后者是时间语义驱动。理解这个区别很重要,否则很容易把缓存失效、内存淘汰、业务数据消失揉成一团。
这篇只讲过期删除。下一步讲内存淘汰时,会看到 NoEviction、VolatileTTL、LRU、LFU、Random 等策略。它们解决的是 Redis 内存不够时删谁的问题,不是 key 到期后怎么删的问题。
业务上怎么设置过期时间
知道 Redis 怎么删过期 key 后,回到业务里,设置过期时间也要谨慎。缓存过期时间不是越短越好。太短会导致频繁回源,数据库压力上升;太长则可能让旧数据停留太久,影响用户看到的数据新鲜度。
对于验证码、临时 token、限流计数这类有明确时间语义的数据,过期时间应该跟业务规则一致。对于商品、文章、配置这类缓存,过期时间要结合数据更新频率、回源成本和允许的陈旧窗口来定。
还要避免大量 key 在同一时间过期。后文讲缓存雪崩时也提到,如果大量数据过期时间设置相近,可能在某个时间点同时失效,数据库压力会突然升高。给缓存过期时间加一点随机扰动,是很常见也很有效的做法。
面试可以这样回答
如果被问 Redis 过期删除策略,可以先说理论上有三种:定时删除、惰性删除、定期删除。定时删除最及时,但会占用 CPU;惰性删除访问时才检查,CPU 友好,但过期 key 可能长期占内存;定期删除随机抽查一部分 key,介于两者之间。
然后说 Redis 的实际选择:Redis 使用惰性删除和定期删除配合。访问 key 时,会通过 `expireIfNeeded` 检查是否过期,过期则删除并返回 null;后台定期从过期字典随机抽样检查,默认 `hz: 10`,一次抽 20 个 key。
最后补上定期删除流程:如果这一轮 20 个 key 里过期数量超过 5 个,也就是比例超过 25%,就继续抽查;如果低于 25%,就停止本轮清理,等下一轮。这样回答能把“为什么不用定时删除”和“Redis 实际怎么做”都讲到。
常见问题
Redis 过期 key 会立刻删除吗?
不一定。Redis 主要使用惰性删除和定期删除。访问 key 时会检查是否过期,后台也会随机抽样清理,但不会为每个 key 都创建定时事件保证到期瞬间删除。
惰性删除有什么缺点?
如果某个 key 已经过期但再也没有被访问,它可能长期留在内存里,造成内存浪费,所以 Redis 还要配合定期删除。
Redis 定期删除默认怎么抽样?
文中提到默认每隔 10 秒检查一次数据库,随机抽取 20 个 key,删除其中过期的 key。如果过期比例超过 25%,会继续重复抽查。
过期删除和内存淘汰有什么区别?
过期删除按 key 的过期时间清理数据;内存淘汰是在 Redis 达到 maxmemory 等内存限制时,根据淘汰策略删除部分 key 来释放空间。