Redis的内存淘汰策略和过期删除策略的区别

导读 本篇文章主要介绍了Redis的相关知识,文章主要介绍了Redis的内存淘汰策略和过期删除策略的区别,.....

推荐学习:Redis视频教程

前言

Redis 是可以对 key 设置过期时间,因此需要有相应的机制删除过期键值,而这项工作是过期键值删除策略。

Redis 的「内存淘汰策略」和「过期删除策略」,很多小伙伴很容易混淆。虽然这两种机制都是删除的,但触发的条件和策略是不同的。

今天就跟大家说一下,「内存淘汰策略」和「过期删除策略」。


过期删除策略

Redis 是可以对 key 设置过期时间,因此需要有相应的机制删除过期键值,而这项工作是过期键值删除策略。

过期时间如何设置?

先说一下对 key 设置过期时间的命令。设置过期时间的命令。 key 过期时间的命令共有 4 个:

  • expire <key> <n>:设置 key 在 n 秒后过期,例如 expire key 100 表示设置 key 在 100 秒后过期;
  • pexpire <key> <n>:设置 key 在 n 毫秒后过期,例如 pexpire key2 100000 表示设置 key2 在 100000 毫秒(100 秒)过期。
  • expireat <key> <n>:设置 key 戳(精确到秒)后过期,如 expireat key3 1655654400 表示 key3 在时间戳 1655654400 过期(精确到秒);
  • pexpireat <key> <n>:设置 key 戳(精确到毫秒)后过期,如 pexpireat key4 1655654400000 表示 key4 在时间戳 1655654400000 后期过期(精确到毫秒)

当然,也可以同时设置字符串 key 设置过期时间,共享 3 种命令:

  • set <key> <value> ex <n> :设置键值对时,指定过期时间(精确到秒);
  • set <key> <value> px <n> :设置键值对时,指定过期时间(精确到毫秒);
  • setex <key> <n> <valule> :设置键值对时,指定过期时间(精确到秒)。

假如你想看一个 key 剩余的生存时间可以使用 TTL <key> 命令。

# 设置键值对时,指定过期时间位 60 秒 > setex key1 60 value1 OK # 查看 key1 过期时间还剩多少? > ttl key1 (integer) 56 > ttl key1 (integer) 52

若突然反悔,取消 key 可使用过期时间 PERSIST <key> 命令。

# 取消 key1 的过期时间 > persist key1 (integer) 1  # 使用完 persist 命令之后, # 查下 key1 存活时间的结果是 -1,表明 key1 永不过期  > ttl key1  (integer) -1

如何判定 key 已过期了?

每当我们对一个 key 设置过期时间,Redis 会把该 key 带过期时间存储在过期字典中(expires dict)也就是说「过期字典」数据库中的所有保存 key 过期时间。

存储过期字典 redisDb 结构如下:

typedef struct redisDb {     dict *dict;    /* 数据库键空间存储所有键对 */     dict *expires; /* 键的过期时间 */     ... } redisDb;

过期字典数据结构如下:

  • 过期字典的 key 指向某个键对象的指针;
  • 过期字典的 value 是一个 long long 保存了该类型的整数 key 过期时间;

过期字典的数据结构如下图所示:

字典实际上是哈希表。哈希表最大的优点是我们可以使用它 O(1) 快速搜索时间复杂性。当我们查询一个时 key 时,Redis 首先检查该 key 过期字典中是否存在:

  • 若不在,则正常读取键值;
  • 如果存在,它将被获得 key 的过期时间,然后与当前系统时间进行比对,如果比系统时间大,那就没有过期,否则判定该 key 已过期。

过期键判断过程如下图所示:


过期删除策略有哪些?

在说 Redis 在过期删除策略之前,先介绍三种常见的过期删除策略:

  • 定时删除;
  • 惰性删除;
  • 定期删除;

其次,分析它们的优缺点。

定期删除策略是什么?

定时删除策略的做法是,在设置 key 当时间到达时,事件处理器将自动执行过期时间 key 删除操作。

定期删除策略的优点:能保证过期 key 尽快删除,即内存可以尽快释放。因此,定期删除对内存最友好。

定时删除策略的缺点:在过期 key 在更多的情况下,删除过期 key 它可能占据相当大一部分 CPU 时间,内存不紧张,但内存不紧张 CPU 在时间紧张的情况下, CPU 时间用于删除与当前任务无关的过期键,无疑会影响服务器的响应时间和吞吐量。因此,定期删除策略对 CPU 不友好。

