一篇吃透Redis过期Key处理和内存淘汰

前言:Redis是我们常用为缓存的中间件,Redis之所以性能强,最主要的原因就是基于内存存储。然而单节点的Redis其内存大小不宜过大,会影响持久化或主从同步性能。因此我们会给一些key设置TTL过期时间,那当key过期的时候,Redis是如何处理的呢?并且由于没有及时删除,Redis的内存不够了,应该怎么办呢?本文我们会提到处理这两个问题的解决方案:过期Key处理内存淘汰机制

  1. 过期Key处理

我们在学习Redis缓存的时候,肯定使用过“expire”命令给Redis的key设置TTL(过期时间),如下图:

一篇吃透Redis过期Key处理和内存淘汰_第1张图片

可以发现,当给一个key设置TTL时,在规定时间内访问能得到value值,但是超过TTL后再访问就只能得到nil了。我们从感觉来看,好像超过TTL值后,key会被立即删除,但是真的是这样吗?

这里我们需要提到两种Key过期处理方法:

1.1 惰性删除

惰性删除是指并不是在TTL到期后就立刻删除,而是在访问一个key的时候,检查该key的存活时间,如果已经过期才执行删除。

这里我们查看Redis源码的时候也可以很明确看到有这个处理方式:

一篇吃透Redis过期Key处理和内存淘汰_第2张图片

从上图可见:在Redis对一个Key进行读写操作时,会先进入一个“expirefNeeded”函数来判断key是否过期。如果没过期则退出该函数,返回0;过期则删除该Key,返回1。然后在接下来的流程中对expirefNeeded函数返回值进行判断,如果是0则进行接下来的一系列操作,如果是1则说明过期,返回null。

1.2 周期删除

周期删除:顾明思议是通过一个定时任务,周期性的抽样部分过期的key,然后执行删除。从Redis源码可以看出,执行周期有两种:

①SLOW模式:Redis服务初始化函数initServer()中设置定时任务,按照server.hz的频率来执行过期key清理。SLOW模式的规则:

  • 执行频率受server.hz影响,默认为10,即每秒执行10次,每个执行周期100ms。

  • 执行清理耗时不超过一次执行周期的25%.默认slow模式耗时不超过25ms

  • 逐个遍历db,逐个遍历db中的bucket,抽取20个key判断是否过期

  • 如果没达到时间上限(25ms)并且过期key比例大于10%,再进行一次抽样,否则结束

②FAST模式:Redis的每个事件循环前会调用beforeSleep()函数,执行过期key清理。FAST模式的规则:

  • FAST模式规则(过期key比例小于10%不执行 ):

  • 执行频率受beforeSleep()调用频率影响,但两次FAST模式间隔不低于2ms

  • 执行清理耗时不超过1ms

  • 逐个遍历db,逐个遍历db中的bucket,抽取20个key判断是否过期如果没达到时间上限(1ms)并且过期key比例大于10%,再进行一次抽样,否则结束

1.3 Redis过期Key处理模式

看完了上述两种过期key处理模式,理解的朋友肯定能看出这两种方式的优缺点了:

惰性删除:

  • 优点:不用单独的有个方法不断运行去处理过期的Key,性能好。

  • 缺点:如果某个数据量很大的key过期后一直没被访问,就会一直在Redis中,占用内存。

周期删除:

  • 优点:不会存在Key过期后一直在Redis中占用内存的情况出现。

  • 缺点:要不断扫描Redis中所有的Key,判断他们是否过期。如果此时Redis中有很多数据的话,会影响性能。

那么,Redis是采用哪种过期Key处理模式呢?显然,无论哪种都存在一定的缺点,当然也不否认有各自的优点。因此Redis采用二者相结合的模式:

  • 惰性清理:每次查找key时判断是否过期,如果过期则删除

  • 定期清理:定期抽样部分key,判断是否过期,如果过期则删除。

1.4 Redis存储结构

看完上文中的周期删除,我们会很好奇:Redis是如何知道某个Key有没有过期的呢?它难道把每个Key的TTL也挤下来了吗?这里我们不得不说Redis的数据存储库结构了。

Redis本身是一个典型的key-value内存存储数据库,因此所有的key、value都保存在Dict结构中。不过Redis本身有多个Dict,一个用来存储数据Key-Value,另一个用来存储每个Key的过期时间Key-TTL。这里我们通过查看Redis的源码,可以得到其数据库结构如下:

一篇吃透Redis过期Key处理和内存淘汰_第3张图片

可见,Redis中有许多Dict结构,因此做周期删除的时候,只需要通过上图中指向存储

Key-TTL数据的Dict的指针“expires”即可。


  1. Redis内存回收-淘汰策略

内存淘汰:就是当Redis内存使用达到设置的上限时,主动挑选部分key删除以释放更多内存的流程。

在Redis中支持八种内存淘汰策略:

  • noeviction: 不淘汰任何key,但是内存满时不允许写入新数据,默认就是这种策略。

  • volatile-ttl: 对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰

  • allkeys-random:对全体key ,随机进行淘汰。

  • volatile-random:对设置了TTL的key ,随机进行淘汰。

  • allkeys-lru: 对全体key,基于LRU算法进行淘汰

  • volatile-lru: 对设置了TTL的key,基于LRU算法进行淘汰

  • allkeys-lfu: 对全体key,基于LFU算法进行淘汰

  • volatile-lfu: 对设置了TTL的key,基于LFI算法进行淘汰

这里提到了两个淘汰算法:

  • LRU(Least Recently Used),最少最近使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。

  • LFU(Least Frequently Used),最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。

你可能感兴趣的:(Redis,redis,缓存,数据库)