找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4764

积分

0

好友

653

主题
发表于 1 小时前 | 查看: 3| 回复: 0

凌晨三点,监控大屏上的 Redis 内存使用率突然飙升到 95%,紧接着淘汰速率暴增,接口 P99 延迟从 5ms 跳到 200ms。这是许多后端工程师都可能遇到的深夜惊魂。排查下来,问题根源往往在于那个看似简单、却在高并发下暗藏杀机的缓存淘汰策略。

系统当时使用的是最经典的 LRU (Least Recently Used) 策略。理论上,LRU 应该淘汰最久未使用的数据。但实际情况是,一批定时任务进行了全量数据扫描,数百万条冷数据被瞬间读入,挤占了缓存空间,导致真正的热数据被驱逐。早高峰流量袭来时,缓存命中率从 97% 暴跌至 40%。

这便是 LRU 典型的 “缓存污染” (Cache Pollution) 问题。在十万 QPS 的系统里,这可能只是让响应慢几秒;但在千万 QPS 的 高并发 系统中,缓存命中率每下降 1%,就意味着数据库每秒要多承受十万次查询,这直接关系到系统的生死存亡。在云栈社区的技术讨论中,这类由基础策略引发的线上故障屡见不鲜。

LRU:直观逻辑背后的代价

LRU 的假设很直观:最近被访问过的数据,未来被访问的概率更高。因此,它维护一个访问时间序列,淘汰最久未被访问的数据。这也是为什么从 Linux 内核页面置换到早期 Memcached,都能看到 LRU 的身影。

LRU 缓存淘汰工作原理流程图

然而,在高 QPS 场景下,LRU 的弱点会被无限放大:

  1. 缓存污染:如前所述,一次批量扫描就能让大量低频冷数据“污染”缓存,驱逐高频热数据。LRU 只关心“最近是否访问”,不关心“访问了多少次”,无法区分一次性冷数据和持续热数据。
  2. 淘汰精度不足:一个刚被偶然访问一次的低频 key,在 LRU 队列中的位置可能与每秒被访问上万次的热 key 一样靠前,仅因为它的访问时间更“新”。
  3. 实现开销大:严格 LRU 需要维护双向链表和哈希表,每次访问都涉及链表节点移动。在千万 QPS 下,这带来巨大的锁竞争和内存操作开销。这也是工程实践中广泛采用近似 LRU 的根本原因。

LRU算法表现与影响维度分析表

LFU:用频率对抗污染

既然 LRU 的问题在于忽略频率,很自然就想到 LFU (Least Frequently Used):淘汰访问次数最少的数据。LFU 能有效抵御缓存污染,在全量扫描场景下,仅访问1次的冷数据会被优先淘汰,而访问数成千上万的热数据则安然无恙。

但 LFU 引入了新的问题:

  • 频率衰减迟钝:历史上访问十万次、但近期不再访问的数据,其高计数会使其长期滞留,无法及时淘汰。
  • 新数据冷启动:新加入缓存的数据计数为0,极易被淘汰,即使它可能是即将爆发的热点。
  • 计数器开销:为每个 key 维护精确计数器(如64位整数)内存消耗巨大,且实现频率衰减需要遍历所有 key,成本高昂。

LFU算法中历史高频数据与新数据淘汰情况对比图

LRU 和 LFU 仿佛一对冤家,各自解决了对方的问题,又各自制造了新的麻烦。 真正的挑战在于如何取二者之长。

Redis 的工程智慧:近似 LRU 与 LFU

在实际架构中,Redis 几乎是分布式缓存的标配,它的实现提供了极佳的工程化视角。

Redis 从未实现严格 LRU,而是采用 近似 LRU:淘汰时,随机采样若干 key(默认5个),淘汰其中最久未访问的。测试表明,采样数达到10时,其效果已非常接近理论最优 LRU。这是一次经典的权衡:用约2%的精度损失,换取了极低的实现复杂度和几乎为零的额外内存开销。

Redis 4.0 引入的 LFU 策略同样是近似实现,包含两个精巧设计:

  1. Morris 计数器:使用8位对数计数器,通过概率递增(计数器值越大,递增概率越低),用1字节就能近似表达百万级的访问频率。
  2. 时间衰减:计数器值会随时间自动衰减,解决“历史热、当前冷”的数据占坑问题。

Redis中基于Morris计数器与时间衰减的LFU机制流程图
Redis严格LRU、近似LRU与LFU策略性能对比表

W-TinyLFU:工业级的极致追求

如果说 Redis 是“够用就好”,那么 W-TinyLFU(Java 流行缓存库 Caffeine 的核心算法)则代表了追求极致命中率的系统化方案。

它将缓存空间划分为:

  • 窗口区 (Window Cache,占约1%):新数据入口,内部使用 LRU 淘汰,给予新数据“观察期”。
  • 主区 (Main Cache,占99%):又分为 保护段 (Protected, 80%)观察段 (Probation, 20%)

