内存管理与淘汰策略
问题
Redis 的过期删除策略是什么?内存满了怎么办?LRU 和 LFU 有什么区别?
答案
一、过期删除策略
Redis 使用 惰性删除 + 定期删除 的组合策略:
| 策略 | 时机 | 说明 |
|---|---|---|
| 惰性删除 | 访问 key 时检查 | 如果过期了才删除。节省 CPU,但可能残留大量过期 key |
| 定期删除 | 每秒执行多次 | 随机抽取一批 key 检查过期。平衡 CPU 和内存 |
定期删除的细节
每秒运行 10 次(默认 hz=10),每次:
- 随机抽取 20 个设了过期时间的 key
- 删除其中过期的 key
- 如果过期比例 > 25%,重复步骤 1
- 每轮最多执行 25ms
二、内存淘汰策略
当内存使用达到 maxmemory 限制时触发:
| 策略 | 说明 | 推荐场景 |
|---|---|---|
| noeviction | 不淘汰,写入报错 | 不允许丢数据 |
| allkeys-lru | 从所有 key 中淘汰最近最少使用的 | 通用缓存(推荐) |
| allkeys-lfu | 从所有 key 中淘汰最不经常使用的 | 有热点数据 |
| allkeys-random | 从所有 key 中随机淘汰 | 无特殊需求 |
| volatile-lru | 从设了过期时间的 key 中 LRU 淘汰 | 混合使用(缓存+持久) |
| volatile-lfu | 从设了过期时间的 key 中 LFU 淘汰 | 混合使用 |
| volatile-random | 从设了过期时间的 key 中随机淘汰 | — |
| volatile-ttl | 淘汰剩余 TTL 最短的 key | 时效性数据 |
三、LRU vs LFU
| 对比项 | LRU(Least Recently Used) | LFU(Least Frequently Used) |
|---|---|---|
| 依据 | 最近 访问时间 | 访问频率 |
| 淘汰目标 | 最久没被访问的 | 访问次数最少的 |
| 适合场景 | 一般缓存 | 有热点数据 |
| Redis 版本 | 4.0 之前(近似 LRU) | 4.0 引入 |
LRU 的缺陷
一次性批量扫描的数据(如 KEYS * 或全表查询结果)会把真正的热点数据挤出去。LFU 通过统计访问频率避免了这个问题。
Redis 的近似 LRU
Redis 并没有维护精确的 LRU 链表(太耗内存),而是使用 近似 LRU:
- 每个 key 记录最后一次访问的时间戳(24 位,存在 Redis Object 头部)
- 淘汰时随机采样 N 个 key(
maxmemory-samples,默认 5) - 从中淘汰时间戳最旧的
maxmemory-samples 越大,越接近精确 LRU,但 CPU 开销也越大。
四、内存优化实践
| 优化手段 | 说明 |
|---|---|
| 合理选择数据结构 | Hash 比多个 String 更省内存 |
| 控制 key 大小 | key 名精简,value 不要太大 |
| 设置合理过期时间 | 避免内存中堆积无用数据 |
| 使用压缩 | 业务层 Gzip/Snappy 压缩 value |
| 控制连接数 | 每个连接约占 10KB 输出缓冲 |
| 监控内存 | INFO memory、MEMORY USAGE key |
# 查看内存使用
INFO memory
# used_memory: 已使用内存
# used_memory_rss: 操作系统分配的内存
# mem_fragmentation_ratio: 碎片率(> 1.5 需关注)
# 查看单个 key 的内存
MEMORY USAGE mykey
常见面试问题
Q1: maxmemory 应该设为多少?
答案:
推荐设置为物理内存的 70%~80%。留 20%~30% 给:
- 操作系统
- fork 子进程(RDB/AOF 重写时的 Copy-On-Write)
- 客户端输出缓冲区
- 碎片开销
Q2: 缓存场景推荐用哪种淘汰策略?
答案:
- 纯缓存(所有数据都可丢):
allkeys-lru或allkeys-lfu - 缓存 + 持久数据混合:
volatile-lru(只淘汰设了过期时间的缓存 key,不动持久数据) - 有热点数据:优先选 LFU(Redis 4.0+)
Q3: 内存碎片怎么处理?
答案:
当 mem_fragmentation_ratio 大于 1.5 时需要关注:
- Redis 4.0+:开启自动碎片整理
activedefrag yes - 重启 Redis(重新加载数据后碎片消除)
- 碎片的主要原因是频繁修改不同大小的 value