作为后端开发,我们每天都在使用 Redis 缓存数据,但很少有人会深入思考:Redis 是怎么删除过期数据的?当内存满了的时候,Redis 会怎么处理?
这两个问题看似简单,实则是 Redis 内存管理的核心。如果理解不到位,很容易出现内存泄漏、缓存雪崩、服务卡顿等线上问题。同时,这也是面试中 100% 会被问到的高频考点:
- Redis 的过期 key 是立即删除的吗?
- 为什么 Redis 不用定时删除策略?
- 8 种淘汰策略分别是什么?线上该怎么选?
- LRU 和 LFU 有什么区别?各自的适用场景是什么?
- Redis 的 LRU 是精确的吗?为什么这么设计?
这篇文章,我们就从问题本质、底层原理、执行流程、源码细节、线上最佳实践五个维度,彻底搞懂 Redis 的数据过期策略和淘汰策略。不仅会讲清楚理论,还会提供可直接落地的配置和避坑指南。

一、先搞懂:两个策略的核心区别
很多人容易混淆过期策略和淘汰策略,其实它们解决的是完全不同的问题:
- 数据过期策略 :处理设置了过期时间的 key。当 key 到达过期时间后,Redis 需要将其删除,释放内存空间。
- 数据淘汰策略 :处理所有 key 。当 Redis 的内存使用量达到配置的最大值(
maxmemory)时,按照一定的规则删除部分数据,保证新的写入请求可以正常进行。
简单来说:过期策略是 "主动清理垃圾",淘汰策略是 "内存满了不得不删东西"。
二、Redis 数据过期策略:自动清理过期数据
我们在使用 Redis 时,通常会给缓存 key 设置一个过期时间,比如set key value ex 3600表示 1 小时后过期。但 Redis 并不是在 key 过期的瞬间就立即删除它,而是有一套完整的过期策略。
1. 三种基本过期策略
Redis 有三种可能的过期策略,各有优缺点,分别在 CPU 和内存之间做不同的权衡:
(1)定时删除
核心原理:在给 key 设置过期时间的同时,创建一个定时器。当 key 到达过期时间时,定时器立即执行删除操作。
优点:内存利用率最高。过期的 key 会立即被删除,不会占用任何额外的内存。
缺点:CPU 利用率极低。如果有大量的 key 同时过期,会有大量的定时器同时触发,消耗大量 CPU 资源,严重影响 Redis 的正常服务。
结论:Redis 没有采用这种策略,因为 Redis 的核心目标是高性能,不能为了内存利用率牺牲 CPU 性能。
(2)惰性删除
核心原理:key 过期时不立即删除,而是在下次访问这个 key 时,检查它是否过期。如果过期就删除,然后返回 null;如果没过期就返回对应的值。
优点:CPU 利用率最高。只有在访问 key 时才会检查是否过期,不会消耗额外的 CPU 资源。
缺点:内存利用率最低。如果大量过期的 key 永远不会被访问,就会一直占用内存,导致严重的内存泄漏。
结论:单独使用惰性删除也不行,会导致内存被大量无效数据占用。
(3)定期删除
核心原理:每隔一段时间,Redis 就会随机抽取一定数量设置了过期时间的 key,检查它们是否过期,如果过期就删除。
优点:在 CPU 和内存之间取得了很好的平衡。通过调整定期检查的频率和每次检查的 key 数量,可以灵活地在 CPU 和内存之间做权衡。
缺点:需要合理配置参数,否则还是会出现内存泄漏或者 CPU 消耗过高的问题。
2. Redis 实际使用的过期策略
Redis 实际使用的是定期删除 + 惰性删除的组合策略,完美结合了两者的优点:
- 定期删除:主动清理大部分过期的 key,避免内存泄漏
- 惰性删除:清理定期删除遗漏的过期 key,进一步释放内存
这种组合策略既保证了 CPU 的利用率,又避免了大量过期 key 占用内存的问题,是目前最优的解决方案。
3. Redis 定期删除的底层实现(面试必问)
很多人以为定期删除就是每隔一段时间遍历所有 key 检查一遍,其实远没有这么简单。如果 Redis 有几千万个 key,遍历一遍需要几分钟,会导致服务完全不可用。
Redis 的定期删除采用了随机抽样 + 循环检查 + 时间限制 的设计,并且有fast 模式 和slow 模式两种执行模式。
3.1 基础执行流程
- Redis 默认每隔100 毫秒 (由redis.conf的hz参数控制,默认 10,表示每秒执行 10 次)执行一次定期删除
- 每次从所有设置了过期时间的 key 中,随机抽取 20 个key
- 检查这 20 个 key,删除其中过期的 key
- 如果过期的 key 比例超过25%,说明还有很多过期的 key,立即回到步骤 2,继续抽取 20 个 key 检查
- 如果过期的 key 比例低于 25%,本次定期删除结束,等待下一次执行
3.2 深入:fast 模式与 slow 模式(源码级细节)
Redis 的定期删除实际上有两种执行模式:slow 模式 (默认模式)和fast 模式(快速模式)。这两种模式的执行频率、时间限制和适用场景完全不同。
slow 模式(默认模式)
这是我们通常所说的定期删除模式,也是绝大多数情况下 Redis 使用的模式。
核心特点:
- 执行频率:由
hz参数控制,默认每秒 10 次(每 100ms 一次) - 最大执行时间:每次执行时间不超过25 毫秒 (
ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC参数,占 CPU 时间的 25%) - 抽样数量:每次随机抽取 20 个 key
- 循环条件:过期 key 比例超过 25% 则继续循环
适用场景:正常业务场景,过期 key 数量适中的情况。
fast 模式(快速模式)
当 Redis 检测到有大量 key 同时过期时,会自动切换到 fast 模式,以更快的速度清理过期 key,避免内存被大量无效数据占用。
触发条件:
- 上一次 slow 模式执行时,过期 key 的比例超过 25%
- 距离上一次 fast 模式执行已经超过了1 毫秒
核心特点:
- 执行频率:最高每秒执行 1000 次(每 1ms 一次)
- 最大执行时间:每次执行时间不超过1 毫秒 (
ACTIVE_EXPIRE_CYCLE_FAST_DURATION) - 抽样数量:每次随机抽取 20 个 key
- 循环条件:过期 key 比例超过 25% 则继续循环,但最多执行 16 次
适用场景:大量 key 同时过期的场景,比如电商秒杀活动结束后,大量秒杀相关的 key 同时过期。
两种模式对比表
| 特性 | slow 模式 | fast 模式 |
|---|---|---|
| 执行频率 | 每秒 10 次(默认) | 最高每秒 1000 次 |
| 单次最大执行时间 | 25 毫秒 | 1 毫秒 |
| 触发条件 | 定时触发 | 上一次 slow 模式过期 key 比例 > 25% |
| 适用场景 | 正常业务场景 | 大量 key 同时过期的场景 |
| CPU 占用 | 低 | 高 |
重要提醒:fast 模式虽然能快速清理大量过期 key,但会消耗大量 CPU 资源,可能导致 Redis 服务卡顿。这也是为什么我们一直强调 "不要给大量 key 设置相同的过期时间" 的根本原因 ------ 大量 key 同时过期会触发 fast 模式,导致 CPU 飙升,服务响应变慢。
3.3 设计的巧妙之处
- 随机抽样保证了不会遍历所有 key,避免了性能问题
- 25% 的阈值保证了过期 key 的比例不会太高,内存不会被大量无效数据占用
- 时间上限保证了不会导致服务长时间阻塞
- fast/slow 双模式设计,既保证了正常情况下的性能,又能应对突发的大量 key 过期场景
4. 过期 key 对持久化和主从复制的影响
这是很多文章会忽略,但实际线上非常重要的细节:
对 RDB 的影响
- 生成 RDB 文件时,过期的 key 不会被写入 RDB 文件
- 加载 RDB 文件时,会检查每个 key 是否过期,如果过期就跳过,不会加载到内存中
对 AOF 的影响
- 当 key 过期被删除时,Redis 会向 AOF 文件中追加一条
DEL命令 - AOF 重写时,过期的 key 不会被写入新的 AOF 文件
对主从复制的影响
- 主库 :会按照正常的过期策略删除过期 key,删除后向所有从库发送
DEL命令 - 从库 :永远不会主动删除任何过期 key ,只会执行主库发送的
DEL命令。这样可以保证主从数据的绝对一致性
重要提醒 :如果主库因为某些原因没有发送DEL命令,从库中的过期 key 会一直存在,直到被访问或者主库发送DEL命令。这也是为什么有时候会出现 "从库有数据,主库没有" 的情况。
三、Redis 数据淘汰策略:内存满了怎么办?
即使有了过期策略,还是会出现内存不足的情况:
- 大量的 key 没有设置过期时间(永久 key)
- 过期的 key 还没来得及被定期删除
- 业务快速增长,数据量超过了 Redis 的内存上限
当 Redis 的内存使用量达到maxmemory参数配置的阈值时,就会触发数据淘汰策略,按照一定的规则删除部分数据,保证新的写入可以正常进行。
1. 内存阈值配置
首先需要在redis.conf中设置 Redis 的最大内存:
# 设置Redis最大使用内存为4GB
maxmemory 4gb
线上环境必须设置这个参数。如果不设置,Redis 会使用系统所有可用内存,最终导致系统 OOM(内存溢出),Redis 进程被操作系统杀死。
2. 8 种淘汰策略详解
Redis 提供了 8 种淘汰策略,通过maxmemory-policy参数配置。这些策略可以分为两大类:不淘汰 和淘汰。
(1)不淘汰策略
noeviction:Redis 的默认策略。当内存满了的时候,新的写入请求会被拒绝,返回OOM错误,读请求可以正常执行。
线上绝对不要使用这个策略。一旦内存满了,整个 Redis 就只能读不能写,会导致所有写业务完全不可用。
(2)淘汰策略
淘汰策略又可以分为两个子类:
allkeys-*系列 :从所有 key中选择数据进行淘汰,不管有没有设置过期时间volatile-*系列 :只从设置了过期时间的 key 中选择数据进行淘汰
每个子类下面又有 4 种具体的策略:
| 策略 | 核心含义 | 适用场景 | 推荐指数 |
|---|---|---|---|
allkeys-lru |
从所有 key 中,淘汰最近最少使用(Least Recently Used)的 key | 绝大多数业务场景,冷热数据区分明显 | ⭐⭐⭐⭐⭐ |
allkeys-lfu |
从所有 key 中,淘汰使用频率最低(Least Frequently Used)的 key | 热点数据非常明显且长期稳定的场景 | ⭐⭐⭐⭐ |
allkeys-random |
从所有 key 中,随机淘汰 key | 所有 key 的访问频率完全均匀的场景 | ⭐⭐ |
volatile-lru |
只从设置了过期时间的 key 中,淘汰最近最少使用的 key | 需要保留永久数据的场景 | ⭐⭐⭐ |
volatile-lfu |
只从设置了过期时间的 key 中,淘汰使用频率最低的 key | 热点数据明显且需要保留永久数据的场景 | ⭐⭐⭐ |
volatile-random |
只从设置了过期时间的 key 中,随机淘汰 key | 所有过期 key 访问频率均匀的场景 | ⭐ |
volatile-ttl |
只从设置了过期时间的 key 中,淘汰最早过期的 key | 有明确过期优先级的场景 | ⭐ |
3. 核心淘汰算法深度解析
LRU 和 LFU 是最常用的两种淘汰算法,也是面试的重点。很多人能背出它们的名字,却搞不清它们的底层实现和区别。
(1)LRU(最近最少使用)
核心思想:如果一个 key 最近被使用过,那么它未来被使用的概率也更高。Redis 会淘汰最长时间没有被访问过的 key。
Redis 的近似 LRU 实现: 很多人以为 Redis 的 LRU 是精确的,其实不是。精确的 LRU 需要维护一个双向链表,记录所有 key 的访问时间,每次访问都要更新链表,这会消耗大量的内存和 CPU。
Redis 实现的是近似 LRU:
- 每个 Redis 对象都有一个 24 位的
lru字段,记录最后一次被访问的时间戳 - 当需要淘汰数据时,Redis 会随机抽取 5 个 key(由
maxmemory-samples参数控制,默认 5) - 淘汰这 5 个 key 中
lru值最小(最久未使用)的那个
这种近似 LRU 的效果已经非常接近精确 LRU,而且性能极高,是 Redis 的经典设计之一。
(2)LFU(使用频率最低)
核心思想:如果一个 key 被使用的频率越高,那么它未来被使用的概率也更高。Redis 会淘汰使用次数最少的 key。
LFU 是 Redis 4.0 以后引入的,它解决了 LRU 的一个致命缺陷:热点 key 冷启动问题。比如一个每天被访问 10 万次的爆款商品,突然因为某个原因 1 小时没有被访问,就会被 LRU 淘汰,而实际上它的使用频率远高于其他 key。
Redis 的 LFU 实现 : Redis 复用了 Redis 对象的 24 位lru字段,将其拆分成两部分:
-
高 16 位:
ldt(Last Decrement Time),记录上次计数器衰减的时间 -
低 8 位:
logc(Logarithmic Counter),记录访问频率的对数值 -
每次访问 key 时,
logc会按照一定的算法递增(不是简单的加 1,而是对数增长,避免计数器溢出) -
每隔一段时间,
logc会衰减,避免旧的高频 key 一直占用内存 -
淘汰时,选择
logc值最小的 key 淘汰
LFU 比 LRU 更能准确地识别热点数据,在很多场景下表现更好。
4. LRU vs LFU:怎么选?
| 特性 | LRU | LFU |
|---|---|---|
| 关注维度 | 最近访问时间 | 访问频率 |
| 解决的问题 | 普通的冷热数据分离 | 热点数据冷启动问题 |
| 实现复杂度 | 低 | 高 |
| 适用场景 | 大多数业务场景,访问模式变化较快 | 热点数据明显且长期稳定的场景 |
选择建议:
- 如果不确定用哪个,优先使用
allkeys-lru,这是绝大多数业务的最佳选择 - 如果你的业务有非常明显的热点数据(比如爆款商品、热门新闻),并且这些热点数据会持续很长时间,可以使用
allkeys-lfu
四、线上最佳实践与避坑指南
1. 过期策略最佳实践
(1)合理设置过期时间
-
不要给不需要过期的 key 设置过期时间
-
不要给所有 key 都设置相同的过期时间,否则会导致大量 key 同时过期,触发 fast 模式,CPU 飙升,服务卡顿
-
给过期时间加上一个随机值,分散过期时间:
java// 基础过期时间1小时,加上0-10分钟的随机值 int baseExpire = 3600; int randomExpire = new Random().nextInt(600); redisTemplate.opsForValue().set(key, value, baseExpire + randomExpire, TimeUnit.SECONDS); -
过期时间不要设置过长,一般不要超过 7 天。如果需要长期保存,可以使用永久 key 或者定期续期
(2)避免大量 key 同时过期
大量 key 同时过期是线上最常见的 Redis 卡顿原因之一。除了给过期时间加随机值,还可以:
- 分批设置过期时间,将过期时间分散到不同的时间段
- 对于批量导入的数据,不要一次性设置相同的过期时间
- 监控
expired_keys指标,如果发现短时间内突然飙升,说明有大量 key 同时过期,需要及时调整
(3)合理调整hz参数
hz参数控制定期删除的执行频率,默认是 10(每秒 10 次)。如果你的 Redis 中有大量的过期 key,可以适当调大这个值,比如设置为 20 或 30,加快过期 key 的清理速度。但不要设置过大,否则会消耗过多的 CPU 资源。
# 调整定期删除频率为每秒20次
hz 20
(4)监控关键指标
一定要监控 Redis 的以下指标:
expired_keys:已经过期并被删除的 key 总数evicted_keys:因为内存不足被淘汰的 key 总数keyspace_hits和keyspace_misses:缓存命中率used_memory和used_memory_peak:内存使用量和峰值redis_cpu_usage:Redis 的 CPU 使用率,如果突然飙升,可能是触发了 fast 模式
2. 淘汰策略最佳实践
(1)永远不要使用默认的noeviction策略
线上环境必须修改maxmemory-policy参数,推荐配置:
maxmemory-policy allkeys-lru
(2)合理设置maxmemory
- 单个 Redis 实例的
maxmemory不要超过 16GB,最好控制在 8GB 以内。如果数据量太大,使用 Redis Cluster 分片集群 - 预留 20%-30% 的内存缓冲区,避免内存突然飙升导致淘汰频繁
- 不要将
maxmemory设置为服务器物理内存的 100%,要给操作系统和 Redis 本身预留足够的内存
(3)合理设置maxmemory-samples
maxmemory-samples参数控制每次淘汰时随机抽取的 key 数量,默认是 5。这个值越大,淘汰越接近精确 LRU,但消耗的 CPU 也越多。
- 一般情况下,默认值 5 已经足够
- 如果对淘汰精度要求很高,可以设置为 10
- 不要设置超过 10 的值,否则会导致 CPU 消耗过高
(4)给所有缓存 key 设置过期时间
即使使用了allkeys-lru策略,也建议给所有缓存 key 设置过期时间。这是内存释放的最后一道防线,即使淘汰策略出现问题,过期策略也能保证内存不会被无限占用。
五、常见误区纠正
-
误区 :Redis 会立即删除过期的 key。 纠正:Redis 使用定期删除 + 惰性删除的组合策略,过期的 key 不会立即被删除,只有在定期检查或被访问时才会被删除。
-
误区 :定期删除只有一种执行模式。 纠正:Redis 定期删除有 slow 和 fast 两种模式。当大量 key 同时过期时,会自动切换到 fast 模式,加快清理速度,但会消耗更多 CPU。
-
误区 :淘汰策略只会淘汰过期的 key。 纠正 :
allkeys-*系列的策略会淘汰所有 key,不管有没有设置过期时间;只有volatile-*系列的策略才只会淘汰设置了过期时间的 key。 -
误区 :Redis 的 LRU 是精确的。 纠正:Redis 实现的是近似 LRU,通过随机抽取 key 来淘汰,不是精确的 LRU。
-
误区 :LFU 比 LRU 更好。 纠正:LFU 和 LRU 各有适用场景。LFU 适合热点数据稳定的场景,LRU 适合访问模式变化较快的场景。
-
误区 :设置了
maxmemory就不会出现 OOM。 纠正:如果淘汰速度跟不上写入速度,或者有大量永久 key 无法被淘汰,还是会出现 OOM。
六、高频面试题解答
-
问:Redis 的过期策略是什么? 答:Redis 使用定期删除 + 惰性删除的组合策略。定期删除每隔 100 毫秒随机抽取部分 key 检查,删除过期的 key;惰性删除在访问 key 时检查是否过期,如果过期就删除。
-
问:定期删除的 fast 模式和 slow 模式有什么区别? 答:slow 模式是默认模式,每秒执行 10 次,每次最多执行 25 毫秒;fast 模式是快速模式,当大量 key 同时过期时触发,最高每秒执行 1000 次,每次最多执行 1 毫秒。fast 模式能快速清理大量过期 key,但会消耗更多 CPU。
-
问:为什么 Redis 不用定时删除策略? 答:定时删除会消耗大量 CPU 资源,严重影响 Redis 的性能。Redis 的核心目标是高性能,所以采用了定期删除 + 惰性删除的组合,在 CPU 和内存之间取得平衡。
-
问:Redis 的淘汰策略有哪些?线上推荐使用哪个? 答:有 8 种淘汰策略,包括 noeviction、allkeys-lru、allkeys-lfu、volatile-lru 等。线上推荐使用 allkeys-lru 策略,这是绝大多数业务场景的最佳选择。
-
问:LRU 和 LFU 有什么区别? 答:LRU 淘汰最近最少使用的 key,关注的是访问时间;LFU 淘汰使用频率最低的 key,关注的是访问次数。LFU 解决了 LRU 中热点 key 偶尔不被访问就被淘汰的问题。
-
问:Redis 的 LRU 是精确的吗?为什么这么设计? 答:Redis 实现的是近似 LRU,通过随机抽取 5 个 key,淘汰其中最久未使用的那个。这样设计是为了避免维护双向链表带来的内存和 CPU 开销,在保证近似效果的同时,获得极高的性能。
-
问:主从复制中,从库会主动删除过期 key 吗? 答:不会。从库不会主动删除任何过期 key,只会执行主库发送的 DEL 命令。这样可以保证主从数据的一致性。
七、总结
Redis 的数据过期策略和淘汰策略,是 Redis 内存管理的两大核心机制。它们的设计都体现了 Redis 在性能和内存利用率之间做的精妙权衡:
- 过期策略通过定期删除 + 惰性删除的组合,以及 fast/slow 双模式设计,既保证了 CPU 性能,又避免了内存泄漏
- 淘汰策略提供了多种算法,适应不同的业务访问模式,保证内存满了之后服务仍然可用
理解了这两个机制的底层原理,特别是定期删除的双模式设计,你就能根据自己的业务场景,做出合理的配置选择,避免线上的各种内存问题。同时,这也是面试中考察 Redis 核心能力的重要部分,掌握了这些内容,你就能轻松应对所有相关的面试题。