基本概念
Least Recently Used,即最近最少使用,是内存管理的一种页面置换算法。算法的核心是:如果一个数据在最近一段时间内没有被访问到,那么它在将来被访问的可能性也很小。换言之,当内存达到极限时,应该把内存中最久没有被访问的数据淘汰掉。
一般实现
理论上只需要一个列表,数据被使用时,不在LRU列表中就放到头部,在就提到头部,相应淘汰尾部的数据即可。或者数据使用时记录使用时间戳,每次比对时间戳,最小的淘汰。
使用hashmap可以使时间复杂度由O(n)降低至O(1)。
使用python 双向链表+hashmap
from collections import OrderedDict
class LRUDict(OrderedDict):
def __init__(self, capacity):
self.capacity = capacity
self.items = OrderedDict()
def __setitem__(self, key, value):
old_value = self.items.get(key)
if old_value is not None:
self.items.pop(key)
self.items[key] = value
elif len(self.items) < self.capacity:
self.items[key] = value
else:
self.items.popitem(last=False)
self.items[key] = value
def __getitem__(self, key):
value = self.items.get(key)
if value is not None:
self.items.pop(key)
self.items[key] = value
return value
def __repr__(self):
return repr(self.items)
变种
一种策略不是万能的,不同的场景决定了不同的用法
redis中的LRU
场景
redis占用内存达到极限时,就需要将一部分数据写入磁盘,此时就需要一种策略来决定的是哪一部分写入磁盘。
特点
redis放在内存中,双向链表+hashmap 这样占用比较大的结构比较奢侈。redis需要采用更节省空间的实现方式。
实现
Redis 在实现上引入了一个 LRU 时钟来代替 unix 时间戳,每个对象的每次被访问都会记录下当前服务器的 LRU 时钟,然后用服务器的 LRU 时钟减去对象本身的时钟,得到的就是这个对象没有被访问的时间间隔(也称空闲时间),空闲时间最大的就是需要淘汰的对象。redis则随机选取 若干(server.maxmemory_samples配置) 个 key,然后比较它们的lru访问时间,然后淘汰最近最久没有访问的key。(假设maxmemory_samples为最大即当前key数量,每次比较所有key的LRU访问时间,此时算法由回到了朴素的LRU算法)
innodb中的LRU
特点
sql经常会有全表扫描这样的查询,如果使用朴素LRU算法,缓冲池的页会被经常刷完,起不到缓冲的作用。
实现
加入midpoint,数据进来先加入到midpoint,在根据设置的时间提到LRU列表头。
其他淘汰算法
LFU算法
LFU(Least Frequently Used ,最近最少使用算法)也是一种常见的缓存算法。
顾名思义,LFU算法的思想是:如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最小频率访问的数据最先被淘汰。
算法实现策略:考虑到 LFU 会淘汰访问频率最小的数据,我们需要一种合适的方法按大小顺序维护数据访问的频率。LFU 算法本质上可以看做是一个 top K 问题(K = 1),即选出频率最小的元素,因此我们很容易想到可以用二项堆来选择频率最小的元素,这样的实现比较高效。最终实现策略为小顶堆+哈希表。
redis4.0提供LFU算法。LFU算法如果有数据短时高频访问,则会一直留在内存中