4. LRU 近似实现:Redis LRU 实现原理、采样与淘汰、对命中率的权衡
4.1 系统边界与分层职责
Redis 并未实现标准的、全序的 LRU(最近最少使用)链表。标准 LRU 需要 O(1) 移动操作和双向链表维护,在 Redis 千万级 key、单线程模型下会导致性能瓶颈和内存碎片。因此 Redis 采用近似 LRU 算法:在系统边界上,它将淘汰决策从“全局有序”降级为“采样最优”,用少量随机样本近似淘汰最久未访问的 key。
| 层级 | 职责 | 与近似 LRU 的关系 |
|---|---|---|
| 应用层 | 设置 maxmemory、淘汰策略参数 | 决定是否启用 allkeys-lru / volatile-lru |
| 内核层 | 维护每个 object 的 lru 字段 | 记录访问时间戳(秒级精度) |
| 淘汰模块 | 采样 → 比较 → 驱逐 | 控制采样池大小,平衡 CPU 与淘汰精确度 |
4.2 数据流与控制流:近似 LRU 的完整路径
- 访问触发:每次 get/set 操作,Redis 更新对应 robj->lru 字段为当前 unix 时间戳(秒)。
- 内存超限检查:执行命令前调用
freeMemoryIfNeeded(),若已用内存 > maxmemory 则进入淘汰流程。 - 策略分支:根据淘汰策略(如 allkeys-lru)选择全局 dict 或过期 dict。
- 采样淘汰:从 dict 中随机选取 N 个 key(默认 N=5),放入淘汰池。池内按 lru 时间戳升序排列,取最旧的 key 驱逐。
- 池退化与提升:淘汰后,池中的剩余候选 key 保留,供后续淘汰复用。此机制减少重复采样开销。
- 逐出完成:释放对象,更新内存计数器,循环直至满足 maxmemory 约束。
关键控制点:采样数 N(通过
maxmemory-samples 配置)是近似 LRU 唯一可调的精度参数。N 越大,淘汰越接近真实 LRU,但 CPU 开销线性增长。
4.3 策略选型权衡:命中率、CPU 与内存碎片
| 维度 | 标准 LRU | Redis 近似 LRU (N=5) | 近似 LRU (N=10) | 近似 LRU (N=20) |
|---|---|---|---|---|
| 命中率(模拟负载) | ~95% | ~88% - 92% | ~92% - 94% | ~94% - 95% |
| 单次淘汰 CPU 开销 | O(1) | O(N + log PoolSize) | O(N + log PoolSize) | O(N + log PoolSize) |
| 内存碎片风险 | 高(双向链表) | 低 | 低 | 低 |
| 适用场景 | 嵌入式、小容量 | 通用生产环境 | 高精度需求 | 大内存、敏感业务 |
推荐配置:生产环境使用
maxmemory-samples 10 作为默认值。若 key 访问分布极度不均(二八效应强烈),调至 5 即可;若追求极致命中率且 CPU 充裕,可上调至 15-20。
4.4 风险点与应对
- 采样抖动导致缓存击穿:近似 LRU 可能错误淘汰热点 key,引发缓存雪崩。应对:叠加
min-eviction-ttl限制或使用多级缓存(本地 + Redis)。 - 时钟精度回绕:Redis lru 字段使用 24 位存储秒级时间戳,约 194 天回绕一次。回绕期间旧 key 可能被误认为最新访问。应对:Redis 5.0+ 已对该场景做隐式处理,升级版本即可。
- 淘汰池无限膨胀:极端写入压力下淘汰池可能持续增大。应对:Redis 源码限制池大小为 16 个 key(硬编码),防止内存泄漏。
- 慢查询与阻塞:单次淘汰若采样过多(N>50),可能阻塞其他命令。应对:监控
evicted_keys指标,N 值不宜超过 20。
4.5 落地步骤与验收标准
落地步骤
- 评估业务 QPS 与内存配额,确认选用 volatile-lru 或 allkeys-lru。
- 配置 maxmemory 为物理内存 60%-70%,预留系统开销。
- 设置 maxmemory-samples 10,部署灰度验证。
- 启用慢查询日志,监控 evicted_keys、expired_keys 变化斜率。
- 压测热点 key 访问,观察 p99 延迟和命中率,微调 samples 参数。
- 写入配置至 redis.conf,全量重启集群确保配置生效。
验收标准
- 淘汰过程中 p99 延迟上升不超过 2ms。
- 缓存命中率维持在 90% 以上(对比标准 LRU 降幅低于 5%)。
- 无异常 key 频繁进入/离开淘汰池(可通过监控 evicted_keys/time 判断)。
- 单次淘汰平均 CPU 耗时低于 50μs(通过 Redis 内部统计
eviction-time)。
常见踩坑:maxmemory-samples 设置过小(如 3)会导致近似 LRU 退化为随机淘汰,命中率急剧下降。务必根据实际访问模式调整,不可盲目使用默认值。