news 2026/8/1 15:21:01

Redis内存淘汰策略深度解析:LRU与LFU原理、对比与实战选型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis内存淘汰策略深度解析:LRU与LFU原理、对比与实战选型

1. 从一次线上告警说起:为什么你的Redis突然“失忆”了?

那天下午,我正在工位上摸鱼,突然钉钉群里一阵急促的告警声响起。点开一看,是核心业务线的缓存服务告警:“Redis实例内存使用率超过95%”。心里咯噔一下,这可不是小事。登录监控一看,一个用作热门商品信息缓存的Redis集群,内存像坐了火箭一样往上蹿,眼看就要触顶。按照常规操作,我第一反应是检查是否有大Key或者大量无过期时间的缓存堆积。但一番排查下来,Key的数量和大小都还算正常,过期时间也设置得明明白白。

问题出在哪?直到我查看了Redis的INFO命令输出,在# Memory部分看到了关键信息:maxmemory_policy: noeviction。瞬间明白了——这个实例配置的内存淘汰策略是noeviction,也就是“不淘汰”。当内存用满后,新的写入操作会直接返回错误,而我们的业务代码没有很好地处理这个错误,导致部分请求失败,用户体验到的就是“数据加载不出来”或者“操作失败”。

这次事故的根因,表面上是内存触顶,深层原因是对Redis内存淘汰机制的理解和配置不到位。Redis作为一个基于内存的数据库,其高性能的前提是数据都在内存中。但内存是有限的、昂贵的资源,不可能无限扩容。当实际数据量超过物理内存时,Redis必须有一套机制来决定“牺牲”哪些数据,以腾出空间给新的或更重要的数据。这套机制,就是内存淘汰策略。它绝不是可以随意忽略的配置项,而是保障Redis服务稳定、数据“有选择地”存活的生死线。

今天,我们就抛开那些枯燥的概念,结合实战中的坑,把Redis最核心的两种淘汰策略——LRU和LFU,掰开了、揉碎了讲清楚。你会明白,为什么你的缓存“热”数据可能被误杀,为什么有些Key看似重要却留不下来,以及面对不同的业务场景,你究竟该选哪一个。

2. 淘汰策略全景图:你的Redis在内存告急时会怎么做?

在深入LRU和LFU之前,我们必须先对Redis的内存淘汰策略有一个全局的认识。这就像打仗,你得先知道手里有哪些牌,每张牌怎么打。

Redis通过maxmemory配置项来设置内存使用上限。一旦使用的内存达到这个限制,后续的写入命令就会触发内存淘汰流程。而具体怎么淘汰,则由maxmemory-policy这个配置项决定。它主要有以下几类策略:

第一类:不淘汰,直接报错

  • noeviction:这是Redis的默认策略。当内存不足时,所有会申请更多内存的命令(如SET,LPUSH,SADD等)都会返回错误(OOM command not allowed when used memory > 'maxmemory'),而只读命令(如GET)则不受影响。这个策略适用于你把Redis当作纯内存数据库,数据绝对不能丢的场景,但要求你的应用必须有完善的错误处理机制。像我们开头遇到的线上问题,就是反面教材。

第二类:从所有Key中淘汰这类策略会从整个键空间(所有数据库,无论是否设置过期时间)中挑选受害者。

  • allkeys-lru: 使用LRU算法淘汰最近最少使用的键。
  • allkeys-lfu: 使用LFU算法淘汰最不经常使用的键。
  • allkeys-random: 随机淘汰任意键。
  • allkeys-lfu是Redis 4.0引入的,如果你的版本低于4.0,则没有这个选项。

第三类:从设置了过期时间的Key中淘汰这类策略只从那些设置了过期时间(TTL)的键中挑选。这通常更符合缓存的使用场景:缓存数据本来就有生命周期,淘汰它们对业务的影响相对可控。

  • volatile-lru: 使用LRU算法淘汰设置了过期时间的键中,最近最少使用的。
  • volatile-lfu: 使用LFU算法淘汰设置了过期时间的键中,最不经常使用的。
  • volatile-random: 随机淘汰任意一个设置了过期时间的键。
  • volatile-ttl: 淘汰过期时间最近(TTL最小)的键。这个策略的目标是尽快释放即将过期的内存。

