news 2026/9/18 21:17:43

Redis 过期策略与 LRU 算法深度解析:定期删除 + 惰性删除与内存淘汰机制实战(doocs/advanced-java 高并发缓存篇)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis 过期策略与 LRU 算法深度解析:定期删除 + 惰性删除与内存淘汰机制实战(doocs/advanced-java 高并发缓存篇)

Redis 过期策略与 LRU 算法深度解析:定期删除 + 惰性删除与内存淘汰机制实战(doocs/advanced-java 高并发缓存篇)

【免费下载链接】advanced-java😮 Core Interview Questions & Answers For Experienced Java(Backend) Developers | 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识项目地址: https://gitcode.com/doocs/advanced-java

Redis 是互联网高并发架构中最核心的缓存组件,而「数据写进去为什么没了」「过期数据为什么还占着内存」正是线上使用 Redis 最常踩的两个坑。本文基于 doocs/advanced-java 高并发知识体系中的 《Redis 的过期策略和 LRU 算法》 展开,系统讲解 Redis 的定期删除 + 惰性删除双策略、maxmemory下的六种内存淘汰机制,并给出可现场手写的 Java 版 LRU 缓存实现,帮助你既能在面试中对答如流,也能在真实项目中正确配置与使用 Redis 缓存。

为什么 Redis 会"丢数据":先把缓存和存储分清楚

很多同学在生产环境遇到过这样的现象:数据写进 Redis,过一会儿再读就没了。这通常不是 Bug,而是把缓存当存储用导致的误判。

Redis 的定位是缓存,核心价值在于"用内存换性能"。与磁盘相比,内存是宝贵而有限的资源:一台机器可能只有几十 G 内存,却可以有几个 T 的硬盘空间。Redis 主要基于内存完成高性能、高并发的读写操作,内存空间天然是稀缺的。

  • 假设 Redis 只能使用 10G 内存,你却写入了 20G 数据,会发生什么?—— 必然要干掉 10G 的数据,只保留 10G;
  • 那么干掉哪些、保留哪些?—— 干掉不常用的数据,保留常用的数据,这就是内存淘汰要回答的问题;
  • 另一类现象是:key 明明设置了过期时间,到了时间却依然占用着内存?—— 这由 Redis 的过期策略来决定。

关于缓存的本质(高性能、高并发两大用途)以及缓存使用不当引发的双写不一致、缓存雪崩/穿透/击穿等问题,可以进一步阅读本仓库的《缓存的使用方式》。

Redis 过期策略:定期删除 + 惰性删除

Redis 的过期策略可以概括为一句话:定期删除 + 惰性删除,两者配合使用。

定期删除(active expiration)

所谓定期删除,指的是 Redis 默认每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除

这里有两个关键点需要特别注意:

  1. 不是遍历所有 key。假设 Redis 里放了 10 万个设置了过期时间的 key,如果每隔几百毫秒就全量检查一遍,Redis 的 CPU 负载会飙升,绝大部分开销都会消耗在"检查过期 key"这件事上,基本等于让 Redis "死掉"。因此 Redis 的做法是每隔 100ms 随机抽取一部分 key 来检查和删除,把检查成本控制在可接受范围;
  2. 这是概率性删除。既然是随机抽样,就必然存在"漏网之鱼"——某些过期 key 到了时间却没被抽查到,也就没被删除。这一部分工作就要交给惰性删除来兜底。

惰性删除(lazy expiration)

惰性删除发生在读取路径上:当你获取某个 key 的时候,Redis 会检查一下这个 key 是否设置了过期时间、是否已经过期;如果已经过期,此时就会将其删除,并且不会返回任何内容给你。

获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。

惰性删除的核心价值在于:保证客户端读到的数据一定是"活着"的,同时把删除动作的代价摊到访问路径上,避免后台做大规模扫描。但它同样有短板——如果一个过期 key 既没被定期删除抽查到,又一直没有被访问,它就永远不会被惰性删除

过期时间从哪来

在 Redis 中,给 key 设置过期时间通常使用以下命令:

# 设置 key 在 60 秒后过期 EXPIRE key 60 # 以毫秒为单位设置过期时间 PEXPIRE key 60000 # 写入时直接带上过期时间(最常用) SET key value EX 60 SETEX key 60 value

两个策略配合仍不够时怎么办

