Redis LFU内存淘汰策略细究
Redis 的 LFU(Least Frequently Used,最不频繁使用)淘汰策略,是在 LRU 基础上做的“升级版”近似算法。它复用了对象头上同一个 24 位的 lru 字段,通过巧妙的编码和对数计数,用极小的内存代价,实现了对访问频率的近似跟踪。 下面从存储结构、计数器增减、淘汰决策到参数配置,逐一细究。 1. 24 位字段的位划分 每个 Redis 对象都有一个 lru 属性(24 位),在 LFU 模式下,它不再存放秒级时间戳,而是被拆成两段: 高 16 位:最后衰减时间(Last Decay Time,单位:分钟) 低 8 位:对数访问计数器(Logarithmic Counter,范围 0–255) 高 16 位:存储的是 (server.unixtime / 60) & 0xFFFF,即当前分钟时间戳的低 16 位。最大表示约 45 天,足够覆盖淘汰场景,即使回绕,只要间隔不超过 45 天就可以正确计算差值。 低 8 位:是一个 0–255 的频率计数器,但它不是访问次数的直接累加,而是经过对数平滑处理的“近似频率”。 当键被访问时,Redis 会调用 updateLFU(),先根据已流逝的时间衰减计数器,再概率性地递增计数器,最后把新的分钟时间戳和计数器重新编码写回 lru。 2. 计数器递增:对数增长 为了让 8 位计数器(0–255)既能表示低频也能区分高频,同时不让热门键快速打满,Redis 采用概率递增。 递增公式(源码级): double p = 1.0 / ((counter - LFU_INIT_VAL) * server.lfu_log_factor + 1); if ((random() & 0xFFFF) < p * 0xFFFF) counter++; LFU_INIT_VAL 默认为 5,新键的计数器初始值就是 5。 lfu_log_factor 是可配置的对数因子,默认 10。 随着 counter 增大,p 会越来越小,递增越来越难。 举例(factor = 10 时,典型访问次数与计数器值): ...