惰性删除策略是什么?惯性删除策略的做法是不主动删除过期键,每次访问数据库 key 时,都检测 key 是否过期,如果过期,删除 key。

惰性删除策略的优点:因为每次访问都会检查 key 这个策略是否过期只会使用很少的系统资源,所以惰性删除策略对 CPU 时间最友好。

惰性删除策略的缺点:如果一个 key 这个已经过期了 key 它仍然保留在数据库中,只要过期 key 如果没有被访问,它占用的内存就不会被释放,造成一定的内存空间浪费。因此,惰性删除策略对内存不友好。

定期删除策略是什么?定期删除策略的做法是每隔一段时间「随机」从数据库中取出一定数量 key 检查并删除过期key。

定期删除策略的优点:减少删除操作对 CPU 过期键对空间的无效占用也可以删除部分过期数据。

定期删除策略的缺点:

  • 内存清理效果不如定期删除,没有惰性删除的系统资源少。
  • 很难确定删除操作执行的长度和频率。如果执行过于频繁,定期删除策略与定期删除策略相同CPU不友好;如果执行太少,就像惯性删除一样,过期 key 占用的内存不会及时释放。

Redis 什么是过期删除策略?

前面介绍了三种过期删除策略,每一种都有优缺点,只使用一种策略不能满足实际需要。

所以, Redis 选择「惰性删除 定期删除」合理使用这两种策略 CPU 平衡时间,避免内存浪费。

Redis 惰性删除是如何实现的?

Redis 惰性删除策略由 db.c 文件中的 expireIfNeeded 代码如下:

int expireIfNeeded(redisDb *db, robj *key) {     // 判断 key 是否过期     if (!keyIsExpired(db,key)) return 0;     ...     /* 删除过期键 */     ...     // 如果 server.lazyfree_lazy_expire 为 1 表示异步删除,相反,同步删除;     return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :                                          dbSyncDelete(db,key); }

Redis 访问或修改 key 之前,都会调用 expireIfNeeded 检查函数,检查函数 key 是否过期:

  • 若过期,则删除 key,至于异步删除或同步删除,根据lazyfree_lazy_expire 参数配置决定(Redis 4.0版本开始提供参数),然后返回 null 给客服端;
  • 若未过期,不做任何处理,再将正常键值返回客户端;

惰性删除的流程图如下:

Redis 如何定期删除?

回忆一下定期删除策略的做法:每隔一段时间「随机」从数据库中取出一定数量 key 检查并删除过期key。

1.这个间隔检查需要多长时间?

在 Redis 默认每秒进行 10 通过对数据库的过期检查 Redis 的配置文件 redis.conf 配置键为 hz 它的默认值是 hz 10。

特别强调的是,每次检查数据库都不是过期字典中的全部 key,相反,从数据库中随机抽取一定数量 key 过期检查。

2.随机抽查的数量是多少?

我查了下源码,实现了定期删除 expire.c 文件下的 activeExpireCycle 在函数中,随机抽查的数量是由 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 定义,它写在代码中,值是 20。

也就是说,当数据库每轮抽查时,会随机选择 20 个 key 判断是否过期。

接下来,详细说说 Redis 定期删除流程:

  • 从过期字典中随机抽取 20 个 key;
  • 检查这 20 个 key 是否过期并删除过期 key;
  • 如果本轮检查已过期 key 超过 5 (20/4),即「已过期 key 的数量」占比「随机抽取 key 的数量」大于 25%,则继续重复步骤 一、过期的 key 比例小于 25%停止删除过期 key,然后等下一轮再检查。

定期删除是一个循环过程。

那 Redis 为确保定期删除不会出现过度循环,导致线程卡死,增加了定期删除循环过程的时间上限,默认情况下不会超过 25ms。

对于定期删除的流程,我写了一个伪代码:

do {     //过期数量     expired = 0;     //随机抽取的数量     num = 20;     while (num--) {         //1. 从过期字典中随机抽取 1 个 key         //2. 判断该 key 是否过期,如果已过期则进行删除,同时对 expired       }      // 超过时限的退出     if (timelimit_exit) return;    /* 如果本轮检查已过期 key 超过 25%,继续随机抽查,否则退出本轮检查 */ } while (expired > 20/4);

定期删除的过程如下:


内存淘汰策略

上述过期删除策略是删除过期策略 key,而当 Redis 运行内存已超过 Redis 设置最大内存后,将使用内存淘汰策略删除合格内存 key,以此来保障 Redis 高效运行。

如何设置 Redis 最大运行内存?

在配置文件 redis.conf 参数可以通过中间 maxmemory <bytes> 设置最大运行内存,只有在 Redis 只有当运行内存达到我们设置的最大运行内存时,才能触发内存淘汰策略。

不同位数的操作系统,maxmemory 默认值不同:

  • 在 64 在位操作系统中,maxmemory 的默认值是 0.无论用户存储多少数据,都意味着没有内存大小限制。 Redis 中,Redis 在检查可用内存之前,直到 Redis 例子因内存不足而崩溃。
  • 在 32 在位操作系统中,maxmemory 的默认值是 3G,因为 32 最大只支持位机 4GB 系统本身需要一定的内存资源来支持运行,因此 32 位操作系统限制最大 3 GB 可用内存非常合理,可以避免内存不足造成的问题 Redis 实例崩溃。

Redis 内存淘汰策略有哪些?

Redis 内存淘汰策略有八种,一般分为八种「不淘汰数据」和「淘汰数据」两类策略。

1.不淘汰数据的策略

noeviction(Redis3.0后,默认内存淘汰策略) :这意味着当运行内存超过最大设置内存时,不会消除任何数据,而是不再提供服务,直接返回错误。

2.淘汰数据的策略

针对「淘汰数据」这种策略可以细分为「淘汰设置过期时间的数据」和「淘汰所有数据」这两种策略。

淘汰设置过期时间的数据:

  • volatile-random:设置过期时间的任何键值;
  • volatile-ttl:优先淘汰更早过期的键值。
  • volatile-lru(Redis3.0 以前,默认内存淘汰策略):淘汰所有设置过期时间的键值中最长未使用的键值;
  • volatile-lfu(Redis 4.0 新的内存淘汰策略):淘汰所有设置过期时间的键值中最少使用的键值;

淘汰所有数据:

  • allkeys-random:随机淘汰任意键值;
  • allkeys-lru:淘汰整个键值中最长未使用的键值;
  • allkeys-lfu(Redis 4.0 新的内存淘汰策略):淘汰整个键值中最少使用的键值。

如何查看当前 Redis 使用的内存淘汰策略?

可以使用 config get maxmemory-policy 命令,查看当前 Redis 内存淘汰策略,命令如下:

127.0.0.1:6379> config get maxmemory-policy 1) "maxmemory-policy" 2) "noeviction"