回到开头的场景:如果定期删除漏掉了很多过期 key,而你又没有及时去查询这些 key(没走惰性删除),大量过期 key 就会堆积在内存中,最终导致Redis 内存耗尽。此时怎么办?

答案是:走内存淘汰机制(maxmemory 淘汰策略)。过期策略负责"及时清理",内存淘汰机制负责"兜底保命",二者共同构成了 Redis 内存管理的完整闭环。

内存淘汰机制:六种 maxmemory 策略

当 Redis 内存不足以容纳新写入的数据时,会触发内存淘汰机制。Redis 提供了以下六种策略:

策略作用范围行为适用性评价
noeviction全部键空间新写入操作直接报错,不淘汰任何 key一般没人用,直接拒绝写入过于"粗暴"
allkeys-lru键空间(全部 key)移除最近最少使用的 key最常用,覆盖所有 key 做 LRU 淘汰
allkeys-random键空间(全部 key)随机移除某个 key一般没人用,既然是淘汰,理应淘汰最少使用的
volatile-lru设置了过期时间的键空间移除最近最少使用的 key一般不太合适,会漏掉大量未设置过期时间的 key
volatile-random设置了过期时间的键空间随机移除某个 key很少使用
volatile-ttl设置了过期时间的键空间更早过期时间的 key 优先移除适合希望优先清理"即将过期"数据的场景

逐一拆解如下:

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。这在生产环境基本不会采用,因为缓存场景下我们宁可淘汰旧数据,也不能让写入全部失败;
  • allkeys-lru:当内存不足以容纳新写入数据时,在整个键空间中,移除最近最少使用的 key。它不区分 key 是否设置了过期时间,覆盖范围最全,能够最大化利用有限内存服务热点数据,是最常用的策略;
  • allkeys-random:在整个键空间中随机移除某个 key。淘汰行为不可控、与业务热度无关,一般没人用——既然要淘汰,肯定是把最近最少使用的 key 干掉;
  • volatile-lru:仅在设置了过期时间的键空间中移除最近最少使用的 key。如果你的 key 大部分都没有设置过期时间,这个策略会"无从下手",所以一般不太合适;
  • volatile-random:在设置了过期时间的键空间中随机移除某个 key,淘汰行为同样不可控;
  • volatile-ttl:在设置了过期时间的键空间中,更早过期时间的 key 优先移除,即优先淘汰"快要过期"的数据,让内存淘汰方向与业务生命周期对齐。

补充说明:volatile-*系列策略只作用于设置过过期时间的 key。如果键空间中不存在任何设置了过期时间的 key,那么volatile-lruvolatile-randomvolatile-ttl的行为会退化为noeviction(即写入报错),这一点在使用时需要注意。此外,Redis 4.0 之后官方还引入了 LFU(Least Frequently Used,最不经常使用)系列策略(如allkeys-lfuvolatile-lfu),在 LRU 的基础上进一步考虑访问频率,适合"一次访问后就长期不用的冷数据"居多的场景,本文以经典的 LRU 体系为主线展开。

如何配置与动态调整

redis.conf配置文件中,通过以下两个参数控制内存淘汰行为:

# 限制 Redis 最大可用内存,例如 10g maxmemory 10gb # 指定内存淘汰策略,例如最常用的 allkeys-lru maxmemory-policy allkeys-lru

生产环境也常常动态调整而不重启进程,使用CONFIG命令即可:

# 查询当前内存上限与淘汰策略 CONFIG GET maxmemory CONFIG GET maxmemory-policy # 动态修改(立即生效,无需重启) CONFIG SET maxmemory 10gb CONFIG SET maxmemory-policy allkeys-lru

关于内存上限的量级,可参考本仓库《生产环境中的 Redis 是怎么部署的》中的实践:线上单实例 Redis 内存一般不要超过 10g,超出后可能带来 fork、持久化、淘汰扫描等多方面的稳定性问题;高并发下缓存的使用也依赖 Redis 单线程模型的高效性,详见《Redis 的线程模型》。

为什么是 LRU:让内存服务热点数据

LRU 是Least Recently Used的缩写,翻译过来就是"最近最少使用"。LRU 算法的核心思想是:当缓存满需要腾空间时,移除"最近最少使用"的数据,把空间让给"最新使用"的数据

