Redis过期策略以及内存淘汰机制

Redis通过将热点数据存储到内存中实现了高效的数据读取,但是内存如果使用不当也是会造成一些问题的。

Redis中有很多无效的缓存,这些缓存数据会降低数据IO的性能,随着系统的运行,redis的数据越来越多,会导致物理内存不足。

因此,Redis为了更好的管理内存,提供了一些管理方案,包括过期策略与内存淘汰。

一、过期策略

1. 如何设置key的过期时间?

redis提供了四种命令来设置key的过期时间:
(1) EXPIRE key seconds // 设置多少秒后过期
(2) PEXPIRE key milliseconds // 设置多少毫秒后过期
(3) EXPIREAT key timestamp 设置 key 过期时间的时间戳(unix timestamp) 以秒计
(4) PEXPIREAT key milliseconds-timestamp // 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计
移除redis的过期时间:
PERSIST key // 移除key的过期时间,key将保持永久
查询剩余生存时间:
TTL key // 以秒为单位,返回给定 key 的剩余生存时间
PTTL key // 以毫秒为单位返回 key 的剩余的过期时间 

2. 设置一个key的过期时间后,并超过了这个过期时间,这个key保存的数据还占据着内存吗?

当key过期后,该key保存的数据还是会占据内存的,因为每当我们设置一个键的过期时间时,Redis会将该键带上过期时间存放到一个过期字典中。当key过期后,如果没有触发redis的删除策略的话,过期后的数据依然会保存在内存中的,这时key已经过期,还是能够获取到这个key的数据。

3. redis删除过期数据的时机? 

redis过期删除策略通常有三种:定时删除,定期删除,惰性删除。redis使用的是:“定期删除+惰性删除”。

3.1 定时删除
在设置某个key 的过期时间同时,我们创建一个定时器,让定时器在该过期时间到来时,立即执行对其进行删除的操作。
优点:定时删除对内存是最友好的,能够保存内存的key一旦过期就能立即从内存中删除。
缺点:对CPU最不友好,在过期键比较多的时候,删除过期键会占用一部分 CPU 时间,对服务器的响应时间和吞吐量造成影响。
3.2 定期删除
每隔一段时间,我们就对一些key进行检查,删除里面过期的key。Redis默认每隔100ms就随机抽取部分设置了过期时间的key,检测这些key是否过期,如果过期了就将其删除。这里有两点需要注意下:

  • 默认的每隔100ms是在Redis的配置文件redis.conf中有一个属性"hz",默认为10,表示1s执行10次定期删除,即每隔100ms执行一次,可以修改这个配置的值来设置默认的间隔时间。
  • 随机抽取部分,而不是全部key。因为如果Redis里面有大量key都设置了过期时间,全部都去检测一遍的话CPU负载就会很高,会浪费大量的时间在检测上面,甚至直接导致redis挂掉。所有只会抽取一部分而不会全部检查。

优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。
缺点:难以确定删除操作执行的时长和频率。如果执行的太频繁,定期删除策略变得和定时删除策略一样,对CPU不友好。如果执行的太少,那又和惰性删除一样了,过期键长时间占用的内存没有及时释放的话,当我们再次获取这个过期的key时,依然会返回这个key的值,就相当于这个过期时间是无效的了。
3.3 惰性删除
设置该key 过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。
优点:对 CPU友好,我们只会在使用该键时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查。
缺点:对内存不友好,如果一个键已经过期,但是一直没有使用,那么该键就会一直存在内存中,如果数据库中有很多这种使用不到的过期键,这些键便永远不会被删除,内存永远不会释放,从而造成内存泄漏。所以redis还引入了另一种内存淘汰机制。

二、 内存淘汰机制 

Redis的内存淘汰机制是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。

在配置文件redis.conf 中,可以通过参数 maxmemory <bytes> 来设定最大内存,当数据内存达到 maxmemory 时,便会触发redis的内存淘汰策略(我们一般会将该参数设置为物理内存的四分之三)。

当 Redis 的内存超过最大允许的内存之后,Redis 会触发内存淘汰策略。(过期策略是指正常情况下清除过期键,内存淘汰是指内存超过最大值时的保护策略)。内存淘汰策略可以通过maxmemory-policy进行配置。

内存淘汰机制 

