当前位置: 首页 > news >正文

第二篇:Redis的过期删除与内存淘汰——数据过期了怎么删?内存满了怎么办?

  1. 第一篇:Redis数据结构底层——String、List、Hash、Set、ZSet各自用什么实现的?
  2. 第二篇:Redis的过期删除与内存淘汰——数据过期了怎么删?内存满了怎么办?

前言

在上一篇文章中,我们拆解了Redis五大基本数据结构的底层实现。但还有一个根本性问题没有解决:Redis是内存数据库,内存是有限的。数据过期了怎么删?内存满了怎么办?

面试中,这两个问题经常被连续追问:

“Redis的过期键是怎么删除的?惰性删除和定期删除有什么区别?”
“内存淘汰策略你用过哪个?LRU和LFU在Redis中是怎么实现的?”
“为什么Redis不采用精确的LRU?”

本文从过期删除策略出发,拆解Redis如何管理有限的内存空间,以及每种淘汰策略的适用场景和选择逻辑。

本文核心问题:

  1. Redis的过期键是怎么删除的?惰性删除+定期删除为什么要组合使用?
  2. 如果Key过期了但还没被删除,能访问到吗?
  3. Redis内存满了怎么办?8种淘汰策略各自适用什么场景?
  4. LRU和LFU在Redis中是怎么实现的?和操作系统的LRU有什么区别?
  5. 为什么Redis采用近似的LRU而不是精确的LRU?
  6. LFU的访问频率是怎么计算的?什么情况下选LFU而不选LRU?
  7. 这些策略在你的秒杀项目中是怎么用的?

读完本文,你将对Redis的内存管理机制拥有从原理到实践的完整理解。


一、过期删除策略——Redis如何清理过期Key?

疑问:设置了EXPIRE的Key,时间到了之后是立即被删除的吗?

回答:不是。Redis使用"惰性删除+定期删除"的组合策略。原因很简单:如果每到期一个Key就要立即删除,CPU会被频繁的删除操作占用,影响正常命令的响应。

1.1 三种删除策略对比

策略做法优势劣势
定时删除每个Key设置一个定时器,到期立即删除内存释放及时CPU消耗大,Key多时定时器数量激增
惰性删除访问Key时才检查是否过期CPU友好过期Key可能长期占用内存
定期删除每隔一段时间扫描一批Key,删除其中过期的内存和CPU的折中扫描频率和数量的平衡点需要通过配置精细调整

1.2 惰性删除——访问时顺手检查

// 当命令访问某个Key时,先检查是否过期intexpireIfNeeded(redisDb*db,robj*key){if(key还没有设置过期时间)return0;if(当前时间<过期时间)return0;// 已过期 → 删除 → 返回"Key不存在"deleteKey(db,key);return1;}

每个读写命令在执行前都会先调用expireIfNeeded检查这个Key是否过期。过期就删除,没过期就正常执行。

问题:如果某个Key过期后无人访问,它就会一直占用内存。这就是所谓的"过期但未删除的Key"——并且这些Key对客户端的任何访问都是不可见的,Redis会表现为"这个Key不存在"。

1.3 定期删除——定时扫描清理

Redis的activeExpireCycle函数以每秒10次的频率运行,每次从expires字典中随机抽取20个Key检查。如果其中过期Key的比例超过25%,则继续扫描这轮,直到比例降到25%以下。同时设置了每次扫描的执行时间上限(默认25%的CPU时间),防止这一轮扫描挤占过多的命令处理时间。

定期删除的时间分配: 每次扫描最多消耗1ms → 每秒10次 → 最多消耗10ms CPU时间 Redis单线程中,这10ms会暂停其他命令的处理 → 所以不能无限扫描,必须在时间和空间之间取平衡

1.4 为什么要组合使用?

