缓存淘汰策略

面试官问:"Redis 内存满了怎么办?"

小张说:"淘汰数据。"

面试官追问:"有哪些淘汰策略?LRU 和 LFU 有什么区别?"

小张说:"LRU 是...最近最少使用?"

面试官继续追问:"那 LFU 呢?"

小陈答不上来。

Redis 内存管理是面试中的高频问题。这道题能说清楚 8 种淘汰策略和 LRU vs LFU 区别的候选人,对 Redis 的内存管理机制有深入理解。

一、Redis 内存限制 🔴

1.1 maxmemory 配置

# 设置 Redis 最大内存
# redis.conf
maxmemory 2gb

# 查看内存使用
INFO memory
# used_memory_human: 1.5G
# maxmemory_human: 2G
# used_memory_peak_human: 1.8G

1.2 内存满了的行为

当 Redis 内存达到 maxmemory 时:
1. 如果有客户端执行写命令
2. Redis 根据淘汰策略淘汰一些 key
3. 如果淘汰后仍不够,才会返回错误(OOM)

二、8 种淘汰策略 🔴

2.1 策略总览

策略说明适用场景
noeviction不淘汰,返回错误纯缓存
allkeys-lru所有 key,用 LRU 淘汰热点数据缓存
allkeys-lfu所有 key,用 LFU 淘汰热点数据缓存
allkeys-random所有 key,随机淘汰无明显热点
volatile-lru有过期时间的 key,用 LRU 淘汰有过期时间的数据
volatile-lfu有过期时间的 key,用 LFU 淘汰有过期时间的数据
volatile-random有过期时间的 key,随机淘汰-
volatile-ttl有过期时间的 key,淘汰 TTL 最短的TTL 敏感

2.2 LRU vs LFU vs Random

LRU(Least Recently Used):
- 淘汰最久未访问的 key
- 适用于:大部分 key 是热点,偶尔有新 key 被访问

LFU(Least Frequently Used):
- 淘汰访问频率最低的 key
- 适用于:访问频率长期稳定的数据

TTL:
- 淘汰剩余 TTL 最短的 key
- 适用于:有明确过期时间的数据

Random:
- 随机淘汰
- 适用于:无明显热点,且需要保持数据多样性

2.3 ❌ 错误示范

候选人原话:"LRU 就是淘汰最近使用过的 key。"

问题诊断:LRU 是淘汰最近最少使用的 key(最久未访问的),不是"最近使用过的"。

候选人原话 2:"allkeys-lru 和 volatile-lru 一样,都是 LRU。"

问题诊断:两者都用 LRU 算法,但作用范围不同。allkeys 是所有 key,volatile 是只有过期时间的 key。

【面试官心理】 这道题我会从 LRU 和 LFU 的区别追问。能说清楚"LRU 只看时间,LFU 考虑频率"的候选人,说明他理解了两种算法的本质区别。

三、LRU 算法实现 🟡

3.1 Redis 的 LRU 实现

// Redis 使用近似 LRU(不是精确 LRU)
// 精确 LRU 需要维护有序链表,每次访问/插入 O(n)
// Redis 的 LRU:采样 N 个 key,选择最久未使用的

// 配置:
maxmemory-samples 5  // 每次采样 5 个 key(默认 5)
// 值越大越精确,但消耗更多 CPU

3.2 LRU vs 近似 LRU

-- 精确 LRU:
-- Object [key] [访问时间] [指向下一个Object的指针]
-- 链表头 = 最久未使用,链表尾 = 最近使用
-- 访问 key 时,移动到链表尾部
-- 淘汰时,从链表头部删除

-- 近似 LRU(Redis):
-- 每个 key 记录最后一次访问时间(lru 字段)
-- 淘汰时,随机采样 N 个 key,选择 lru 最老的淘汰
-- 优点:O(1) 操作,不需要维护链表
-- 缺点:不精确,但效果接近精确 LRU

四、LFU 算法实现 🟡

4.1 LFU 的计数器衰减

// LFU 配置:
lfu-log-factor 10   // 频率计数器的对数因子(越大,计数器增长越慢)
lfu-decay-time 1    // 计数器衰减时间(分钟)

// LFU 计数器衰减:
// 如果一个 key 长时间不访问,LFU 计数器会衰减
// 衰减速率由 lfu-decay-time 控制
// 比如 lfu-decay-time=1,表示每 1 分钟,计数器减半

4.2 LRU vs LFU 场景对比

-- 场景 1:热点 key 偶尔被新 key 打断
-- 数据:key1 访问 10000 次(但上次访问是 10 分钟前)
--       key2 访问 5 次(但刚刚被访问)
-- LRU:淘汰 key1(最久未访问)
-- LFU:淘汰 key2(访问频率最低)

-- 场景 2:热点 key 持续访问
-- 数据:key1 访问 10000 次(持续访问)
--       key2 访问 5 次(刚访问)
-- LRU:取决于时间
-- LFU:淘汰 key2
-- 结论:LFU 更适合长期热点的场景

五、生产选型 🟡

5.1 策略选择

# 推荐配置
maxmemory 2gb
maxmemory-policy allkeys-lru  # 大多数场景
# maxmemory-policy allkeys-lfu  # 长期热点数据
# maxmemory-policy volatile-lru  # 有 TTL 的数据

5.2 监控内存

-- INFO memory
# maxmemory: 2147483648  (2GB)
# used_memory: 1610612736  (1.5GB)
# used_memory_rss: 1711276032  (物理内存占用)
# mem_fragmentation_ratio: 1.06  (内存碎片率)
# evicted_keys: 0  (被淘汰的 key 数量)

-- evicted_keys 增长说明:
-- 如果 evicted_keys 持续增长,考虑:
-- 1. 增加 maxmemory
-- 2. 降低 TTL
-- 3. 优化 key 大小

【面试官心理】 淘汰策略是 Redis 内存管理的核心。能说清楚 LRU 和 LFU 的适用场景、以及近似 LRU 实现原理的候选人,说明他对 Redis 的内部机制有深入理解。


级别考察重点期望回答
P5策略名称8 种策略名称、LRU 基本概念
P6原理理解LRU vs LFU 区别、近似 LRU
P7生产应用策略选择、监控调优