W-TinyLFU 频率记录的核心
LRU与LFU的对比
LRU(Least recently used,最近最少使用) 算法根据数据的历史访问记录来进行淘汰数据。
LFU(Least frequently used,最近最不常使用)算法根据一段时间数据的历史访问记录来进行淘汰数据。
试想一下在0-10分钟内A数据访问量为1000,B数据访问量为20,而在10-20分钟的时候,B访问量为500,而A没有访问量。如果按照LRU的原则,我们应该淘汰B。但是10-20分钟的时候A是没有访问量的,所以在LFU的原则应该淘汰的是A,而接下来20-30分钟这段时间内,最有可能被访问的也应该是B。这应该更加贴合实际情况。但是LRU算法也不是一无是处的,LRU可以很好的应对突发的流量情况,因为它不需要累计数据频率。
W-TinyLFU算法原理
说到LFU,它是基于时间周期进行衰减的,或者在最近某个时间段内的频率。所以LFU是需要使用额外空间记录每一个数据的访问频率的,几时数据没有在缓存中也需要记录,这个随着数据的增多,需要维护的额外空间也就越大。W-TinyLFU算法就是利用有限的空间可以记录随时间变化的访问频率,它使用的是Count-Min Sketch记录器(一个布隆过滤器的变种)。
可以把这个想象成一个二位数据int[width][depth],depth代表使用不一样的hash算法,如果只用一个hash算法,那么两个key产生hash冲突的概率是0.1,那么四个hash冲突 的概率则为0.1的四次方。所以从四个hash算法中找到对应的数值,取最小的数值则为这个key的实际频率。更加详细的例子可以参考深入解密来自未来的缓存-Caffeine中2.1.1的内容。
Caffeine中对LFU的实现
在Caffeine中采用了一个long类型的一维数组进行记录的,这个数组的大小是最靠近设定大小的2的N次方。每个hash算法的结果仅占用4位,也就是说64位的long类型可以使用16个hash算法和每个算法记录算法的频率最大为15,但是实际上只是采用了4种hash算法而已。
/**
* Increments the popularity of the element if it does not exceed the maximum (15). The popularity
* of all elements will be periodically down sampled when the observed events exceeds a threshold.
* This process provides a frequency aging to allow expired long term entries to fade away.
*
* @param e the element to add
*/
public void increment(@Nonnull E e) {
if (isNotInitialized()) {
return;
}
int hash = spread<