纯惰性删除:大量过期Key无人访问 → 永久占用内存 纯定期删除:高频扫描太耗CPU → 影响正常响应时间 两者组合: 惰性删除保证"谁访问谁清理",CPU消耗分散到具体命令上 定期删除保证"无人访问的过期Key也会被清理",内存不会无限占用

就像垃圾回收——惰性删除是"用到才清理",定期删除是"定期大扫除"。两者配合,内存和CPU各退一步。


二、内存淘汰策略——当内存满了怎么办?

疑问:如果所有Key都没有设置过期时间,或者过期删除还没来得及清理,内存就满了呢?

回答:这时候Redis会按照配置的"内存淘汰策略"来决定如何处理新写入请求。8种策略各有取舍,决定哪些Key应该优先被淘汰。

2.1 8种淘汰策略速查表

策略范围淘汰规则适用场景
noeviction不淘汰,写入失败报错严格模式,数据一条不能丢
allkeys-lru所有Key淘汰最近最少使用的Key最通用的缓存场景
allkeys-lfu所有Key淘汰访问频率最低的Key热点数据集中,冷数据分散
allkeys-random所有Key随机淘汰数据访问模式无明显规律
volatile-lru有过期时间的Key淘汰最近最少使用的Key只在缓存数据中淘汰
volatile-lfu有过期时间的Key淘汰访问频率最低的Key缓存数据,热点集中
volatile-random有过期时间的Key随机淘汰缓存数据,访问模式不规律
volatile-ttl有过期时间的Key淘汰即将过期的Key希望优先淘汰快过期的数据

2.2 allkeys vs volatile——范围的区别

allkeys系列:从"所有Key"中挑选淘汰 → 适合整库都是缓存的场景(如Redis专门做缓存层) → 如果库中有持久化数据,allkeys可能淘汰不该淘汰的Key volatile系列:只从"设置了过期时间"的Key中挑选淘汰 → 适合缓存+持久化数据混合的场景 → 没有设置过期时间的Key是持久数据,不会被淘汰

2.3 秒杀项目中的选择

秒杀系统中,Redis里存两类数据:库存数据(有时效性,秒杀结束即过期)和课程信息缓存(有过期时间,预热时写入)。不存在需要永久保留的重要数据。如果Redis内存满了,LRU自动淘汰冷Key——很久没被访问的缓存自动让位给热数据。

实际配置allkeys-lru—— 所有Key都参与LRU淘汰。简单、高效、适合全缓存的场景。


三、Redis的LRU实现——为什么是"近似"的?

疑问:操作系统中的LRU用链表实现,Redis为什么不用?

回答:因为精确的LRU需要维护一个双向链表,每次访问都要把节点移到链表头部。Redis单线程架构下,频繁的链表操作会严重影响正常命令的吞吐。

3.1 精确LRU的问题

精确LRU需要:每次get命中时→定位节点→将节点从当前位置摘除→重新插入链表头部。这个操作本身就需要时间和指针操作。在高频访问场景下,这个维护成本与Redis"快"的核心诉求矛盾。

3.2 Redis的近似LRU

Redis在redisObject结构体中为每个Key存储了一个24位的lru时钟字段。这个字段记录的不是时间戳,而是Redis内部维护的当前分钟级时钟计数器(默认每100ms更新一次)。当需要淘汰时,随机抽取一批Key,比较它们的lru值——值越小的表示越久没被访问,优先淘汰。

随机抽取5个Key → 比较它们的lru时钟值 KeyA: lru=158 ← 最小,最近没被访问 KeyB: lru=163 KeyC: lru=167 ← 最大,刚被访问过 → 淘汰KeyA

为什么随机抽就能保证质量?单次随机抽取在极端情况下可能误淘汰一个近期频繁访问的Key。但抽取10次、100次、1000次后,长期活跃的热Key会在多轮随机抽样中持续排在时钟值最大的一端,而长期不活跃的冷Key在多次抽样中几乎每次都是时钟值最小的候选——统计上,频繁被访问的Key被随机抽中的概率相同,但被"选中为最小lru"的概率显著低于冷Key。

