redis 4.0的lfu实现通过复用24位lru字段(高16位存衰减时间ldt,低8位存对数计数器logc)、概率递增与时间衰减机制,在零新增内存开销下高效估算访问热度,避免溢出与全量遍历。

Redis 4.0 的 LFU 实现不是传统意义上的“计数器累加”,它用空间换逻辑精度,但整体反而更省内存——关键在于复用已有字段、避免额外存储、且不维护全局访问历史。
LFU 复用 lru 字段,不新增内存开销
传统 LFU 需为每个 key 单独分配一个整型计数器(比如 4 字节 int),而 Redis 的所有 redisObject 结构体里本就有一个 24bit 的 lru 字段。在 LFU 模式下,这个字段被拆成两部分复用:
- 高 16bit 存
ldt(Last Decrement Time),即上一次衰减时间戳(单位是分钟级的 server.lruclock 折算值) - 低 8bit 存
logc(Logarithmic Counter),是对数计数器,取值范围是 0–255
也就是说:**没新增任何字段,也没扩展结构体大小**。对比传统实现动辄多占 4–8 字节/对象,Redis 这种设计对亿级 key 的场景能省下数百 MB 内存。
对数计数器 logc 避免整数溢出和精度浪费
如果直接记录原始访问次数,高频 key 很快会溢出(比如 uint8 最大 255,uint32 也撑不过几百万次),而真实业务中“访问 100 万次”和“访问 100 万零 1 次”对淘汰决策几乎无差别。Redis 的解法是:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
logc不是线性递增,而是按概率增长(阈值P = 1 / ((counter - 5) * lfu_log_factor + 1)) - 初始值默认为 5(
LFU_INIT_VAL),防止新 key 被误判为冷数据 - 最大只到 255,之后不再增长 —— 这个上限已足够区分绝大多数热度层级
这种设计让低频 key 快速“显形”,高频 key 增长变慢,既保留了相对排序能力,又彻底规避了大整数存储和溢出处理开销。
衰减机制靠时间戳差值计算,不存访问日志
传统 LFU 若想支持“近期频率”而非“历史总频次”,往往需要维护滑动窗口或定期重置计数器,这要么得存时间序列,要么得定时遍历全量 key。Redis 完全避开这条路:
- 每次访问时,只比对当前
server.lruclock和ldt是否超过衰减周期(默认 1 分钟) - 若超时,则先对
logc衰减(比如右移 1 位或减 1),再按概率更新 - 衰减判断仅依赖两个 16bit 时间戳差值,不查表、不遍历、不持久化历史
这意味着:**没有后台线程扫 key,没有额外哈希表或定时器结构,也没有 per-key 的时间队列**——所有状态都压在那 24bit 里。
真正容易被忽略的是:ldt 和 logc 共享同一块内存,且衰减触发条件依赖 server.lruclock 的粗粒度更新(默认每秒/每毫秒一次)。这意味着 LFU 在 Redis 里本质是个“低频采样+概率建模”的轻量估算器,不是精确计数器。它省下的不只是字节,更是维护一致性所需的 CPU 和锁开销。










