Redis内存淘汰策略

发布于:2025-08-09 ⋅ 阅读:(16) ⋅ 点赞:(0)

前言

​    Redis作为一种内存数据库,内存的空间是较为稀缺的,所以在Redis长期提供服务过程中,会遇到内存资源不够用的情况。即Redis内部存储数据超过上限,此时Redis提供了多种内存数据淘汰策略以保证Redis服务的可用性。

一、Redis存储上限

​    Redis服务作为一个进程启动,向外部提供服务,其消耗的内存资源上限也是能够配置的。通过对redis.conf配置文件中maxmemory字段可以设置Redis内存占用上限。

​    内存上限配置策略一般是取总内存大小的一半。具体原因有两个考量:

​    1、Redis的数据组织方式通过hashtable,而hashtable使用过程中会有扩容/缩容过程,期间涉及到rehash操作。主要是扩容期间的rehash操作,此时他会在两张hashtable中进行数据迁移或者当前其他的客户端命令操作。因此为保证Redis在极端情况下也能够拥有足够内存空间进行rehash操作一般取内存大小的一半。不过更重要的是下面原因。

​    2、Redis的持久化方式中有一种方式为rdb。该方式的底层实现是通过fork一个子进程将当前Redis数据库数据内容持久化到磁盘中。而fork操作的底层实现中会创建一个新的子进程,并直接复制父进程的内存空间以及内存资源,并且设计到写时复制操作。所以可以将这个过程抽象为:Redis数据库在rdb持久化时,会将当前Redis进程做一个快照,相当于复制一份当前Redis的内存资源到当前内存。此时内存中相当于有两份。然后子进程再开始进行Redis数据库数据的持久化操作。那么也是考虑到极端情况下,Redis内存资源告急状态下,依然能够保证rdb持久化操作。尽量选择Redis内存资源上限为总内存大小的一半。

二、内存淘汰策略

​    对于Redis的内存淘汰策略,也就是当Redis需要对内存中数据进行淘汰时选择一种什么样的方式进行淘汰。Redis也是提供了多种内存淘汰方式。

  • no-envicition:该策略对于写请求不再提供服务,会直接返回错误,当然排除del等特殊操作,redis默认是no-envicition策略。
  • allkeys-random:从redis中随机选取key进行淘汰
  • allkeys-lru:使用LRU(Least Recently Used,最近最少使用)算法,从redis中选取使用最少的key进行淘汰
  • volatile-random:从redis中设置过过期时间的key,进行随机淘汰
  • volatile-ttl:从redis中选取即将过期的key,进行淘汰
  • volatile-lru:使用LRU(Least Recently Used,最近最少使用)算法,从redis中设置过过期时间的key中,选取最少使用的进行淘汰
  • volatile-lfu:使用LFU(Least Frequently Used,最不经常使用),从设置了过期时间的键中选择某段时间之内使用频次最小的键值对清除掉
  • allkeys-lfu:使用LFU(Least Frequently Used,最不经常使用),从所有的键中选择某段时间之内使用频次最少的键值对清除

​    其中上述淘汰策略中按照淘汰范围主要分为两大类:1、全部key 2、设置过过期时间的key

​    按照淘汰算法有:1、随机 2、TTL 3、LRU(Least Recently Used)最长时间未使用 4、LFU(Least Frequency Used)最少次数使用

三、内存淘汰算法

1、随机

​    随机算法较容易理解,就是从数据库中随机淘汰一些Key。

2、TTL

​    TTL算法是从设置了过期时间的key中,将key的剩余时间进行排序,淘汰掉最早过期的一批key。

3、LRU(Least Recently Used)

​    LRU算法是通过key的最后访问时间来判定,因为在Redis的所有key中都是由redisObject结构体来描述的。如下所示:

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
    int refcount;
    void *ptr;
} robj;

​    在redisObject结构体中lru字段是一个24个bit位的时钟信息。这个时钟信息可以理解为Redis进程他自己的一个时间戳。当一个key被插入到Linux系统时会将当前系统时间戳插入到lru字段。或者当使用该key值时也会更新其lru字段为使用时的系统时间戳。然后当需要进行LRU淘汰策略时,将该字段跟系统当前时间进行作差即可得到最应该淘汰的key。

​    在Redis中采用的LRU算法并不是常见的内存淘汰机制算法中的LRU算法,而是一种近似LRU算法。他的具体淘汰策略为:在每一次的命令执行期间都会判断当前系统内存使用是否超过上限。如果超过上限则随机选择一些KV键值对,形成一个待淘汰KV键值对候选集合,然后使用LRU算法思想,根据这些KV键值对的lru字段记录的时间戳,此时该字段反应的最近使用该字段的时间,然后选出最长时间未使用的KV键值对进行淘汰。

4、LFU(Least Frequency Used)

​    LFU算法是通过近期key的访问频率来判定,具体访问频率的反映也是复用了上述lru字段,不过将其进行了拆分,其中高16位记录的是时间戳,低8位记录热度值。其中Redis对该值的生成设计了一个算法。其大概思路是对key值的访问次数进行数值归一化,将数据访问次数映射为热度值,将数据从0-正无穷这个取值区间映射到了0-255这个维度。

四、内存淘汰时机

​    介绍了上述具体的Redis内存淘汰策略,具体Redis在何时触发这些淘汰策略呢?

​    Redis提供了两种淘汰时机:定期淘汰、惰性淘汰两种。

1、定期淘汰

​    redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定期遍历这个字典来删除到期的 key。

​    Redis 默认会每秒进行十次过期扫描(100ms一次),过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。

​        1.从过期字典中随机 20 个 key;

​        2.删除这 20 个 key 中已经过期的 key;

​        3.如果过期的 key 比率超过 1/4,那就重复步骤 1;

​    redis默认是每隔 100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载。

2、惰性淘汰

​    所谓惰性淘汰也是利用了分治的思想,将淘汰key值这件大事,放到每一次key值的使用过程中。具体过程为:当Redis具体处理某个key值时,会对key的过期时间进行检查,如果过期了就返回null。并将该key值的内存释放。

​    其中对于上述Redis内存淘汰时机的总结为两个方面:1、惰性淘汰:当每次执行客户端想要获取某个KV键值对信息时,会判断该key值是否通过expire/pexpire设置过过期时间,如果过期则返回null并淘汰该KV键值对。

​    2、定期淘汰 + 执行可能增加内存占用的命令:在Redis中可以配置定期淘汰的时间周期,然后在一定时间周期内进行配置的内存淘汰策略(LRU、LFU等)。同时在执行set、lpush等增加数据库数据成员内存的操作时,也会对应检查是否超过了当前的内存上限。如果超过也会执行对应的内存淘汰策略。

​    通过上述两种方式,利用分治的思想,将内存淘汰这件较为耗费时间的工作,分摊在了一段一段时间周期中,并且为了保证实时处理客户端命令过程中保证数据能在合理的时间有合理的返回,也设计了惰性淘汰策略,同时将内存淘汰这件事分摊在了一个一个命令的执行周期中。两种方式综合设计保障Redis的内存资源管理。

 更多资料:0voice · GitHub


网站公告

今日签到

点亮在社区的每一天
去签到