可见,当前 Redis 使用的是 noeviction 内存淘汰策略的类型,是 Redis 3.0 默认使用的内存淘汰策略表明,当运行内存超过最大设置内存时,不会淘汰任何数据,但新操作会报告错误。

如何修改 Redis 内存淘汰策略?

有两种方法可以设置内存淘汰策略:

  • 方式一:通过“config set maxmemory-policy <策略>命令设置。其优点是设置后立即生效,无需重启 Redis 服务,缺点是重启 Redis 之后,设置将失效。
  • 方式二:通过修改 Redis 修改配置文件,设置maxmemory-policy <策略>它的优点是重启 Redis 服务后配置不会丢失,缺点是必须重启 Redis 只有设置服务才能生效。

LRU 算法和 LFU 算法有什么区别?

LFU 内存淘汰算法是 Redis 4.0 之后增加了内存淘汰策略,那么为什么要增加这个算法呢?这一定是为了解决它 LRU 算法问题。

接下来,让我们来看看这两种算法的区别。Redis 这两种算法是如何实现的?

什么是 LRU 算法?

LRU 全称是 Least Recently Used 最近最少使用的翻译将选择淘汰最近最少使用的数据。

传统 LRU 基于算法的实现「链表」结构,链表中的元素按操作顺序从前到后排列,最新的操作键将移动到表头。当需要内存消除时,只需删除链表尾部的元素,因为链表尾部的元素代表最长的未使用元素。

Redis 这种方法还没有实现。 LRU 因为传统的算法 LRU 算法存在两个问题:

  • 所有缓存数据都需要链表管理,这将带来额外的空间开支;
  • 当数据被访问时,数据需要移动到链表的末端。如果有大量的数据被访问,它将带来大量的链表移动操作,这将非常耗时,然后减少 Redis 缓存性能。

