Redis 内存淘汰策略详解
一、为什么需要淘汰策略?
Redis是一个基于内存的数据库,所有数据都存储在内存中。然而内存是稀缺资源,当内存使用达到上限时,必须要有一种机制来决定哪些数据该被清理。
核心配置
| 配置项 | 说明 |
|---|---|
| maxmemory | 限定Redis使用的最大内存字节数 |
| maxmemory-policy | 达到上限时的淘汰策略 |
| maxmemory-samples | LRU/LFU算法采样的数量 |
Redis对象结构
Redis每个键值对都是一个redisObject,包含以下字段:
| 字段 | 说明 |
|---|---|
| type | 数据类型(String/List/Hash等) |
| encoding | 编码方式(int/embstr/raw等) |
| lru | 记录对象空转时长(LRU时钟) |
| refcount | 引用计数,对象被复用的次数 |
| ptr | 指向实际数据的指针 |
二、八种淘汰策略
分类总览
+-------------------------+
| 淘汰策略选择 |
+-------------------------+
|
+---------------------+---------------------+
| | |
v v v
+---------------+ +-----------------+ +---------------+
| volatile-* | | allkeys-* | | no-eviction |
| (过期key中) | | (所有key中) | | (禁止淘汰) |
+---------------+ +-----------------+ +---------------+
1. volatile-lru(LRU - 最近最少使用)
算法思想:
从已设置过期时间的键中,选择最近最长时间未被使用的key进行淘汰。
适用场景:
- 数据有明显的冷热之分
- 希望保留热点数据,过期数据自然淘汰
工作原理:
- RedisObject中的lru字段记录最近一次访问的时间戳
- 淘汰时遍历所有带过期时间的key
- 选择lru值最小(最久未被访问)的key淘汰
配置示例:
maxmemory-policy volatile-lru
maxmemory-samples 5
2. volatile-lfu(LFU - 最少次数使用)
算法思想:
从已设置过期时间的键中,选择最近一段时间内访问次数最少的key进行淘汰。
适用场景:
- 数据访问频率有明显规律
- 需要淘汰低频数据保留高频数据
工作原理:
- RedisObject中的lru字段高16位存储分钟级别的访问时间,低8位存储访问计数器
- 淘汰时选择refcount最小(访问次数最少)的key淘汰
3. volatile-ttl(TTL - 最近要过期)
算法思想:
从已设置过期时间的键中,优先淘汰剩余存活时间最短的key。
适用场景:
- 数据有明确的有效期
- 希望优先清理即将过期的数据
工作原理:
- 每个key都记录了过期时间戳
- 淘汰时计算各key的剩余TTL
- 优先淘汰TTL最小(最快过期)的key
4. volatile-random(随机淘汰)
算法思想:
从已设置过期时间的键中,随机选择某个key进行淘汰。
适用场景:
- 数据重要性相近
- 需要快速释放内存而不关心具体淘汰哪个
5. allkeys-lru(全库LRU)
算法思想:
从所有键中选择最近最长时间未被使用的key进行淘汰。
适用场景:
- 全部数据都有被访问的可能
- 缓存空间明显不足,需要释放内存
- 没有明确过期时间的数据也需要清理
特点:
与volatile-lru相比,选择范围是所有key,不限于带过期时间的key。
6. allkeys-lfu(全库LFU)
算法思想:
从所有键中选择访问次数最少的key进行淘汰。
适用场景:
- 所有数据都需要管理
- 希望保留高频访问数据
7. allkeys-random(全库随机)
算法思想:
从所有键中随机选择某个key进行淘汰。
适用场景:
- 数据访问频率相近
- 需要快速腾出内存空间
8. no-eviction(禁止淘汰)
行为:
当内存使用达到maxmemory时,不淘汰任何数据,写入操作直接失败。
返回值:
(error) OOM command not allowed when used memory > maxmemory
适用场景:
- 作为纯粹的功能服务,不希望丢失任何数据
- 对内存有严格监控,到达阈值后人工干预
三、策略对比
| 策略 | 选择范围 | 选择依据 | 特点 |
|---|---|---|---|
| volatile-lru | 过期key | 最近最少使用 | 保留热点,淘汰冷数据 |
| volatile-lfu | 过期key | 最近最少使用次数 | 保留高频,淘汰低频 |
| volatile-ttl | 过期key | 最短剩余TTL | 优先淘汰快过期的 |
| volatile-random | 过期key | 随机 | 快速释放,不挑数据 |
| allkeys-lru | 所有key | 最近最少使用 | 激进淘汰,适合缓存场景 |
| allkeys-lfu | 所有key | 最近最少使用次数 | 保留高频访问数据 |
| allkeys-random | 所有key | 随机 | 快速腾空间 |
| no-eviction | 无 | 无 | 只读不写,禁止淘汰 |
四、配置建议
选择策略的决策树
需要淘汰数据?
|
+--否--> no-eviction
|
+--是--> 数据有明确过期时间?
|
+--否--> 全部数据都是候选
| |
| +---> 区分访问频率?
| |
| +--是--> allkeys-lfu
| |
| +--否--> allkeys-lru 或 allkeys-random
|
+--是--> 区分访问频率?
|
+--是--> volatile-lfu
|
+--否--> volatile-lru
推荐配置
通用缓存场景:
maxmemory 2gb
maxmemory-policy allkeys-lru
maxmemory-samples 5
内存敏感场景:
maxmemory 1gb
maxmemory-policy volatile-lru
maxmemory-samples 5
五、LRU算法采样
Redis默认使用采样方式近似实现LRU,而不是精确计算所有key。
maxmemory-samples 5 # 采样5个key进行对比
采样数量影响:
| 采样数 | 近似精度 | 内存开销 |
|---|---|---|
| 5 | 约60%准确 | 低 |
| 10 | 约80%准确 | 中 |
| 16 | 约90%准确 | 高 |
采样数越大越接近精确LRU,但CPU开销也相应增加。
六、面试追问
| 问题 | 回答要点 |
|---|---|
| Redis LRU和MySQL LRU有什么区别? | MySQL是精确LRU,需要维护有序链表;Redis是采样近似LRU,性能更高 |
| LFU比LRU好在什么地方? | LFU能识别高频热点数据,LRU可能误杀周期性访问的数据 |
| 为什么需要随机策略? | 当数据访问频率相近时,随机策略能快速释放内存 |
| no-eviction有什么用? | 保护数据不丢失,达到阈值后人工介入排查 |
根据零声教育教学写作https://github.com/0voice