MySQL与Redis中对LRU算法的使用

637 阅读4分钟

MySQL与Redis中对LRU算法的使用

LRU也称为最近最少未使用算法,作为最常用的内存淘汰算法,在主流的系统中都可以见到相应的使用场景,而在MySQL与Redis中也有使用,可以说都是用来对存储空间进行管理,及时淘汰更新数据,提高存储空间利用率。

Redis内存淘汰机制

在redis.conf中有一行参数用来配置内存淘汰策略的

maxmemory-policy volatile-LRU

volatile-LRU:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 allkeys-LRU:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 no-enviction(驱逐):禁止驱逐数据,新写入操作会报错 可以看得出来如果使用了 volatile-LRU策略,则将会使用LRU数据结构来对内存中的数据进行清理。

Redis的LRU具体实现:

传统的LRU是使用栈的形式,每次都将最新使用的移入栈顶,但是用栈的形式会导致执行select *的时候大量非热点数据占领头部数据,所以需要改进。Redis每次按key获取一个值的时候,都会更新value中的LRU字段为当前秒级别的时间戳。Redis初始的实现算法很简单,随机从dict中取出五个key,淘汰一个LRU字段值最小的。在3.0的时候,又改进了一版算法,首先第一次随机选取的key都会放入一个pool中(pool的大小为16),pool中的key是按LRU大小顺序排列的。接下来每次随机选取的keyLRU值必须小于pool中最小的LRU才会继续放入,直到将pool放满。放满之后,每次如果有新的key需要放入,需要将pool中LRU最大的一个key取出。淘汰的时候,直接从pool中选取一个LRU最小的值然后将其淘汰。

MySQL中InnoDB引擎中对LRU的使用场景

image-20210415142630548

缓冲池是一个很大的内存区域,其中存放各种类型的页。数据库中的缓冲池是通过LRU算法来进行管理的。因为缓冲池中的页还可能会被分配给自适应哈希索引、Lock信息、 Insert Buffer等页,而这部分页不需要LRU算法进行维护,因此不存在于LRU列表中。

LRU存储过程

LRU列表用来管理已经读取的页,但当数据库刚启动时,LRU列表是空的,即没有任何的页。这时页都存放在Free列表中,当需要从缓冲池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表中删除,放入到LRU列表中。否则,根据LRU算法,淘汰LRU列表末尾的页,将该内存空间分配给新的页。

最频繁使用的页在LRU列表的前端,而最少使用的页在LRU列表的尾端。当缓冲池不能存放新读取到的页时,将首先释放LRU列表中尾端的页。在 InnoDB存储引擎中,缓冲池中页的大小默认为16KB,同样使用LRU算法对缓冲池进行管理。稍有不同的是InnoDB存储引擎对传统的LRU算法做了一些优化在InnoDB的存储引擎中,LRU列表中还加入了 midpoint位置。新读取到的页,虽然是最新访问的页,但并不是直接放入到LRU列表的首部,而是放入到LRU列表的 midpoint 位置。这个算法在 InnoDB存储引擎下称为 midpoint insertion strategy。在默认配置下,该位置在LRU列表长度的58处。 midpoint 1位置可由参数 innodb old blocks pct:控制。

为什么不采用朴素的LRU算法,直接将读取的页放入到LRU列表的首部呢,这是因为若直接将读取到的页放入到LRU的首部,那么某些SQL操作可能会使缓冲池中的页被刷新出,从而影响缓冲池的效率。常见的这类操作为索引或数据的扫描操作。这类操作需要访问表中的许多页,甚至是全部的页,而这些页通常来说又仅在这次査询操作中需要,并不是活跃的热点数据。如果页被放入LRU列表的首部,那么非常可能将所需要的热点数据页从LRU列表中移除,而在下一次需要读取该页时,InnoDB存储引擎需要再次访问磁盘。