Redis 核心机制:数据过期策略与淘汰策略深度解析

作为后端开发,我们每天都在使用 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 实际使用的是定期删除 + 惰性删除的组合策略,完美结合了两者的优点:

  1. 定期删除:主动清理大部分过期的 key,避免内存泄漏
  2. 惰性删除:清理定期删除遗漏的过期 key,进一步释放内存

这种组合策略既保证了 CPU 的利用率,又避免了大量过期 key 占用内存的问题,是目前最优的解决方案。

3. Redis 定期删除的底层实现(面试必问)

很多人以为定期删除就是每隔一段时间遍历所有 key 检查一遍,其实远没有这么简单。如果 Redis 有几千万个 key,遍历一遍需要几分钟,会导致服务完全不可用。

Redis 的定期删除采用了随机抽样 + 循环检查 + 时间限制 的设计,并且有fast 模式slow 模式两种执行模式。

3.1 基础执行流程
  1. Redis 默认每隔100 毫秒 (由redis.conf的hz参数控制,默认 10,表示每秒执行 10 次)执行一次定期删除
  2. 每次从所有设置了过期时间的 key 中,随机抽取 20 个key
  3. 检查这 20 个 key,删除其中过期的 key
  4. 如果过期的 key 比例超过25%,说明还有很多过期的 key,立即回到步骤 2,继续抽取 20 个 key 检查
  5. 如果过期的 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,避免内存被大量无效数据占用。

触发条件

  1. 上一次 slow 模式执行时,过期 key 的比例超过 25%
  2. 距离上一次 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_hitskeyspace_misses:缓存命中率
  • used_memoryused_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 设置过期时间。这是内存释放的最后一道防线,即使淘汰策略出现问题,过期策略也能保证内存不会被无限占用。

五、常见误区纠正

  1. 误区 :Redis 会立即删除过期的 key。 纠正:Redis 使用定期删除 + 惰性删除的组合策略,过期的 key 不会立即被删除,只有在定期检查或被访问时才会被删除。

  2. 误区 :定期删除只有一种执行模式。 纠正:Redis 定期删除有 slow 和 fast 两种模式。当大量 key 同时过期时,会自动切换到 fast 模式,加快清理速度,但会消耗更多 CPU。

  3. 误区 :淘汰策略只会淘汰过期的 key。 纠正allkeys-*系列的策略会淘汰所有 key,不管有没有设置过期时间;只有volatile-*系列的策略才只会淘汰设置了过期时间的 key。

  4. 误区 :Redis 的 LRU 是精确的。 纠正:Redis 实现的是近似 LRU,通过随机抽取 key 来淘汰,不是精确的 LRU。

  5. 误区 :LFU 比 LRU 更好。 纠正:LFU 和 LRU 各有适用场景。LFU 适合热点数据稳定的场景,LRU 适合访问模式变化较快的场景。

  6. 误区 :设置了maxmemory就不会出现 OOM。 纠正:如果淘汰速度跟不上写入速度,或者有大量永久 key 无法被淘汰,还是会出现 OOM。

六、高频面试题解答

  1. 问:Redis 的过期策略是什么? 答:Redis 使用定期删除 + 惰性删除的组合策略。定期删除每隔 100 毫秒随机抽取部分 key 检查,删除过期的 key;惰性删除在访问 key 时检查是否过期,如果过期就删除。

  2. 问:定期删除的 fast 模式和 slow 模式有什么区别? 答:slow 模式是默认模式,每秒执行 10 次,每次最多执行 25 毫秒;fast 模式是快速模式,当大量 key 同时过期时触发,最高每秒执行 1000 次,每次最多执行 1 毫秒。fast 模式能快速清理大量过期 key,但会消耗更多 CPU。

  3. 问:为什么 Redis 不用定时删除策略? 答:定时删除会消耗大量 CPU 资源,严重影响 Redis 的性能。Redis 的核心目标是高性能,所以采用了定期删除 + 惰性删除的组合,在 CPU 和内存之间取得平衡。

  4. 问:Redis 的淘汰策略有哪些?线上推荐使用哪个? 答:有 8 种淘汰策略,包括 noeviction、allkeys-lru、allkeys-lfu、volatile-lru 等。线上推荐使用 allkeys-lru 策略,这是绝大多数业务场景的最佳选择。

  5. 问:LRU 和 LFU 有什么区别? 答:LRU 淘汰最近最少使用的 key,关注的是访问时间;LFU 淘汰使用频率最低的 key,关注的是访问次数。LFU 解决了 LRU 中热点 key 偶尔不被访问就被淘汰的问题。

  6. 问:Redis 的 LRU 是精确的吗?为什么这么设计? 答:Redis 实现的是近似 LRU,通过随机抽取 5 个 key,淘汰其中最久未使用的那个。这样设计是为了避免维护双向链表带来的内存和 CPU 开销,在保证近似效果的同时,获得极高的性能。

  7. 问:主从复制中,从库会主动删除过期 key 吗? 答:不会。从库不会主动删除任何过期 key,只会执行主库发送的 DEL 命令。这样可以保证主从数据的一致性。

七、总结

Redis 的数据过期策略和淘汰策略,是 Redis 内存管理的两大核心机制。它们的设计都体现了 Redis 在性能和内存利用率之间做的精妙权衡:

  • 过期策略通过定期删除 + 惰性删除的组合,以及 fast/slow 双模式设计,既保证了 CPU 性能,又避免了内存泄漏
  • 淘汰策略提供了多种算法,适应不同的业务访问模式,保证内存满了之后服务仍然可用

理解了这两个机制的底层原理,特别是定期删除的双模式设计,你就能根据自己的业务场景,做出合理的配置选择,避免线上的各种内存问题。同时,这也是面试中考察 Redis 核心能力的重要部分,掌握了这些内容,你就能轻松应对所有相关的面试题。

相关推荐
多敲代码防脱发1 小时前
Spring进阶(BeanFactory与ApplicationContext)
java·数据库·spring boot·后端·spring
zhangxingchao1 小时前
AI应用开发一: AI 编程、大模型调用和 Agent
前端·人工智能·后端
m0_702036532 小时前
html标签如何提升可访问性_aria-label与title区别【指南】
jvm·数据库·python
会编程的土豆2 小时前
Gin 核心概念速记
数据库·后端·gin·goland
ㄟ留恋さ寂寞2 小时前
Golang格式化输出占位符都有什么_Golang fmt占位符教程【通俗】
jvm·数据库·python
Donk_672 小时前
iSCSI 服务器
运维·服务器·数据库
Achou.Wang2 小时前
Concurrency patterns - Go 并发模式
开发语言·后端·golang
存在morning2 小时前
【GO语言开发实践】三 GO 工程化快速上手
开发语言·后端·golang
kyriewen2 小时前
用户打开飞行模式都能打开你的网站?Service Worker 做离线缓存,PWA 实战
前端·javascript·面试