传统的 LRU 算法法无法避免的两个问题

 首先了解一下传统 LRU 是如何管理内存数据的?

 传统的LRU(最近最少使用)算法是一种缓存淘汰算法,主要用于管理内存数据。当内存空间不足时,LRU算法会淘汰最近最少使用的数据。

以下是传统LRU算法的管理步骤:

1. 维护一个双向链表:该链表中每个节点表示一个被缓存的数据块,并按照数据块最近一次被访问的时间顺序进行排序,即越靠近链表头表示最近被访问,越靠近链表尾表示最久未被访问。

2. 当一块新的数据进入缓存时,将该数据添加到链表头。

3. 当需要淘汰一块数据时,将链表尾(最久未被访问)的数据块进行淘汰,并从链表中删除该节点。

4. 当一块数据被访问时,将该数据块移到链表头,表示该数据块是最近被访问的。

5. 当访问的数据块不在链表中时,说明该数据已经被淘汰,需要从磁盘或其他存储设备中获取数据。

传统LRU算法的缺点是,在实际应用中,每个数据块的访问时间分布可能不均匀,导致一些数据块虽然访问次数较少,但是被频繁访问。这种情况下,LRU算法可能会误判并将这些数据块淘汰,从而影响查询速度。针对这种情况,有一些改进的LRU算法(如2Q算法、ARC算法)进行了优化。

 传统的 LRU 算法法无法避免的两个问题:

  • 1.预读失效导致缓存命中率下降;
  • 2.缓存污染导致缓存命中率下降;

 什么是预读失效?

因为计算机的局部性原理,每次从内存读入缓冲区都会多读一些,因为这些内存临近的内容很有可能被再次读到。但是如果这些内容不被使用,预读的作用就消失了,而且会占用热点数据的位置

简单点说就是有人占着位置不干活,太可恶啦!这样效率就打了折扣。

什么是缓存污染?

由于缓存的读取速度比非缓存要快上很多,所以在高性能场景下,系统在读取数据时,是首先从缓存中查找需要的数据,如果找到了则直接读取结果,如果找不到的话,则从内存或者硬盘中查找,再将查找到的结果存入缓存,以备下次使用。
实际上,对于一个系统来说,缓存的空间是有限且宝贵的,我们不可能将所有的数据都放入缓存中进行操作,即便可以数据安全性也得不到保证,而且,如果缓存的数据量过大大,其速度也会变得越来越慢。
这个时候就需要考虑缓存的淘汰机制,但是淘汰哪些数据,又保留哪些数据,这是一个问题。如果处理不得当,就会造成“缓存污染”问题。
而缓存污染,是指系统将不常用的数据从内存移到缓存,造成常用数据的挤出,降低了缓存效率的现象。

举个例子:一家公司,员工永远是100人(老板穷招不起更多人),这时有人加入公司就必须有人退出公司,然后公司招了一个应届毕业生,而炒了一个10年开发经验的Java大佬(不考虑薪资),对于公司来说绝对是一个损失!


 解决方案如下:

为了避免「预读失效」造成的影响,Linux 和 MySQL 对传统的 LRU 链表做了改进:

  • Linux 操作系统实现两个了 LRU 链表:活跃 LRU 链表(active list)和非活跃 LRU 链表(inactive list)
  • MySQL Innodb 存储引擎是在一个 LRU 链表上划分来 2 个区域:young 区域 和 old 区域

但是如果还是使用「只要数据被访问一次,就将数据加入到活跃 LRU 链表头部(或者 young 区域)」这种方式的话,那么还存在缓存污染的问题

 为了避免「缓存污染」造成的影响,Linux 操作系统和 MySQL Innodb 存储引擎分别提高了升级为热点数据的门槛:

  • Linux 操作系统:在内存页被访问第二次的时候,才将页从 inactive list 升级到 active list 里。
  • MySQL Innodb:在内存页被访问第二次的时候,并不会马上将该页从 old 区域升级到 young 区域,因为还要进行停留在 old 区域的时间判断
    • 如果第二次的访问时间与第一次访问的时间在 1 秒内(默认值),那么该页就不会被从 old 区域升级到 young 区域;
    • 如果第二次的访问时间与第一次访问的时间超过 1 秒,那么该页就从 old 区域升级到 young 区域;

通过提高了进入 active list (或者 young 区域)的门槛后,就很好了避免缓存污染带来的影响。

猜你喜欢

转载自blog.csdn.net/m0_62600503/article/details/131291858