目录

一 过期删除策略

1.1 定时删除

1.2 惰性删除

1.3 定期删除

二 Redis 的过期删除策略

2.1 惰性删除策略的实现

2.2 定期删除策略的实现

三 AOF、RDB 和复制功能对过期键的处理

3.1 生成 RDB 文件

3.2 载入 RDB 文件

3.3 AOF 文件写入

3.4 AOF 重写

3.5 复制


对 Redis 过期键删除策略一直懵懵懂懂、一知半解,今天有时间就整理一下,加深一下自己的印象,同时也希望能帮助其他小伙伴,废话不多说,直接上干货~


一 过期删除策略

Redis 数据库键的过期时间都保存在过期字典中,根据过期时间判断一个键是否过期,如果一个键过期了,那么它是什么时候会被删除呢?

Redis 数据库有三种不同的删除策略:

定时删除:在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作。

惰性删除:放任过期键不管,但是每次从键空间汇总获取键时,都检查取得的键是否过期,如果过期的话就删除该键;如果没有过期就返回该键。

定期删除:每个一段时间,程序就会对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,有算法决定。

1.1 定时删除

优点:定时删除策略对内存是最友好的,通过使用定时器,定时删除策略可以保证过期键可以尽可能快的被删除,并释放过期键所占用的内存;

缺点:定时删除策略对CPU时间是最不友好的,在过期键比较多的情况下,删除过期键这一行为可能会占用相当一部分CPU时间,在内存不紧张但CPU时间非常紧张的情况下,将CPU时间用在删除和当前无关的过期键上,无疑是对服务器的响应时间和吞吐量造成影响。

例如,如果正有大量的命令请求在等待服务器处理,并且服务器当前不缺少内存,那么服务器应该优先将CPU时间用在处理客户端的命令请求上,而不是用在删除过期键上。

此外,创建一个定时器需要用到 Redis 服务器中的时间事件,而当前时间事件的实现方式——无序链表,查找一个事件的时间复杂度为O(N)——并不能高效地处理大量时间事件。

因此,要让服务器创建大量的定时器,从而实现定时删除策略,在现阶段来说并不现实。

1.2 惰性删除

优点:惰性删除策略对CPU时间来说是最友好的,程序只会在取出键时才对键进行过期检查,这可以保证删除过期键操作只会在非做不可的情况下进行,并且删除的目标仅限于当前处理的键,这个策略不会在删除其他无关的过期键上花费任何CPU时间。

缺点:惰性删除策略对内存是最不友好的,如果一个键已经过期,而这个键又仍然保留在数据库中,那么只要这个过期键被删除,它锁占用的内存就不会释放。

在使用惰性删除策略时,如果数据库中有非常多的过期键,而这些过期键又恰好没有被访问到的话,那么它们也许不会被删除(除非手动执行FLUSHDB),我们甚至可以将这种情况看做是一种内存泄漏——无用的垃圾数据占用了大量的内存,而服务器却不会自己去释放它们,这对于运行状态非常依赖内存的 Redis 服务器来说,肯定不是一个好消息。

例如,对于一些和时间有关的数据,比例日志(log),在某个时间点之后,对它们的访问就会大大的减少,甚至不再访问,如果这类过期数据大量地积压在数据库中,用户以为服务器已经自动将它们删除了,但实际上这些键仍然存在,而且键所占用的内存也没有释放,那么造成的后果肯定是非常严重的。

1.3 定期删除

从上面两种删除策略来看,在单一使用时都有明显的缺陷:

  • 定时删除占用太多CPU时间,影响服务器的响应时间和吞吐量。
  • 惰性删除浪费太多内存,有内存泄漏的危险。

定期删除策略是前两种策略的一种整合和折中:

定期删除策略是每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。

此外,通过定期删除过期键,定时删除策略有效地减少了因为过期键而带来的内存浪费。

定期删除策略的难点是如何确定删除操作执行的时长和频率:

  • 如果删除操作执行的太频繁,或者执行的时间太长,定期删除策略就会退化成定时删除策略,以至于将CPU时间过多的消耗在删除过期键上面。
  • 如果删除操作执行的太少,或者执行的时间太短,定期删除策略就会和惰性删除策略一样,出现内存浪费的情况。

因此,采用定期删除策略的话,服务器必须根据情况,合理地设置删除操作执行时长和执行频率。

定时删除和定期删除为主动删除策略,而惰性删除则为被动删除策略。


二 Redis 的过期删除策略

上面介绍了定时删除、惰性删除和定期删除三种过期键删除策略,Redis服务器实际使用的是惰性删除和定期删除两种策略:通过配合使用这两种删除策略,服务器可以很好的在合理使用CPU时间和避免浪费内存空间之间取得平衡。

2.1 惰性删除策略的实现

过期键的惰性删除策略由 db.c/expireIfNeeded 函数实现,所有读写数据库的 Redis 命令在执行之前都会调用 expireIfNeeded 函数对输入键进行检查:

  • 如果输入键已经过期,那么 expireIfNeeded 函数将输入键从数据库中删除。
  • 如果输入键没有过期,那么 expireIfNeeded 函数不做动作。

命令调用 expireIfNeeded 函数过程如图 2-1-1 所示。

expireIfNeeded 函数就像一个过滤器,它可以在命令真正执行之前,过滤掉过期的输入键,从而避免命令接触到过期键。

redis 键过期没有被删除 redis过期键的删除策略_redis

另外,因为每个被访问的键可能因为过期而被 expireIfNeeded 函数删除,所以每个命令的实现都必须同时处理键存在以及键不存在的两种情况:

  • 当键存在时,命令按照键存在的情况执行。
  • 当键不存在或者键因过期而被 expireIfNeeded 函数删除时,命令按照键不存在的情况执行

