Redis 的过期策略和内存淘汰机制是什么?如何选择合适的策略?
Redis 的过期策略和内存淘汰机制是两个不同层面的问题:过期策略决定「过期 key 何时被删」,内存淘汰策略决定「内存不够时删谁」。
过期策略
Redis 采用惰性删除 + 定期删除的组合策略,不使用定时删除。
惰性删除:访问 key 时才检查是否过期。由 expireIfNeeded() 函数实现,所有读写命令执行前都会调用。优点是 CPU 友好,缺点是过期 key 若不被访问就永远占内存。
定期删除:每秒执行约 10 次(受 hz 配置控制),每次随机抽取 20 个设置了过期时间的 key 检查,若过期则删除。若本轮过期 key 超过 25%,则继续抽样,直到低于 25% 或超时(25ms)。由 activeExpireCycle() 函数实现。
为什么不单独用定时删除?创建大量定时器会严重消耗 CPU 资源,Redis 出于性能考虑弃用此方案。
二者配合的效果:定期删除保证过期 key 不会长期滞留,惰性删除兜底处理定期删除遗漏的 key。
内存淘汰策略
当 Redis 内存使用达到 maxmemory 限制时,根据淘汰策略决定删除哪些 key。共 8 种策略:
| 策略 | 淘汰范围 | 算法 | 适用场景 |
|---|---|---|---|
| noeviction | 不淘汰 | - | 数据不能丢失 |
| allkeys-lru | 全部 key | LRU | 纯缓存,热点集中 |
| allkeys-lfu | 全部 key | LFU | 纯缓存,访问频率差异大 |
| allkeys-random | 全部 key | 随机 | 所有 key 访问概率相近 |
| volatile-lru | 有过期时间的 key | LRU | 混合存储,保留持久数据 |
| volatile-lfu | 有过期时间的 key | LFU | 同上,优先淘汰低频临时数据 |
| volatile-random | 有过期时间的 key | 随机 | 临时数据随机淘汰 |
| volatile-ttl | 有过期时间的 key | TTL 最短 | 优先淘汰即将过期的 key |
LRU 与 LFU 的实现
Redis 使用近似 LRU,并非精确 LRU。每个 key 记录最后访问时间戳(24bit lru 字段),淘汰时随机采样 N 个 key(默认 5 个),删除其中最久未访问的。采样数越大越接近精确 LRU,但 CPU 开销也越大。
LFU 在 Redis 4.0 引入,复用 lru 字段的高 16 位记录衰减时间、低 8 位记录访问计数器。计数器会随时间衰减,避免历史高频 key 永远不被淘汰。
如何选择
- 纯缓存场景(数据全可丢失):
allkeys-lru(推荐)或allkeys-lfu - 部分数据持久化(重要数据不设过期时间):
volatile-lru或volatile-lfu - 数据绝对不能丢:
noeviction - 所有 key 访问概率均匀:
allkeys-random
生产环境推荐优先考虑 allkeys-lru,绝大多数缓存场景都适用。若使用 Redis 4.0+ 且访问频率差异明显,allkeys-lfu 更精准。
用 INFO memory 命令监控内存使用,关注 used_memory、used_memory_peak、maxmemory 等指标。
追问
Q:过期策略和内存淘汰策略的关系? 过期策略处理的是「已过期的 key 何时删除」,是时间驱动的;内存淘汰策略处理的是「内存不足时删谁」,是空间驱动的。两者互补:即使过期策略遗漏了部分过期 key,内存淘汰策略也能在内存紧张时兜底清理。
Q:为什么 Redis 的 LRU 是近似的? 精确 LRU 需要维护全局链表,每次访问都要移动节点,O(1) 的 key 访问变成 O(n) 的链表操作。近似 LRU 随机采样 5 个 key 淘汰最旧的,牺牲少量精度换取 O(1) 的访问性能。实测表明近似 LRU 的命中率接近精确 LRU。
Q:volatile 系列策略的一个潜在问题? 如果没有 key 设置过期时间,volatile 策略等同于 noeviction,不会淘汰任何 key,可能导致内存满后写入全部失败。