这里有一个非常重要的选择逻辑:volatile-xxx策略只在有键设置了过期时间时才会生效。如果内存满了,但没有任何键有过期时间,那么这些策略的行为会退化成noeviction,同样会报错。所以,如果你选择了volatile-lru,请务必为你的大部分缓存键合理设置过期时间。

那么,面对这么多选项,我们该如何选择?这里有一个简单的决策流:

  1. 数据能否丢?如果你的Redis实例承担了持久化存储的角色(例如会话存储、排行榜),数据绝对不能丢,那么noeviction是唯一选择,但你必须确保应用能优雅处理OOM错误,并有监控告警。
  2. 是否是纯缓存?如果是,那么优先在volatile-xxx策略中选择。这能保证你的永久性数据(如果存在)绝对安全。
  3. 缓存访问模式是否明确?如果你的缓存有明显的“热点”数据(如电商首页商品、新闻头条),且希望尽可能保留这些热点,那么LRULFU是更好的选择。randomttl更简单粗暴,适用于访问模式非常随机或你更关心内存回收速度的场景。
  4. 是否所有数据都可淘汰?如果你把Redis当作缓存,并且所有数据都可以被淘汰,那么allkeys-xxx策略也是可以的,这样能最大化内存的利用率。

注意:在生产环境中,allkeys-randomvolatile-random很少被使用,因为随机淘汰可能导致重要的热点数据被意外清除,引发缓存穿透,打垮后端数据库。volatile-ttl在某些特定场景下有用,比如你希望尽快腾出空间,并且过期时间设置得比较准确。

3. LRU算法深度拆解:它真的是你以为的“最近最少使用”吗?

LRU,全称Least Recently Used,最近最少使用。它的核心思想非常直观:如果一个数据最近被访问过,那么它将来被访问的可能性也更高。因此,当需要淘汰时,应该淘汰最久没有被访问的数据。

想象一下你书桌的桌面。你最近正在看的书、正在写的笔记本,肯定会放在手边(内存里)。而一本几个月前翻过一下,之后再也没碰过的书,就会被你塞回书架(淘汰掉)。LRU就是基于这种“时间局部性”原理。

3.1 教科书里的LRU与Redis的“近似LRU”

在数据结构教科书里,实现一个完美的LRU通常需要“哈希表+双向链表”。链表维护了数据的访问顺序,最近访问的移到头部,最久未访问的在尾部;哈希表提供O(1)的查找能力。当需要淘汰时,直接移除链表尾部的节点即可。

但是,Redis没有采用这种经典实现。为什么?因为内存开销和性能。每个Redis对象本身已经是一个复杂结构(redisObject),如果再给每个对象加上前驱和后继指针来组成一个全局链表,内存消耗会显著增加。更重要的是,每次访问一个Key(GET,SET等),都需要修改这个全局链表(移动到头部),这是一个写操作,在Redis单线程模型下,会对性能产生不小的影响。

因此,Redis采用了一种近似LRU算法。它通过在每个Redis对象的redisObject结构体中,额外增加一个unsigned lru:22的字段(22位,在LRU模式下用于存储时间戳)。这个字段只有24字节(在64位系统中)的额外开销,非常小。

它的工作流程是这样的:

  1. 采样:当需要淘汰数据时,Redis并不会遍历所有Key,而是从键空间中随机抽取一定数量的Key(默认是5个,可通过maxmemory-samples配置),放入一个“候选池”。
  2. 比较:比较池中所有Key的lru字段值(即上次访问的时间戳)。
  3. 淘汰:淘汰掉池中lru值最小的那个Key,也就是候选样本里“最久远”未被访问的。

3.2 近似LRU的优劣与配置调优

这种近似算法带来了明显的优势:

  • 内存开销极小:每个Key只多了24字节。
  • 性能影响小:访问Key时只需要更新自己对象的lru字段,是纯内存操作,速度快。淘汰时的随机采样,时间复杂度是O(N),其中N是采样数量,而不是键总数,速度也很快。

但它的代价就是“近似”可能不准确。你可能会淘汰掉一个并不是全局最久未使用的Key,而是“那一批样本里”最久未使用的。这会导致淘汰精度下降。

