← 返回题目列表

常见的缓存淘汰策略有哪些?LRU 和 LFU 有什么区别?

高频 中等 第 3 / 25 题 更新于 2026/07/28
缓存淘汰策略LRULFU

简化版

缓存内存有限,装满后要淘汰一些数据腾地方,这就是淘汰策略。三种经典:FIFO(先进先出,淘汰最早放入的)、LRU(Least Recently Used,淘汰最久没被访问的,看「最近有没有用过」)、LFU(Least Frequently Used,淘汰访问次数最少的,看「用得频不频」)。LRU 关注时间(最近性),LFU 关注频率(热度)。Redis 提供了 8 种淘汰策略(如 allkeys-lruallkeys-lfuvolatile-ttl 等)供配置。

详细版

三种经典策略对比:

策略淘汰依据淘汰谁缺点
FIFO进入时间最早放入的不管冷热,可能淘汰掉常用数据
LRU最近访问时间最久未被访问的偶发的批量访问会冲掉真热点
LFU访问频率访问次数最少的历史高频但现在冷的数据难淘汰

LRU 的经典实现哈希表 + 双向链表。哈希表 O(1) 定位节点,双向链表维护访问顺序(每次访问把节点移到头部,淘汰时删尾部)。

Redis 的 8 种淘汰策略

  • noeviction:不淘汰,内存满写入报错(默认)。
  • allkeys-lru / volatile-lru:对所有 key / 只对设了过期时间的 key 用 LRU。
  • allkeys-lfu / volatile-lfu:LFU(Redis 4.0+)。
  • allkeys-random / volatile-random:随机淘汰。
  • volatile-ttl:淘汰剩余存活时间最短的。

完整版教学

一、为什么需要淘汰策略

缓存放在内存里,内存容量有限(也昂贵),不可能无限存。当缓存写满、又要放新数据时,必须踢掉一些旧数据腾出空间。踢谁?这就是淘汰策略要回答的。好的策略应该尽量踢掉「以后不太会用」的数据,保留「以后还会频繁用」的数据,从而维持高命中率。但「以后会不会用」无法预知,所以各策略用不同的「历史规律」去近似预测。

二、FIFO:先进先出,最简单也最粗糙

FIFO(First In First Out) 按数据进入缓存的先后顺序淘汰,最早进来的最先被踢。实现简单(一个队列即可)。但它的问题是完全不考虑数据冷热——一个很早放入但一直被高频访问的热点数据,会仅仅因为「进来得早」被淘汰,命中率差。所以实际很少单独用 FIFO 做缓存淘汰。

三、LRU:淘汰「最久没用过的」,赌最近性

LRU(Least Recently Used,最近最少使用) 的假设是:最近被访问过的数据,接下来也更可能被访问(时间局部性)。所以它淘汰最久没有被访问的数据。

  • 每次访问一个数据(读或写),就把它标记为「最新使用」。
  • 淘汰时,踢掉「最久未被使用」的那个。

经典实现:哈希表 + 双向链表(LeetCode 146 就考这个):

  • 双向链表按访问时间排序:头部是最近用的,尾部是最久没用的。
  • 哈希表key → 链表节点,实现 O(1) 查找。
  • 访问/更新:把对应节点移到链表头部。
  • 淘汰:删除链表尾部节点(最久未用),O(1)。

LRU 的缺点:偶发的批量扫描会污染缓存。比如一次性遍历大量冷数据(如全表扫描),会把这些一次性数据全塞进缓存头部,把真正的热点挤到尾部淘汰掉。

四、LFU:淘汰「用得最少的」,赌频率

LFU(Least Frequently Used,最不经常使用) 的假设是:访问频率高的数据更有价值。它淘汰访问次数最少的数据,给每个数据维护一个访问计数器。

LFU 解决了 LRU 的批量扫描污染问题——一次性访问的冷数据计数低,很快被淘汰,不会挤掉高频热点。但 LFU 也有缺点:

  • 历史包袱:某个数据过去访问量巨大、现在已经不热了,但因为历史计数高,迟迟淘汰不掉(需要计数衰减机制来缓解,Redis 的 LFU 就带衰减)。
  • 新数据劣势:刚进来的数据计数低,还没来得及积累访问就可能被淘汰。

LRU vs LFU 简明区分:LRU 看「最近有没有用」(时间维度),LFU 看「一共用了多少次」(频率维度)

五、Redis 的淘汰策略配置

Redis 通过 maxmemory-policy 配置,提供 8 种策略,可分两个维度理解:

维度一:对哪些 key 生效

  • allkeys-*:对所有 key。
  • volatile-*:只对设置了过期时间的 key(没设过期的永不淘汰)。

维度二:用什么算法

  • -lru:LRU(Redis 的 LRU 是近似 LRU——不维护完整链表,而是随机采样几个 key 淘汰其中最久未用的,省内存)。
  • -lfu:LFU(4.0+)。
  • -random:随机。
  • volatile-ttl:淘汰剩余存活时间最短的(快过期的先走)。

外加 noeviction(默认):不淘汰,内存满时写操作直接报错。

注意:Redis 的 LRU/LFU 是近似算法(采样实现),不是严格的全局 LRU/LFU,这是为了在海量 key 下节省维护成本,用少量精度换性能。

六、常见误区与追问

考点正确口径
LRU淘汰最近最少使用,关注时间近远
LFU淘汰访问频率最低,关注次数
TTL按过期时间失效,不等同于内存淘汰
LRU example:
access A,B,C,A
cache full, insert D -> evict B

LFU example:
A count=10, B count=1, C count=2
insert D -> evict B

LRU 看“最近有没有用”,LFU 看“用得多不多”,两者适合的热点形态不同。

  • 误区:LRU 和 LFU 都是按过期时间淘汰。 它们是内存不足时的淘汰策略,TTL 是键的生命周期。
  • 误区:LRU 一定识别真正热点。 偶发批量扫描会污染 LRU,让最近访问但不常用的数据挤掉长期热点。
  • 误区:LFU 没有历史包袱。 历史高频 key 可能长期占据缓存,需要衰减机制。
  • 追问:Redis 有哪些近似淘汰策略? 如 allkeys-lru、volatile-lru、allkeys-lfu、volatile-ttl、random、noeviction 等。
  • 追问:什么时候选 LRU? 访问局部性强、最近访问更可能再次访问时适合 LRU。
  • 追问:什么时候选 LFU? 热点稳定且希望抵抗偶发扫描污染时 LFU 更合适。

七、加强记忆

缓存淘汰策略 = 内存满时决定踢掉谁。三经典:FIFO(按进入顺序踢,不看冷热、最粗糙)、LRU(踢最久没被访问的,赌「最近用过的还会用」,经典实现 哈希表+双向链表 O(1),缺点是批量扫描污染缓存)、LFU(踢访问次数最少的,赌「高频更有价值」,缺点是历史高频包袱、新数据劣势)。LRU 看时间(最近性)、LFU 看频率(热度)。Redis 提供 8 种(allkeys/volatile × lru/lfu/random/ttl + noeviction),且 LRU/LFU 是采样近似实现。核心区分:LRU 问「多久没用」,LFU 问「用了几次」