redis不做真实lru因链表维护成本高:每key多占16–24字节内存、访问时cache line抖动、淘汰需全局串行锁;改用24位lru时间戳+随机采样近似lru,兼顾性能与效果。

Redis为什么不做真实LRU:链表维护成本太高
真实LRU需要为每个key维护一个双向链表节点,每次访问(GET/SET等)都得把对应节点移到表头,淘汰时删表尾。这听起来干净,但实际在Redis里会出三类问题:
-
redisObject结构体本身不存指针,加链表节点意味着每个key多占16–24字节(取决于平台),对亿级key实例就是几百MB纯开销
- 每次访问触发链表重排,是O(1)但带cache line抖动,高并发下CPU流水线频繁中断
- 所有淘汰逻辑必须串行锁住整个键空间,
maxmemory触发时可能卡住主线程几十毫秒
Redis选择用24位lru字段记毫秒时间戳(取模2²⁴,约194天周期),访问时只更新这个整数——零内存分配、无指针操作、完全无锁。
近似LRU怎么工作:采样不是“随便挑”,而是可控精度的权衡
Redis不扫全量key,而是在淘汰时从数据库中随机选maxmemory-samples个key(默认5),比出其中lru值最小的那个干掉。
- 采样数越小(如设为1),相当于纯随机淘汰,LRU效果几乎消失
- 采样数越大(如设为10或20),命中真正冷数据的概率越高,但每次淘汰耗时线性上升
- 实测:从5调到10,缓存命中率在电商商品页场景提升约3.2%,但单次
EVAL脚本触发淘汰的P99延迟增加0.8ms
你不需要改默认值,除非你压测发现淘汰后大量穿透DB——这时先看INFO memory里的evicted_keys和expired_keys比值,再决定是否调maxmemory-samples。
真实LRU反而可能更慢:别被教科书算法名字骗了
很多人以为“精确=快”,但在Redis这种单线程+内存敏感场景里,真实LRU的“精确”是带惩罚的:
- Java里
LinkedHashMap能高效做LRU,是因为它跑在GC管理的堆上,节点复用自由;Redis的redisObject直接malloc/free,链表节点生命周期难对齐
- 真实LRU要求所有访问路径(包括后台过期扫描、RDB/AOF加载时的key重建)都同步更新链表位置,稍有遗漏就导致链表断裂或重复节点
- Redis 7.0曾实验过基于跳表的近似LRU变种,结果发现即使采样数降到3,综合吞吐反超链表方案11%,因为避免了指针跳转带来的TLB miss
所以“近似”不是妥协,是针对Redis运行模型做的重新设计——它把计算压力从每次访问,转移到了内存紧张时那几次淘汰上。
什么时候该怀疑LRU失效:看指标,别猜逻辑
近似LRU不是万能的,但它的问题有迹可循,关键看三个指标:
-
mem_used长期贴近maxmemory,但evicted_keys增长极慢 → 说明根本没触发淘汰,不是LRU问题,是配置或业务误用
-
evicted_keys飙升,同时keyspace_hits/keyspace_misses比值断崖下跌 → 典型的突发流量冲垮近似LRU,冷热key混杂时采样失准
-
lru_clock(来自INFO server)和系统时间差超过2小时 → 说明机器时间被NTP大幅校正过,lru字段时间戳已不可靠,必须重启实例
LFU在这些场景里往往更稳,但它的衰减逻辑(counter随时间自动下降)也有自己的盲区——比如凌晨低峰期,高频key的计数器可能被衰减到接近冷key水平。这点比LRU更难监控,也更容易被忽略。