这里就引出了关键的配置项:maxmemory-samples。它的默认值是5。增大这个值会让淘汰精度提高,因为考察的样本更多了,更有可能找到真正“最近最少使用”的Key,但相应的,每次淘汰时的CPU消耗也会增加。这是一个典型的“时间换空间”或“精度换性能”的权衡。

那么,该设置为多少呢?Redis官方提供了一个非常直观的测试结果。他们使用一个符合幂律分布的访问请求流来测试,发现:

  • samples=5时,已经能够非常好地近似真实LRU。
  • sample提高到10,精度提升非常有限,但CPU消耗几乎翻倍。

所以,对于绝大多数生产环境,保持默认的maxmemory-samples 5是完全足够的,不建议盲目调大。除非你有非常确切的证据(通过监控发现淘汰精度严重影响缓存命中率),并且愿意承担更高的CPU成本。

3.3 LRU的经典陷阱与适用场景

LRU算法虽然直观,但在某些场景下会表现出明显的缺陷:

“冷数据污染”问题:想象一个场景,你的Redis内存很大,平时只访问其中20%的热点数据。某天,有一个一次性的大数据扫描任务(比如全量数据导出),它依次读取了Redis里所有80%的冷数据。在LRU算法下,这些冷数据的lru时间戳全部被更新成了“最新”。当内存不足需要淘汰时,之前那20%真正的热点数据,因为“最近”没被扫描任务访问,反而可能因为lru时间戳更旧而被淘汰掉!这就是一次全量扫描“污染”了LRU队列,导致热点数据被误伤。

周期性访问问题:如果一个数据每隔固定的长周期(比如一天)才被访问一次,而其他数据访问更频繁。在LRU看来,这个长周期数据每次被访问后都是“最新的”,它很难被淘汰,即使它的整体访问频率很低。它占着茅坑不拉屎,挤占了那些更活跃数据的内存空间。

因此,LRU更适用于访问模式相对稳定,且没有上述“批量扫描”或“超长周期访问”干扰的场景。例如:

  • 用户会话(Session)存储。用户最近活跃的会话需要保留。
  • 新闻客户端的热点新闻列表,最新的新闻总是最热的。
  • 一些临时性的、访问时间局部性很强的计算中间结果。

实操心得:使用volatile-lru时,一定要给Key设置一个合理的、不同梯度的过期时间。例如,核心热点数据TTL设长些(如30分钟),普通数据设短些(如5分钟)。这样即使发生误淘汰,也能通过过期时间这个安全网来兜底,并且结合volatile-ttl的思想,让内存回收更平滑。

4. LFU算法深度解析:如何量化数据的“热度”?

LFU,全称Least Frequently Used,最不经常使用。它的核心思想是:如果一个数据在过去被访问的次数越多,那么它将来被访问的可能性也越高。因此,当需要淘汰时,应该淘汰访问频率最低的数据。

还是用书桌的比喻。LRU关心的是“你最近一次碰这本书是什么时候”,而LFU关心的是“过去一年里,你翻看这本书的总次数”。显然,一本你经常查阅的工具书(高频),即使今天没看,也不应该被扔掉;而一本去年旅游买回来只翻过一次的画册(低频),就算昨天刚拿出来掸了掸灰,也该考虑清理了。

LFU算法能更好地应对LRU中提到的“冷数据污染”和“周期性访问”问题。一次性的全量扫描,只会让每个冷数据的计数器增加1,无法撼动那些计数器成百上千的真正热点数据。长周期访问的数据,因为总访问次数低,也更容易被淘汰。

4.1 Redis中LFU的精妙实现:不仅仅是计数器

如果只是简单地对每个Key维护一个访问计数器,会带来两个大问题:

  1. 空间问题:计数器可能会无限增长(比如一个全局计数器),需要很多位来存储。
  2. 时间衰减问题:一个在过去非常热门,但近期已经“过气”的数据,会因为历史的高计数而长期霸占内存,无法被淘汰。我们需要算法能反映“近期热度”,而不是“历史总热度”。

