Redis的缓存淘汰策略LRU与LFU

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis的缓存淘汰策略LRU与LFU相关的知识,希望对你有一定的参考价值。

参考技术A Redis缓存淘汰策略与Redis键的过期删除策略并不完全相同,前者是在Redis内存使用超过一定值的时候(一般这个值可以配置)使用的淘汰策略;而后者是通过定期删除+惰性删除两者结合的方式淘汰内存过期键的。
这里参照官方文档的解释重新叙述一遍过期删除策略:当某个key被设置了过期时间之后,客户端每次对该key的访问(读写)都会事先检测该key是否过期,如果过期就直接删除;但有一些键只访问一次,因此需要主动删除,默认情况下redis每秒检测10次,检测的对象是所有设置了过期时间的键集合,每次从这个集合中随机检测20个键查看他们是否过期,如果过期就直接删除,如果删除后还有超过25%的集合中的键已经过期,那么继续检测过期集合中的20个随机键进行删除。这样可以保证过期键最大只占所有设置了过期时间键的25%。

在Java中LRU的实现方式是使用HashMap结合双向链表,HashMap的值是双向链表的节点,双向链表的节点也保存一份key value。

LFU是在Redis4.0后出现的,LRU的最近最少使用实际上并不精确,考虑下面的情况,如果在|处删除,那么A距离的时间最久,但实际上A的使用频率要比B频繁,所以合理的淘汰策略应该是淘汰B。LFU就是为应对这种情况而生的。

redis淘汰策略

一、最大内存设置

redis 默认的最大的内存设置为0,相当于基于物理机的最大值

二、淘汰策略
1. 8种策略
  1. volatile-lru,针对设置了过期时间的key,使用lru算法进行淘汰。
  2. allkeys-lru,针对所有key使用lru算法进行淘汰。
  3. volatile-lfu,针对设置了过期时间的key,使用lfu算法进行淘汰。
  4. allkeys-lfu,针对所有key使用lfu算法进行淘汰。
  5. volatile-random,从所有设置了过期时间的key中使用随机淘汰的方式进行淘汰。
  6. allkeys-random,针对所有的key使用随机淘汰机制进行淘汰。
  7. volatile-ttl,删除生存时间最近的一个键。
  8. noeviction(默认),不删除键,值返回错误。

主要是4种算法,针对不同的key,形成的策略。
算法:

  1. lru 最近很少的使用的key(根据时间,最不常用的淘汰)
  2. lfu 最近很少的使用的key (根据计数器,用的次数最少的key淘汰)
  3. random 随机淘汰
  4. ttl 快要过期的先淘汰

key :

  1. volatile 有过期的时间的那些key
  2. allkeys 所有的key
2.内存淘汰算法的具体工作原理是:
  • 客户端执行一条新命令,导致数据库需要增加数据(比如set key value)
  • Redis会检查内存使用,如果内存使用超过 maxmemory,就会按照置换策略删除一些 key
  • 新的命令执行成功
三、lru和lfu算法
1.lru

LRU是Least Recently Used的缩写,也就是表示最近很少使用,也可以理解成最久没有使用。也就是说当内存不够的时候,每次添加一条数据,都需要抛弃一条最久时间没有使用的旧数据。标准的LRU算法为了降低查找和删除元素的时间复杂度,一般采用Hash表和双向链表结合的数据结构,hash表可以赋予链表快速查找到某个key是否存在链表中,同时可以快速删除、添加节点,如图所示。

双向链表的查找时间复杂度是O(n),删除和插入是O(1),借助HashMap结构,可以使得查找的时间复杂度变成O(1),Hash表用来查询在链表中的数据位置,链表负责数据的插入.
当新数据插入到链表头部时有两种情况

  1. 当链表中没有这个key,且链表满了,把链表尾部的数据丢弃掉,新加入的缓存直接加入到链表头中。
  2. 当链表中的某个缓存被命中时,直接把数据移到链表头部,原本在头节点的缓存就向链表尾部移动

这样,经过多次Cache操作之后,最近被命中的缓存,都会存在链表头部的方向,没有命中的,都会在链表尾部方向,当需要替换内容时,由于链表尾部是最少被命中的,我们只需要淘汰链表尾部的数据即可。

2.redis中的lru算法

