Redis 和 Caffeine 的区别是什么?

#中间件#面试八股

Redis 和 Caffeine 的区别是什么?

结论:两者不是替代关系,而是不同层级的缓存。Caffeine 是进程内(本地)缓存,访问走 JVM 堆,纳秒级、零网络;Redis 是进程外(分布式)缓存,访问走网络,毫秒级、多实例共享。真实系统普遍是 Caffeine 做 L1、Redis 做 L2,难点不在"选哪个",而在"L1 怎么失效"。

一、定位差异对照

维度Caffeine(本地缓存)Redis(分布式缓存)
数据位置JVM 堆内,与业务同进程独立进程 / 独立机器
访问延迟百纳秒级0.2~2 ms(同机房)
容量上限受堆大小与 GC 约束受物理内存约束,可到数百 GB
一致性各实例各自一份,天然不一致全局一份,天然一致
淘汰算法W-TinyLFU(窗口 LRU + 频率草图)近似 LRU / LFU / random / ttl
持久化无,重启即空RDB / AOF
扩展方式加堆、加实例(各自一份)主从、哨兵、Cluster
适用数据极热、可容忍短暂过期、体积小全量热点数据、需跨实例共享
典型故障GC 压力、堆外泄漏网络抖动、缓存击穿、集群切换

二、Caffeine 为什么快,以及快到什么程度

业务线程
  │  get(key)
  ├─► Caffeine:ConcurrentHashMap 定位 → 命中直接返回对象引用(~100 ns)
  │
  └─► Redis:序列化 → TCP 往返 → 反序列化(~0.5 ms,跨机房 2~10 ms)
                └─ 一次网络往返 ≈ 本地命中的 5000 倍

Caffeine 的核心不是"用 HashMap",而是三件事叠加:

  • 读路径无锁:读操作只更新频率草图(Count-Min Sketch + 布隆过滤器),不阻塞;
  • 写路径分段:按 key 哈希分到不同缓冲区,异步批量刷入淘汰结构,减少竞争;
  • W-TinyLFU 淘汰:小窗口 LRU 吸收突发流量,主区域按频率准入,避免"扫表式"的一次性 key 冲掉热点。
// Caffeine:按大小 + 过期时间 + 异步刷新
Cache<String, User> local = Caffeine.newBuilder()
        .maximumSize(100_000)
        .expireAfterWrite(Duration.ofMinutes(5))
        .refreshAfterWrite(Duration.ofMinutes(1))
        .recordStats()
        .build(key -> loadFromRedisOrDb(key));   // 未命中时同步加载,天然防击穿

三、两级缓存的读写与失效

读:L1 命中 → 返回
    L1 未命中 → L2(Redis) 命中 → 回填 L1 → 返回
    L2 未命中 → 查 DB → 写 L2 → 写 L1 → 返回

写:更新 DB → 删 L2 → 广播失效消息(Redis Pub/Sub 或 MQ)→ 各实例删 L1
// 失效广播:每个实例订阅自己的频道,收到后只删本地 key
public void evict(String key) {
    redis.delete("user:" + key);                       // 先删 L2
    redis.convertAndSend("cache:evict", key);          // 再广播
}

@RedisListener(topic = "cache:evict")
public void onEvict(String key) {
    local.invalidate(key);                             // 各实例删 L1
}

要点:先删 L2 再广播,顺序反了会出现"某个实例旧值回填 L2"的窗口;广播本身不可靠(Pub/Sub 不持久化),所以要给 L1 配一个较短的 expireAfterWrite 兜底,把最长不一致时间压到秒级。

四、常见追问

  • 问:既然有 Redis,为什么还要 Caffeine?答:挡住超高 QPS 的热点 key,把网络与 Redis 的 CPU 省下来;单机 QPS 从几万抬到几十万,Redis 只承担未命中的那部分。
  • 问:本地缓存会不会内存泄漏?答:会。用 maximumSize + weakValues 兜底,避免把大对象或线程上下文缓存进去;排查时看 GC 日志与堆 dump 里的 retained size。
  • 问:L1 和 L2 的数据不一致窗口怎么量化?答:最长不一致 ≈ 广播延迟 + L1 的 expireAfterWrite。业务能接受 1~5 秒就用短 TTL 兜底,不能接受就不要用 L1,或改成"按版本号校验"。
  • 问:Caffeine 的 W-TinyLFU 与 Redis 的 LRU 谁更准?答:在"突发扫描 + 稳定热点"混合负载下 TinyLFU 命中率更高;Redis 的 LRU 是采样近似,为省内存牺牲了精度。
  • 问:多级缓存下缓存击穿怎么处理?答:Caffeine 的 build(loader) 自带单 key 串行加载,Redis 层再用 SETNX 互斥,DB 层用单飞(singleflight)合并,三层各挡一次。

小结

  • Caffeine 解决"快",Redis 解决"共享",二者是层级关系而不是竞品。
  • 本地缓存必须回答三个问题:容量多大、最长不一致多久、失效怎么广播。
  • 多级缓存的收益来自命中率分层,成本来自一致性复杂度;没有失效兜底的 L1 不要上生产。
笔记加载中…