redis 3.2 沿用 3.0 起的 evictionpool 近似 lru 机制,其核心是固定大小为16的数组,通过多轮随机采样+idle排序实现高效淘汰,非链表因避免伪共享与内存浪费;maxmemory-samples 影响等效采样量但不改变池容量,池顶key是多次采样中idle最大者,鲁棒性源于滑动窗口最大值过滤。

Redis 3.2 并没有引入新的 LRU 机制——它沿用的是 3.0 起就已落地的 evictionPool 机制。所谓“效果更好”,实际是默认配置、采样逻辑和时钟精度在 3.0–3.2 迭代中趋于稳定,让近似 LRU 的鲁棒性更可预期,而非算法本身有结构性升级。
evictionPool 是什么,为什么不是链表
它是一个长度固定为 16 的 evictionPoolEntry 数组(编译期常量 EVPOOL_SIZE),只在触发淘汰时被重置、填充、排序,不参与日常访问。Redis 放弃双向链表,是因为:
- 单线程下每次
GET/SET都要调整全局顺序,会引发严重 cache line 失效和伪共享 - 每个 key 额外加 16 字节指针开销,在亿级 key 场景下内存浪费显著
- 池机制把高开销操作(排序+淘汰)从“每秒数万次”压降到“每秒几次”,解耦了高频低代价(随机采样)和低频高代价(保序筛选)
maxmemory-samples 参数如何影响 pool 填充效果
该参数控制单轮采样键的数量,默认值为 5。但 pool 的填充不是只采一次:Redis 会在一次淘汰周期内反复调用 evictionPoolPopulate(),最多尝试 EVICT_MAX_TRIES 次(默认 100 次),直到池满或尝试耗尽。
- 实际等效采样量 ≈
maxmemory-samples × 尝试次数,哪怕设为 5,也意味着平均采样 500 个 key 才填满 16 格 pool - 盲目调大
maxmemory-samples(如设为 20)可能让 pool 过早收敛到局部冷 key,尤其在周期性访问模式下(例如每小时一批定时任务刷缓存) - 调高后单次淘汰 CPU 时间增加约 8–12μs(16 核实测),需关注
INFO stats中的eviction_slow_log_entries是否突增
为什么 pool 顶的 key 更可能是真冷数据
pool 按 idle(近似空闲时间)升序排列,新采样 key 只有 idle 比当前某格更大时才插入,并维持有序。真正被淘汰的是池顶(index 0)那个 idle 最大的 key。
- 它不是“单次抽中最久没用的”,而是“在最多 100 轮随机采样中,反复幸存下来、且 idle 值始终排在前 16 名”的 key
- 相当于做了多次独立抽样 + 滑动窗口最大值过滤,大幅降低因单次运气差而误杀热点 key 的概率
- 即使某个热点 key 某轮没被抽中,只要它真实 idle 小,后续轮次仍大概率被捞入池中并被挤出——池本身是动态竞争结构
真正容易被忽略的点在于:pool 大小不可运行时修改,EVPOOL_SIZE=16 是硬编码在 evict.c 里的。你调再大的 maxmemory-samples,池子也只留 16 个位置;而如果业务冷热边界模糊(比如大量 key idle 在 10–20 分钟区间密集分布),16 格池可能不够拉开区分度——这时与其调参,不如考虑切换到 allkeys-lfu 策略。











