Redis、Memcached、Guava、Ehcache中的算法

缓存那些事,一是内存爆了要用LRU(最近最少使用)、LFU(最少访问次数)、FIFO的算法清理一些;二是设置了超时时间的键过时便要删除,用主动或惰性的方法。html

在看全部的细节以前,能够看一篇至关专业的《缓存算法》,世界真宽阔,算法真奇妙。java

 

1. LRU

简单粗暴的Redis

今天看Redis3.0的发行通告里说,LRU算法大幅提高了,就翻开源码来八卦一下,结果啼笑皆非,这旧版的"近似LRU"算法,实在太简单,太偷懒,太Redis了。git

Github的Redis项目里搜索lru,找到代码在redis.c的freeMemoryIfNeeded()函数里。github

先看2.6版的代码: 居然就是随机找三条记录出来,比较哪条空闲时间最长就删哪条,而后再随机三条出来,一直删到内存足够放下新记录为止.......可怜我看配置文档后的想象,一直觉得它会帮我在整个Redis里找空闲时间最长的,哪想到我有一百万条记录的时候,它随便找三条就开始删了。redis

好,收拾心情再看3.0版的改进:如今每次随机五条记录出来,插入到一个长度为十六的按空闲时间排序的队列里,而后把排头的那条删掉,而后再找五条出来,继续尝试插入队列.........嗯,好了一点点吧,起码每次随机多了两条,起码不仅在一次随机的五条里面找最久那条,会连同以前的一块儿作比较......算法

中规中矩的Memcached

相比之下,Memcached实现的是再标准不过的LRU算法,专门使用了一个教科书式的双向链表来存储slab内的LRU关系,代码在item.c里,详见memcached源码分析-----LRU队列与item结构体,元素插入时把本身放到列头,删除时把本身的先后两个元素对接起来,更新时先作删除再作插入。c#

分配内存超限时,很天然就会从LRU的队尾开始清理。api

一样中规中矩的Guava Cache

Guava Cache一样作了一个双向的Queue,见LocalCache中的AccessQueue类,也会在超限时从Queue的队尾清理,见evictEntries()函数缓存

和Redis旧版同样的Ehcache/Hazelcast

文档,竟然和Redis2.6同样,直接随机8条记录,找出最旧那条,刷到磁盘里,再看代码,Eviction类和 OnHeapStore的evict()函数oracle

再看Hazelcast,几乎同样,随机取25条。 这种算法,切换到LFU也很是简单。

小结

不事后来再想一想,也许Redis原本就不是主打作Cache的,这种内存爆了须要经过LRU删掉一些元素不是它的主要功能,默认设置都是noeviction——内存不够直接报错的,因此就懒得建个双向链表,并且每次访问时都要更新它了,看Google Group里长长的讨论,新版算法也是社区智慧的结晶。况且,Ehcache和Hazelcast也是和它的旧版同样的算法,Redis的新版还比这二者强了。

后来,根据@刘少壮同窗的提示,JBoss的InfiniSpan里还实现了比LRU更高级的LIRS算法,能够避免一些冷数据由于某个缘由被大量访问后,把热数据挤占掉。

 

2. 过时键删除

若是能为每个设置了过时的元素启动一个Timer,一到时间就触发把它删掉,那无疑是能最快删除过时键最省空间的,在Java里用一条DeplayQueue存着,开条线程不断的读取就能作到。但由于该线程消耗CPU较多,在内存不紧张时有点浪费,彷佛你们都不用这个方法。

因此有了惰性检查,就是每次元素被访问时,才去检查它是否已经超时了,这个各家都同样。但若是那个元素后来都没再被访问呢,会永远占着位子吗?因此各家都再提供了一个按期主动删除的方式。

Redis

代码在redis.c的activeExpireCycle()里,看过文档的人都知道,它会在主线程里,每100毫秒执行一次,每次随机抽20条Key检查,若是有1/4的键过时了,证实此时过时的键可能比较多,就不等100毫秒,马上开始下一轮的检查。不过为免把CPU时间都占了,又限定每轮的总执行时间不超过1毫秒。

Memcached

Memcached里有个文不对题的LRU爬虫线程,利用了以前那条LRU的队列,能够设置多久跑一次(默认也是100毫秒),沿着列尾一直检查过去,每次检查LRU队列中的N条数据。虽然每条Key设置的过时时间可能不同,但怎么说命中率也比Redis的随机选择N条数据好一点,但它没有Redis那种过时的多了立马展开下一轮检查的功能,因此每秒最多只能检查10N条数据,须要本身本身权衡N的设置。

Guava Cache

在Guava Cache里,同一个Cache里全部元素的过时时间是同样的,因此它比Memached更方便,顺着以前那条LRU的Queue检查超时,不限定个数,直到不超时为止。并且它这个检查的调用时机并非100毫秒什么的,而是每次各类写入数据时的preWriteCleanup()方法中都会调用。

吐槽一句,Guava的Localcache类里面已经4872行了,一点都不轻量了。

Ehcache

Ehcache更乱,首先它的内存存储中只有惰性检查,没有主动检查过时的,只会在内存超限时不断用近似LRU算法(见上)把内存中的元素刷到磁盘中,在文件存储中才有超时检查的线程,FAQ里专门解释了缘由。

而后磁盘存储那有一条8小时左右跑一次的线程,每次遍历全部元素.....见DiskStorageFactory里的DiskExpiryTask。 一圈看下来,Ehcache的实现最弱。

相关文章
相关标签/搜索