写在前面:最近碰到操作系统中关于页面置换的一些问题,与此同时学习redis的过程中发现它的内存淘汰机制有些相似,在此一并记录下来。

首先是操作系统中的页面替换算法

最佳置换算法(OPT)

这是一种理想情况下的页面置换算法,但实际上是不可能实现的。该算法所选择淘汰的页面为后面永远不会使用到的页面或者是最长时间内不会使用的页面。但由于我们无法预知接下来执行的过程中哪些页面不会被访问到,所以这个算法无法实现,但是最佳页面置换算法可以用于对可实现算法的性能进行衡量比较。

先进先出置换算法(FIFO)

最简单的页面置换算法是先入先出(FIFO)法。这种算法的实质是,总是选择在主存中停留时间最长(即最老)的一页置换,即先进入内存的页,先退出内存。理由是:最早调入内存的页,其不再被使用的可能性比刚调入内存的可能性大。建立一个FIFO队列,收容所有在内存中的页。被置换页面总是在队列头上进行。当一个页面被放入内存时,就把它插在队尾上。
FIFO的一个缺点是,它有一种异常现象,即在增加存储块的情况下,反而使缺页中断率增加了。当然,导致这种异常现象的页面走向实际上是很少见的。这种现象又叫Belady异常。

最近最久未使用算法(LRU)

选择最近最长时间未访问过的页面予以淘汰,该算法认为过去一段时间内未访问过的页面,在最近的将来可能也不会被访问。该算法为每个页面设置一个访问字段,来记录页面自上次被访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰。
因实现LRU算法必须有大量硬件支持,还需要一定的软件开销。所以实际实现的都是一种简单有效的LRU近似算法。
一种LRU近似算法是最近未使用算法(Not Recently Used,NUR)。它在存储分块表的每一表项中增加一个引用位,操作系统定期地将它们置为0。当某一页被访问时,由硬件将该位置1。过一段时间后,通过检查这些位可以确定哪些页使用过,哪些页自上次置0后还未使用过。就可把该位是0的页淘汰出去,因为在之前最近一段时间里它未被访问过。

最少使用算法(LFU)

该算法首先淘汰一段时间内使用次数最少的页面,注意和LRU的区别,LRU是淘汰最长时间没使用的在采用最少使用置换算法时,应为在内存中的每个页面设置一个移位寄存器,用来记录该页面被访问的频率。该置换算法选择在之前时期使用最少的页面作为淘汰页。

Clock算法

LRU算法的近似实现

redis内存淘汰机制

redis作为其独特之处就在于它将数据放在内存中,大大提升了速度。但同时也带来这样一个问题,往往内存的大小有限,当内存剩余空间不足以继续添加数据时redis 内就会施行数据淘汰策略,清除一部分内容然后保证新的数据可以保存到内存中。
内存淘汰机制是为了更好的使用内存,用一定得miss来换取内存的利用率,保证redis缓存中保存的都是热点数据。

要实行淘汰机制首先对redis进行相关配置。
我们可以通过配置redis.conf中的maxmemory这个值来开启内存淘汰功能(该参数默认值为0即不设置内存上限),设置后当内存超出设定值后就会执行淘汰机制。
通过指定maxmemory-policy noeviction选项人为配置淘汰策略,淘汰策略主要有以下几种。

  1. voltile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
  4. allkeys-lru:从主数据集中挑选最近最少使用的数据淘汰
  5. allkeys-random:从主数据集中任意选择数据淘汰
  6. no-enviction:达到阈值后所有再申请内存的指令都不被允许

注:假设我们有一批键存储在Redis中,则有那么一个哈希表用于存储这批键及其值,如果这批键中有一部分设置了过期时间,那么这批键还会被存储到另外一个哈希表中,这个哈希表中的值对应的是键被设置的过期时间。设置了过期时间的数据集为主主数据集的子集。

各策略适用场景

  • 如果我们的应用对缓存的访问符合幂律分布(也就是存在相对热点数据),或者我们不太清楚我们应用的缓存访问分布状况,我们可以选择allkeys-lru策略。
  • 如果我们的应用对于缓存key的访问概率相等,则可以使用allkeys-random这个策略。
  • volatile-lru策略和volatile-random策略适合我们将一个Redis实例既应用于缓存和又应用于持久化存储的时候,然而我们也可以通过使用两个Redis实例来达到相同的效果,值得一提的是将key设置过期时间实际上会消耗更多的内存,因此我们建议使用allkeys-lru策略从而更有效率的使用内存。