过期删除策略和内存淘汰策略有什么区别?
面试官您好,过期删除策略和内存淘汰策略是Redis内存管理中两个完全独立、但又协同工作的机制。
我们可以把Redis想象成一个 "智能冰箱",来理解它们的区别:
- 过期删除策略 :就像是冰箱的 "保质期管理员" 。它的职责是,每天检查一下冰箱里的牛奶、面包,看看哪些已经过了保质期,然后把它们扔掉。
- 内存淘汰策略 :就像是冰箱的 "空间管理员" 。它的职责是,当你买了一瓶新的大可乐,发现冰箱已经塞满了、放不下了 ,它就得决定扔掉哪个"相对不那么重要"的旧东西(比如那瓶快喝完的、不常喝的酱油),来腾出空间。
1. 过期删除策略 (Expired Key Eviction)
-
目的与对象:
- 目的 :为了清理那些已经达到预设生存时间(TTL)、逻辑上已经失效的键值对,从而释放它们占用的内存。
- 处理对象 :所有设置了过期时间的、并且已经到期的键。
-
工作机制:
- Redis并不会在键到期的那一刻就立即删除它。它采用的是一种 "主动"和"被动"相结合 的策略:
- 惰性删除 (Passive Deletion) :当一个客户端尝试访问 一个键时,Redis会先检查这个键是否已过期。如果过期了,就立即删除它,并返回
nil
。这是一种"懒汉式"的清理。 - 定期删除 (Active Deletion) :只靠惰性删除,会导致大量已过期但从未被访问的键永远留在内存中。因此,Redis会每秒钟进行数次(默认10次)的后台扫描。它会随机地从设置了过期时间的键中抽取一部分,检查并删除其中已过期的。这是一种"主动"的、尽力而为的清理。
- 惰性删除 (Passive Deletion) :当一个客户端尝试访问 一个键时,Redis会先检查这个键是否已过期。如果过期了,就立即删除它,并返回
- Redis并不会在键到期的那一刻就立即删除它。它采用的是一种 "主动"和"被动"相结合 的策略:
2. 内存淘汰策略 (Eviction Policy)
-
目的与对象:
- 目的 :当Redis的物理内存使用量,达到了
maxmemory
这个配置上限 时,为了能继续服务新的写入请求,必须从现有的所有键中,选择一些"牺牲品"来删除,以腾出空间。 - 处理对象 :所有键 (或者只在设置了过期时间的键中选择,取决于具体策略)。它不关心这些键是否过期,只关心哪个键"最应该被扔掉"。
- 目的 :当Redis的物理内存使用量,达到了
-
工作机制(淘汰算法):
- Redis提供了多种淘汰策略,我们可以通过
maxmemory-policy
参数来配置。常见的有:noeviction
(默认):不淘汰任何数据,对新的写入请求直接返回错误。allkeys-lru
:从所有键 中,淘汰那个最近最少使用(LRU) 的。volatile-lru
:只从设置了过期时间的键中,淘汰那个最近最少使用的。allkeys-lfu
(Redis 4.0新增):从所有键中,淘汰那个最不经常使用(LFU) 的。这个比LRU更智能。allkeys-random
:随机淘汰。- 等等...
- Redis提供了多种淘汰策略,我们可以通过
总结:两者的核心区别
特性 | 过期删除策略 | 内存淘汰策略 |
---|---|---|
执行时机 | 被动(访问时) + 主动(定期扫描) | 内存达到maxmemory 上限时 |
处理对象 | 已过期的键 | 所有 或设置了TTL的键 |
解决问题 | 清理逻辑上已失效的数据 | 物理内存不足的问题 |
触发条件 | 键的TTL到期 | 内存使用量触顶 |
性质 | 常规的、必然的清理机制 | 紧急的、被动的自我保护机制 |
一句话总结:
- 过期删除 是处理 "时间上" 的失效。
- 内存淘汰 是处理 "空间上" 的不足。
一个键,可能还没等到过期,就因为内存满了,被内存淘汰策略提前"请"出去了。也可能,一个键已经过期了很久,但因为一直没有被访问到,也没被定期删除抽中,就一直"赖"在内存里,直到下一次内存满了,才可能被淘汰掉。这两个机制共同保障了Redis内存的健康和高效利用。
介绍一下Redis内存淘汰策略
面试官您好,Redis的内存淘汰策略,是当Redis的物理内存使用量达到maxmemory
上限 时,为了能继续服务新的写入请求,而必须执行的一种 "自我保护"机制。
它定义了"当内存满了,应该扔掉哪个数据来腾出空间"的规则。Redis提供了非常丰富的淘汰策略,正如您所分析的,我们可以将这八种策略,按照两个维度来进行一个清晰的分类。
第一层分类:是否进行淘汰?
- 1. 不进行淘汰:
noeviction
- 这是Redis 3.0之后至今的默认策略。
- 行为 :当内存达到上限时,它不淘汰任何数据 。对于后续的所有写入命令 (如
SET
,LPUSH
),它会直接返回一个错误 。但对于读命令(GET
)和删除命令(DEL
)则不受影响。 - 适用场景 :这个策略非常适合那些数据不能被丢失的应用,比如用Redis做分布式锁或配置中心。它通过报错的方式,明确地告诉客户端:"我的内存满了,请处理一下",而不是擅自丢弃数据。
第二层分类:如果进行淘汰,淘汰的范围是?
如果我们需要Redis在内存满时自动清理空间,就可以选择以下七种淘汰策略。这七种又可以根据 "淘汰范围" 分为两大类:
-
a. 只在"设置了过期时间(TTL)"的键中进行淘汰
- 这种策略的核心思想是:优先保留那些"永久"数据,只从那些"临时"数据中找"牺牲品"。
volatile-lru
: 在设置了过期时间的键中,淘汰那个最近最少使用(Least Recently Used) 的。这是 Redis 3.0之前的默认策略,非常适合用Redis做缓存的场景。volatile-lfu
(Redis 4.0新增): 在设置了过期时间的键中,淘汰那个最不经常使用(Least Frequently Used) 的。相比LRU,它能更好地处理"偶发性高频访问"导致热点数据被淘汰的问题,更智能。volatile-ttl
: 在设置了过期时间的键中,淘汰那个剩余生存时间(Time To Live)最短的,也就是最快要过期的。volatile-random
: 在设置了过期时间的键中,随机淘汰一个。
-
b. 在"所有键"的范围内进行淘汰
- 这种策略把所有键都一视同仁,进行全局淘汰。
allkeys-lru
: 在所有键 中,淘汰那个最近最少使用的。这是最常用的策略之一,当Redis纯粹被用作缓存时,这个策略非常有效。allkeys-lfu
(Redis 4.0新增): 在所有键 中,淘汰那个最不经常使用的。同样,在缓存场景下,它通常比LRU表现更好。allkeys-random
: 在所有键中,随机淘汰一个。适用于对数据没有明显热点区分的应用。
如何选择?------ 实践建议
我的选型策略通常是:
- 首先,明确我的业务场景:我用Redis是做什么的?是纯缓存,还是有需要持久化的数据?
- 如果数据都不能丢 :坚持使用默认的
noeviction
,并做好内存使用的监控和告警。 - 如果Redis主要用作缓存 :
- 优先考虑
allkeys-lfu
(如果Redis版本>=4.0)。它能更好地保留真正的热点数据。 - 如果版本较低,或者业务场景更符合"最近访问过的就是热点"这个模型,那么
allkeys-lru
是一个非常好的选择。
- 优先考虑
- 如果我希望Redis能自动清理"冷"的临时数据,但要保留"热"的永久数据 :我会使用
volatile-lfu
或volatile-lru
。这样,那些没有设置过期时间的、需要持久化的重要数据,就不会被淘汰策略所影响。
通过对这些策略的理解和合理配置,我们就能让Redis在内存受限的情况下,依然能够智能地、高效地为我们的业务服务。
介绍一下Redis过期删除策略
面试官您好,Redis的过期删除策略,是一个非常经典的、在 "CPU性能开销" 和 "内存空间占用" 之间做极致权衡的设计。
它并没有采用单一的删除方式,而是巧妙地结合了两种策略:惰性删除 和定期删除。
1. Redis为什么不采用"定时删除"?
- 首先,我们需要理解为什么Redis不采用最直观的"定时删除"(即为每个key创建一个定时器,到期后立即删除)。
- 原因 :如果大量key在同一时间集中过期,那么Redis的主线程就会花费大量时间去执行删除操作,从而严重阻塞对其他客户端请求的响应。这对于追求高性能的Redis是不可接受的。
因此,Redis选择了下面这两种更"聪明"的策略。
2. 策略一:惰性删除 (Lazy Deletion) ------ "被动清理"
- 它是什么? 这是一种"懒汉式"的删除策略。Redis并不会主动去监控key是否过期。
- 工作机制 :当一个客户端尝试访问(如
GET
)或修改一个key 时,Redis会首先调用一个内部函数(expireIfNeeded
),检查这个key的过期时间。- 如果已过期 :此时,Redis会立即将这个key删除 ,然后向客户端返回
nil
(或空),就像这个key从未存在过一样。从Redis 4.0开始,如果这个key关联的是一个大对象,删除操作还可以通过lazyfree
机制,交由后台线程异步执行,避免阻塞主线程。 - 如果未过期:则正常执行后续的命令。
- 如果已过期 :此时,Redis会立即将这个key删除 ,然后向客户端返回
- 优点:对CPU非常友好,因为它只在必要时才进行删除操作,不会浪费任何CPU时间在"可能无用"的检查上。
- 缺点 :如果一个key已过期,但之后再也没有被访问过,那么它就会一直"赖"在内存里,永远不会被删除,造成了内存泄漏。
3. 策略二:定期删除 (Periodic Deletion) ------ "主动抽查"
为了弥补惰性删除的不足,Redis引入了定期删除机制,来主动地清理一部分过期key。
- 它是什么? 这是一种"主动的、但又有限度的"清理策略。
- 工作机制 :
- 执行频率 :Redis有一个周期性的任务(
serverCron
),它会默认每秒执行10次 (由hz
参数控制)来执行一些后台操作,其中就包括了定期删除。 - 抽样与检查 :在每一次的定期删除任务中,Redis不会遍历所有 的key,而是从设置了过期时间的key集合(
expires
字典)中,随机抽取一部分(默认是20个)进行检查。 - 删除:它会删除掉这批样本中所有已过期的key。
- 循环与终止条件 :
- 如果 在这一轮抽样中,被删除的过期key的比例超过了25% (即超过5个),Redis会认为当前数据库中可能还存在大量的过期key。于是,它会立即开始下一轮的抽样和删除,而不会等待下一个1/10秒。
- 如果 过期key的比例低于25% ,它就认为大部分key还是"健康"的,于是就停止本轮 的定期删除,等待下一次
serverCron
的触发。
- 时间上限 :为了防止因为过期key太多,导致这个删除循环一直执行下去而阻塞主线程,Redis还为这个循环设置了一个时间上限(默认是25ms),确保每次定期删除任务的执行时间不会过长。
- 执行频率 :Redis有一个周期性的任务(
总结
- 惰性删除 ,用最低的CPU成本 ,保证了最终被访问到的数据一定是有效的。
- 定期删除 ,通过有限的、随机的抽样 ,尽力地去清理那些"沉睡"的过期key,控制了内存泄漏的规模。
这两种策略的完美结合,使得Redis能够在CPU性能 和内存占用这两个看似矛盾的目标之间,取得了一个非常出色的平衡。
Redis的缓存失效会不会立即删除?
面试官您好,您提出的这个问题非常关键,它直击了Redis在设计其过期删除策略时所做的核心权衡(Trade-off)。
我们不采用"到期立即删除"(也就是定时删除 )的策略,其根本原因在于:为了保证Redis作为高性能内存数据库的核心服务能力,我们不能让"删除过期键"这种非核心的维护任务,来阻塞或影响处理客户端请求的核心任务。
1. "定时删除"的理想与现实
-
理想中的模型 :为每一个设置了过期时间的键,都创建一个定时器(timer)。当这个键的过期时间到达时,定时器会立即触发一个回调,将这个键删除。
-
这种模型的优点 :内存可以被最及时、最精准地回收,对内存非常友好。
-
现实中的两大瓶颈:
-
a. CPU性能瓶颈(这是最主要的原因)
- 这种策略对CPU是极其不友好的。
- Redis的核心工作线程是单线程的。它需要用这个宝贵的线程,去处理所有客户端的读写请求。
- 想象一个场景 :在一个秒杀活动或大型促销结束后,有数百万个 缓存键在同一秒内集中过期。如果采用定时删除,那么在这一秒内,Redis的主线程就需要去执行数百万次回调和删除操作。
- 后果 :在这期间,主线程会被完全占用 ,无法去响应任何新的客户端请求。从外部看,Redis就好像 "卡死"或"宕机" 了一样,这会造成严重的服务中断。
- 所以,我们不能为了"内存的实时性",而牺牲了Redis最重要的 "高响应性"和"高吞吐量"。
-
b. 实现上的成本与复杂性
- 为每一个键都创建一个定时器,在数据结构上,通常需要使用像"时间轮"或者"最小堆"这样的结构来管理。
- 当需要设置过期时间的键非常多时(在Redis中这是常态),维护这个巨大的定时器集合,其本身的数据结构操作(如插入、删除)也会带来不小的性能开销。
-
2. Redis的权衡与选择
面对"定时删除"的这些问题,Redis做出了一个非常务实和聪明的选择,即采用了 "惰性删除 + 定期删除" 的组合策略:
- 惰性删除 :将删除的CPU成本,分摊到了每一次对键的访问中。只有当你需要用它的时候,才顺便检查一下,这是一种极致的"懒加载"思想。
- 定期删除 :通过有限的、随机的抽样 ,来主动清理一部分"沉睡"的过期键。它通过限制执行频率 和单次执行时间上限,确保了这个维护任务永远不会过度占用CPU,从而影响到主服务。
总结一下,Redis之所以不采用"过期立即删除"的策略,本质上是在以下几者之间做出的一个经典权衡:
- 内存回收的及时性
- CPU资源的消耗
- 对主服务响应能力的影响
最终,Redis选择牺牲了一部分内存回收的实时性 (允许过期键在内存中多"停留"一会儿),来换取整个系统稳定、持续的高性能服务能力。这充分体现了其作为高性能缓存和数据库的设计哲学。