2. 缓存基础回顾:本地缓存与分布式缓存、缓存穿透/击穿/雪崩、缓存更新策略(TTL、LRU、LFU)

在构建高性能的《Development环境缓存一致性闭环聚合查询》体系之前,必须重新夯实缓存的核心基础。本章将系统梳理本地缓存与分布式缓存的架构差异、三大经典缓存故障(穿透/击穿/雪崩)的成因与解决方案,以及 TTL、LRU、LFU 三种主流淘汰策略的适用场景。

2.1 本地缓存 vs 分布式缓存:架构与选型

缓存按部署位置与数据共享范围,可分为本地缓存(进程内缓存)与分布式缓存(跨进程共享缓存)。两者在延迟、容量、一致性模型上存在本质差异。

对比维度 本地缓存(如 Caffeine、Guava Cache) 分布式缓存(如 Redis、Memcached)
数据存储位置 应用进程堆内存 独立缓存服务节点(网络访问)
访问延迟 纳秒级(无网络开销) 微秒~毫秒级(依赖网络 RTT)
容量上限 受限于单机堆内存(通常 GB 级) 可横向扩展至 TB 级
数据一致性 仅单进程内一致,多实例间需额外同步 通过主从/集群协议保证最终一致性
典型场景 热点元数据、配置信息、不变字典 用户会话、商品详情、高并发读写
选型建议:在聚合查询优化中,通常采用多级缓存架构——本地缓存兜底高频只读热点,分布式缓存承载跨服务共享数据,两者配合实现延迟与一致性的平衡。
本地缓存 vs 分布式缓存架构对比 本地缓存(进程内) App 实例 1 App 实例 2 本地缓存 本地缓存 ⚠ 数据不共享,各自独立 延迟:纳秒级 | 容量:堆内有限 分布式缓存(独立服务) App 实例 1 App 实例 2 Redis 集群 ✓ 数据共享,全局一致 延迟:微秒级 | 容量:可扩展

2.2 缓存穿透、击穿与雪崩

这三个故障是缓存系统最常见的稳定性杀手,理解其触发条件与防护手段是架构师的必修课。

2.2.1 缓存穿透

定义:查询一个根本不存在的数据,缓存和数据库都查不到,每次请求都穿透到数据库。

  • 危害:恶意攻击或大量无效 key 直接打垮数据库。
  • 解决方案:
    • 缓存空对象(null 值也缓存,TTL 设置较短如 60s)。
    • 布隆过滤器(Bloom Filter)前置拦截,判断 key 是否存在。

2.2.2 缓存击穿

定义:一个热点 key 在缓存失效的瞬间,大量并发请求同时穿透到数据库。

  • 危害:单点热点导致数据库瞬时压力飙升。
  • 解决方案:
    • 互斥锁(Mutex Key):只允许一个线程去加载数据,其他等待。
    • 逻辑过期(不设置 TTL,后台异步刷新)。

2.2.3 缓存雪崩

定义:大量缓存 key 在同一时间失效,或缓存节点整体宕机,所有请求直接打到数据库。

  • 危害:数据库被瞬间击垮,引发级联故障。
  • 解决方案:
    • TTL 添加随机偏移(如基础 TTL + 随机 1~5 分钟)。
    • 缓存集群高可用(Redis Sentinel / Cluster)。
    • 本地缓存兜底 + 限流降级。
实践警示:在 Development 环境中,缓存穿透和击穿常因测试数据不完整而被忽略,但上线后可能瞬间暴露。建议在开发阶段就集成布隆过滤器与互斥锁逻辑,避免后期重构。
缓存三大故障:穿透 · 击穿 · 雪崩 ① 缓存穿透 查询不存在的数据 请求 缓存 MISS 数据库查询(无结果) → 每次穿透到 DB 方案:空对象 / 布隆过滤器 ② 缓存击穿 热点 key 失效瞬间 并发请求 key 过期 全部打到数据库 → 单点热点压力 方案:互斥锁 / 逻辑过期 ③ 缓存雪崩 大量 key 同时失效 大量请求 批量过期 数据库崩溃 → 级联故障 方案:TTL 随机化 / 高可用

2.3 缓存更新策略:TTL、LRU、LFU

缓存系统无法无限存储,必须通过淘汰策略管理内存。TTL(Time To Live)是过期策略,LRU(Least Recently Used)和 LFU(Least Frequently Used)是淘汰策略,三者协同工作。

2.3.1 TTL(生存时间)

  • 机制:为每个缓存 key 设置一个过期时间,到期后自动删除。
  • 适用场景:数据有明确时效性(如验证码 5 分钟、会话 30 分钟)。
  • 注意事项:避免大量 key 同时过期导致雪崩,应引入随机偏移。

2.3.2 LRU(最近最少使用)

  • 机制:淘汰最长时间未被访问的数据。维护一个访问顺序链表,每次访问将节点移到头部,淘汰时移除尾部。
  • 适用场景:数据访问具有时间局部性,近期访问过的数据很可能再次被访问。
  • 实现方式:LinkedHashMap(Java)、Redis 的近似 LRU(allkeys-lru / volatile-lru)。

2.3.3 LFU(最不经常使用)

  • 机制:淘汰访问频率最低的数据。维护每个 key 的访问计数,淘汰时选择计数最小的。
  • 适用场景:数据访问具有频率局部性,高频热点数据需要长期保留。
  • 注意事项:需解决“旧热点”问题(早期高频但后续不再访问的数据可能长期占据缓存)。
策略 淘汰依据 核心数据结构 典型实现 适用场景
TTL 时间到期 定时器 / 惰性删除 Redis EXPIRE 有时效性的数据
LRU 最近访问时间 双向链表 + 哈希表 Redis allkeys-lru 时间局部性强的负载
LFU 访问频率 最小堆 / 多级链表 Redis allkeys-lfu 频率局部性强的热点
实践建议:在聚合查询优化中,建议组合使用 TTL + LRU:TTL 保证数据最终一致性,LRU 保证热数据不被冷数据挤出。对于明确的热点 key(如商品详情),可考虑 LFU 策略防止被突发冷流量冲掉。

2.4 缓存更新策略选择矩阵

在实际开发中,需要根据数据特征选择最合适的策略组合。以下矩阵帮助快速决策:

数据特征 推荐策略 原因
强时效性(如验证码、限时活动) TTL 为主 到期自动失效,保证数据新鲜度
热点集中(如热搜、排行榜) LRU + TTL 保留近期热点,避免冷数据污染
长尾高频(如基础配置、字典) LFU + 无 TTL 保留高频访问项,减少重复加载
混合负载(如聚合查询结果) LRU + 短 TTL + 随机偏移 平衡热点保留与数据一致性