redis的lru字段在lru模式下存储的是对象最后一次访问时间相对于全局server.lruclock的24位偏移值,非绝对时间戳;在lfu模式下则拆分为高16位ldt(分钟级时间戳)和低8位logc(初始为5的对数计数器)。

Redis 的 LRU 不是标准链表实现,而是用 24 位 lru 字段 + 随机采样来近似判断“最近最少使用”,精度可控但开销极低。
Redis 的 lru 字段到底存什么?
每个 redisObject 结构体里有个 lru 成员,占 24 位,存储的是“最近一次访问时间”相对于全局 lruclock 的秒级偏移(不是绝对时间戳)。由于只有 24 位,它会每 ~194 天回绕一次,但这对淘汰逻辑无实质影响——只要相对顺序能大致反映访问新旧即可。
这个字段在每次 key 被读写时更新,不依赖额外数据结构,也不触发链表移动,所以零内存膨胀、零 O(n) 移动开销。
-
lru值越小,代表该 key 越久没被访问(注意:不是“数值小=时间早”,而是“差值大=更久未访问”) - 它不记录毫秒级精度,也不做全量排序,只服务于采样比较
- 如果你用
OBJECT IDLETIME <key></key>查看,返回的就是当前lruclock - lru的秒数
为什么不用精确 LRU?采样怎么工作?
精确 LRU 需要维护全局双向链表,每次访问都要调整节点位置,对高并发写密集场景会造成显著锁竞争和 CPU 毛刺。Redis 选择用空间换时间,用概率逼近确定性。
当触发淘汰时(used_memory > maxmemory),Redis 会:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 根据配置的
maxmemory-samples(默认 5)随机选出 N 个候选 key - 比较它们的
lru值,挑出“最老”的那个(即lruclock - lru最大的) - 如果该 key 是
volatile-lru策略,则还要先过滤掉没设过期时间的 key - 重复该过程,直到释放足够内存
增大 maxmemory-samples(比如调到 10 或 20)能提升淘汰准确率,但会增加 CPU 占用;小于 5 则容易误杀热点 key,尤其在访问模式陡峭时(比如突发流量后大量冷 key 残留)。
volatile-lru 和 allkeys-lru 的关键区别在哪?
区别不在算法本身,而在候选集范围 —— 这直接影响淘汰安全性和适用场景:
-
volatile-lru:只从已设置EXPIRE的 key 中采样淘汰。适合缓存层,保证永不过期的元数据、配置类 key 不被误删 -
allkeys-lru:对所有 key 无差别采样。适合纯缓存场景,且你明确接受“任何 key 都可能被淘汰” - 如果用了
volatile-lru,但大部分 key 没设过期时间,那么实际可用的候选集很小,可能导致频繁触发evicted_keys但内存降不下来,甚至退化为类似noeviction的行为
注意:volatile- 类策略不会主动清理未设置过期时间的 key,哪怕内存已满;而 allkeys- 类策略会无条件参与淘汰,不管有没有 EXPIRE。
LRU 在 Redis 里真正“失效”的几个典型时刻
近似算法再好,也有边界条件让它表现失真:
- 冷启动后首次大批量写入:所有 key 的
lru值接近,采样无法区分新旧,容易随机淘汰 - 访问模式突变(如 AB 测试切流):旧热点突然变冷,但
lru值还没衰减足够,仍被保护;新热点因时间短,lru值新,反而易被淘汰 -
maxmemory-samples过小 + key 总数极少(比如只有几十个):随机性压倒统计意义,淘汰结果接近随机 - 大量 key 同时被访问(如定时任务批量读):它们的
lru值被集中刷新,导致“集体年轻化”,掩盖真实热度差异
这些情况不是 bug,而是设计取舍的结果 —— 它优先保障吞吐和延迟稳定性,把精度让渡给工程可维护性。真要强一致性 LRU,得自己在客户端或 proxy 层补。










