视频看了几百小时还迷糊?关注我,几分钟让你秒懂!
在使用 Redis 时,你是否遇到过这些问题:
- 缓存数据明明设置了过期时间,为什么还占着内存不释放?
- Redis 内存爆了,新数据写不进去,报错
(error) OOM command not allowed? - 为什么有些 key 过期后还能查到?
这些问题的根源,都指向 Redis 的两大核心机制:过期策略(Expire Policy)和内存淘汰策略(Eviction Policy)。
本文将用通俗语言 + 原理图解 + Spring Boot 实战代码,带你彻底搞懂:
- ✅ Redis 如何自动清理过期数据?
- ✅ 内存不足时,Redis 怎么决定删哪些 key?
- ✅ 如何配置策略避免生产事故?
一、过期策略(Expire Policy):自动清理“过期”数据
🎯 目标
当 key 设置了 TTL(Time To Live),到期后自动删除,避免脏数据长期占用内存。
🔧 实现方式:惰性删除 + 定期删除(双保险!)
1. 惰性删除(Lazy Expiration)
- 触发时机:客户端访问某个 key 时
- 操作:先检查是否过期,若过期则删除,再返回结果
- 优点:CPU 友好,不主动扫描
- 缺点:长期不访问的过期 key 会一直占内存(“内存泄漏”)
// 示例:GET user:1001 // Redis 内部逻辑: // if (key exists && isExpired(key)) { // delete(key); // return null; // }2. 定期删除(Active Expiration)
- 触发时机:Redis 每隔100ms主动扫描
- 操作:
- 随机抽取20 个设置了过期时间的 key
- 删除其中已过期的 key
- 如果过期比例 > 25%,重复扫描(最多 16 次或耗时 > 25ms)
- 优点:清理“僵尸”过期 key,减少内存浪费
- 缺点:无法保证实时性(可能延迟几秒到几分钟)
📌总结:
- 惰性删除:访问时才清理(保数据正确性)
- 定期删除:后台定时清理(保内存效率)
- 两者互补,但都无法做到“秒级精准删除”!
二、内存淘汰策略(Eviction Policy):内存不足时的“紧急预案”
🎯 目标
当 Redis 内存使用达到maxmemory上限时,自动删除部分 key,腾出空间给新数据。
⚠️ 注意:过期策略 ≠ 淘汰策略!
- 过期策略:只删已过期的 key
- 淘汰策略:内存不够时,不管是否过期都可能删!
🔧 配置方式
# redis.conf maxmemory 2gb # 设置最大内存 maxmemory-policy allkeys-lru # 设置淘汰策略📊 8 种淘汰策略详解(附适用场景)
| 策略 | 作用范围 | 淘汰规则 | 适用场景 |
|---|---|---|---|
noeviction | 所有 key | 不淘汰,新写入报错 | 数据不能丢(如计数器) |
allkeys-lru | 所有 key | LRU:最近最少使用 | 通用推荐!缓存场景 |
volatile-lru | 仅带 TTL 的 key | LRU | 混合存储(部分缓存+部分持久) |
allkeys-lfu | 所有 key | LFU:最不经常使用 | 访问频率差异大(热点/冷门明显) |
volatile-lfu | 仅带 TTL 的 key | LFU | 同上,但只淘汰缓存数据 |
allkeys-random | 所有 key | 随机删除 | 对数据无偏好 |
volatile-random | 仅带 TTL 的 key | 随机删除 | 同上 |
volatile-ttl | 仅带 TTL 的 key | TTL 最短优先 | 优先清理快过期的数据 |
💡LRU vs LFU:
- LRU:关注“最近是否用过” → 适合短期热点
- LFU:关注“总共用了多少次” → 适合长期热点
三、Spring Boot 实战:如何正确使用?
✅ 场景 1:纯缓存系统(推荐allkeys-lru)
# application.yml spring: redis: # ... 其他配置// 设置缓存(无需手动设 TTL,靠 LRU 自动淘汰) redisTemplate.opsForValue().set("product:1001", productJson); // 或设一个较长 TTL 作为兜底 redisTemplate.opsForValue().set("product:1001", productJson, 2, TimeUnit.HOURS);📌优势:热点数据常驻内存,冷数据自动淘汰,内存利用率高。
✅ 场景 2:混合存储(部分数据不能丢)
// 永久数据(如用户 ID 映射)→ 不设 TTL redisTemplate.opsForValue().set("user:id:name:1001", "张三"); // 缓存数据(如商品详情)→ 设 TTL redisTemplate.opsForValue().set("product:1001", json, 30, TimeUnit.MINUTES);# redis.conf maxmemory-policy volatile-lru # 只淘汰带 TTL 的 key✅效果:永久数据永不淘汰,缓存数据按 LRU 清理。
❌ 反例 1:不设 maxmemory → 内存爆炸!
# 错误:未配置 maxmemory # 后果:Redis 一直吃内存,直到系统 OOM 杀死进程!❌ 反例 2:用noeviction但不做容量规划
// 错误:所有 key 都不设 TTL,且 maxmemory-policy=noeviction redisTemplate.opsForValue().set("log:" + System.currentTimeMillis(), log); // 后果:内存满后,所有 SET 操作失败,服务不可用!四、生产环境最佳实践
1.必须设置maxmemory
- 建议设为物理内存的70%~80%(留空间给 OS 和 fork)
- 示例:机器 16GB →
maxmemory 12gb
2.选择合适的淘汰策略
- 90% 场景用
allkeys-lru - 若有永久数据 + 缓存数据 → 用
volatile-lru+缓存 key 必须设 TTL
3.监控关键指标
# 查看过期/淘汰统计 redis-cli info memory # 输出: # expired_keys:12345 # 累计过期 key 数 # evicted_keys:6789 # 累计淘汰 key 数(>0 说明内存压力大!)4.避免 Big Key
- 一个大 Hash/List 可能导致淘汰时阻塞主线程
- 定期运行
redis-cli --bigkeys检测
五、常见问题解答
❓ Q1:为什么过期 key 还能查到?
- 原因:还没被惰性删除 or 定期删除扫描到
- 验证:执行
TTL key,若返回-2表示已删除,-1表示无过期时间,>0表示未过期
❓ Q2:volatile-lru没有可淘汰 key 会怎样?
- 答案:退化为
noeviction,新写入报错!
❓ Q3:LRU 是精确的吗?
- 答案:Redis 使用近似 LRU(采样 5~10 个 key 选最旧的),节省内存。可通过
maxmemory-samples调整精度(默认 5)。
六、总结:一张表搞定策略选择
| 你的需求 | 过期策略 | 淘汰策略 | 是否设 TTL |
|---|---|---|---|
| 纯缓存(如商品详情) | ✅ | allkeys-lru | 可设(兜底) |
| 混合存储(用户信息+缓存) | ✅ | volatile-lru | 缓存必须设 |
| 数据绝对不能丢(如分布式锁) | ❌ | noeviction | 不设 |
| 日志类数据(可丢失) | ✅ | allkeys-random | 建议设 |
结语
Redis 的过期与淘汰策略,是内存高效利用的核心。理解它们,你就能:
- 避免内存泄漏
- 防止 OOM 崩溃
- 让热点数据常驻内存
记住:不设maxmemory的 Redis,就像没装刹车的跑车——迟早出事!
视频看了几百小时还迷糊?关注我,几分钟让你秒懂!