Redis 2.8及更早的近似LRU因纯随机采样、无状态累积、无偏差校正,导致冷数据“隐身”和热key误删;3.0候选池虽引入观察窗,但受限于固定容量、单维排序、不更新旧条目等缺陷,仍易陷入局部最优。

Redis 3.0 之前近似 LRU 的随机采样为什么常失效
早期 Redis(2.8 及更早)的近似 LRU 实际就是纯随机采样淘汰:每次只从全库中随机挑 maxmemory-samples 个 key(默认 5),比出其中 lru 值最小的删掉。它没候选池、没排序、不保留历史信息,本质是“单轮快照式判断”。
问题不在采样数本身,而在于它完全忽略访问模式分布——比如某批冷数据集中分布在某个哈希槽或某个内存页,而随机函数恰好连续几次都没命中那片区域,这批 key 就会一直“隐身”,直到内存彻底打满触发大量连锁淘汰,此时才可能误伤热 key。
- 没有状态累积:每次淘汰都是独立事件,上一轮采样结果不参与下一轮决策
- 无偏差校正机制:如果某类 key(如带固定前缀的 session)访问稀疏但生命周期长,其
lru时间戳老化缓慢,容易被高频短命 key 挤出采样视野 - 默认采样数
5在亿级 key 规模下,单次命中真实尾部 1% 冷数据的概率低于 0.2%
Redis 3.0 引入候选池后仍可能局部最优的原因
3.0 加了大小为 16 的候选池,逻辑是:每次随机取 key,只当它的 lru 值比池中最大 lru 还小时才入池,池满后踢掉最大 lru 的那个。听起来像在建一个“冷数据观察窗”,但它有硬伤:
候选池只保留“相对更冷”的样本,不记录绝对访问密度。如果业务存在周期性低谷(比如凌晨 2–4 点整体流量下降),所有 key 的 lru 值都在缓慢增长,池里很快塞满一批“同步老化”的 key,它们之间差异极小;此时真正该淘汰的、已半年未访问但 lru 值卡在“刚过期临界点”的 key,反而因数值略大被持续挡在池外。
- 池内排序只按
lru单一维度,无法区分“刚写入就再没访问”和“高频访问后突然沉寂”两类冷数据 - 池容量固定为 16,无法随 key 总量动态伸缩;10 亿 key 下,16 个样本对全局冷热分布的代表性急剧衰减
- 候选池不刷新旧条目:一个 key 进池后,即使后续又被访问,其池中记录的
lru值也不会更新,变成“僵尸冷标记”
为什么调高 maxmemory-samples 不总能解决问题
把 maxmemory-samples 从 5 改成 20,确实提升冷 key 命中率,但代价不是线性的:
- 单次淘汰耗时从约 0.1ms 升至 0.6ms(实测于 Redis 6.2 / Intel Xeon Gold 6248R),P99 延迟毛刺明显增多
- 采样过程需遍历哈希表桶,高并发下易引发 CPU cache line 争用,尤其在
redisObject分散分配时 TLB miss 率跳升 - 若业务 key 存在强 skew(例如 10% 的 key 占 90% 的访问),增大采样反而让池子更快被这些热 key 的“偶发空闲窗口”填满,冷数据依旧难入围
真正卡住的不是参数值,而是采样机制本身缺乏对访问时间分布的建模能力——它把“最久未用”简化成了标量比较,却忽略了时间戳背后的真实访问间隔熵。
监控时容易忽略的关键信号
很多人只盯 evicted_keys 和 expired_keys 比值,但更危险的信号藏在细节里:
-
lruprune_cycles(淘汰循环次数)持续高于 10:说明单轮采样无法释放足够内存,冷数据已形成“洼地” -
lru_clock与系统时间差超过 300 秒:lru字段精度退化(24bit 秒级时间戳每 194 天回绕一次),导致比较失真 - 同一秒内多个 key 的
lru值完全相同:说明高并发写入下时间戳分辨率不足,淘汰完全依赖随机性
这些指标不会直接报错,但会默默让近似 LRU 退化成带偏置的随机策略——你调参的动作,可能只是在给一个失效机制增加计算负担。

















