问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Redis内存淘汰策略深度解析

创作时间:
作者:
@小白创作中心

Redis内存淘汰策略深度解析

引用
CSDN
1.
https://blog.csdn.net/XYY_CN/article/details/140571995

Redis作为一款高性能的内存数据库,当内存使用达到预设阈值时,其内存淘汰策略决定了如何释放空间以维持系统正常运行。本文将深入解析Redis 7.0版本的内存淘汰机制,从源码层面揭示其工作原理。

Redis将数据存储在内存中,并设置了一个内存使用阈值。当内存使用超过这个阈值时,Redis会根据预设的内存淘汰策略来删除一些键,从而释放内存空间,确保系统能够继续处理写操作。本文将参考Redis 7.0源码,详细解析当内存使用超过限制时,Redis是如何进行键淘汰的。

1. 源码入口

在源码中,内存淘汰的主要处理函数是performEvictions,这个函数在处理每条命令时被调用。其调用链如下:

如果Redis设置了maxmemory,同时Redis没有处在超时执行的Lua脚本,则会进行内存淘汰检查和执行对应的淘汰策略。

2. 相关配置

在启动配置文件中,有几个配置项与内存淘汰密切相关:

  • maxmemory <bytes>:Redis最大内存限制,如果没有配置,默认值在32位系统下为3G,64位系统无限制。
  • maxmemory-policy:8种内存淘汰策略中的一种。
  • maxmemory-samples:LRU、LFU、TTL采用的是近似算法,维护了一个大小为16的数组,每次采样maxmemory-samples个键更新这个数组。
  • lazyfree-lazy-eviction:是否启动惰性淘汰,开启的情况下会尝试异步删除。

3. 淘汰策略

淘汰策略由配置项maxmemory-policy决定,共有8种策略,从预选键的范围来看可分为3类:

  • noeviction:表示不进行淘汰。
  • allkeys:在所有键上进行具体的策略。
  • volatile:只在设置了过期时间的键上进行具体的策略。

而具体的淘汰算法实际是4种。

3.1 随机淘汰

遍历每个DB,从中随机选择一个键。注意如果上次选定了DB_A,则下次会从DB_A的下一个DB开始选择。随机选择的策略在源码中对应dictGetRandomKey(dict)函数。该函数包含两个步骤:首先是随机选择一个哈希表中的槽,其次在槽的链表上选择一个键。选择槽的时候会考虑rehash,不会选择刚刚rehash结束的键。

3.2 LRU(Least Recently Used)

LRU/LFU/TTL策略在同一个条件分支下。首先是遍历每个DB,调用evictionPoolPopulate函数,采样若干个键放入pool中,由于pool中的键是按照idle从小到大排序的,然后从后往前遍历pool获取一个键,即为候选键bestKey。不同的策略实际是idle值计算方式不同。

LRU策略优先淘汰最近一段时间没有被访问到的键。通过estimateObjectIdleTime函数获取键的idle值。

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;

每个键对象包含了一个lru字段,该字段在LRU淘汰策略下表示上一次被访问时的lru时钟时间,差值即为该键的空闲时间。考虑到lru为24位,只能保存一段时间内的值,可能存在值的套圈,所以有了else的条件判断。

3.3 LFU(Least Frequently Used)

LFU策略考虑访问频率,idle = 255-LFUDecrAndReturn(o)。在LFU策略下,键对象的lru字段低8位表示访问频次,高16位表示上次访问的时间。255-访问频次作为idle的值,访问频率越低idle值越大。

3.4 TTL

直接获取键的过期时间,选择最近的过期键。

4. 候选键删除

得到候选键和对应的DB后,Redis的删除过程如下:

  1. 获取当前内存值delta
  2. 执行删除,根据配置项决定是同步删除还是异步删除
  3. 重新获取内存值,计算本次删除的内存大小delta(此时异步删除可能还没有执行)
  4. 删除可能持续很久,每释放16次键,同步删除信号给副本;异步删除情况下,检查内存是否已经满足要求
  5. 检查当前删除耗时是否大于配置值,如果超出时间约束,启动后台任务继续执行删除

4.1 同步和异步删除

async=0时,表示同步删除;async=1表示异步删除。

删除键所在DB如果有设置过期的键集合,则首先尝试删除该键的过期时间,然后调用dictUnLink将键从字典中移除,但是此时并没有释放键和节点的内存。如果是异步删除则调用freeObjAsync异步删除,同时设置该节点对应的值为nullptr。最后调用dictFreeUnlinkedEntry,对于同步的情况,val不为空,会直接删除,而异步的情况,val为空,不执行删除val操作,而是在异步任务中真正的删除。

而异步删除freeObjAsync实际上还会检查对象值是否大于设置阈值以及当前是否没有被共享,两个条件都满足时才会启动后台删除线程。这个值的计算遵循:string类型为 1,list类型返回存储元素数,set(hashtable实现)、zset(跳表实现)、hash(hashtable)返回包含元素数。

5. 总结

Redis在处理线上命令时会检测内存是否超出阈值,当超出阈值时,计算本次需要释放多少内存。然后根据淘汰策略选择删除候选键,随机选择是随机选择字典的槽和链表上的节点,lru/lfu/ttl 都是采用的近似算法,维护一个固定大小的数组,按照键的idle值从小到大排序,每次选择idle最大的键,每次也会采样指定的键来更新这个数组。选定候选键后根据配置执行同步或者异步删除。为了防止阻塞线上请求,删除不能执行太久,所以每16次都会检查内存是否已经满足要求,满足则直接退出,检查当前删除动作是否已经超时,超时则转化到后台执行。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号