面试官:我问你Redis内存满了怎么办,你竟然告诉我LRU!

举报
托尼学长 发表于 2024/11/09 20:09:57 2024/11/09
【摘要】 我说的是真实情况,有很多候选人都折在这一道看似简单的Redis面试题上。面试官:“我看你简历上写的熟悉Redis是吧,那你说说如果Redis服务器的内存满了,它将会怎么处理?”候选人略一思考,说:“如果Redis内存满了的话,那肯定是得进行LRU操作了啊。”面试官:“你确定会进行LRU吗?那你们redis.conf中的maxmemory-policy参数是如何配置的?”候选人想了想,似乎什么...

我说的是真实情况,有很多候选人都折在这一道看似简单的Redis面试题上。

面试官:“我看你简历上写的熟悉Redis是吧,那你说说如果Redis服务器的内存满了,它将会怎么处理?”

候选人略一思考,说:“如果Redis内存满了的话,那肯定是得进行LRU操作了啊。”

面试官:“你确定会进行LRU吗?那你们redis.conf中的maxmemory-policy参数是如何配置的?”

候选人想了想,似乎什么都没想起来,说:“抱歉,这个我确实不太清楚。”

本文我们就来深入聊聊这道面试题,以及与其相关的底层技术实现原理。

关于maxmemory-policy

我们在上文中所提到的“内存满了”这种说法,是指超出Redis服务器的物理内存限制了吗?其实不是的。

Redis还提供了另外一个配置参数 —— maxmemory,该参数是用来配置内存大小的。一旦Redis所使用的内存超过了该参数,就会启动 maxmemory-policy 中所配置的策略。

这里需要说明的是,对于64位的操作系统,‌maxmemory 参数的默认值为0,‌表示没有内存大小限制;‌而对于32位的操作系统,‌maxmemory 参数的默认值3GB。

举个例子:

maxmemory 6gb
maxmemory-policy allkeys-lru 或者 volatile-lru


只有这样配置 maxmemory-policy 参数,才如上文中的候选人所说,当Redis的内存满了会进行LRU操作。

其实 maxmemory-policy 参数的配置项有很多,下面且听我一一道来。

1.png

noeviction(默认):当Redis所使用的内存达到了maxmemory的时候,就不再提供除了del、hdel、unlink以外的其他写操作,但读操作可以继续进行。

volatile-lru通过近似LRU(最近最少使用)算法来淘汰Redis中的key,淘汰范围是Redis中设置过期时间的key。

volatile-lfu通过LFU(最不常用算法来淘汰Redis中的key,淘汰范围是Redis中设置过期时间的key。

volatile-ttl汰Redis中剩余生存时间最短的key,淘汰范围是Redis中设置过期时间的key。

volatile-random以随机的方式淘汰Redis中的key,淘汰范围是 Redis中设置过期时间的key。

allkeys-lru通过近似LRU(最近最少使用算法来淘汰Redis中的key,淘汰范围是Redis中的所有key。

allkeys-lfu通过LFU(最不常用算法来淘汰Redis中的key,淘汰范围是Redis中的所有key。

allkeys-random以随机的方式淘汰Redis中的key,淘汰范围是Redis中的所有key。


上述的八种策略中,除了noeviction策略之外,剩下的7种策略都会涉及到淘汰key的操作。

而对于淘汰key的判断和执行,是通过每次的用户请求来进行触发的,步骤如下图所示:

2.png


LRU 算法和 LFU算法

可能同学们会觉得LRU和LFU两种算法容易混淆,我在这里解释一下。

如果有一条许久不曾被访问的冷数据,偶然间被访问了一次,如果按照LRU算法的规则,这条数据就会被定义为热数据,短期内不会被淘汰。

但按照LFU算法的规则,这条数据可能仍然是最近一段时间内被访问频率最低的,还是会被淘汰掉的。

LRU关注于“最近是否被访问”,LFU关注于“最近的访问频率如何”,相对而言,后者的实现方式更加合理一些。


近似 LRU 算法

标准的LRU算法,其底层是通过双向链表 + Hash进行实现的,双向链表可以保证数据的插入和删除操作的时间复杂度为O(1),而Hash表可以实现数据查找的时间复杂度为O(1),将两者的优势结合起来堪称完美。

3.png

但根据Redis官方文档所述,Redis并没有按照标准的LRU算法进行实现,而是采取了一种“近似LRU”的实现方式。

其原因在于:

(1)Redis内部只实现了Hash表,而标准的LRU算法则需要双向链表 + Hash表两种数据结构换言之,需要在Redis内存中再额外增加一个双向链表,这个内存消耗的代价是不可接受的。

(2)每个Redis请求,LRU的双向链表也需要进行同步操作,这种实现方式对性能影响不小。

而Redis本身实现的“近似LRU”算法,则远远不需要付出这么大的内存和性能代价,但也牺牲了一些内存淘汰的准确率。

4.png

我们来看一下具体的实现方式:

(1)Redis通过引入LRU时钟值的方式,来记录数据每次被访问的时间。

(2)随机挑选出一批数据,将其一一与“按照空闲时间升序排列的”待淘汰数据池中的数据进行比对。

btw:空闲时间 = 当前时钟值 - LRU时钟值,待淘汰数据池的默认大小为16。

  • 待淘汰数据池中已满,且该数据的空闲时间最小,则跳过;
  • 待淘汰数据池中未满,且该数据要写入的位置为空,则执行写入操作;
  • 待淘汰数据池中未满,且该数据要写入的位置不为空,则将目前处于该位置及后面的元素都后移一个位置,再执行写入操作;
  • 待淘汰数据池中已满,则将数据要写入位置前面的元素都前移一个位置,再执行写入操作;

批次数据的数量,是由 maxmemory-samples 参数来决定的,默认值为5。该参数值设置越大,就越能提升内存淘汰的准确率,但也会增加服务器的CPU消耗。

(3)从待淘汰数据池中,淘汰掉空闲时间最大的那条数据,同时会根据Redis中的惰性删除配置,来决定在Redis字典中执行同步删除还是异步删除。

(4)此时若释放的内存空间未能小于 maxmemory 参数值,则重复执行上述步骤中的(2)(3),直至达成目标为止。

结语

不得不说,这道Redis面试题确实是个好问题,因为其可以由浅及深地挖出来很多Redis的底层实现原理,对候选人的技术水平是个综合且全面的考查。

而换言之,如果候选人能够在技术面试中,有理有据地把这道题所涉及到的技术底层实现原理跟面试官讲清楚,也是一个大大的加分项。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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