Redis的LFU实现(自4.0版本起)非常巧妙地解决了这两个问题。它复用了redisObject里那个24位的lru字段,但这次把它拆分成两部分来用:

  • 高16位:存储一个“分钟级的时间戳”(对UNIX时间戳取模,精度到分钟)。这个时间戳并非最后访问时间,而是用于衰减的“上一次衰减时间”。
  • 低8位:存储一个“访问频率计数器”,简称counter。这个counter的最大值就是255。

8位计数器,最大值255,这够用吗?Redis通过一个概率递增算法,让这个小小的计数器能够有效区分不同热度的Key。counter值越大,下一次访问时counter再增加的概率就越低。它的递增逻辑近似于对数增长,这意味着从0到1很容易,但从100到101就很难。这正好符合我们的直觉:区分冷数据和温数据很重要,但区分“非常热”和“极其热”就没那么重要了。

4.2 热度衰减机制:让“过气明星”优雅退场

这是Redis LFU实现中最精彩的部分。如果只有计数器,一个曾经火爆的Key会永远赖在内存里。Redis引入了“衰减”机制。简单来说,如果一个Key在一段时间内没有被访问,它的counter值会随着时间的推移而减少。

衰减的速度由一个配置参数lfu-decay-time控制(单位是分钟)。它的默认值是1。算法大致逻辑是:根据当前时间与Key的“上一次衰减时间”(存储在lru字段的高16位)的差值,来计算这个Key的counter应该衰减多少。

例如,lfu-decay-time=1意味着,如果某个Key在过去1分钟内没有被访问,它的counter值就可能被减少。这个衰减不是定时任务全局扫描,而是在Key被访问时惰性计算的。当Redis尝试访问一个Key并准备更新其LFU信息时,会先根据时间差计算出它应该衰减多少,然后再进行可能的递增。

你可以通过lfu-decay-time来控制热度的“半衰期”。设置得越大,热度衰减越慢,历史访问记录影响越久;设置得越小,衰减越快,算法越关注近期热度。通常建议保持默认值1,除非你有非常特殊的业务周期特征。

4.3 LFU的配置与实战观察

与LFU相关的配置主要有两个:

  1. lfu-log-factor:这个参数控制counter递增的难度。默认值是10。因子越大,counter增长到相同值所需的访问次数就越多。通常不需要调整。
  2. lfu-decay-time:上面提到的衰减时间,默认1(分钟)。

如何观察一个Key的LFU信息?使用OBJECT FREQ <key>命令。它会返回该Key当前的counter值。这是监控缓存热点、调试LFU策略的利器。

LFU的适用场景非常广泛,尤其是当你的业务有明显的“热点”数据,并且希望缓存能智能地长期保留这些热点时:

  • 社交网络热点内容:明星八卦、爆款文章,短期内访问频率极高。
  • 电商平台热门商品:iPhone新品、秒杀商品,在活动期间访问集中。
  • 视频网站热门榜单:排行榜前列的视频,会被反复访问。
  • 任何访问模式符合“二八定律”的场景:20%的数据承载了80%的流量。

踩坑记录:我们有一个内容推荐系统,使用Redis缓存文章特征向量。最初使用volatile-lru,发现每当有运营人员进行后台批量数据分析(全量扫描)时,线上推荐接口的缓存命中率就会骤降,响应时间飙升。切换为volatile-lfu后,这个问题基本消失。因为批量扫描只会给每个文章增加1点热度,无法撼动那些被千万用户点击过的热门文章的高热度值。这个切换带来了显著的性能稳定性提升。

5. LRU vs LFU:面对业务场景,你该如何抉择?

现在,我们对LRU和LFU都有了深入的理解。是时候把它们放在一起,做一次全面的对比,并给出最终的选择指南了。

我们可以从以下几个维度进行对比:

特性维度LRU (最近最少使用)LFU (最不经常使用)
核心思想淘汰最久未访问的数据淘汰访问频率最低的数据
关注点访问的时间远近(何时访问)访问的频率高低(访问多少次)
数据结构redisObject中存储最后访问时间戳redisObject中存储频率计数器+衰减时间戳
内存开销极低 (24位时间戳)极低 (复用24位字段)
计算开销低 (更新时间戳)略高 (需要概率计算与衰减计算)
抗扫描干扰弱。批量扫描会污染缓存,误伤热点。强。单次扫描对计数器影响微乎其微。
处理周期性访问差。长周期访问的数据难以被淘汰。好。低频访问的数据容易被淘汰。
数据热度稳定性热度变化快。一段时间不访问就会变“冷”。热度变化慢。历史访问次数影响大,热度更持久。
配置复杂度简单,主要调maxmemory-samples较复杂,涉及lfu-log-factorlfu-decay-time
Redis版本要求所有版本支持。Redis 4.0 及以上版本。

