为什么 Redis 不立刻删除已经过期的数据?

举报
Rolle 发表于 2023/11/30 19:57:20 2023/11/30
【摘要】 实现过期机制的一般思路从系统设计的角度来说,过期之类的机制可以考虑使用四种思路来实现。定时删除:是指针对每一个需要被删除的对象启动一个计时器,到期之后直接删除。延迟队列:也就是把对象放到一个延迟队列里面。当从队列里取出这个对象的时候,就说明它已经过期了,这时候就可以删除。懒惰删除:是指每次要使用对象的时候,检查一下这个对象是不是已经过期了。如果已经过期了,那么直接删除。定期删除:是指每隔一...

实现过期机制的一般思路

从系统设计的角度来说,过期之类的机制可以考虑使用四种思路来实现。

  • 定时删除:是指针对每一个需要被删除的对象启动一个计时器,到期之后直接删除。
  • 延迟队列:也就是把对象放到一个延迟队列里面。当从队列里取出这个对象的时候,就说明它已经过期了,这时候就可以删除。
  • 懒惰删除:是指每次要使用对象的时候,检查一下这个对象是不是已经过期了。如果已经过期了,那么直接删除。
  • 定期删除:是指每隔一段时间就遍历对象,找到已经过期的对象删除掉。针对这四种思路的优缺点,你可以参考下面的表格。

image.png

大部分的缓存框架,比如 Redis,它们都使用了懒惰删除和定期删除结合的策略。定时删除和延迟队列对于缓存这种场景来说,性能太差。

Redis 的过期删除机制简单来说就是懒惰删除和定期删除。懒惰删除是指 Redis 会在查询 key 的时候检测这个 key 是否已经过期,如果已经过期,那么 Redis 就会顺手删除这个 key。

单纯使用懒惰删除肯定是不行的,因为一个 key 过期之后,可能一直没有被使用过。所以 Redis 结合了定期删除策略。Redis 每运行一段时间,就会随机挑选出一部分 key,查看是否过期,如果已经过期了,就把 key 删除掉。Redis 的定期删除要比我这里讲的复杂很多,毕竟 Redis 是一个追求高性能的中间件,所以肯定要有复杂的机制控制住定期删除的开销。

为什么不立刻删除?

答案就是做不到,或者即便能做到,代价也太高。

最简单的做法就是每一个 key 启动一个定时器,到时间了就删掉。但是这里会有 2 个问题。key 太多了,一个 key 一个计时器,Redis 承受不住那么大的计时器开销。修改过期时间的时候,要重置计时器的时间,这会进一步带来额外的开销。

那么还有一种思路就是把所有的 key 额外再按照过期时间组一个延迟队列,排在最前面的就是最近要过期的。不过这个思路也有 3 个问题。

  1. 延迟队列的本身开销很大,尤其是在 key 很多的情况下。
  2. 修改过期时间需要调整延迟队列中各个 key 的顺序。
  3. 延迟队列一般需要一个线程配合使用,如果引入这个线程,那么 Redis 就需要做更多并发控制,性能会下降。

并不是做不到,只不过代价比较高昂不值得而已。

Redis 是怎么控制定期删除开销的?

假如说现在 Redis 有 100 万 key,那么显然 Redis 在定期删除过期 key 的时候,是不可能遍历完这 100 万个 key 的。而 Redis 也确实没有遍历全部的 key,简单来说 Redis 会在每一个循环中遍历 DB。如果当次定期删除循环没有遍历完全部 DB,那么下一个循环就会从当次最后遍历的 DB 的下一个继续遍历下去。

针对每一个 DB,都会有这样一个步骤。

  1. 如果 DB 里存放的 key 都没有设置过期时间,那么遍历下一个 DB。
  2. 从设置了过期时间的 key 中抽一批,默认一批是 25 个。
  3. 逐个检查这些 key。如果这个 key 已经过期了,那么执行删除操作。
  4. 每遍历 16 个 key,就检测执行时间。如果执行时间已经超过了阈值,那么就中断这一次定期删除循环。
  5. 如果这一批过期的 key 比例超过一个阈值,那么就抽取下一批 key 来检查,这个阈值也是可以通过参数来控制的。

总结

  • 在每一个定期删除循环中,Redis 会遍历 DB。如果这个 DB 完全没有设置了过期时间的 key,那就直接跳过。否则就针对这个 DB 抽一批 key,如果 key 已经过期,就直接删除。
  • 如果在这一批 key 里面,过期的比例太低,那么就会中断循环,遍历下一个 DB。如果执行时间超过了阈值,也会中断。不过这个中断是整个中断,下一次定期删除的时候会从当前 DB 的下一个继续遍历。
  • 总的来说,Redis 是通过控制执行定期删除循环时间来控制开销,这样可以在服务正常请求和清理过期 key 之间取得平衡。

为什么要随机抽样,同一个 DB 内按照顺序遍历下去不就可以吗 ?

  • 确保每个 key 都能遍历到。
  • 随机只是为了保证每个 key 都有一定概率被抽查到。假设说我们在每个 DB 内部都是从头遍历的话,那么如果每次遍历到中间,就没时间了,那么 DB 后面的 key 你可能永远也遍历不到。

如何控制定期删除的频率?

在 Redis 里面,定期删除的频率可以通过 hz 参数来控制。不过 hz 控制的是所有的后台任务,并不是单独控制这一个定期删除循环。

假如说 hz 的值是 N,那么就意味着每 1/N 秒就会执行一次后台任务。举例来说,如果 hz=10,那么就意味着每 100ms 执行一次后台任务。

正常来说,Redis 这个值不要超过 100。越大就意味着后台任务执行的频率越高,CPU 使用率越高。

从库和主库的区别是,主库发现 key 过期后会执行删除操作。但是从库不会,从库会等待主库的删除命令。从库上的懒惰删除特性和主库不一样。主库上的懒惰删除是在发现 key 已经过期之后,就直接删除了。但是在从库上,即便 key 已经过期了,它也不会删除,只是会给你返回一个 NULL 值。

持久化处理过期 key

Redis 里面有两种持久化文件,RDB 和 AOF。

RDB 简单来说就是快照文件,也就是当 Redis 执行 SAVE 或者 BGSAVE 命令的时候,就会把内存里的所有数据都写入 RDB 文件里。后续主库可以载入这个文件来恢复数据,从库也可以利用这个文件来完成数据同步。对于 RDB 来说,一句话总结就是主库不读不写,从库原封不动。

也就是说,在生成 RDB 的时候,主库会忽略已经过期的 key。在主库加载 RDB 的时候,也会忽略 RDB 中已经过期的 key。而从库则是整个 RDB 都加载进来,因为从库在加载完 RDB 之后,很快就能从主库里面收到删除的指令,从而删除这个过期的 key。

AOF 是之前我们就提到过的 Append Only File。Redis 用这个文件来逐条记录执行的修改数据的命令。不管 Redis 是定期删除,还是懒惰删除过期 key,Redis 都会记录一条 DEL 命令。

因为每一条修改命令都要记录,所以 AOF 就会很大。这时候 Redis 就会考虑重写整个 AOF,也就是直接把整个内存中的数据写下来,写完就可以把之前的 AOF 文件都删了。在重写过程中,Redis 会忽略已经过期的 key。

摘抄极客时间

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。