Redis LRU缓存淘汰机制(过期处理机制)

2 篇文章 0 订阅
2 篇文章 0 订阅

本打算将MySQL系列从原理到集群架构一口气写完,最近有朋友说让我聊聊面试中的高频问题,这种比较实际而且立竿见影的话题。那我就先把这块东西先放着,后面穿插着把MySQL系列补全。今天我们就新开一篇说说Redis的缓存过期删除算法:LRU。

说到Redis的LRU淘汰算法我们就不得不先了解下它的过期策略。过期策略从字面意思就是:当key过期时就会自动删除。那么我们想一下两个问题:什么时候删除过期key?如何删除?(前提是设置有过期时间的key)

什么时候删除过期key

Redis将会将设置了过期时间的key存放在一个独立的集合中,被动查询和主动定时轮训来删除过期的key。先来说说主动查询删除,这种模式就类似于程序中的懒加载,就程序需要访问这个key的时候Redis才会被动删除这个过期key然后给客户端返回null值。那么主动轮询删除就是默认每10秒通过后台线程扫描这个集合删除过期缓存的方式。

怎么删除

鉴于Redis是单线程的,在淘汰缓存的同时又不能影响对外提供服务,所以这里就不能简单粗暴的直接删除。假如同时间有大量的过期缓存需要删除的话,后台线程一直处于阻塞删除中,那么肯定就会影响对外提供服务。那么Redis是通过什么算法来删除的呢?我们下面来一个个说

  • 贪心策略

在主动删除中Redis默认都会每隔10秒去遍历一次设置了过期key的那个集合,首先随机选出20个key,然后删除其中已经过期的key,如果过期key的比例超过当前20个key的四分之一那么就循环到第一步直到条件不满足退出。同时为了保证像我们之前担心的线程阻塞的问题,算法还加了超时时间(默认25ms),超时也会退出。也就是说恰好当客户端碰上贪心策略清理过期key,那么意味着它最大只会等待25ms。

在此需要注意的是客户端的超时时间最好根据这个时间的值来参考设置,不然话当恰好遇到大量需要淘汰的key, 客户端超时时间<算法最大阻塞时间的情况,,客户端就会在短时间出现大量连接超时。而且你在Redis的slowlog中也找不到日志,因为慢查询是指的逻辑处理过程不包含等待时间。

这样做的一个好处是当缓存稀疏到一定程度,过期数据的删除就相对比较缓慢了(你设置了过期,不等于马上就在Redis内存中或者AOF文件中不存在了,它需要等待回收),当有大量缓存集中失效的话就会导致缓存服务持续卡顿,不能正常的对外提供服务,严重点的话就是传说中的“缓存雪崩”,服务持续不可用是很恐怖的。为了防止缓存集中过期,我在这里提供大家一个小办法,也就是设置过期时间在一个随机范围:expire(key,random.randint(1600) + 6400),就是在我们允许的过期时间范围浮动,这样新的缓存就会及时的回填。

  • 从节点过期淘汰策略 因为从节点比较特殊,它只有被动删除功能,没有主动删除功能。在主节点主动过期删除后会在AOF文件中添加一条del命令同步到从节点,从节点通过执行这条del命令来删除这条过期key。话有说到前面的点,当主节点卡顿的时候从节点也会出现延迟,所以设置合理的过期时候还是很有必要的。当从节点接收到完整的rdb文件后,也需要将当前内存进行一次性清空,用来加载最新数据。4.0以后提供下列异步处理参数设置

  • slave-lazy-flush:从节点接收完rdb文件后flush;

  • lazyfree-lazy-eviction:内存达到maxmemory时进行淘汰

  • lazyfree-lazy-expire key:过期删除

  • lazyfree-lazy-server-del rename:指令删除destKey

  • LRU过期策略

这种策略是适用于当Redis存储内存值接近或者超过maxmemory参数(maxmemory_policy)设置时就会触发LRU策略。当Redis发生这种情况的时候系统提供了几种策略:

