redis将24位lru字段拆为高16位ldt(分钟级衰减时间戳)和低8位logc(概率对数计数器),ldt每约45.5天折返,logc初始为5、按概率递增且非线性映射访问量,淘汰时同频次下回退lru逻辑并分桶链表管理。

Redis用24位lru字段同时存时间戳和频次
Redis没给LFU单独开空间,而是把原本用于LRU的lru字段(24 bit)拆成两段复用:高16位存“最后衰减时间”(ldt),低8位存对数计数器(logc)。这不是简单的时间+次数拼接,而是为节省内存做的硬约束设计。
这意味着:
– logc最大只能到255,但通过概率递增+时间衰减,它实际能区分百万级访问量;
– ldt只有16位,精度是分钟级(不是毫秒),且每约45天会折返一次(2¹⁶ = 65536 分钟 ≈ 45.5 天)。
logc不是线性计数器,而是概率对数增长
每次GET或SET触发LFULogIncr(),但不会无脑+1。它用随机概率控制增长斜率:
- 新key插入时,
logc初始值是LFU_INIT_VAL(默认5),不是0——防冷启动即被淘汰 - 旧counter为c时,增长概率P = 1 / ((c − 5) ×
lfu_log_factor+ 1),lfu_log_factor默认10 - c越小(如5→6),P越大;c越大(如200→201),P急剧下降,避免快速触顶255
这种设计让logc=10可能对应几百次访问,logc=20可能对应几万次——不是1:1映射,而是近似√n曲线。
ldt不是“最后访问时间”,而是“最后衰减时间”
很多人误以为高16位存的是“上次被访问的时间”,其实它是LFUDecrAndReturn()执行时写入的“上一次做counter衰减的时间点”。这个值只在两种情况下更新:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 后台淘汰逻辑扫描key时,顺手调用衰减函数
- 某个key被访问前,发现距上次衰减已超
lfu-decay-time(默认1分钟),就先衰减再访问
所以ldt本质是个衰减锚点,不是访问日志。计算空闲时间时,用当前分钟时间戳减ldt(注意处理折返),再除以lfu-decay-time,得出该key该衰减几轮。
同频次淘汰靠LRU兜底,必须分桶维护链表
当两个key的logc都是7,Redis不能靠logc本身决定谁先淘汰——这时它退回到LRU逻辑,比谁的ldt更旧(即谁更久没被衰减过,间接反映更久没被访问)。
但实现上不能只靠一个全局链表排序,因为logc会随衰减动态变。Redis内部按logc值分桶,每个桶挂一个双向链表,链表内节点按访问时间倒序(新近访问的在头)。这样查最低频次桶后,直接取该桶链表尾部节点即可。
这个细节常被忽略:你以为LFU只看频次,其实它始终带着时间维度兜底;你以为lru字段全归LFU管,其实高16位只服务衰减,不记录访问瞬间。










