时间缓存算法
缓存这个东西就是为了提高运行速度的,由于缓存是在寸土寸金的内存里面,不是在硬盘
里面,所以容量是很有限的。LRU这个算法就是把最近一次使用时间离现在时间最远的数据删除掉。先说说List:每次访问一个元素后把这个元素放在
List一端,这样一来最远使用的元素自然就被放到List的另一端。缓存满了t的时候就把那最远使用的元素remove掉。但更实用的是
HashMap。因为List太慢,要删掉的数据总是位于List底层数组的第一个位置,删掉之后,后面的数据要向前补位。。所以复杂度是O(n),那就
用链表结构的LinkedHashMap呗~,LinkedHashMap默认的元素顺序是put的顺序,但是如果使用带参数的构造函数,那么
LinkedHashMap会根据访问顺序来调整内部 顺序。
LinkedHashMap的get()方法除了返回元素之外还可以把被访问的元素放到链表的底端,这样一来每次顶端的元素就是remove的元素。
构造函数如下:
public LinkedHashMap (int initialCapacity, float loadFactor, boolean accessOrder);
initialCapacity 初始容量
loadFactor 加载因子,一般是 0.75f
accessOrder false 基于插入顺序 true 基于访问顺序(get一个元素后,这个元素被加到最后,使用了LRU 最近最少被使用的调度算法)
来个例子吧:
import java.util.*;
class Test
{
public static void main(String[] args) throws Exception{
Map<Integer,Integer> map=new LinkedHashMap<>(10,0.75f,true);
map.put(9,3);
map.put(7,4);
map.put(5,9);
map.put(3,4);
//现在遍历的话顺序肯定是9,7,5,3
//下面访问了一下9,3这个键值对,输出顺序就变喽~
map.get(9);
for(Iterator<Map.Entry<Integer,Integer>> it=map.entrySet().iterator();it.hasNext();){
System.out.println(it.next().getKey());
}
}
}
输出
7
5
3
9
好玩吧~
下面开始实现LRU缓存喽~
import java.util.*;
//扩展一下LinkedHashMap这个类,让他实现LRU算法
class LRULinkedHashMap<K,V> extends LinkedHashMap<K,V>{
//定义缓存的容量
private int capacity;
private static final long serialVersionUID = 1L;
//带参数的构造器
LRULinkedHashMap(int capacity){
//调用LinkedHashMap的构造器,传入以下参数
super(16,0.75f,true);
//传入指定的缓存最大容量
this.capacity=capacity;
}
//实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素
@Override
public boolean removeEldestEntry(Map.Entry<K, V> eldest){
System.out.println(eldest.getKey() + "=" + eldest.getValue());
return size()>capacity;
}
}
//测试类
class Test{
public static void main(String[] args) throws Exception{
//指定缓存最大容量为4
Map<Integer,Integer> map=new LRULinkedHashMap<>(4);
map.put(9,3);
map.put(7,4);
map.put(5,9);
map.put(3,4);
map.put(6,6);
//总共put了5个元素,超过了指定的缓存最大容量
//遍历结果
for(Iterator<Map.Entry<Integer,Integer>> it=map.entrySet().iterator();it.hasNext();){
System.out.println(it.next().getKey());
}
}
}
输出结果如下
9=3
9=3
9=3
9=3
9=3
7
5
3
6
分析一下:使用带参数构造器,且启用LRU模式的LinkedHashMap会在每次有新元素加入的时候,判断当前储存元素是否超过了缓存上限,也就是执行
一次removeEldestEntry方法,看最后的遍历结果,发现果然把9删除了,LRU发挥作用了~
㈡ 什么是缓存及一级缓存,二级缓存
缓存:通常人们所说的Cache就是指缓存SRAM。 SRAM叫静态内存,“静态”指的是当我们将一笔数据写入SRAM后,除非重新写入新数据或关闭电源,否则写入的数据保持不变。
由于CPU的速度比内存和硬盘的速度要快得多,所以在存取数据时会使CPU等待,影响计算机的速度。SRAM的存取速度比其它内存和硬盘都要快,所以它被用作电脑的高速缓存(Cache)。
有了高速缓存,可以先把数据预写到其中,需要时直接从它读出,这就缩短了CPU的等待时间。高速缓存之所以能提高系统的速度是基于一种统计规律,主板上的控制系统会自动统计内存中哪些数据会被频繁的使用,就把这些数据存在高速缓存中,CPU要访问这些数据时,就会先到Cache中去找,从而提高整体的运行速度。一般说来,256K的高速缓存能使整机速度平均提高10%左右。
主板上通常都会提供256K到1M的缓存。在CPU内部也有高速缓存,如486CPU有8K的高速缓存,Pentium有16K的高速缓存。Pentium II有32K 一级缓存,AMD K6-2中有64K的一级Cache,AMD K6-3中有64K 的一级 Cache,和256K 的二级 Cache,Cyrix MII 中有64K的Cache。
为了区分它们,CPU内部的缓存叫内部高速缓存(Internal Cache)或一级高速缓存,主板上的缓存叫外部高速缓存(External Cache)或二级高速缓存。不过现在的Pentium II 的CPU已经将主板上的二级缓存封装在CPU的盒子中,AMD K6-3的CPU内部也集成了256K的二级Cache,对于这类CPU来说,主板上提供的已是三级缓存了。
许多人认为,“缓存”是内存的一部分
许多技术文章都是这样教授的
但是还是有很多人不知道缓存在什么地方,缓存是做什么用的
其实,缓存是CPU的一部分,它存在于CPU中
CPU存取数据的速度非常的快,一秒钟能够存取、处理十亿条指令和数据(术语:CPU主频1G),而内存就慢很多,快的内存能够达到几十兆就不错了,可见两者的速度差异是多么的大
缓存是为了解决CPU速度和内存速度的速度差异问题
内存中被CPU访问最频繁的数据和指令被复制入CPU中的缓存,这样CPU就可以不经常到象“蜗牛”一样慢的内存中去取数据了,CPU只要到缓存中去取就行了,而缓存的速度要比内存快很多
这里要特别指出的是:
1.因为缓存只是内存中少部分数据的复制品,所以CPU到缓存中寻找数据时,也会出现找不到的情况(因为这些数据没有从内存复制到缓存中去),这时CPU还是会到内存中去找数据,这样系统的速度就慢下来了,不过CPU会把这些数据复制到缓存中去,以便下一次不要再到内存中去取。
2.因为随着时间的变化,被访问得最频繁的数据不是一成不变的,也就是说,刚才还不频繁的数据,此时已经需要被频繁的访问,刚才还是最频繁的数据,现在又不频繁了,所以说缓存中的数据要经常按照一定的算法来更换,这样才能保证缓存中的数据是被访问最频繁的
3.关于一级缓存和二级缓存
为了分清这两个概念,我们先了解一下RAM
ram和ROM相对的,RAM是掉电以后,其中才信息就消失那一种,ROM在掉电以后信息也不会消失那一种
RAM又分两种,
一种是静态RAM,SRAM;一种是动态RAM,DRAM。前者的存储速度要比后者快得多,我们现在使用的内存一般都是动态RAM。
有的菜鸟就说了,为了增加系统的速度,把缓存扩大不就行了吗,扩大的越大,缓存的数据越多,系统不就越快了吗
缓存通常都是静态RAM,速度是非常的快,
但是静态RAM集成度低(存储相同的数据,静态RAM的体积是动态RAM的6倍),
价格高(同容量的静态RAM是动态RAM的四倍),
由此可见,扩大静态RAM作为缓存是一个非常愚蠢的行为,
但是为了提高系统的性能和速度,我们必须要扩大缓存,
这样就有了一个折中的方法,不扩大原来的静态RAM缓存,而是增加一些高速动态RAM做为缓存,
这些高速动态RAM速度要比常规动态RAM快,但比原来的静态RAM缓存慢,
我们把原来的静态ram缓存叫一级缓存,而把后来增加的动态RAM叫二级缓存。
一级缓存和二级缓存中的内容都是内存中访问频率高的数据的复制品(映射),它们的存在都是为了减少高速CPU对慢速内存的访问。
通常CPU找数据或指令的顺序是:先到一级缓存中找,找不到再到二级缓存中找,如果还找不到就只有到内存中找了
二级缓存(L2 CACHE)是处理器内部的一些缓冲存储器。它分内部和外部两种芯片:内部的芯片二级缓存运行速度与主频相同,而外部的二级缓存则只有主频的一半。
由于一级缓存容量的限制,为了再次提高CPU的运算速度,在CPU外部放置一高速存储器,即二级缓存。
二级缓存工作主频比较灵活,可与CPU同频,也可不同。CPU在读取数据时,先在一级缓存中寻找,再从二级缓存寻找,然后是内存,在后是外存储器。所以二级缓存对系统的影响是不容忽视的。
大量使用二级缓存带来的结果是处理器运行效率的提升和成本价格的大幅度不等比提升。
举例来说:服务器上用的至强处理器和普通的P4处理器其内核基本上是一样的,就是二级缓存不同。至强的二级缓存是2MB~16MB,P4的二级缓存是512KB,于是最便宜的至强也比最贵的P4贵,原因就在二级缓存不同。
㈢ Redis的LRU缓存淘汰算法实现
LRU, 最近最少使用 (Least Recently Used,LRU),经典缓存算法。
LRU会使用一个链表维护缓存中每个数据的访问情况,并根据数据的实时访问,调整数据在链表中的位置,然后通过数据在链表中的位置,表示数据是最近刚访问的,还是已有段时间未访问。
LRU会把链头、尾分别设为MRU端和LRU端:
LRU可分成如下情况:
case2图解:链表长度为5,从链表头部到尾部保存的数据分别是5,33,9,10,8。假设数据9被访问一次,则9就会被移动到链表头部,同时,数据5和33都要向链表尾部移动一位。
所以若严格按LRU实现,假设Redis保存的数据较多,还要在代码中实现:
最终导致降低Redis访问性能。
所以,无论是为节省内存 or 保持Redis高性能,Redis并未严格按LRU基本原理实现,而是 提供了一个近似LRU算法实现 。
Redis的内存淘汰机制是如何启用近似LRU算法的?redis.conf中的如下配置参数:
所以,一旦设定maxmemory选项,且将maxmemory-policy配为allkeys-lru或volatile-lru,近似LRU就被启用。allkeys-lru和volatile-lru都会使用近似LRU淘汰数据,区别在于:
Redis如何实现近似LRU算法的呢?
近似LRU算法仍需区分不同数据的访问时效性,即Redis需知道数据的最近一次访问时间。因此,有了LRU时钟:记录数据每次访问的时间戳。
Redis对每个KV对中的V,会使用个redisObject结构体保存指向V的指针。那redisObject除记录值的指针,还会使用24 bits保存LRU时钟信息,对应的是lru成员变量。这样,每个KV对都会把它最近一次被访问的时间戳,记录在lru变量。
redisObject定义包含lru成员变量的定义:
每个KV对的LRU时钟值是如何计算的?Redis Server使用一个实例级别的全局LRU时钟,每个KV对的LRU time会根据全局LRU时钟进行设置。
这全局LRU时钟保存在Redis全局变量server的成员变量 lruclock
当Redis Server启动后,调用initServerConfig初始化各项参数时,会调用getLRUClock设置lruclock的值:
于是,就得注意, 若一个数据前后两次访问的时间间隔 1s,那这两次访问的时间戳就是一样的! 因为LRU时钟精度就是1s,它无法区分间隔小于1秒的不同时间戳!
getLRUClock函数将获得的UNIX时间戳,除以LRU_CLOCK_RESOLUTION后,就得到了以LRU时钟精度来计算的UNIX时间戳,也就是当前的LRU时钟值。
getLRUClock会把LRU时钟值和宏定义LRU_CLOCK_MAX(LRU时钟能表示的最大值)做与运算。
所以默认情况下,全局LRU时钟值是以1s为精度计算得UNIX时间戳,且是在initServerConfig中进行的初始化。
那Redis Server运行过程中,全局LRU时钟值是如何更新的?和Redis Server在事件驱动框架中,定期运行的时间事件所对应的serverCron有关。
serverCron作为时间事件的回调函数,本身会周期性执行,其频率值由redis.conf的 hz配置项 决定,默认值10,即serverCron函数会每100ms(1s/10 = 100ms)运行一次。serverCron中,全局LRU时钟值就会按该函数执行频率,定期调用getLRUClock进行更新:
这样,每个KV对就能从全局LRU时钟获取最新访问时间戳。
对于每个KV对,它对应的redisObject.lru在哪些函数进行初始化和更新的呢?
对于一个KV对,其LRU时钟值最初是在这KV对被创建时,进行初始化设置的,这初始化操作在 createObject函数 中调用,当Redis要创建一个KV对,就会调用该函数。
createObject除了会给redisObject分配内存空间,还会根据maxmemory_policy配置,初始化设置redisObject.lru。
LRU_CLOCK返回当前全局LRU时钟值。因为一个KV对一旦被创建,就相当于有了次访问,其对应LRU时钟值就表示了它的访问时间戳:
那一个KV对的LRU时钟值又是何时再被更新?
只要一个KV对被访问,其LRU时钟值就会被更新!而当一个KV对被访问时,访问操作最终都会调用 lookupKey 。
lookupKey会从全局哈希表中查找要访问的KV对。若该KV对存在,则lookupKey会根据maxmemory_policy的配置值,来更新键值对的LRU时钟值,也就是它的访问时间戳。
而当maxmemory_policy没有配置为LFU策略时,lookupKey函数就会调用LRU_CLOCK函数,来获取当前的全局LRU时钟值,并将其赋值给键值对的redisObject结构体中的lru变量
这样,每个KV对一旦被访问,就能获得最新的访问时间戳。但你可能好奇:这些访问时间戳最终是如何被用于近似LRU算法进行数据淘汰的?
Redis之所以实现近似LRU,是为减少内存资源和操作时间上的开销。
近似LRU主要逻辑在performEvictions。
performEvictions被evictionTimeProc调用,而evictionTimeProc函数又是被processCommand调用。
processCommand,Redis处理每个命令时都会调用:
然后,isSafeToPerformEvictions还会再次根据如下条件判断是否继续执行performEvictions:
一旦performEvictions被调用,且maxmemory-policy被设置为allkeys-lru或volatile-lru,近似LRU就被触发执行了。
执行可分成如下步骤:
调用getMaxmemoryState评估当前内存使用情况,判断当前Redis Server使用内存容量是否超过maxmemory配置值。
若未超过maxmemory ,则返回C_OK,performEvictions也会直接返回。
getMaxmemoryState评估当前内存使用情况的时候,若发现已用内存超出maxmemory,会计算需释放的内存量。这个释放内存大小=已使用内存量-maxmemory。
但已使用内存量并不包括用于主从复制的复制缓冲区大小,这是getMaxmemoryState通过调用freeMemoryGetNotCountedMemory计算的。
而若当前Server使用的内存量超出maxmemory上限 ,则performEvictions会执行while循环淘汰数据释放内存。
为淘汰数据,Redis定义数组EvictionPoolLRU,保存待淘汰的候选KV对,元素类型是evictionPoolEntry结构体,保存了待淘汰KV对的空闲时间idle、对应K等信息:
这样,Redis Server在执行initSever进行初始化时,会调用evictionPoolAlloc为EvictionPoolLRU数组分配内存空间,该数组大小由EVPOOL_SIZE决定,默认可保存16个待淘汰的候选KV对。
performEvictions在淘汰数据的循环流程中,就会更新这个待淘汰的候选KV对集合,即EvictionPoolLRU数组。
performEvictions调用evictionPoolPopulate,其会先调用dictGetSomeKeys,从待采样哈希表随机获取一定数量K:
于是,dictGetSomeKeys返回采样的KV对集合。evictionPoolPopulate根据实际采样到的KV对数量count,执行循环:调用estimateObjectIdleTime计算在采样集合中的每一个KV对的空闲时间:
接着,evictionPoolPopulate遍历待淘汰的候选KV对集合,即EvictionPoolLRU数组,尝试把采样的每个KV对插入EvictionPoolLRU数组,取决于如下条件之一:
有一成立,evictionPoolPopulate就能把采样KV对插入EvictionPoolLRU数组。等所有采样键值对都处理完后,evictionPoolPopulate函数就完成对待淘汰候选键值对集合的更新了。
接下来,performEvictions开始选择最终被淘汰的KV对。
因evictionPoolPopulate已更新EvictionPoolLRU数组,且该数组里的K,是按空闲时间从小到大排好序了。所以,performEvictions遍历一次EvictionPoolLRU数组,从数组的最后一个K开始选择,若选到的K非空,就把它作为最终淘汰的K。
该过程执行逻辑:
一旦选到被淘汰的K,performEvictions就会根据Redis server的惰性删除配置,执行同步删除或异步删除:
至此,performEvictions就淘汰了一个K。若此时释放的内存空间还不够,即没有达到待释放空间,则performEvictions还会 重复执行 前面所说的更新待淘汰候选KV对集合、选择最终淘汰K的过程,直到满足待释放空间的大小要求。
performEvictions流程:
近似LRU算法并未使用耗时且耗空间的链表,而使用 固定大小的待淘汰数据集合 ,每次随机选择一些K加入待淘汰数据集合。
最后,按待淘汰集合中K的空闲时间长度,删除空闲时间最长的K。
根据LRU算法的基本原理,发现若严格按基本原理实现LRU算法,则开发的系统就需要额外内存空间保存LRU链表,系统运行时也会受到LRU链表操作的开销影响。
而Redis的内存资源和性能都很重要,所以Redis实现近似LRU算法:
一个算法的基本原理和算法的实际执行,在系统开发中会有一定折中,需综合考虑所开发的系统,在资源和性能方面的要求,以避免严格按照算法实现带来的资源和性能开销。
㈣ 常用缓存替换算法的理解
目前已知的常用缓存替换算法有:随机法、先入先出法FIFO、 最近最少使用法LRU 、最不经常使用法LFU等。缓存替换算法有很多种,FIFO是最简单的,LRU是最常用的,最优缓存替换算法则是命中率最佳的。因为我们无法预知数据的未来访问模式,通常最优替换算法是无法实现的。
LRU是最常用的缓存替换算法,当前的很多论文,甚至顶会论文都是基于LRU算法进行缓存替换算法的改进。作为最通用的替换策略,LRU算法适合具有良好时间局部性的负载,能较好的适应程序负载的动态变化。LRU算法利用历史信息预测数据的使用情况,将最久没有使用的块替换,良好的反映程序的局部性。
㈤ LRU 缓存淘汰算法
当要缓存某个数据的时候,先在链表中查找这个数据。如果没有找到,则直接将数据放到链表的尾部;如果找到了,我们就把它移动到链表的尾部,然后淘汰头部数据。
因为查找数据需要遍历链表,所以单纯用链表实现的 LRU 缓存淘汰算法的时间复杂很高,是 O(n)。如果我们将散列表和双向链表两种数据结构组合使用,可以将这三个操作的时间复杂度都降低到 O(1)。
因为我们的散列表是通过链表法解决散列冲突的,所以每个结点会在两条链中。一个链是刚刚我们提到的双向链表,另一个链是散列表中的拉链。前驱和后继指针是为了将结点串在双向链表中,hnext 指针是为了将结点串在散列表的拉链中。
这整个过程涉及的查找操作都可以通过散列表来完成。其他的操作,比如删除头结点、链表尾部插入数据等,通过双向链表都可以在 O(1) 的时间复杂度内完成。所以,这三个操作的时间复杂度都是 O(1)。
至此,我们就通过散列表和双向链表的组合使用,实现了一个高效的、支持 LRU 缓存淘汰算法的缓存系统原型。
散列表中数据是经过散列函数打乱之后无规律存储的,但是 LinkedHashMap可以 做到按照数据的插入顺序来存储。
每次调用 put() 函数,往 LinkedHashMap 中添加数据的时候,都会将数据添加到链表的尾部,再次将键值为 3 的数据放入到 LinkedHashMap 的时候,会先查找这个键值是否已经有了,然后,再将已经存在的 (3,11) 删除,并且将新的 (3,26) 放到链表的尾部,访问到 key 为 5 的数据的时候,我们将被访问到的数据移动到链表的尾部。
所以按照访问时间排序的 LinkedHashMap 本身就是一个支持 LRU 缓存淘汰策略的缓存系统。
散列表这种数据结构虽然支持非常高效的数据插入、删除、查找操作,但是散列表中的数据都是通过散列函数打乱之后无规律存储的。也就说,它无法支持按照某种顺序快速地遍历数据。如果希望按照顺序遍历散列表中的数据,那我们需要将散列表中的数据拷贝到数组中,然后排序,再遍历。
因为散列表是动态数据结构,不停地有数据的插入、删除,所以每当我们希望按顺序遍历散列表中的数据的时候,都需要先排序,那效率势必会很低。为了解决这个问题,我们将散列表和链表(或者跳表)结合在一起使用。
㈥ 什么叫缓存
所谓的缓存,就是将程序或系统经常要调用的对象存在内存中,一遍其使用时可以快速调用,不必再去创建新的重复的实例。这样做可以减少系统开销,提高系统效率。
1、通过文件缓存;顾名思义文件缓存是指把数据存储在磁盘上,不管你是以XML格式,序列化文件DAT格式还是其它文件格式;
2、内存缓存;也就是创建一个静态内存区域,将数据存储进去,例如我们B/S架构的将数据存储在Application中或者存储在一个静态Map中。
3、本地内存缓存;就是把数据缓存在本机的内存中。
4、分布式缓存机制;可能存在跨进程,跨域访问缓存数据
对于分布式的缓存,此时因为缓存的数据是放在缓存服务器中的,或者说,此时应用程序需要跨进程的去访问分布式缓存服务器。
(6)时间缓存算法扩展阅读
当我们在应用中使用跨进程的缓存机制,例如分布式缓存memcached或者微软的AppFabric,此时数据被缓存在应用程序之外的进程中。
每次,当我们要把一些数据缓存起来的时候,缓存的API就会把数据首先序列化为字节的形式,然后把这些字节发送给缓存服务器去保存。
同理,当我们在应用中要再次使用缓存的数据的时候,缓存服务器就会将缓存的字节发送给应用程序,而缓存的客户端类库接受到这些字节之后就要进行反序列化的操作了,将之转换为我们需要的数据对象。
㈦ 缓存替换算法是什么
Cache替换算法是影响代理缓存系统性能的一个重要因素,一个好的Cache替换算法可以产生较高的命中率。目前已经提出的算法可以划分为以下三类: (1)传统替换算法及其直接演化,其代表算法有:①LRU(Least Recently Used)算法:将最近最少使用的内容替换出Cache;②LFU(Lease Frequently Used)算法:将访问次数最少的内容替换出Cache;③Pitkow/Recker[10]提出了一种替换算法:如果Cache中所有内容都是同一天被缓存的,则将最大的文档替换出Cache,否则按LRU算法进行替换。 (2)基于缓存内容关键特征的替换算法,其代表算法有:①Size[10]替换算法:将最大的内容替换出Cache;②LRU— MIN[11]替换算法:该算法力图使被替换的文档个数最少。设待缓存文档的大小为S,对Cache中缓存的大小至少是S的文档,根据LRU算法进行替换;如果没有大小至少为S的对象,则从大小至少为S/2的文档中按照LRU算法进行替换;③LRU—Threshold[11] 替换算法:和LRU算法一致,只是大小超过一定阈值的文档不能被缓存 ...
㈧ LFU缓存策略算法实现
LFU(Least Frequently Used),最近最少使用策略,也就是说在一段时间内,数据被使用频次最少的,优先被淘汰。
它是一种用于管理计算机内存的缓存算法,采用LFU算法的最简单方法是为每个加载到缓存的块分配一个计数器。每次引用该块时,计数器将增加一。当缓存达到容量并有一个新的内存块等待插入时,系统将搜索计数器最低的块并将其从缓存中删除。
我们可以在链表的开始插入元素,然后每插入一次计数一次,接着按照次数重新排序链表,如果次数相同的话,按照插入时间排序,然后从链表尾部选择淘汰的数据,这是一个最简单的LFU实现的思路。
这里实现了comparable接口,覆写了compare方法,这里 的主要作用就是在排序的时候需要用到,在compare方法里面我们首先比较节点的访问次数,在访问次数相同的情况下比较节点的访问时间,这里是为了 在排序方法里面通过比较key来选择淘汰的key。
这里采用linkedHashMap的主要原因是维护key的顺序。
添加元素的逻辑主要是先从缓存中根据key获取节点,如果获取不到,证明是新添加的元素,然后和容量比较,大于预定容量的话,需要找出count计数最小(计数相同的情况下,选择时间最久)的节点,然后移除掉那个。
我们知道内存淘汰算法,常见的除了LFU还有LRU,LRU和LFU侧重点不同,LRU主要体现在对元素的使用时间上,而LFU主要体现在对元素的使用频次上。
LFU的缺陷是:在短期的时间内,对某些缓存的访问频次很高,这些缓存会立刻晋升为热点数据,而保证不会淘汰,这样会驻留在系统内存里面。而实际上,这部分数据只是短暂的高频率访问,之后将会长期不访问,瞬时的高频访问将会造成这部分数据的引用频率加快,而一些新加入的缓存很容易被快速删除,因为它们的引用频率很低。
我们要根据业务场景选择合适的内存淘汰策略。
参考资料: https://mp.weixin.qq.com/s/qq3sieAkfvUpOS48Mwlczw