1. noeviction:拒绝写请求,正常提供读请求,这样可以保证已有数据不会丢失(默认策略);
2. volatile-lru:尝试淘汰设置了过期时间的key,最少使用的key被淘汰,没有设置过期时间的key不会淘汰;
3. volatile-ttl:跟volatile-lru几乎一样,但是他是使用的key的ttl值进行比较,最先淘汰ttl最小的key;
4. volatile-random:其他同上,唯一就是他通过很随意的方式随机选择淘汰key集合中的key;
5. allkeys-lru:区别于volatile-lru的地方就是淘汰目标是全部key,没设置过期时间的key也不能幸免;
6. allkeys-random:这种方式同上,随机的淘汰所有的key。
  • LRU过期算法

正经的LRU算法是这样的:它把设置了过期key的集合用链表的方式,按照使用频率是顺序排列。当这个链表空间满掉以后就会从最尾部剔除,将新加入的元素放在链表头部,或者当key被访问时也会将此key移动到链表头部,所以这样链表就是按照最多访问排列的先后顺序。(有时候面试的人会问你能不能手撸一个LRU算法?别怕,我们可以通过Java的LinkList重新方法来写一个只需要关注业务处理的 LRU)

  • Redis LRU 过期算法 Redis的过期算法是基于正经LRU的变种,之所以不使用正经LRU算法,是因为它需要消耗大量内存,对Redis现有数据结构有较大的改造。这种变种算法是在现有的数据结构基础上使用随机采样方法来淘汰key。他是这样操作的:给每个key增加一个额外的字段,这个字段占24bit,也就是最后一次被访问的时间戳。然后随机采样出5个key(通过maxmemory_samples来调整,采样数量越大越接近于正经的LRU算法,但是也带来了淘汰速率的问题)淘汰掉最旧的key,直到Redis占用内存小于maxmemory为止。在3.0以后增加了LRU淘汰池,进一步提高了与LRU算法的近似效果。

采样的方式要看你使用的是那种淘汰策略,volatile-*这些都是从设置有过期时间的key中淘汰,allkeys-*这些的目标都是所有key和是否设置过期时间无关。关于淘汰池:他是一个数组,数组大小等于maxmemory_samples,在每一次淘汰循环中,新随机得到的key列表会和淘汰池中的key进行合并,淘汰掉最旧的key后保留剩余较旧的key列表放入淘汰池中待下一次循环使用。

  • 人肉淘汰

这种方式就是通过del命令手工主动淘汰。这里有一个坑,大家要注意:当你删除一个超级大的key时,那么这个删除命令是很具有杀伤性的,会导致Redis暂时卡顿。在4.0以后引入了unlink指令,它是一个后台异步线程,不会阻塞当前线程。当然在此大家不要担心多线程竞争写的问题,当unlink指令接收到后,其他线程将无法看到这部分key的。同样还提供了异步删库指令:flushall async这样的参数。这些异步操作,它会将这些key的回收操作包装成一个任务放入Redis的异步任务队列执行。(当key占用内存很小时,则直接使用del指令)

结尾

关于Redis的缓存淘汰策略与算法我们就讲到这里,还有一个著名的LRU变种算法请复习《MySql那些事儿(二):InnoDB架构介绍之内存篇》,它是为了让常用数据最大可能的驻留内存,将链表分成新老区域两段,新加入的元素只会进入链表的老区域的头部,避免了在大量的一次性批量操作不常用数据后导致常用数据被淘汰的方法,这样也就提高了内存的命中率。下一期我们将继续讲Redis的缓存穿透与热点缓存的解决处理办法。喜欢这篇文章的朋友请帮忙转发,谢谢。

本文经过作者同意只是转发,做笔记用的。下面是作者公众号,有兴趣的可以看一下,满满的干货!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值