常用的缓存淘汰算法

340 阅读4分钟

「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战」。

无论是Redis,还是MySQL都能看到缓存的身影,有缓存就必定存在缓存淘汰算法,比如像LRU 算法、LFU算法等待,本篇文章我们就来介绍一下 这些缓存淘汰算法。

简单介绍

为什么要使用缓存?

首先呢,由于从磁盘中读取文件的速度相较于从内存中读取数据的速度是很慢的,因此我们想要让程序具备高性能,就得将常用的数据存入内存中(我们称之为缓存),以此来加快数据的读取速度。

为什么要去淘汰缓存中的数据呢?

因为服务器内存资源是有限的,不可能不断的将数据存入内存中而不淘汰。尤其是对Java应用来说,大量使用内存资源会有GC问题,过多的使用内存会造成频繁的FullGC,从而导致应用停顿。所以当内存占用到一定程度,我们就需要淘汰内存中没有用的数据。

缓存淘汰算法又能起哪些作用?

缓存淘汰算法就是能够帮助我们按照一定规则去淘汰缓冲中的数据,而不是无脑淘汰。好的缓存淘汰算法能够让我们淘汰掉不会用到的数据,保留可能会访问的数据。

FIFO 算法

FIFO(First-in, first-out,先进先出)算法是最简单的缓存淘汰算法,其原理正如它名字一样,最近使用过的对象放到缓存队列的末尾,队列头部保存的是最早使用的对象。

实现方式一般就是借助队列,先进来的永远排在最前边,当内存不足时将其删除。

image.png

特点:命中率低,复杂度低,速度快,存储成本低,使用价值低

LRU 算法

LRU(Least recently used,最近最少使用)算法是根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”,也就是热点数据。

实现方式一般就是借助链表完成,当存在于缓存中的数据被访问到,就会被重新插到链表尾端。

image.png

特点:命中率高,复杂度较低,存储成本一般,速度较低

LFU 算法

LFU(Least frequently used,最不经常使用)算法是基于最近访问频率来进行淘汰,它比 LRU 更精准地表示 key 的热度。

如果一个 key 长时间不被访问,只是刚刚偶然被用户访问了一下,那么在使用 LRU 算法的时候它是不容易被淘汰的,因为 LRU 算法认为当前这个 key 是很热的。而 LFU 是需要追踪最近一段时间的访问频率,如果某个 key 只是偶然被访问一次是不足以变得很热的,它需要在近期一段时间内被访问很多次才有机会被认为很热。

实现一般就是通过链表实现,并且需要维护该数据的热度,优先淘汰热度最低的。

image.png

特点:命中率高,复杂度较高,存储成本较高,速度较低

这种算法也是有一个致命弱点的,某一个数据就在开始被疯狂访问,但是后续再没有访问,缓存无法淘汰。

Redis中的缓存淘汰策略

淘汰策略就是对淘汰算法的一层封装,使我们可以对缓存中间件灵活设置淘汰规则,在某些缓存淘汰策略中会用到上述算法,或者是近似改造过的缓存淘汰算法。

  • noeviction 拒绝写请求 (DEL 请求可以使用),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略
  • volatile-lru 尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。
  • volatile-ttl 也是淘汰设置了过期时间的key,但是不依赖于 LRU算法,而是 key 的剩余时间 ttl 的值,ttl 越小越优先被淘汰。
  • volatile-random 也是淘汰设置了过期时间的key,不过淘汰的 key 是过期 key 集合中随机的 key。
  • allkeys-lru 这个策略要淘汰的是全体 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。
  • allkeys-random 这个策略要淘汰的是全体 key 集合,而不只是过期的 key 集合,不过淘汰的策略是随机的 key。

Redis 4.0版本新增LFU算法

  • volatile-lfu 对带过期时间的 key 进行 lfu 淘汰
  • allkeys-lfu 对所有的 key 执行 lfu 淘汰算法。

打开了基于LFU算法的淘汰策略之后,就可以使用 object freq指令获取对象的 lfu 计数值了。