上篇文章介绍了最常用的LRU算法及实现,本篇总结常用缓存淘汰算法,归总对比。
(Least Frequently Used):最近最低使用频次被淘汰
实现:通过count记录缓存数据的使用次数,数据块按照引用计数排序,计数相同则按照时间排序。
1. 新加入数据插入到队列尾部(因为引用计数为1);
2. 队列中的数据被访问后,引用计数增加,队列重新排序;
3. 当需要淘汰数据时,将已经排序的列表最后的数据块删除。
public int removeCache() {
Iterator> iterator = cacheMap.values().iterator();
int count = 0 ;
long minAccessCount = Long.MAX_VALUE ;
while(iterator.hasNext()){
CacheObject cacheObject = iterator.next();
if(cacheObject.isExpired() ){
iterator.remove();
count++ ;
continue ;
}else{
minAccessCount = Math.min(cacheObject.accessCount , minAccessCount) ;
}
}
if(count > 0 ) return count ;
if(minAccessCount != Long.MAX_VALUE ){
iterator = cacheMap.values().iterator();
while(iterator.hasNext()){
CacheObject cacheObject = iterator.next();
cacheObject.accessCount -= minAccessCount ;
if(cacheObject.accessCount <= 0 ){
iterator.remove();
count++ ;
}
}
}
return count;
}
缺点:
1、需要维护一个队列记录所有数据的访问记录,每个数据都需要维护引用计数。
2、需要记录所有数据的访问记录,内存消耗较高
2、需要基于引用计数重排序,性能消耗较高。
优点:
1、按照频率排序,LFU命中效率要优于LRU。
2、能避免因非热点数据介入导致的缓存命中率下降的问题。(先经过一次频率计算了)
LFU算法变种详情访问:LFU
相比LRU,LRU-K需要多维护一个队列,用于记录所有缓存数据被访问的历史。只有当数据的访问次数达到K次的时候,才将数据放入缓存。当需要淘汰数据时,LRU-K会淘汰第K次访问时间距当前时间最大的数据。
实现:优先级队列,算法复杂度和代价比较高
1. 数据第一次被访问,加入到访问历史列表;
2. 如果数据在访问历史列表里后没有达到K次访问,则按照一定规则(FIFO,LRU)淘汰;
3. 当访问历史队列中的数据访问次数达到K次后,将数据索引从历史队列删除,将数据移到缓存队列中,并缓存此数据,缓存队列重新按照时间排序;
4. 缓存数据队列中被再次访问后,重新排序;
5. 需要淘汰数据时,淘汰缓存队列中排在末尾的数据,即:淘汰“倒数第K次访问离现在最久”的数据。
优点:LRU-K具有LRU的优点(热点高频数据命中率高,命中率比LRU要高),同时能降低“缓存污染”问题
缺点:
1、适应性差,需要大量的数据访问才能将历史访问记录清除掉。
2、历史记录提高内存消耗:由于LRU-K还需要记录那些被访问过、但还没有放入缓存的对象,因此内存消耗会比LRU要多;当数据量很大的时候,内存消耗会比较可观。
3、时间排序拉高CPU消耗:LRU-K需要基于时间进行排序(可以需要淘汰时再排序,也可以即时排序),CPU消耗比LRU要高。
实际应用中LRU-2是综合各种因素后最优的选择,LRU-3或者更大的K值命中率会高
基本队列,不多说
Two queues算法类似于LRU-2,不同点在于2Q将LRU-2中的访问历史队列(注意这不是缓存数据的)改为一个FIFO缓存队列,即2Q算法=一个是FIFO队列,一个是LRU队列。命中率高于LRU
实现:当数据第一次访问时,2Q算法将数据缓存在FIFO队列里面,当数据第二次被访问时,则将数据从FIFO队列移到LRU队列里,两个队列各自按照自己的方法淘汰数据。
1. 新访问的数据插入到FIFO队列;
2. 如果数据在FIFO队列中一直没有被再次访问,则最终按照FIFO规则淘汰;
3. 如果数据在FIFO队列中被再次访问,则将数据移到LRU队列头部;
4. 如果数据在LRU队列再次被访问,则将数据移到LRU队列头部;
5. LRU队列淘汰末尾的数据。
优先缓存访问次数多的数据,根据访问频率将数据划分为多个队列,不同的队列具有不同的访问优先级
实现:
1. 新插入的数据放入Q0;
2. 每个队列按照LRU管理数据;
3. 当数据的访问次数达到一定次数,需要提升优先级时,将数据从当前队列删除,加入到高一级队列的头部;
4. 为了防止高优先级数据永远不被淘汰,当数据在指定的时间里访问没有被访问时,需要降低优先级,将数据从当前队列删除,加入到低一级的队列头部;
5. 需要淘汰数据时,从最低一级队列开始按照LRU淘汰;每个队列淘汰数据时,将数据从缓存中删除,将数据索引加入Q-history头部;
6. 如果数据在Q-history中被重新访问,则重新计算其优先级,移到目标队列的头部;
7. Q-history按照LRU淘汰数据的索引。
对比点 |
对比 |
命中率 |
LRU-2 > MQ(2) > 2Q > LRU |
复杂度 |
LRU-2 > MQ(2) > 2Q > LRU |
代价 |
LRU-2 > MQ(2) > 2Q > LRU |
实际应用中需要根据业务的需求和对数据的访问情况进行选择,并不是命中率越高越好。例如:虽然LRU看起来命中率会低一些,且存在”缓存污染“的问题,但由于其简单和代价小,实际应用中反而应用更多。
总结自:LRU算法 缓存淘汰策略