目录

Redis数据淘汰策略 概论 (一)Redis数据淘汰策略 LRU深入分析 (二)Redis数据淘汰策略 LFU深入分析 (三)

1.前言

上篇文章我们整体上介绍了redis的淘汰策略,而其中LRU和LFU两种算法又是极其重要的,接下来我们从底层原理开始逐一揭开它们的神秘面纱!

2. LFU

2.1 LFU

LFU (least frequently used),从字面上理解是最不频繁使用的
它会按照最近的使用频率淘汰掉使用频率比较低的key,它比LRU更加精准地表示了一个key被访问的热度。

如果一个key长时间不被访问,只是刚刚偶然被用户访问了一下,那么在LRU算法下他是不容易被淘汰,因为LRU算法认为这个key是很“热”的,而LFU算法需要追踪最近一段时间的访问频率,如果某个key只是偶然被访问了一次是不足以变得很“热”的,它需要在近一段时间内被访问很多次才有机会被LFU算法判定为很“热”;

有兴趣可查看redis官方文档关于这块的介绍,点击查看

2.2 LFU的字段存储设计

LFU模式和LRU模式在lru字段都使用了24bit来存储,不同的是LFU模式下,存储了两个值,分别是ldt(last decrement time)和logc(logistic counter),如下图;

furion 对接redis lfu redis_redis


ldt是16个bit,用来存储上一次logc的更新时间。因为只有16个bit,所以精度不能特别高。它取的是分钟时间戳对2^16 进行取模(而lru中是取的秒钟时间戳对2^24进行取模),大约每隔45天就会折返。这部分内容和上篇文章中的LRU的类似;

logc是8个bit,用来存储访问频次,8个bit表示的最大整数是255,存储频次远远不够,这里采用了对数来解决这个问题,这8个bit存储的是频次的对数值,而且这个值还会随时间衰减,如果它的值比较小,那么久很容易被回收。为了确保新创建的对象不被回收,新对象的这8个bit会被初始化为一个大于零的值 LFU_INIT_VAL(默认是=5)。

furion 对接redis lfu redis_furion 对接redis_02

//nowInMinutes
//server.unixtime 为Redis缓存的系统时间戳
unsigned long LFUGetTimeInMinutes(void){
	return (server.unixtime/60) & 65535;
}
//idle_in_minutes
unsigned long LFuTimeElapsed(unsigned logn ldt){
	unsigned long now = LFUGetTimeInMinutes;
	if (now >= ldt) return now -ldt;    //正常比较
	return 65535 - ldt +now;            //折返比较
}

ldt是在内存达到maxmemory的设置时才会触发,在每个指令执行之前都会触发。每次淘汰都是采用随机策略,随机挑选若干个key,更新这个key的“热度”,淘汰掉“热度”最低的key。因为reids采用的是随机算法,如果key比较多的话,那么ldt更新得可能会比较慢。不过既然它是分钟级别的精度,也没必要更新得过于频繁。

ldt更新的同时也会一同衰减logc的值。衰减也有特定的算法,他将现有的logc值减去对象的空闲时间(分钟数)再除以一个衰减系数lfu_decay_time(默认为1)。如果lfu_decay_time的值大于1,那么就会衰减得比较慢,如果它等于零,那就表示不衰减,lfu_decay_time可以进行设置。

3.如何打开LFU模式

Redis4.0给淘汰策略配置参数maxmemory增加了两个选项,分别是针对有过期时间的volatile-lfu和针对所有key的allkeys-lfu,打开了选项后就可以使用object freq指令获取对象的LFU计数值了。

>config set maxmemory-policy allkeys-lfu
OK
>set yulouchunqiu ohmygod
OK
//获取计数值,初始化为LFU_INIT_VAL=5
>object freq yulouchunqiu
(integer) 5
//访问一次
>get yulouchunqiu
"ohmygod"
//计数值增加了
>object freq yulouchunqiu
(integer) 6

4.总结

lfu算法比lru算法能更加精准地表示一个key被访问的热度
引入了对数来解决存储空间存储频次不够的问题

5.思考

如果你是java用户,如何实现一个LFU算法?