其核心操作在于 准入过滤器:当窗口区的数据被淘汰时,需与主区观察段的数据进行频率 PK,胜者留下,败者出局。

频率统计采用 Count-Min Sketch 概率数据结构。其精妙之处在于,仅用每个 key 约 4 位的空间,就能进行足够准确的频率估计,并且通过定期将所有计数器右移一位(除以2)即可实现全局衰减,无需遍历 key。

W-TinyLFU缓存结构分区与数据流动示意图
纯LRU、纯LFU与W-TinyLFU算法性能综合对比表

ARC:自适应的动态平衡术

ARC (Adaptive Replacement Cache) 走了另一条路:它不预设 LRU/LFU 的固定比例,而是动态调整。

ARC 维护四个列表:

  • T1:最近只被访问一次的数据(LRU 思想)。
  • T2:最近被访问两次及以上的数据(LFU 思想)。
  • B1、B2:分别记录从 T1、T2 淘汰的 key 的历史(只存元数据)。

T1 和 T2 共享缓存空间,其大小比例由一个参数 p 控制。ARC 的智能体现在 p 的动态调整上:

  • 若访问命中了 B1(从 T1 淘汰的历史 key),说明 T1 空间不足,应增加 p,扩大 T1。
  • 若访问命中了 B2(从 T2 淘汰的历史 key),说明 T2 空间不足,应减小 p,扩大 T2。

ARC 的核心洞察是:最优的 LRU/LFU 混合比例并非固定,而是随访问模式动态变化的。 尽管其专利已过期,但较高的实现复杂度和内存开销(需维护淘汰历史)限制了其在分布式缓存中的广泛应用。

ARC自适应缓存替换算法请求处理与参数调整逻辑图

不同系统规模的策略选型指南

选择淘汰策略需结合系统规模与业务特点,它不只是一个技术配置项。

  1. 十万 QPS 级别LRU 通常足够。此时缓存命中率的小幅波动,数据库层一般能够承受。实现简单、理解成本低是其主要优势。
  2. 百万 QPS 级别需要开始严肃对待缓存污染。应考虑启用 Redis 的 LFU 策略,或在本地缓存层引入 Caffeine (W-TinyLFU)。目标是提升命中率稳定性。
  3. 千万 QPS 级别采用多级缓存,分层施策。常见的架构是 L1 本地缓存 + L2 分布式缓存(如 Redis)。
    • L1 本地缓存:空间有限,淘汰频繁,对算法精度敏感,适合使用 W-TinyLFU 或 ARC 等 算法
    • L2 分布式缓存 (Redis):空间相对充裕,直接使用其内置的、经过充分优化的近似 LFU 策略即可,无需在客户端重复造轮子。

千万QPS下多级缓存架构与分层淘汰策略配置图
不同系统规模下缓存淘汰策略选型推荐表

不容忽视的隐藏成本与监控

除了算法本身的命中率,还需关注以下隐藏成本和可观测性:

  • 淘汰风暴:当缓存满时,集中淘汰大量 key 会阻塞服务(如 Redis 主线程)。应对策略:设置内存使用阈值(如85%),提前开始渐进式淘汰;利用 Redis 6.0+ 的异步淘汰 (lazyfree-lazy-eviction) 特性。
  • 与 TTL 的交互:一个低频但 TTL 很长的 key,可能比一个高频但 TTL 将到的 key 更晚被 LRU 淘汰。需要将淘汰策略与过期策略统一设计。
  • 精细化监控:仅有整体命中率是远远不够的,你需要:
    • 分业务、分 key 模式的命中率统计。
    • 淘汰速率与淘汰操作延迟。
    • 被淘汰 key 的“年龄”(距上次访问时间)分布。
    • 没有细粒度监控,你对缓存效率的评估就如同盲人摸象。

结语:从选择算法到设计系统

缓存淘汰策略的演进,是从单一维度(时间)到多维度(频率、适应性)的探索过程。但在真实的系统设计中,比选择算法更重要的,是对业务数据访问模式的深刻理解。你知道有定时全量扫描,就该选择抗污染的 LFU 变种;你知道热点切换极快(如秒杀),就要警惕那些对历史频率依赖过重的策略。

在千万 QPS 的体系里,缓存淘汰必须与分层架构、容量规划、监控告警深度融合,形成一套完整的缓存治理方案。最后留一个开放思考:当缓存中同时存在“高频小对象”和“低频大对象”时,如何将对象大小纳入淘汰决策,实现缓存空间的价值最大化?这指向了学术界称为“Size-Aware Eviction”的方向,也是工业界未来值得探索的课题。




上一篇:干货分享:开源AI视频剪辑工具CutClaw如何实现音乐感知同步与长视频智能剪辑
下一篇:RTOS面试指南:29个精选高频问题与深度解析(附答案)
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-10 06:08 , Processed in 0.604214 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表