选择决策流程图:

面对一个具体的业务场景,你可以遵循以下思路来选择:

  1. 你的数据访问模式是否有明显的、持久的热点?比如爆款商品、热搜话题。如果是,LFU通常是更好的选择,它能牢牢抓住这些热点。
  2. 你的业务是否存在后台批量任务(如数据分析、全量导出)?如果存在,并且这些任务会扫描大量缓存数据,那么必须选择LFU来避免LRU的缓存污染问题。
  3. 你的业务数据是否具有强烈的“最新性”?比如新闻Feed、实时排行榜,最新的数据就是最热的,旧的数据价值迅速衰减。这种情况下,LRU的表现可能更符合直觉,因为它天然地淘汰旧数据。
  4. 你的访问模式是否是均匀的、随机的,或者没有明显规律?如果找不到规律,volatile-ttl(依赖过期时间)或allkeys-random(随机淘汰)可能是更简单直接的选择,但需要警惕缓存穿透。
  5. 你的Redis版本是否低于4.0?如果是,那么LFU不可用,只能在LRU和其他策略中做选择。

一个综合案例:一个典型的电商平台,其Redis缓存可能分为多种用途:

  • 商品详情缓存:有明显的热点商品(iPhone、茅台)。访问模式是少数商品被高频访问。推荐使用volatile-lfu,并为商品设置合理的过期时间(如30分钟)。
  • 用户会话缓存:用户最近活跃的会话需要保留。会话通常有固定有效期,且更关注“最近是否活跃”。推荐使用volatile-lru,并设置会话过期时间(如30分钟)。
  • 秒杀库存缓存:这是一个特殊场景,数据极度热点,但生命周期极短(秒杀结束后即失效)。对于这种场景,volatile-ttl可能是最合适的,因为你可以设置一个很短的、精确的TTL(如5分钟),让Redis在内存不足时优先淘汰那些即将过期的库存数据,既保护了热点,又快速回收了内存。

6. 实战配置、监控与问题排查指南

理论再好,不落地也是空谈。这一部分,我们直接上命令和配置,看看怎么用,怎么监控,出了问题怎么查。

6.1 如何配置与查看淘汰策略

配置方法(二选一):

  1. 配置文件redis.conf:找到maxmemory-policy配置项,修改为你需要的策略,例如maxmemory-policy volatile-lfu。然后重启Redis或通过CONFIG REWRITE命令重写配置(如果允许)。
  2. 运行时动态修改(推荐):通过CONFIG SET命令在线修改,无需重启。这对于生产环境调整非常友好。
    # 设置最大内存为1GB CONFIG SET maxmemory 1gb # 设置淘汰策略为 volatile-lfu CONFIG SET maxmemory-policy volatile-lfu # 使配置持久化到 redis.conf (如果希望重启后生效) CONFIG REWRITE

查看当前策略:

# 查看所有配置信息中的淘汰策略 CONFIG GET maxmemory-policy # 或者通过 INFO 命令查看 INFO memory # 在输出的 Memory 部分找到 maxmemory_policy 字段

6.2 关键监控指标

光配置完还不够,必须建立监控,了解策略的运行效果。

  1. 内存使用率与淘汰触发:通过INFO memory关注used_memory,used_memory_peak,maxmemory以及evicted_keys(累计淘汰的Key数量)。如果evicted_keys在持续增长,说明淘汰正在频繁发生,可能需要扩容内存或优化缓存使用。
  2. 缓存命中率:这是衡量缓存有效性的黄金指标。可以通过监控系统计算:keyspace_hits / (keyspace_hits + keyspace_misses)。命中率下降,可能意味着淘汰策略不合理,误伤了热点数据。
  3. OBJECT命令探查:这是一个强大的诊断工具。
    # 查看某个Key的空闲时间(LRU视角下的“冷”程度),单位秒。数值越大,越久没被访问。 OBJECT IDLETIME mykey # 查看某个Key的访问频率(LFU视角下的“热”程度),仅当策略为LFU时有效。 OBJECT FREQ mykey

