缓存策略:本地缓存 vs 分布式缓存(Redis)

高并发接口里,缓存是提速的第一板斧。我个人习惯把缓存分成两类:本地缓存和分布式缓存。选错了,系统要么扛不住压力,要么数据乱成一锅粥。这一节我带你彻底搞懂它们的区别。

本地缓存的底牌

本地缓存其实就是把数据塞到应用进程的内存里。比如 Guava Cache、Caffeine、EhCache。你想想看,数据就在自己家里,读取速度约等于 CPU 寄存器到 L1 缓存的延迟——毫秒级,甚至纳秒级。适合存那些冷热分明、几乎不变的数据。

我的习惯: 对于配置类、字典类、行政区划这类几乎不改的数据,我会优先用 Caffeine。它底层用了 W-TinyLFU 算法,比 LRU 更能抵御偶发流量冲击。
// Caffeine 示例(Spring Boot 自动配置)
@Bean
public Cache<String, UserProfile> userProfileCache() {
    return Caffeine.newBuilder()
        .maximumSize(10_000)
        .expireAfterWrite(5, TimeUnit.MINUTES)
        .recordStats()
        .build();
}

本地缓存的三道坎

  1. 容量有限——JVM 堆就那么大,几十 GB 顶天了。
  2. 数据一致性问题——每个节点自己玩自己的,别的节点改了数据,这头还是旧值。
  3. 重启即丢失——应用重启,缓存清空。高峰时集体回源能把数据库打崩。

我在项目里遇到过这个坑:有一回业务促销,配置中心改了限流阈值,本地缓存写了 10 分钟过期。结果前 5 分钟新配置根本没生效,把流量全放进了核心链路……后来我调整成了弱引用 + 异步刷新,才算稳住。

分布式缓存(Redis)的优势与软肋

Redis 是分布式缓存的事实标准。数据不在进程里,而在独立集群中。好处很明显:

  • 容量可以水平扩展,几百 GB 甚至 TB 级
  • 所有服务实例共享同一份缓存数据,一致性天然比本地缓存好
  • 持久化机制让你不怕重启丢数据

但代价也不小。一次 Redis 读取的网络延迟大概 1~5ms,比本地缓存慢了 1000 倍。另外,缓存穿透、雪崩、击穿这些高并发病,都跟 Redis 强相关。

避坑指南: 我曾经因为 Redis 连接池配得太大,把 Redis 服务端线程池打满,导致全站接口响应变慢。后来我限制单实例 MaxTotal 不超过 50,并且加了本地缓存兜底。

核心对比表

维度本地缓存分布式缓存(Redis)
延迟< 0.1ms1~5ms
容量堆大小(通常 ≤ 64GB)集群 TB 级
一致性最终一致(需自己同步)强一致(单 key 串行化)
持久化无RDB / AOF
跨进程共享不支持原生支持
运维成本无需要单独部署、监控
适用场景配置、字典、热点 KOL 信息用户会话、库存、排行榜

混合缓存架构:两全其美的方案

实际生产里,我极少只用一种。更常见的做法是本地缓存作为 L1,Redis 作为 L2。流程大概是:

  1. 请求先查 L1(本地),命中直接返回。
  2. 未命中,查 L2(Redis),异步回写 L1。
  3. Redis 也未命中,回源数据库,双写 Redis 和本地。

这样做的好处很明显:热点数据被 L1 挡住了 90% 的流量,Redis 的 QPS 压力骤降,数据库几乎只扛穿透流量。我曾在某电商大促中把接口 RT 从 80ms 降到 6ms,核心就是这套两级缓存。

关键参数设置
L1 过期时间 < L2 过期时间(例如 L1 30秒,L2 5分钟)。这样可以确保 L1 先失效,新数据有机会从 L2 拿到,避免 L2 过期后所有流量直接跳到数据库。

何时该单独上本地缓存?

  • 数据几乎不变,且单个实例就够了(例如静态配置)
  • 对延迟极其敏感,不能忍受一次网络 IO(例如行情价格)
  • Redis 不可用时的降级兜底策略

何时该放弃本地缓存?

  • 数据频繁更新,跨节点一致性要求高(例如多人抢购库存)
  • 缓存容量超过单节点堆大小
  • 微服务实例太多,本地缓存导致内存资源浪费

嗯,这里要注意一点:本地缓存不是越全越好。我曾经把用户信息全部塞进本地缓存,结果每个节点 8GB 堆,100 个实例就是 800GB,Redis 集群才 200GB。后来我改成只缓存 Top 10% 的热点用户,用滑动窗口动态调整,内存占用降了 80%。

缓存穿透、击穿、雪崩的统一解法

问题表现解法
缓存穿透查数据库里都没有的数据,缓存永远不会命中布隆过滤器 + 缓存空值(加短过期)
缓存击穿热点 key 过期瞬间,大量并发涌入数据库本地缓存 + 互斥锁 / 异步刷新
缓存雪崩大量 key 同时过期,或 Redis 宕机过期时间加随机值 + Redis 集群 + 本地缓存兜底
曾经踩过的坑: 我试过用布隆过滤器防穿透,但误判率不是 0%,导致极少数正常用户被拦截。最终我改成了布隆 + 空值缓存双保险,误判时让用户重试一次,反而体验更好。

知识体系:缓存选型决策流程

以下这张图概括了本文的核心决策路径——从数据特征出发,判断用本地缓存还是分布式缓存,以及如何组合。

缓存策略选型决策图 缓存策略选型决策图 数据接口请求 数据一致性要求高? 是 否 分布式缓存(Redis) 本地缓存 容量 > 单机堆? 动态热点衰减 混合:L1 + L2 仅本地缓存 高性能接口 核心原则:热点靠本地,冷数据靠 Redis,一致性靠时效+空值兜底

拿到这张图你就清楚了:没有银弹。我自己的做法是:先用日志和监控分析接口的读写比率、数据变化频率、字节大小,然后参照上图画个草图,再结合业务允许的延迟和一致性要求,做出选择。

最后一个小技巧: 上线前压测时,务必分别验证 L1 命中率、L2 命中率、穿透率三个指标。如果 L1 命中率低于 80%,说明热点选取或过期策略有问题,需要调整。