redis 的 lfu(least frequently used,最不经常使用)淘汰算法主要用于 maxmemory-policy 设置为 allkeys-lfu 或 volatile-lfu 时,以最少使用频率的键进行淘汰。其核心实现涉及到 访问频率计数 和 时间衰减机制,源码主要集中在 src/server.c 和 src/evict.c 文件中。
1. lfu 计数存储
redis 采用 8-bit 的 lru 字段 来存储访问频率计数,存储在 robj 结构体的 lru 字段中:
struct redisobject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:lru_bits; // 用于 lru/lfu 计算
int refcount;
void *ptr;
};
其中 lru 变量的 8-bit 空间被拆分:
- 前 6-bit(
counter):用于存储访问计数,最大值 63。 - 后 2-bit(
clock):用于时间衰减计算。
2. 访问计数的计算
lfu 计数在每次访问键时都会递增,但递增方式不是简单 +1,而是使用 对数增长 方式,避免某些键因高访问量而垄断:
unsigned long lfudecrandreturn(robj *o) {
unsigned long counter = lfugetcounter(o);
if (counter == 0) return 0;
if (rand() % (counter + 1) == 0) counter--;
lfusetcounter(o, counter);
return counter;
}
计数增长时:
int lfuincrandreturn(robj *o) {
unsigned long counter = lfugetcounter(o);
if (counter < 63) {
if (rand() % (counter + 1) == 0) counter++;
}
lfusetcounter(o, counter);
return counter;
}
这意味着:
- 初始时计数增长较快 (
1 → 2 → 3…) - 计数越高,增长概率越低(符合 对数曲线)
- 这样可以防止某些高访问量键长期存活。
3. lfu 访问频率的衰减
由于有些数据可能短期内访问频繁,但长期不再被访问,因此 redis 采用了 时间衰减机制:
每 1 分钟 递减一次访问计数。
使用 2-bit 记录最近访问的时间 lfu_clock,每隔 60s 触发 衰减:
#define lfu_init_val 5 // 初始访问计数
unsigned long lfudecrandreturn(robj *o) {
unsigned long counter = lfugetcounter(o);
if (counter == 0) return 0;
if (rand() % (counter + 1) == 0) counter--;
lfusetcounter(o, counter);
return counter;
}
该方法会按照一定概率减少计数,确保 近期访问过的键不会轻易被淘汰,而 长时间未访问的键会逐步淘汰。
4. 淘汰策略
当 maxmemory 超出时,redis 需要淘汰一部分数据,lfu 主要执行:
遍历数据,找到访问计数最小的键。
采用 volatile-lfu 或 allkeys-lfu 进行数据删除:
evictionpoolpopulate(dict *sample_dict) {
// 从字典中随机采样 n 个键
for (i = 0; i < evpool_size; i++) {
lfu = lfugetcounter(entry);
if (lfu < min_lfu) {
min_lfu = lfu;
min_entry = entry;
}
}
// 淘汰访问次数最少的
dictdelete(sample_dict, min_entry);
}
采用 近似随机采样,而不是遍历所有键,提高效率。
5. 关键总结
- 存储方式:使用
robj.lru变量的 8-bit 空间存储访问计数和时间信息。 - 访问计数增长:采用对数增长策略,防止单个键因访问量过大而占用内存。
- 时间衰减:每分钟对访问频率计数进行衰减,确保长期未访问的键被淘汰。
- 淘汰策略:采样多个键,找到访问计数最少的键进行删除。
redis 的 lfu 机制相比 lru 更适用于热点数据访问场景,避免了某些短期流行的键占用大量缓存,同时也能让真正的 高频访问数据 存活更久。
到此这篇关于浅谈redis中lfu算法源码解析的文章就介绍到这了,更多相关redis lfu算法内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论