6.3 常见问题排查思路

问题一:缓存命中率突然暴跌。

  • 排查步骤
    1. 检查evicted_keys是否在同期有陡增。如果是,说明发生了大量淘汰。
    2. 确认是否近期有代码发布,引入了新的、未设置过期时间的大Key,或者改变了数据访问模式。
    3. 如果使用的是LRU策略,回忆是否有后台批量任务(如报表生成、数据迁移)运行,可能导致“缓存污染”。
    4. 使用OBJECT IDLETIMEOBJECT FREQ抽样检查一些核心热点Key,看它们的“热度”是否异常降低。
  • 解决方向:如果是LRU污染,考虑切换到LFU。如果是内存不足,考虑扩容或优化缓存数据大小(如压缩、拆分大Key)。

问题二:使用了volatile-lru但内存满后依然报OOM错误。

  • 排查步骤
    1. 使用INFO keyspace命令,查看expires的数量。如果这个数量为0或极少,说明几乎没有Key设置过期时间。
    2. 使用SCAN命令抽样检查一些Key,用TTL命令查看其剩余生存时间。
  • 根因volatile-xxx策略只淘汰有过期时间的Key。如果内存中大部分Key都没有设置TTL,当内存满时,这些策略无Key可淘汰,行为就退化成了noeviction
  • 解决方案:为缓存Key合理设置过期时间。这是一个必须养成的好习惯。

问题三:从LRU切换到LFU后,预期中的热点数据留存更久了,但感觉响应速度有点微小的下降。

  • 排查步骤:这是正常现象。LFU的计数器更新和衰减计算比LRU单纯更新时间戳要稍微复杂一点,会带来极微小的CPU开销。通过INFO stats命令查看used_cpu_sysused_cpu_user的变化,通常这个开销在1%以内,对于绝大多数应用是可接受的。
  • 权衡:用微不足道的CPU开销,换取缓存命中率的显著提升和服务的稳定性,这笔交易几乎总是划算的。

内存淘汰策略不是“设置完就忘”的配置。它需要你根据业务形态进行初选,通过监控指标进行验证,在业务变化时进行调整。理解LRU和LFU背后的原理,能让你在出现问题时,不再盲目,而是能够有理有据地分析和解决。记住,没有最好的策略,只有最适合你当前业务场景的策略。

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

2025年六大AI降重工具实测对比与选型指南

1. 项目概述&#xff1a;AI降重工具的现状与需求 在学术写作和内容创作领域&#xff0c;AI降重工具已经成为许多人的"救命稻草"。随着AI生成内容的普及&#xff0c;如何让这些内容通过查重系统的检测&#xff0c;成为了一个日益突出的需求。2025年&#xff0c;市场上…

作者头像 李华
网站建设 2026/8/1 15:18:05

人工智能法来了:使用国外大模型将受何影响?

7月31日&#xff0c;国家发展改革委政策研究室主任蒋毅在新闻发布会上明确表示&#xff0c;将"加快人工智能法的立法进程"。这是继2023年国务院将《人工智能法》列入年度立法计划后&#xff0c;主管部门再一次公开释放加速立法的信号。 几乎同一周&#xff0c;《中国…

作者头像 李华
网站建设 2026/8/1 15:17:32

移动端C++开发:跨平台优化与实践指南

1. 移动平台C开发概述在移动互联网时代&#xff0c;C依然是高性能应用开发的核心语言选择。不同于PC端开发&#xff0c;移动平台的C开发需要考虑ARM架构特性、多平台适配、性能优化等独特挑战。根据2023年开发者调查报告&#xff0c;超过67%的高性能移动应用&#xff08;如游戏…

作者头像 李华
网站建设 2026/8/1 15:16:03

AniShort创作者激励计划再加码~

AniShort创作者激励计划再加码~达标保底2000积分&#xff0c;单作品最高奖励52000积分&#xff01;更有商单推荐&#xff01;让灵感发光&#xff0c;让创作有回响&#xff01;赶紧访问官网参加吧&#xff01;

作者头像 李华