实际上,Redis使用的LRU算法其实是一种不可靠的LRU算法,它实际淘汰的键并不一定是真正最少使用的数据,它的工作机制是:

  • 随机采集淘汰的key,每次随机选出5个key
  • 然后淘汰这5个key中最少使用的key

这5个key是默认的个数,具体的数值可以在redis.conf中配置


当近似LRU算法取值越大的时候就会越接近真实的LRU算法,因为取值越大获取的数据越完整,淘汰中、的数据就更加接近最少使用的数据。这里其实涉及一个权衡问题,
如果需要在所有的数据中搜索最符合条件的数据,那么一定会增加系统的开销,Redis是单线程的,所以耗时的操作会谨慎一些。为了在一定成本内实现相对的LRU,早期的Redis版本是基于采样的LRU,也就是放弃了从所有数据中搜索解改为采样空间搜索最优解。Redis3.0版本之后,Redis作者对于基于采样的LRU进行了一些优化:

  • Redis中维护一个大小为16的候选池,当第一次随机选取采用数据时,会把数据放入到候选池中,并且候选池中的数据会更具时间进行排序。
  • 当第二次以后选取数据时,只有小于候选池内最小时间的才会被放进候选池。
  • 当候选池的数据满了之后,那么时间最大的key就会被挤出候选池。当执行淘汰时,直接从候选池中选取最近访问时间小的key进行淘汰。
    如图4-22所示,首先从目标字典中采集出maxmemory-samples个键,缓存在一个samples数组中,然后从samples数组中一个个取出来,和回收池中以后的键进行键的空闲时间,从而更新回收池。
  • 回收池满了,并且当前插入的key的空闲时间最小(也就是回收池中的所有key都比当前插入的key的空闲时间都要大),则不作任何操作。
  • 回收池未满,并且插入的位置x没有键,则直接插入即可
  • 回收池未满,且插入的位置x原本已经存在要淘汰的键,则把第x个以后的元素都往后挪一个位置,然后再执行插入操作。
  • 回收池满了,将当前第x个以前的元素往前挪一个位置(实际就是淘汰了),然后执行插入操作。
    这样做的目的是能够选出最真实的最少被访问的key,能够正确不常使用的key。因为在Redis3.0之前是随机选取样本,这样的方式很有可能不是真正意义上的最少访问的key。
    缺点:
    LRU算法有一个弊端,加入一个key值访问频率很低,但是最近一次被访问到了,那LRU会认为它是热点数据,不会被淘汰。同样,
    经常被访问的数据,最近一段时间没有被访问,这样会导致这些数据被淘汰掉,导致误判而淘汰掉热点数据,于是在Redis 4.0中,新加了一种LFU算法。
2.lfu

LFU(Least Frequently Used),表示最近最少使用,它和key的使用次数有关,其思想是:根据key最近被访问的频率进行淘汰,比较少访问的key优先淘汰,反之则保留。
LRU的原理是使用计数器来对key进行排序,每次key被访问时,计数器会增大,当计数器越大,意味着当前key的访问越频繁,也就是意味着它是热点数据。 它很好的解决了LRU算法的缺陷:一个很久没有被访问的key,偶尔被访问一次,导致被误认为是热点数据的问题
LFU的实现原理如图4-23所示

LFU维护了两个链表,横向组成的链表用来存储访问频率,每个访问频率的节点下存储另外一个具有相同访问频率的缓存数据。具体的工作原理是:

  • 当添加元素时,找到相同访问频次的节点,然后添加到该节点的数据链表的头部。如果该数据链表满了,则移除链表尾部的节点当获取元素或者修改元素是,都会增加对应key的访问频次,并把当前节点移动到下一个频次节点。
  • 添加元素时,访问频率默认为1,随着访问次数的增加,频率不断递增。而当前被访问的元素也会随着频率增加进行移动。

以上是关于Redis的缓存淘汰策略LRU与LFU的主要内容,如果未能解决你的问题,请参考以下文章

缓存数据库Redis之三:内存淘汰策略及优化

缓存数据库Redis之三:内存淘汰策略及优化

Redis 为何使用近似 LRU 算法淘汰数据,而不是真实 LRU?

缓存失效策略(FIFO,LRU,LFU)

字节二面,让手写一个LFU缓存策略算法,当场我不干了!

LRU与LFU比较