微信公众号:IT一刻钟。 大型现实非严肃主义现场, 一刻钟与你分享优质技术架构与见闻,作一个有剧情的程序员。 关注可第一时间了解更多精彩内容,按期有福利相送哟。mysql
因为缓存的读取速度比非缓存要快上不少,因此在高性能场景下,系统在读取数据时,是首先从缓存中查找须要的数据,若是找到了则直接读取结果,若是找不到的话,则从内存或者硬盘中查找,再将查找到的结果存入缓存,以备下次使用。程序员
实际上,对于一个系统来讲,缓存的空间是有限且宝贵的,咱们不可能将全部的数据都放入缓存中进行操做,即使能够数据安全性也得不到保证,并且,若是缓存的数据量过大大,其速度也会变得愈来愈慢。 这个时候就须要考虑缓存的淘汰机制,可是淘汰哪些数据,又保留哪些数据,这是一个问题。若是处理不得当,就会形成“缓存污染”问题。算法
而缓存污染,是指系统将不经常使用的数据从内存移到缓存,形成经常使用数据的挤出,下降了缓存效率的现象。sql
LFU,英文名Least Frequently Used,字面意思就是最不常用的淘汰掉算法,是经过数据被访问的频率来判断一个数据的热点状况。其核心理念是“历史上这个数据被访问次数越多,那么未来其被访问的次数也多”。缓存
LFU中每一个数据块都有一个引用计数器,全部数据块按照引用数从大到小的排序。安全
步骤:微信
分析:因为是根据频数进行热点判断和淘汰,因此先天具有避免偶发性、周期性批量操做致使临时非热点数据大量涌入缓存,挤出热点数据的问题。 虽然具有这种先天优点,但依旧存在另外一种缓存污染问题,即历史热点数据污染当前热点数据,若是系统访问模式发生了改变,新的热点数据须要计数累加超过旧热点数据,才能将旧热点数据进行淘汰,形成热点效应滞后的问题。数据结构
复杂度与代价:每次操做都须要进行计数和排序,而且须要维护每一个数据块计数状况,会占用较高的内存与cpu。架构
一个小思考,根据LFU算法,如何以O(1)时间复杂度实现get和put操做缓存?性能
LFU-Aging是基于LFU的改进算法,目的是解决历史热点数据对当前热点数据的污染问题。有些数据在开始时使用次数不少,但之后就再也不使用,这类数据将会长时间留在缓存中,因此“除了访问次数外,还要考虑访问时间”,这也是LFU-Aging的核心理念。
虽然算法将时间归入了考量范围,但LFU-Aging并非直接记录数据的访问时间,而是增长了一个最大平均引用计数的阈值,而后经过当前平均引用计数来标识时间,换句话说,就是将当前缓存中的平均引用计数值看成当前的生命年代,当这个生命年代超过了预设的阈值,就会将当前全部计数值减半,造成指数衰变的生命年代。
分析:优势是当访问模式发生改变的时候,生命年代的指数衰变会使LFU-Aging可以更快的适用新的数据访问模式,淘汰旧的热点数据。
复杂度与代价:在LFU的基础上又增长平均引用次数判断和统计处理,对cpu的消耗更高,而且当平均引用次数超过指定阈值(Aging)后,还须要遍历每个数据块的引用计数,进行指数衰变。
Window-LFU顾名思义叫作窗口期LFU,区别于原义LFU中记录全部数据的访问历史,Window-LFU只记录过去一段时间内(窗口期)的访问历史,至关于给缓存设置了有效期限,过时数据再也不缓存。当须要淘汰时,将这个窗口期内的数据按照LFU算法进行淘汰。
分析:因为是维护一段窗口期的记录,数据量会比较少,因此内存占用和cpu消耗都比LFU要低。而且这段窗口期至关于给缓存设置了有效期,可以更快的适应新的访问模式的变化,缓存污染问题基本不严重。
复杂度与代价:维护一段时期内的数据访问记录,并对其排序。
LRU算法,英文名Least Recently Used,意思是最近最少使用的淘汰算法,根据数据的历史访问记录来进行淘汰数据,核心思想是“若是数据最近被访问过1次,那么未来被访问的几率会更高”,相似于就近优先原则。
步骤:
分析:偶发性的、周期性的批量操做会使临时数据涌入缓存,挤出热点数据,致使LRU热点命中率急剧降低,缓存污染状况比较严重。
复杂度与代价:数据结构复杂度较低;每次须要遍历链表,找到命中的数据块,而后将数据移到头部。
LRU-K是基于LRU算法的优化版,其中K表明最近访问的次数,从某种意义上,LRU能够看做是LRU-1算法,引入K的意义是为了解决上面所提到的缓存污染问题。其核心理念是从“数据最近被访问过1次”蜕变成“数据最近被访问过K次,那么未来被访问的几率会更高”。
LRU-K与LRU区别是,LRU-K多了一个数据访问历史记录队列(须要注意的是,访问历史记录队列并非缓存队列,因此是不保存数据自己的,只是保存对数据的访问记录,数据此时依旧在原始存储中),队列中维护着数据被访问的次数以及时间戳,只有当这个数据被访问的次数大于等于K值时,才会从历史记录队列中删除,而后把数据加入到缓存队列中去。
步骤:
分析:LRU-K下降了“缓存污染”带来的问题,命中率比LRU要高。实际应用中LRU-2是综合各类因素后最优的选择,LRU-3或者更大的K值命中率会高,但适应性差,一旦访问模式发生变化,须要大量的新数据访问才能将历史热点访问记录清除掉。
复杂度与代价:LRU-K队列是一个优先级队列。因为LRU-K须要记录那些被访问过,但尚未放入缓存的对象,致使内存消耗会不少。
URL-Two queues算法相似于LRU-2,不一样点在于URL-Two queues将LRU-2算法中的访问历史队列(注意这不是缓存数据的)改成一个FIFO缓存队列,即:URL-Two queues算法有两个缓存队列,一个是FIFO队列(First in First out,先进先出),一个是LRU队列。
当数据第一次访问时,URL-Two queues算法将数据缓存在FIFO队列里面,当数据第二次被访问时,则将数据从FIFO队列移到LRU队列里面,两个队列各自按照本身的方法淘汰数据。
步骤:
分析:URL-Two queues算法和LRU-2算法命中率相似,可是URL-Two queues会减小一次从原始存储读取或计算数据的操做。命中率要高于LRU。
复杂度与代价:须要维护两个队列,代价是FIFO和LRU代价之和。
emmmm...
这个名字实际上是我取的,大概是这种算法尚未被命名?固然,这是一个玩笑话。
我是在mysql底层实现里发现这个算法的,mysql在处理缓存淘汰时是用的这个方法,有点像URL-Two queues的变体,只是咱们只须要维护一个队列,而后将队列按照5:3的比例进行分割,5的那部分叫作young区,3的那部分叫作old区。具体是怎么样的请先看我把图画出来:
步骤:
分析:五三LRU算法算做是URL-Two queues算法的变种,原理实际上是同样的,只是把两个队列合二为一个队列进行数据的处理,因此命中率和URL-Two queues算法同样。
复杂度与代价:维护一个队列,代价较低,可是内存占用率和URL-Two queues同样。
Multi Queue算法根据访问频率将数据划分为多个队列,不一样的队列具备不一样的访问优先级,其核心思想是“优先缓存访问次数多的数据”。
Multi Queue算法将缓存划分为多个LRU队列,每一个队列对应不一样的访问优先级。访问优先级是根据访问次数计算出来的,例如: Q0,Q1....Qn表明不一样的优先级队列,Q-history表明从缓存中淘汰数据,但记录了数据的索引和引用次数。
步骤:
分析:Multi Queue下降了“缓存污染”带来的问题,命中率比LRU要高。
复杂度与代价:Multi Queue须要维护多个队列,且须要维护每一个数据的访问时间,复杂度比LRU高。Multi Queue须要记录每一个数据的访问时间,须要定时扫描全部队列,代价比LRU要高。虽然Multi Queue的队列看起来数量比较多,但因为全部队列之和受限于缓存容量的大小,所以这里多个队列长度之和和一个LRU队列是同样的,所以队列扫描性能也相近。
还有哪些优秀的缓存淘汰算法,或者你有更好的想法或问题,欢迎留言给我!
喜欢就点一下「当心心」呗~