Redis如何实现到期删除的?
作者:程序员马丁
Ragent AI —— 从 0 到 1 纯手工打造企业级 Agentic RAG,拒绝 Demo 玩具!AI 时代,助你拿个offer。
答题思路
回答话术
在 Redis 中,我们可以用 EXPIRE、PEXPIRE、EXPIREAT 和 PEXPIREAT四个命令来按毫秒或秒设置 Key 的过期时间。其中,前两者指定的是 Key 的有效时间,而后两者指定的是 Key 的到期时间点。
这些时间最终会被转换为一个时间戳并,与 Key 一一对应保存在一个到期字典中,然后 Redis 会根据 Key 在到期字典中的到期时间,通过主动和被动两种方式清理到期的 Key。
被动删除是指每次访问 Key 键时,Redis 会检查 Key 是否已到期,如果是就将其删除并返回空值。不过如果仅靠被动删除是不够的,因为如果 Key 的访问频率不高,可能会导致一些数据一直不能被删除,内存也无法得到释放,因此所以还需要定期的主动删除。
主动删除是指 Redis 会每秒主动扫描 10 次到期字典,随机抽取 20 个 Key 并删除其中已经到期的部分。然后,如果这次抽样中到期键的 Key 的比例超过 25%,就会继续抽样,直到不满足条件或超时为止。
以上两种删除机制互相配合,基本能保证 Redis 中到期键的数量不会超过总数据量的 25%。
另外,Redis 在持久化的时候也会针对到期的 Key 做额外的处理。Redis 在 AOF 的时候,如果 Key 过期了,则会向文件追加一条 DEL 指令,而如果是在 AOF 重写和 RDB 的时候,则检查并直接忽略掉过期的 Key。
最后是集群,在集群里面,当主节点发现 Key 到期时,会向所有从节点发送 DEL 命令,但是当从节点发现键到期时,只会将其标记为已删除,直到收到主节点的删除指令才会真正删除,以确保数据一致性。
问题详解
1. 到期时间
在 Redis 中,你可以使用以下四种命令为 Key 设置到期时间:
EXPIRE:以秒为单位,设置 Key 的有效时间。PEXPIRE:以毫秒为单位,设置 Key 的有效时间。EXPIREAT:以秒为单位,设置 Key 的到期时间戳。PEXPIREAT:以毫秒为单位,设置 Key 的到期时间戳。
其中,前两者指定的是 Key 的有效时长,而后两者指定的是 Key 到期时间点。
不过,在 Redis 底层实现中,四种命令最终都会变为 Key 到期时间点对应的时间戳,并被记录在一个到期字典中(哈希表)。
2. Redis 的删除策略
按官方文档的说法,Redis 的过期删除有两种方式:
- 主动删除:每 10 秒扫描一次数据库,随机抽 20 个 key,并删除其中到期的 key。如果到期 Key 占比超过 25%,那么继续抽样,直到不满足条件或超时为止;
- 被动删除:访问 Key 时检查到期时间,如果已经到期就删除;
官方文档对此进行了解释:Redis 官网 -- EXPIRE 指令介绍
当然,为了面试的时候有更多可说的,我们可以适当的扩展一下,介绍一下常见的几种实现方式,和它们的优缺点:
2.1. 定时删除
这里我们顺便提一下一个 Redis 没有使用,而非常简单粗暴的思路,那就是定时删除。
简单的来说,就是在设置 Key 的到期时间时,一并设置一个定时事件,等到事件触发时删除 key。
- 优点: 可以及时释放资源,确保过期键能够被及时删除。
- 缺点: 频繁的删除操作可能会占用大量的 CPU 时间。
总的来说,这个策略对内存优化而对 CPU 不友好,在 CPU 紧张而内存宽裕的场景中,它会将更多的 CPU 资源花费到没那么紧要的删除到期 Key 操作上。
综合上述考量, Redis 并没有使用这种方式。
2.2. 惰性删除
惰性删除就是 Redis 提到的被动删除。
被动删除不会主动的删除到期的 key,而是当访问 Key 时再检查是否到期,如果到期了再将其删除。
它的优缺点与定时删除刚好相反:
- 优点: 只在取出键时进行检查,避免了频繁的删除操作。
- 缺点: 可能会导致内存积压问题。
惰性删除对 CPU 最友好,但是对内存就不友好了。尤其是当你需要在 Redis 中存放大量具备到期时间且不需要频繁访问的数据时,会造成内存积压。
2.3. 定期删除
定期删除就是 Redis 提到的主动删除。
具体来说,整个过程如下:
- Redis 定时(取决于
hz配置,默认为 10,即每秒 10 次)依次遍历 16 个数据库:- 如果此时到期字典可用率较低,考虑到哈希冲突严重时链表可能很长,遍历需要额外的时间成本,那么将会直接跳过该数据库,等到下一次循环其重哈希以后再进行处理。
- 如果字典可用率处于正常水平,那么就依次从数据库的到期字典中对 Key 进行抽样(20 个),并删除已超时的 Key;
- 如果此次抽样中,到期 Key 的占比高于一定阈值(25%),则会再进行一次抽样删除,直到到期 Key 占比没那么高。或者本次任务执行超时为止;
- 如果此次执行超时了,那么将会记录当前处理的数据库下标,然后下次进行抽样时就直接从当前数据库开始执行,如此反复。
相对于定时删除和惰性删除,定期删除在内存和 CPU 消耗中取得了一个比较好的平衡。
另外,使用抽样避免全量操作的思想在 Redis 中挺常见的,比如内存淘汰策略中的近似 LRU 和 LFU,具体可以参见文档:✅ Redis 常用内存淘汰策略?