Redis 是如何实现 LRU 算法的?

Redis 实现的是一种相似性 LRU 算法的目的是为了更好地节省内存实现方法是 Redis 在对象结构中添加一个额外的字段来记录数据的最后一次访问时间。

当 Redis 当内存被淘汰时,数据将通过随机采样被淘汰。它是随机采集的 5 个值(这个值可以配置),然后淘汰最使用的。

Redis 实现的 LRU 算法的优点:

  • 大链表不需要维护所有数据,节省空间占用;
  • 每次数据访问都不需要移动链表项,以提高缓存性能;

但是 LRU 算法有一个问题,不能解决缓存污染问题。例如,应用程序一次读取大量数据,而这些数据只读取一次,因此这些数据将保留 Redis 长期缓存会造成缓存污染。

因此,在 Redis 4.0 之后引入了 LFU 解决这个问题的算法。

什么是 LFU 算法?

LFU 全称是 Least Frequently Used 最近最不常用的翻译,LFU 算法根据数据访问次数淘汰数据,其核心思想是如果数据在过去被访问过很多次,那么未来被访问的频率会更高。

所以, LFU 算法记录每个数据的访问次数。当数据再次访问时,它会增加添加此数据的访问次数。这解决了数据在缓存中长期保留的问题。 LRU 算法也更合理。

Redis 是如何实现 LFU 算法的?

LFU 算法相比于 LRU 算法的实现记录了更多「访问数据的频率」的信息。Redis 对象的结构如下:

typedef struct redisObject {     ...      // 24 bits,记录对象的访问信息     unsigned lru:24;     ... } robj;

Redis 对象头中的 lru 字段,在 LRU 算法下和 LFU 算法下的使用方法不同。

在 LRU 算法中,Redis 对象头的 24 bits 的 lru 用于记录字段 key 所以在 LRU 模式下,Redis根据对象头 lru 比较最后一次字段记录的值 key 访问时间长,淘汰最长未使用的 key。

在 LFU 算法中,Redis对象头的 24 bits 的 lru 字段分为两段存储,高 16bit 存储 ldt(Last Decrement Time),低 8bit 存储 logc(Logistic Counter)。

  • ldt 是用来记录 key 访问时间戳;
  • logc 是用来记录 key 的访问频次,它的值越小表示使用频率越低,越容易淘汰,每个新加入的 key 的logc 初始值为 5。

注意,logc 访问频率(访问频率)不是简单的访问次数,而是因为 logc 会随着时间的推移而衰减。

在每次 key 被访问时,会先对 logc 做一个衰减操作,衰减的值跟前后访问时间的差距有关系,如果上一次访问的时间与这一次访问的时间差距很大,那么衰减的值就越大,这样实现的 LFU 算法根据访问频率淘汰数据,而不仅仅是访问次数。需要考虑访问频率 key 访问发生在多长时间内。key 之前的访问距离现在越长,那么这个 key 访问频率会相应降低,被淘汰的概率会更大。

对 logc 衰减操作完成后,开始对抗 logc 增加操作不仅仅是直接的 1,而是根据概率增加,如果 logc 越大的 key,它的 logc 越难再增加。

所以,Redis 在访问 key 时,对于 logc 变化如下:

  • 首先,根据上次访问距离当前时间,对 logc 进行衰减;
  • 然后按一定的概率增加 logc 的值

redis.conf 为调整提供了两个配置项 LFU 从而控制算法 logc 的增长和衰减:

  • lfu-decay-time 用于调整 logc 衰减速度,以分钟为单位,默认值为1,lfu-decay-time 值越大,衰减越慢;
  • lfu-log-factor 用于调整 logc 增长速度,lfu-log-factor 值越大,logc 增长越慢。

总结

Redis 过期删除策略是「惰性删除 定期删除」,删除的对象已过期 key。

内存淘汰策略是解决内存过多的问题 Redis 当运行内存超过最大运行内存时,会触发内存淘汰策略,Redis 4.0 之后共同实现 8 内存淘汰策略,我也这么做 8 分类种策略,如下:

推荐学习:Redis视频教程

以上就是Redis请更多关注内存淘汰策略与过期删除策略的区别php中文网其他相关文章!

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。2022-08-04 00:00:33

猜你喜欢

最新文章