3.3 近似的效果

Redis的近似LRU在大多数场景下和精确LRU的淘汰效果相差不到10%,但维护成本极低——只需要在对象上存一个时间戳字段和淘汰时做随机抽样比较,不需要额外的链表结构和每次访问的指针操作。


四、Redis的LFU实现——从"访问时间"到"访问频率"

疑问:LFU和LRU有什么区别?什么场景选LFU?

回答:LRU关心的是"多久没被访问了",越久没被访问越容易淘汰。LFU关心的是"访问频率有多低",访问次数越少越容易淘汰。

4.1 LRU的缺陷

场景:热点数据集中,但有一次全库扫描 KeyA:平时被访问1000次,最近1分钟没被访问 → LRU时钟=160 KeyB:只在全库扫描期间被访问过1次 → LRU时钟=161 LRU判断:KeyA更久没被访问 → 淘汰KeyA 合理判断:KeyB总共才被访问1次 → 应该淘汰KeyB

LRU只看"最近一次访问是多久之前",不看"总共被访问过多少次"。一次全量扫描产生的"一次性热Key"就会把真正的热Key挤出缓存。LFU修正了这个问题——它看的是访问频率。

4.2 Redis LFU的实现

Redis 4.0引入LFU后,把原来24位的lru字段分成两部分:高16位存ldt(最后访问时间戳,精确到分钟),低8位存logc(访问频率的对数计数器)。

原来的lru字段(24位):[ lru时钟 ] LFU的lru字段(24位): [ ldt(16位) | logc(8位) ] 最后访问时间 对数频率计数

对数计数器:每次Key被访问时,计数器加1的概率是递减的——从0加到1较大概率,加到大几十后几乎不再增加。这样用8位就能表达"访问了1次"到"访问了几百万次"的动态范围。高16位记录最后访问时间,用于随着时间自动衰减计数器——如果Key最近没被访问,它的频率会逐渐降低。

4.3 LRU vs LFU 场景选择

LRU适用场景LFU适用场景
热点随时间变化热点相对固定
新数据比旧数据重要高频数据比低频数据重要
秒杀场景——活动开始后新商品成为热点内容推荐——标签热度取决于累计点击

五、秒杀项目中的策略配置

# 秒杀项目的Redis配置 maxmemory 2gb # 最多使用2GB内存 maxmemory-policy allkeys-lru # 所有Key参与LRU淘汰

选择逻辑

  • allkeys:秒杀系统的Redis只做缓存,不需要区分"持久数据"和"缓存数据"——全库都可以淘汰
  • lru:秒杀活动开始后,新商品成为新的热点,LRU能快速把旧缓存淘汰掉,让新热点占据内存
  • 不选lfu:LFU需要累积访问次数。秒杀活动持续时间短(几分钟到几小时),新的热点还没累积足够的频率就被LFU判定为"低频"而淘汰,反而不如LRU能快速响应热点的变化
  • 不选noeviction:内存满了直接影响业务正常写入,不可接受

六、面试中这样回答

面试官:“Redis的过期键是怎么删除的?”

回答框架

“Redis用惰性删除+定期删除的组合。惰性删除是在每次访问Key时检查是否过期——CPU开销分散到具体命令上,不影响整体响应时间。定期删除是后台每秒10次随机扫描,每次抽20个Key,过期比例超过25%就继续扫,但每次最多耗时1ms——防止单次扫描占用太多CPU。两者配合,惰性删除清理’有人访问的过期Key’,定期删除清理’没人访问的过期Key’。”

面试官:“内存满了怎么办?”

回答框架

“Redis有8种淘汰策略。我在秒杀项目中用的是allkeys-lru——Redis里的数据全是缓存,所有Key都参与LRU淘汰。LRU在Redis中是用近似算法:给每个Key存一个24位的时钟值,淘汰时随机抽一批比较,值最小的淘汰。不维护链表,CPU开销极小。秒杀场景下新的热点商品快速变成最近访问的Key,旧的冷Key自动被淘汰,适合热点变化快的场景。”


