(转)https://www.51cto.com/article/608285.html
一、概述
1、什么是LRU
所谓的LRU(Least recently used)算法的基本概念是当内存的剩余的可用空间不够时,缓冲区尽可能的先保留使用者最常使用的数据。
换句话说就是优先清除”较不常使用的数据”,并释放其空间。
之所以”较不常使用的数据”要用引号是因为这里判断所谓的较不常使用的标准是人为的、不严格的,所谓的MRU(Most recently used)算法的意义正好和LRU算法相反。
2、LRU链:
任何缓存的大小都是有限制的,并且总不如被缓存的数据多。就像Buffer cache用来缓存数据文件,数据文件的大小远远超过Buffer cache。
当缓存中已经没有空闲内存块时,如果新的数据要求进入缓存,就只有从缓存中原来的数据中选出一个牺牲者,用新进入缓存的数据覆盖这个牺牲者。这个牺牲者的选择,是很重要的。
缓存是为了数据可以重用,因此,通常应该挑选缓存中最没有可能被重用的块当作牺牲者。
牺牲者的选择,从CPU的L1、L2缓存,到共享池、Buffer cache池,绝大多数的缓存池都是采用著名的LRU算法,不过在Oracle中,Oracle采用了经过改进的LRU算法。
具体的算法它没有公布,不过LRU算法总的宗旨就是“最近最少”,其意义是将最后被访问的时间距现在最远的内存块作为牺牲者。
比如说,现在有三个内存块,分别是A、B、C,A被访问过10次,最后一次访问是在10:20;B被访问过15次,最后一次访问是10:18;C也被访问10次,最后一次被访问是在10:22。当需要选择牺牲者时,B访问次数最多,牺牲者肯定不是它。A、C访问次数一样,但A在10:20被访问,而C在10:22被访问,A最后被访问的更早些,牺牲者就是A。
3、LRU功能详解
为了实现LRU的功能,Oracle在Buffer cache中创建了一个LRU链表,Oracle将Buffer cache中所有内存块,按照访问次数、访问时间排序串在链表中。链表的两头我们分别叫做热端与冷端, 如下图:
Oracle为内存中的每个块都添加了一个记录访问次数的标志位,假设图中每个块的访问次数如下:
上面就是Oracle中Buffer cache管理LRU的原理。按照这种方式运作,Oracle可以把常用的块尽量长的保持在Buffer cache中。而且,每有新块进入Buffer cache,Oracle都会从冷端头处,从右向左搜索牺牲块。因为越靠近冷端,块的访问次数有可能越少、最后的访问时间离现在最远。
4、脏块与脏LRU链:
Oracle中修改块的规则是只对Buffer cache中的块进行修改,并不直接修改磁盘中的块。如果要修改的块不在Buffer cache中,Oracle会先将它读入Buffer cache,再在Buffer cache中进行修改。
脏块在被写回磁盘前,也就是在它还是脏块时,它是不能被覆盖的,因为,脏块含有用户修改过的数据,而这些数据还没被写到磁盘,如果此时覆盖了脏块,用户的修改结果将会丢失。
5、总结
从LRU链与脏LRU链的原理,我们可以发现Oracle把很多工作,都留到了在LRU的冷端搜索牺牲者时。当块的访问次数增加的超过2时,块在LUR链的位置不变;当块变脏时,块的LRU链位置也不变。只有当从LRU的冷端搜索牺牲者时,才会将发现的脏块移到脏LRU链,将访问次数超过2的,插入到热端,这就是Oracle改进了的LRU算法。
当访问次数大于2的块太多,或者脏块太多,反正这些块都是不能覆盖的,Oracle不得不移动它们到它们该去的位置。当碰到的这样的块超过LRU中总块数的40%时,也就是说搜索了一小半LRU链,还是没有发现可以覆盖的牺牲者,Oracle就不在找了,它会唤醒DBWn刷新脏块。在DBWn刷新期间的等待,就会被记入到free buffer waits事件中。
在寻找牺牲者过程中发现脏块,Oracle将其移动到脏LRU链,但是脏LRU链中脏块数目达到限制,DBWn被唤醒开始刷新脏块,Oracle必须等待刷新脏块完毕,才能再继续寻找牺牲者,这其间的等待事件,也会被记入free buffer inspected。
总之,free buffer waits事件发生的主要原因就是在LRU中寻找牺牲者的时间过长。如果这个等待事件频繁出现,说明Buffer cache中脏块太多了,这通常是DBWn写刷新速度慢造成的。