redis不用双向链表实现标准lru,因其需为每个key额外存储prev/next指针,导致内存翻倍、每次访问需o(1)链表调整且破坏单线程简洁性;而采样lru仅用24位lru字段+随机采样,默认5个样本,在精度与性能间取得平衡。

Redis为什么不用双向链表实现标准LRU
因为 Redis 选择牺牲一点淘汰精度,换取显著的内存节省和性能提升。标准 LRU(哈希表 + 双向链表)在 Java 或 C++ 中容易实现,但在 Redis 这种单线程、高吞吐、海量 key 的场景下,维护全局双向链表成本太高。
常见错误现象:OOM command not allowed when used memory > 'maxmemory' 频繁出现,但你查 redis-cli info memory 发现 key 数量极大,而链表操作已成瓶颈;或者发现 INFO stats 中 evicted_keys 激增但 expired_keys 很低,说明淘汰压力集中在 LRU 策略上,而链表维护拖慢了主线程。
- 每个 key 的
redisObject都要额外存prev/next指针 → 内存开销翻倍(尤其小 value 场景) - 每次
GET/SET都要更新链表位置 → 需加锁或原子操作,破坏单线程简单性 - 链表遍历尾部淘汰是 O(n),哪怕只做一次,对百万级 key 来说也卡顿明显
采样 LRU 是怎么工作的
Redis 的近似 LRU 不维护访问顺序,只靠时间戳 + 随机采样做决策。核心是:不追求“绝对最久未用”,只要“大概率是最久未用”就够了。
使用场景:缓存集群中某节点内存逼近 maxmemory,触发 volatile-lru 或 allkeys-lru 策略时,Redis 会执行以下逻辑:
- 从当前所有可淘汰 key(或带过期时间的 key)中,随机抽取
maxmemory-samples个(默认为 5) - 比较它们的
lru字段(24 位逻辑时钟,非真实时间) - 淘汰其中
lru值最小的那个 key
这个 lru 字段在每次 key 被访问时更新,但更新本身只是写一个整数,比移动链表节点便宜两个数量级。
采样数量 maxmemory-samples 怎么调
它不是越大越好,也不是越小越省;是个精度与性能的折中点。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
参数差异:
-
maxmemory-samples 1:极快,但淘汰近乎随机,LRU 效果弱 -
maxmemory-samples 5(默认):实测在多数业务中命中率 ≈ 80%~90%,平衡点 -
maxmemory-samples 10:精度提升有限(+3%~5%),但采样开销翻倍,尤其 key 总数超千万时,CPU 占用明显上升
性能影响:该参数只影响淘汰阶段,不影响日常读写。但如果设置过高(如 100),在内存持续打满、频繁触发淘汰时,会导致主线程周期性卡顿(latency-monitor-threshold 可能报 eviction 类延迟)。
Java LinkedHashMap 为什么能严格 LRU,而 Redis 不行
因为运行环境和目标完全不同。LinkedHashMap 是进程内、单实例、key 数量可控(通常几千到几万)的缓存结构;Redis 是独立服务、多客户端并发、key 可达亿级、要求亚毫秒响应的系统。
容易踩的坑:
- 想在 Redis 上“模拟”标准 LRU(比如用
ZSET存访问时间),结果发现ZADD+ZREMRANGEBYRANK淘汰比原生 LRU 还慢,且内存暴涨 - 误以为
maxmemory-samples调大就能“修复”淘汰不准,却忽略了 Redis 本就不承诺精确 LRU —— 它的设计哲学是“够用就好” - 在监控中看到
lru_clock值增长缓慢(INFO server),误判为时钟异常,其实只是逻辑时钟每 100ms 自增一次,和系统时间无关
真正需要强 LRU 语义的场景(比如金融类会话缓存),应该在应用层用 LinkedHashMap 或 Caffeine 实现,而不是强求 Redis。