例如,图 2-1-2 展示了GET命令的执行过程,在这个执行过程中,命令需要判断件是否存在以及键是否过期,然后根据判断来执行合适的动作。

2.2 定期删除策略的实现

过期键的定期删除策略由 redis.c/activeExpireCycle 函数实现,每当 Redis 的服务器周期性操作 redis.c/serverCron 函数执行时,activeExpireCycle 函数就会被调用,它在规定的时间内,分多次遍历服务器中的各个数据库,从数据库的 expires 字典中随机检查一部分键的过期时间,并删除其中的过期键。

activeExpireCycle 函数的工作模式可以总结如下:

  • 函数每次运行时,都会从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中的过期键。
  • 全局变量 current_db 会记录当前 activeExpireCycle 函数检查的进度,并在下一次 activeExpireCycle 函数调用时,接着上一次的进度进行处理。比如说如果当前 activeExpireCycle 函数在遍历10号数据库时返回了,那么下次 activeExpireCycle 函数执行时,将从11号数据库开始查找并删除过期键。
  • 随着 activeExpireCycle 函数的不断执行,服务器中的所有数据库都会被检查一遍,这时函数将 current_db 变量重置为0,然后再次开始新一轮的检查工作。

三 AOF、RDB 和复制功能对过期键的处理

接下来我们来了解一下 RDB 持久化功能、AOF 持久化功能以及复制功能是如何处理数据库中的过期键的。

3.1 生成 RDB 文件

在执行 SAVE 命令或者 BGSAVE 命令创建一个新的 RDB 文件时,程序会对数据库中的键进行检查,已过期的键不会被保存到新创建的 RDB文 件中。

比如:如果数据库中包含三个键 k1、k2、k3,并且k2已经过期,那么执行SAVE命令或者BGSAVE命令时,程序只会将 k1 和 k3 的数据保存到 RDB 文件中,而 k2 则被忽略。

因此,数据库中的过期键不会对生成新的 RDB 文件造成影响。

3.2 载入 RDB 文件

在启动 Redis 服务器时,如果服务器开启了 RDB 功能,那么服务器将对 RDB 文件进行载入:

  • 如果服务器以主服务器模式运行,那么在载入 RDB 文件时,程序会对文件中保存的键进行检查,未过期的键会被载入到数据库中,而过期键则会被忽略,所以过期键对载入 RDB 文件的主服务器不会造成影响。
  • 如果服务器以从服务器模式运行,那么在载入 RDB 文件时,文件中保存的所有键,不论是否过期,都会被载入到数据库中。不过,因为主从服务器在进行数据同步的时候,从服务器的数据库就会被清空,所以一般来说,过期键在载入 RDB 文件的从服务器也不会造成影响。

比如:如果数据库中包含三个键 k1、k2、k3,并且k2已经过期,那么当服务器启动时:

  • 如果服务器以主服务器模式运行,那么程序只会将 k1 和 k3 载入到数据库,k2 会被忽略。
  • 如果服务器以从服务器模式运行,那么 k1、k2 和 k3 都会被载入到数据库。

3.3 AOF 文件写入

当服务器以 AOF 持久化模式运行时,如果数据库中的某个键已经过期,但它还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键而产生任何影响。

当过期键被惰性删除或者定期删除之后,程序会向 AOF 文件追加(append)一个 DEL 命令,来显式地记录该键已被删除。

比如:如果客户端使用 GET message 命令,试图访问过期的 message 键,那么服务器将执行一下三个动作:

  1. 从数据中删除 message 键;
  2. 追加一条 DEL message 命令到 AOF 文件;
  3. 向执行 GET 命令的客户端返回空回复。

3.4 AOF 重写

和生成 RDB 文件时类似,在执行 AOF 重写的过程中,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的 AOF 文件中。

比如:如果数据库中包含三个键 k1、k2、k3,并且k2已经过期,那么在进行重写工作时,程序只会对 k1 和 k3 进行重写,而 k2 则被忽略。

因此,数据库中包含过期键不会对 AOF 重写造成影响。

3.5 复制

当服务器运行在复制模式下时,从服务器的过期键删除动作由主服务器控制:

  • 主服务器在删除一个过期键之后,会显式地向所有从服务器发送一个 DEL 命令,告知从服务器删除这个过期键。
  • 从服务器在执行客户端发送的读命令时,即使碰到过期键也不会将过期键删除,而是继续像处理未过期的键一样来处理过期键。
  • 从服务器只有在接到主服务器发来的 DEL 命令之后,才会删除过期键。

通过由主服务器来控制从服务器统一地删除过期键,可以保证主从服务器数据的一致性,也正是因为这个原因,当一个过期键仍然存在于主服务器的数据库时,这个过期键在从服务器里的复制品也会继续存在

比如:有一对主从服务器,它们的数据库中都保存着同样的三个键 message、xxx 和 yyy,其中 message 为过期键,如图 3-5-1 所示。

redis 键过期没有被删除 redis过期键的删除策略_Redis_02

如果这时有客户端向从服务器发送命令 GET message ,那么从服务器将发现 message 键已过期,但是从服务器并不会删除 message 键,而是继续将 message 键的值返回给客户端,就像 message 键并没有过期一样。如图 3-5-2 所示。

假设在此之后,有客户端向主服务器发送命令 GET message,那么主服务器将发现键 message 已经过期:主服务器会删除 message 键,向客户端返回空回复,并向从服务器发送 DEL message 命令,如图 3-5-3 所示。

从服务器在接收到主服务器发来的 DEL message 命令后,也会从数据库中删除 message 键,在这之后,主从服务器都不再保存过期键 message 了,如图 3-5-4 所示。

redis 键过期没有被删除 redis过期键的删除策略_数据库_03


参考:《Redis设计与实现》  黄建宏 著