LFU算法在Redis中的实现机制和应用
Redis 4.0提供了两种新的内存淘汰策略:
volatile-lfu
:基于LFU
算法对设置了过期时间的key进行删除allkeys-lfu
:基于LFU
算法对所有的key进行删除
Redis 4.0.3正式支持基于LFU
算法的热点key发现机制。
LFU数据结构
redisObject
对象中的unsigned lru:LRU_BITS
字段为24bit大小,用来记录LRU/LFU的信息。
记录LFU信息时:
0-15bit
:last access time,即上次访问时间,单位分钟
16-24bit
:LOG_C,即访问频率,简称counter,显然其最大为255
显然,LFU
算法是通过对key
的访问频率来筛选key
,那么其内部实现就必然要能区分出不同key
的访问频率。
counter
counter
是一个基于概率的计数器,它的作用并非精准地记录某个key
的访问次数。
实际上,在Redis
源码中通过一个公式来对counter
进行计算来体现key
的访问次数。
公式 : 1 / (( counter - LFU_INIT_VAL ) * server.lfu_log_factor + 1)
其中LFU_INIT_VAL = 5
,下文解释为什么该值为5。
server.lfu_log_factor
是Redis
配置项中的概率因子,默认为10,通过该公式的计算,可以记录较多的key
访问次数。
//计算key的访问频率的源码
uint8_t LFULogIncr(uint8_t counter) {
if (counter == 255) return 255;
double r = (double)rand()/RAND_MAX;
double baseval = counter - LFU_INIT_VAL;
if (baseval < 0) baseval = 0;
double p = 1.0/(baseval*server.lfu_log_factor+1);
if (r < p) counter++;
return counter;
}
factor | 100hits | 1000hits | 100k hits | 1M hits | 10M hits |
---|---|---|---|---|---|
0 | 104 | 255 | 255 | 255 | 255 |
1 | 18 | 49 | 255 | 255 | 255 |
10 | 10 | 18 | 142 | 255 | 255 |
100 | 8 | 11 | 49 | 143 | 255 |
该表格来自于阿里云团队根据公式计算出的key
访问次数与counter
的关系,可以看到server.lfu_log_factor
越大,就越能记录更高的key
访问次数,当server.lfu_log_factor = 10
时,counter
可体现1百万次的key
访问记录。
counter的衰减因子
实际上Redis
运行中,所有存活已久的key
的访问次数都在增加,counter
最终都会收敛到255
。
极端情况下,可能存在一些key
从Redis
第一次启动就存在,一直未消失,但被访问次数并不高,只是因为存活时间久,导致累计访问次数很高,其counter
很容易收敛到255,这种情况下,给热点key
的筛选带来了干扰。
即,counter
只增长不衰减就无法区分热点key
。
因此Redis提供了衰减因子server.lfu_decay_time
,单位是分钟。
原理:如果一个key
很长时间没有被访问,counter
就应该减小,具体减小的程度由该衰减因子控制。
默认server.lfu_decay_time = 1
,这种情况下,N分钟没有访问key
,key
的counter
就减N。
业务中,使用默认值就可以。
为什么LFU_INIT_VAL = 5
由衰减因子可得,访问次数很大的
key
,但由于长时间不被访问,导致其counter
变成很小,当counter
衰减到0时,Redis
淘汰该key
。理论上,新创建的
key
,其counter=0
,因为从未被访问过,但这样会导致key
被Redis
误淘汰。
基于以上2点,对于新生成的key
,LFU_INIT_VAL = 5
,使得其在创建时counter!=0
,其不在被创建时就立即被淘汰。
There is yet another problem, new keys need a chance to survive after
all. In vanilla LFU a just added key has an access score of 0, so it
is a very good candidate for eviction. In Redis new keys start with
an LFU value of 5. This initial value is accounted in the increment
and halving algorithms. Simulations show that with this change keys have
some time in order to accumulate accesses: keys with a score less than
5 will be preferred (non active keys for a long time).
发现热点key
通过衰减因子影响counter
的计算,对一个key
的访问频率可以得到较好的体现。
因此,Redis
会在每次对key
进行读写访问时,更新lru:LRU_BITS
的访问时间和counter
,这样就可以及时地得到正确的LFU值。
通过OBJECT FREQ KEY
可以访问对应key的counter
值,通过redis-cli --hotkeys
可得到当前热点key列表。
开发中可以通过渐进式遍历scan
获取key,再OBJECT FREQ KEY
获取counter
值,进行排序后发现热点key。
注意:使用发现热点key
的功能的前提是采用lru
内存淘汰策略,通过config get maxmemory-policy
查看你设置的Redis
的内存淘汰策略。
内存淘汰
很显然,当采用lru
内存淘汰策略时,内存空间不足时,counter
值小的key
会被淘汰。