这条朴素策略之所以有效,是因为互联网业务的访问分布天然符合局部性原理:往往最常被读取的数据,也就是被访问次数最多的数据(热点数据)。利用好 LRU 算法,我们能够显著提升对热点数据的缓存命中率,进而提高缓存服务的内存使用率。

LRU 的直观逻辑可以用下面这张图来理解:缓存按"最近使用"到"最少使用"排列,当某个项被get()访问时,它会被移动到缓存的最前面(最近使用位置),而最少使用的项会逐渐沉到列表尾部,成为下一次淘汰的候选。

在 Redis 内部,LRU 淘汰正是依托于这种"访问即更新位置"的机制:每次 key 被访问,其 LRU 时钟都会更新,内存不足时优先淘汰 LRU 时钟最久未被更新的 key。

手写一个 LRU 算法:基于 LinkedHashMap 的 Java 实现

面试中"手写 LRU"通常考察两件事:是否理解 LRU 的核心思想,以及是否熟悉 JDK 现成数据结构。从零手工实现双向链表 + 哈希表版的 LRU 代码量太大,现场手写不现实;更务实的做法是:利用 JDK 已有的LinkedHashMap实现一个 Java 版的 LRU 缓存

LinkedHashMap内部由哈希表 + 双向链表组成:哈希表保证 O(1) 的存取定位,双向链表维护元素的访问顺序。其构造器支持accessOrder参数——当accessOrder = true时,链表顺序即"访问顺序":每次get/put命中的元素都会被移动到链表尾部(最新位置),链表头部自然沉淀为"最久未使用"的元素,这正好就是 LRU 需要的语义。

public class LRUCache<K, V> extends LinkedHashMap<K, V> { private int capacity; /** * 传递进来最多能缓存多少数据 * * @param capacity 缓存大小 */ public LRUCache(int capacity) { super(capacity, 0.75f, true); this.capacity = capacity; } /** * 如果map中的数据量大于设定的最大容量,返回true,再新加入对象时删除最老的数据 * * @param eldest 最老的数据项 * @return true则移除最老的数据 */ @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { // 当 map中的数据量大于指定的缓存个数的时候,自动移除最老的数据 return size() > capacity; } }

代码要点拆解

  1. super(capacity, 0.75f, true)三个参数的含义
    • 第一个参数initialCapacity:初始容量,这里直接用缓存容量capacity
    • 第二个参数loadFactor:负载因子,0.75fHashMap的默认值,即当元素个数超过容量 × 0.75 时触发扩容;
    • 第三个参数accessOrder置为true是关键——开启"访问顺序"模式后,LinkedHashMap内部的双向链表会按访问(get/put)顺序维护节点,最近访问的节点移到链表尾部,最久未访问的节点沉淀在链表头部;
  2. removeEldestEntry(Map.Entry<K, V> eldest)钩子方法LinkedHashMap在每次put插入新元素后都会回调此方法,参数eldest就是当前链表头部(最老)的节点。我们重写它,在size() > capacity时返回trueLinkedHashMap就会自动把最老的数据移除——这正是 LRU 淘汰动作的落点;
  3. 用法:对外表现与普通Map完全一致,putgetremove均可直接使用,超过容量自动淘汰最久未使用的条目。

removeEldestEntry底层依赖的正是哈希表 + 双向链表的结构:哈希表以 O(1) 完成 key 定位,双向链表以 O(1) 完成节点的移动与头尾摘除,两者结合保证了 LRU 缓存在读写和淘汰上都是高效操作。

不依赖 LinkedHashMap 的经典实现思路

如果你被要求"不能直接用LinkedHashMap",也需要掌握经典的哈希表 + 双向链表手写思路,其骨架可以概括为:

  • 哈希表(HashMap:key → 链表节点,保证 O(1) 定位;
  • 双向链表:头节点为最近使用,尾节点为最少使用,每个节点持有 key 和 value;
  • get(key):在哈希表中找到节点,将其从链表当前位置摘除并移动到头部;
  • put(key, value):若 key 已存在,更新值并移动到头部;若不存在,插入到头部,若此时容量超限,则删除尾节点(最久未使用),并同步从哈希表移除。

上述思路的每一步都对应一个确定的数据结构操作,理解之后无论用哪种语言都能快速落地。

面试答题框架与要点回顾

围绕"Redis 过期策略 + LRU"这道高频面试题,可以按以下主线组织回答:

  1. 先定性:Redis 是缓存不是存储,内存有限,必然存在淘汰与过期清理;
  2. 过期策略:定期删除(默认每隔 100ms 随机抽取部分设置过期时间的 key 检查删除)+ 惰性删除(访问 key 时发现过期立即删除),两者互补,前者防堆积、后者防漏删,但不保证所有过期 key 及时被清;
  3. 兜底机制:当过期 key 堆积导致内存耗尽时,由maxmemory-policy指定的内存淘汰机制接管;
  4. 六种策略noevictionallkeys-lruallkeys-randomvolatile-lruvolatile-randomvolatile-ttl,并说明allkeys-lru最常用、volatile-*只作用于设置过过期时间的 key;
  5. 手写 LRU:现场给出基于LinkedHashMapaccessOrder = true+ 重写removeEldestEntry)的实现,并补充哈希表 + 双向链表的原理级思路。

延伸阅读

本文属于 doocs/advanced-java 高并发架构「缓存」知识链中的一环,建议结合以下同仓库文档串成完整知识体系:

  • 为什么要用缓存:缓存的高性能与高并发价值
  • Redis 的数据类型与使用场景
  • Redis 单线程模型与高并发原理
  • Redis 持久化机制:RDB 与 AOF
  • Redis 集群模式与分布式寻址算法
  • 缓存雪崩、缓存穿透与缓存击穿
  • 缓存与数据库双写一致性
  • 生产环境中的 Redis 部署方案

【免费下载链接】advanced-java😮 Core Interview Questions & Answers For Experienced Java(Backend) Developers | 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识项目地址: https://gitcode.com/doocs/advanced-java

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 21:16:54

医院后勤一站式服务中心:工单系统设计与派单规则实践

简介&#xff1a;这份数字化医院后勤一站式服务中心方案PPT共33页&#xff0c;面向医院后勤管理者、信息科人员及智慧医院建设规划方&#xff0c;聚焦传统后勤经验管理、信息不透明、缺乏统一调度和服务满意度低等痛点&#xff0c;提出一体化服务与信息化转型路径。资源为单一P…

作者头像 李华
网站建设 2026/9/18 21:16:19

Oracle数据库设计规范:从命名到字段类型的全链路实践

简介&#xff1a;《8数据库设计规范》是一份面向Oracle数据库设计人员的规范文档&#xff0c;旨在解决系统设计中命名混乱、字段类型随意、数据完整性难以保证等问题。文档从数据库策略、命名规范、数据模型产出物等方面展开&#xff0c;适用于需要建立统一数据模型的企业项目团…

作者头像 李华
网站建设 2026/9/18 21:16:16

GitHub热榜深度解读:AI项目从部署到实用的开发者工具趋势

今天打开GitHub趋势榜&#xff0c;扫了一圈2026年9月10日的日榜。热榜这东西&#xff0c;很多人当新闻刷一下就过去了&#xff0c;但日榜其实是很好的信息源——它反映的是当下开发者真正在动手做的事。今天榜单上AI项目依然占了半边天&#xff0c;但有意思的是&#xff0c;和上…

作者头像 李华
网站建设 2026/9/18 21:16:06

提示词工程:AI交互中的精准指令设计

1. 提示词工程的核心价值解析在AI交互领域&#xff0c;提示词&#xff08;Prompt&#xff09;就像人类与机器之间的翻译密码。去年为某金融客户优化客服机器人时&#xff0c;当我把"处理投诉"的指令改为"请以共情语气分三步处理&#xff1a;确认问题→提供补偿方…

作者头像 李华
网站建设 2026/9/18 21:15:33

机理引导残差学习:电池SOH估计的物理-AI融合方法

1. 这不是又一篇“堆参数”的电池论文&#xff0c;而是把物理规律真正焊进神经网络的实操路径你有没有遇到过这样的场景&#xff1a;实验室里训练好的电池SOH估计模型&#xff0c;一放到真实电芯上跑&#xff0c;误差直接翻倍&#xff1f;明明在公开数据集上R高达0.98&#xff…

作者头像 李华
网站建设 2026/9/18 21:13:27

STM32F411启动全流程:从复位向量到main()的底层链路解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华