LRU算法是什么
LRU算法全称是最近最少使用算法(Least Recently Use),基本思想是基于局部性原理的时间局部性:
- 如果一个信息正在被访问,那么在近期,它还有可能被访问
为什么要用LRU算法
LRU广泛被作用于缓存机制中,当缓存达到一定的上限后,需要从已有的数据中淘汰一部分数据,这种淘汰选择就是通过LRU算法完成的
基本的LRU算法
1、基于数组
实现: 为每一个缓存数据增加一个额外的属性 - 最近被访问时间戳,每当被访问后,都更新这个缓存数据的访问时间。当指定的数据空间已达到一定的阈值,遍历整个数组,淘汰时间戳最小的。
缺点: 维护额外的时间戳,耗费额外的空间;淘汰时,需要遍历整个数组。
2、基于有界双向链表。
实现: 访问数据时,数据不在链表中,则将数据插入链表头部,如果在链表中,则将数据移至链表头部。当数据空间满时,从队列尾部进行淘汰。
缺点: 查询、插入数据都需要遍历链表
3、基于双向链表和哈希表 (LinkedHashMap实现)
实现: 改进方案2,通过哈希表映射,将查询和插入的时间复杂度变成o(1)
LRU 算法优化(理论)
LRU-k
LRU-K算法有两个队列,一个是缓存队列,一个是数据访问历史队列。当访问一个数据时,首先先在访问历史队列中累加访问次数,当历史访问记录超过K次后,才将数据缓存至缓存队列,从而避免缓存队列被污染。同时访问历史队列中的数据可以按照LRU的规则进行淘汰
一般来讲,当K的值越大,则缓存的命中率越高,但是也会使得缓存难以被淘汰。综合来说,使用LRU-2的性能最优。
代码: //TODO
Two Queue
Two Queue可以说是LRU-2的一种变种,将数据访问历史改为FIFO队列。好处的明显的,FIFO更简易,耗用资源更少,但是相比LRU-2会降低缓存命中率。
代码: //TODO
Multi Queue
Multi Queue的实现则复杂的多,顾名思义,Multi Queue是由多个LRU队列组成的。每一个LRU队列都有一个相应的优先级,数据会根据访问次数计算出相应的优先级,并放在该队列中。
-
数据插入和访问:当数据首次插入时,会放入到优先级最低的Q0队列。当再次访问时,根据LRU的规则,会移至队列头部。当根据访问次数计算的优先级提升后,会将该数据移至更高优先级的队列的头部,并删除原队列的该数据。同样的,当该数据的优先级降低时,会移至低优先级的队列中。
-
数据淘汰:数据淘汰总是从最低优先级的队列的末尾数据进行,并将它加入到Q-history队列的头部。如果数据在Q-history数据中被访问,则重新计算该数据的优先级,并将它加入到相应优先级的队列中。否则就是按照LRU算法完全淘汰。
LRU使用场景
Memcached
Memcached 前面有文章已讲解过,思想与Multi Queue类似,详情看: Memcached内存管理模型与LRU算法优化
MySQL
MySQL change buffer缓存,使用的是类LRU-k的算法,读取新数据页页,不是放于LRU列表首部,而是放于5/8处,因为索引或数据扫描需要访问很多页,非活跃热点数据,放于首部,可能会移除真正的热点数据
Redis
Redis中的数据整体上是一个大的字典,Redis采用了基于采样的近似LRU算法,redis并没有去遍历所有对象,每个对象都记有最近访问时间戳。LRU算法也比较简单,Redis有一个全局的LRU时钟,每次随机取出5(maxmemory-samples默认参数)个redis对象,淘汰时间戳最小的。