目前Redis提供了以下几种(2个LFU的策略是4.0后出现的):

  1. noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。(默认选项,一般不会选用)
  2. allkeys-lru:当内存不足以容纳新写入数据时,在整个键空间中,移除最近最少使用的key。(这个是最常用的)
  3. allkeys-lfu:当内存不足以容纳新写入数据时,在整个键空间中,移除最不经常(最少)使用的key。
  4. allkeys-random:当内存不足以容纳新写入数据时,在整个键空间中,随机移除某个key。
  5. volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
  6. volatile-lfu:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最不经常(最少)使用的key。
  7. volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
  8. volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

LRU

LRU是Least Recently Used的缩写,也就是表示最近很少使用,也可以理解成最久没有使用。也就是说当内存不够的时候,每次添加一条数据,都需要抛弃一条最久时间没有使用的旧数据。

LRU 是基于链表结构实现的,链表中的元素按照操作顺序从前往后排列,最新操作的键会被移动到表头,当需要进行内存淘汰时,只需要删除链表尾部的元素即可。

需要注意的是Redis并没有使用标准的LRU实现方法作为LRU淘汰策略的实现方式,这是因为:    

要实现LRU,需要将所有数据维护一个链表,这就需额外内存空间来保存链表,每当有新数据插入或现有数据被再次访问,都要调整链表中节点的位置,尤其是频繁的操作将会造成巨大的开销(不要忘了Redis就是为了存储热点数据而出现的,这必然导致数据出现高频的访问)

 为了解决这一问题,Redis使用了近似的LRU策略进行了优化,平衡了时间与空间的效率。

LRU虽然看起来实现了按照数据的热度对内存中的key进行管理,但是在某些情况下却仍然存在一些问题,假设有一个数据很久没有被访问了,偶然间被访问了一次,lur字段值被更新,那么在lru的策略下,这个近期刚被访问过的节点会被保留,但显然这个key并不是我们想要保留的热点数据。为了解决这一问题,便有了LFU。

LFU 

LFU(Least Frequently Used),表示最近最少使用,它和key的使用次数有关,其思想是:根据key最近被访问的频率进行淘汰,比较少访问的key优先淘汰,反之则保留。

相比LRU算法,LFU增加了访问频率的这样一个维度来统计数据的热点情况,LFU主要使用了两个双向链表去形成一个二维的双向链表,一个用来保存访问频率,另一个用来访问频率相同的所有元素,其内部按照访问时间排序。

FIFO

FIFO(Fist in first out)先进先出,基于队列的先进先出原则,最先进入的数据会被最先淘汰掉。这是最简单、最公平的一种思想。

这种算法有个很严重的缺点,就是会导致缺页率增加。缺页率指的是判断一个页面置换算法优劣的指标。随着分配页面的增加,被置换的内存页面往往是被频繁访问的,因此FIFO算法会使一些页面频繁地被替换和重新申请内存,从而导致缺页率增加。由于缺页率会随着分配页面的增加而增加,使得redis的开销也逐渐增加,所以这种算法已经不再使用。

淘汰策略的选择
如果数据呈现幂等分布(即部分数据访问频率较高而其余部分访问频率较低),建议使用 allkeys-lru或allkeys-lfu。
如果数据呈现平等分布(即所有数据访问概率大致相等),建议使用 allkeys-random。
如果需要通过设置不同的ttls来确定数据过期的顺序,建议使用volatile-ttl。
如果你想让一些数据长期保存,而一些数据可以消除,建议使用volatile-lru或volatile-random。
由于设置expire会消耗额外的内存,如果你打算避免Redis内存浪费在这一项上,可以选择allkeys-lru策略,这样就可以不再设置过期时间,高效利用内存。

使用建议

虽然Redis提供了内存淘汰策略,但我们最好还是精简对Redis的使用,尽量不要淘汰内存数据。下面是一些使用建议:

不要放垃圾数据,及时清理无用数据。
key尽量都设置过期时间。对具有时效性的key设置过期时间,通过redis自身的过期key清理策略来降低过期key对于内存的占用,同时也能够减少业务的麻烦,不需要定期手动清理了。
单Key不要过大,这种key造成的网络传输延迟会比较大,需要分配的输出缓冲区也比较大,在定期清理的时候也容易造成比较高的延迟. 最好能通过业务拆分,数据压缩等方式避免这种过大的key的产生。
 
不同业务如果公用一个业务的话,最好使用不同的逻辑db分开。这是因为Redis的过期Key清理策略和强制淘汰策略都会遍历各个db。将key分布在不同的db有助于过期Key的及时清理。另外不同业务使用不同db也有助于问题排查和无用数据的及时下线。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值