Redis如何实现到期删除的?
# 答题思路
- 定时删除:在设置键的过期时间时,创建一个定时事件,当过期时间到达时,由事件处理器自动执行键的删除操作。
- 惰性删除:放任键过期不管,但是在每次从 dict 字典中取出键值时,要检查键是否过期,如果过期的话,就删除它,并返回空,否则就返回键值。
- 定期删除:每隔一段时间,对 expires 字典进行检查,删除里面的过期键。
# 回答话术
在 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 到期时间点对应的时间戳,并被记录在一个到期字典中(哈希表)。
具体可参见:数据库 — Redis 设计与实现 (opens new window)
# 2. 删除策略
按官方文档的说法,Redis 的过期删除有两种方式:
- 主动删除:每 10 秒扫描一次数据库,随机抽 20 个 key,并删除其中到期的 key。如果到期 key 占比超过 25%,那么继续抽样,直到不满足条件或超时为止;
- 被动删除:访问 key 时检查到期时间,如果已经到期就删除;
官方文档对此进行了解释:Redis 官网 -- EXPIRE 指令介绍 (opens new window)
当然,为了面试的时候有更多可说的,我们可以适当的扩展一下,介绍一下常见的几种实现方式,和它们的优缺点:
# a. 定时删除
定时删除,就是在设置 key 的到期时间时,一并设置一个定时事件,等到事件触发时删除 key。
- 优点: 可以及时释放资源,确保过期键能够被及时删除。
- 缺点: 频繁的删除操作可能会占用大量的 CPU 时间。
总的来说,这个策略对内存优化而对 CPU 不友好,在 CPU 紧张而内存宽裕的场景中,它会将更多的 CPU 资源花费到没那么紧要的删除到期 key 操作上。
综合上述考量, Redis 并没有使用这种方式。
# b. 惰性删除
惰性删除就是 Redis 提到的被动删除。
被动删除不会主动的删除到期的 key,而是当访问 key 时再检查是否到期,如果到期了再将其删除。
它的优缺点与定时删除刚好相反:
- 优点: 只在取出键时进行检查,避免了频繁的删除操作。
- 缺点: 可能会导致内存积压问题。
惰性删除对 CPU 最友好,但是对内存就不友好了。尤其是当你需要在 Redis 中存放大量具备到期时间且不需要频繁访问的数据时,会造成内存积压。
# c. 定期删除
定期删除就是 Redis 提到的主动删除。
定期删除会定时的随机从数据库中对 key 抽样(20 个),然后删除其中的过期键。如果此次抽样中,到期 key 的占比高于一定阈值(25%),则会再进行一次抽样删除,直到到期 key 占比没那么高。或者本次任务执行超时为止。
相对于定时删除和惰性删除,定期删除在内存和 CPU 消耗中取得了一个比较好的平衡。
另外,使用抽样避免全量操作的思想在 Redis 中挺常见的,比如内存淘汰策略中的近似 LRU 和 LFU,具体可以参见文档:✅ Redis常用内存淘汰策略? (opens new window)
# 3. 在持久化时
Redis 使用 AOF 与 RBD 两种方式来持久化内存中数据,这个过程同样需要考虑如何处理过期的 key:
- AOF:当 key 因为到期而被删除时,将会向 AOF 追加一条
**DEL**
命令。如果在这个过程中进行了 AOF 重写,那么重写后的 AOF 文件中则将直接忽略掉这个过期的 key。 - RDB:与 AOF 重写类似,在创建 RDB 的时候,过期的 key 会被直接忽略。
具体关于 AOF 与 RDB 相关内容,可以参见文章:✅ Redis宕机数据会丢失么? (opens new window)
# 4. 在集群中
当集群中的实例发现 key 到期后,实例会根据它自己是主节点还是从节点而采取不同的行为:
- 如果是主节点,它会在删除这个过期 key 后向所有从节点发送一个
DEL
命令。 - 如果是从节点,那么它将会将这个 key 标记为到期,但并不会真正的删除。只有当接到从主节点发来的
DEL
命令之后,才会真正的将过期键删除掉。
从节点不会主动删除 key,这是为了保证与主节点数据的一致性,以便当主从切换时后,仍然可以正常的处理过期 key。
不过当系统中有大量频繁过期的 key,且一个主节点有较多从节点的时候,这会带来更多的内存消耗。