总结

  • 过期删除用惰性+定期组合:惰性解决CPU峰值,定期解决内存泄露。两者各退一步,内存和CPU达到平衡
  • 内存满了用淘汰策略:allkeys系列从所有Key中淘汰,volatile系列只淘汰有过期时间的Key
  • Redis的LRU是近似的——通过在对象上存一个时钟字段和淘汰时随机抽样比较,省掉了双向链表的维护开销,效果和精确LRU差距不大
  • LFU用对数计数器+时间衰减实现了"低频优先"的淘汰,解决了LRU被一次性扫描冲掉热数据的问题
  • 秒杀项目选allkeys-lru:全缓存场景不需要区分持久数据,LRU能快速响应秒杀时热点商品的变化
  • 内存管理是取舍的艺术——没有完美的策略,只有恰好适合当前场景的平衡点

下一篇预告:Redis原理(三)——缓存穿透、击穿、雪崩:从原理到解决方案。拆解三大缓存问题的成因和解决思路,同时会对接项目实战中的多级缓存设计——这篇文章会和你的项目文章形成理论+实战的呼应。

http://www.jsqmd.com/news/781799/

相关文章:

  • Blueboat性能优化秘籍:让你的JavaScript应用运行速度提升300%
  • 树莓派Zero W打造开源电子宠物:软硬结合与低功耗设计实战
  • 视频转文字的方法有哪些?2026年视频转文字工具推荐完全对比
  • Vale编译器构建系统详解:跨平台编译与依赖管理终极指南
  • LitElement自定义渲染根终极指南:解锁Shadow DOM的高级配置
  • Claude全面入侵Office,微软慌了!
  • 5分钟快速上手:Windows DLL注入器Xenos终极使用指南
  • 开源AI网关实战:LLM API治理、成本控制与安全合规指南
  • 如何用AKShare快速搞定金融数据获取?终极实战指南
  • Vrite高级功能揭秘:实时评论、版本历史和冲突解决
  • 终极navi指南:10个变量依赖和扩展功能的实战技巧
  • 碧蓝航线自动化革命:如何用AzurLaneAutoScript实现7x24小时全托管游戏体验
  • XUnity自动翻译器:让外语游戏秒变中文版的终极解决方案
  • 原神FPS解锁器:终极免费工具轻松突破60帧限制,享受丝滑游戏体验
  • FP8量化在生成式推荐系统OneRec-V2中的优化实践
  • Sanic消息格式终极指南:如何自定义协议与优化数据交换性能
  • FeignClient注解及参数问题---SpringCloud微服务
  • 5分钟打造个性化Windows桌面:TranslucentTB透明任务栏终极指南
  • 基于RAG技术构建私有知识库智能问答系统:从原理到实践
  • Docker-Mailserver终极指南:如何配置DKIM和DMARC实现专业邮件身份验证
  • Chatbox:桌面端AI助手聚合客户端,统一管理多模型与本地部署
  • 如何为ChatGPT-Micro-Cap-Experiment贡献代码:从零开始的AI金融项目贡献指南
  • Gallop Arena:LLM竞技场评估平台,自动化模型对比与Elo排名实战
  • 电脑崩了,密码全丢!我用一块 PE 盘,从“空白桌面”里抢回上百个网站登录凭证
  • DownKyi三分钟快速上手:B站视频下载难题一站式解决方案
  • 传统机器学习入门指南:从感知机到逻辑回归的完整实现教程 [特殊字符]
  • ARM SIMD指令集优化:VLD2/VLD3结构化加载详解
  • Hydroxide 数据迁移指南:如何安全导入导出 ProtonMail 邮件和联系人
  • 终极指南:Bottlerocket容器网络模型深度解析与性能优化
  • 水的低处与 ABAP 的高处