第 4 章 本地缓存选型:Caffeine vs Ehcache vs Guava Cache

系统边界与分层职责

本地缓存位于应用进程内部,作为 JVM 堆内或堆外的高速数据暂存层。其典型系统边界如下:

  • 调用端:业务服务、DAO 层、网关过滤器——通过缓存抽象接口(如 Spring Cache、JCache)发起 get/put 操作。
  • 本地缓存实例:嵌入在应用进程中,与业务代码直接共享堆内存。相比分布式缓存(Redis),无需网络开销,延迟通常在微秒级。
  • 数据源:数据库、文件系统、远程 RPC——当缓存未命中时,由业务方自行回源加载,并回调缓存实例的 put 方法填充。

本章讨论的三款缓存组件均覆盖此边界,但它们在堆外内存支持、淘汰算法、统计指标以及Spring 集成方案上存在显著差异。

组件职责与核心对比

三者在 JVM 内存中的职责划分可借助以下表格快速理解:

维度CaffeineEhcache 3.xGuava Cache
堆内内存支持(通过 size 控制计数或权重)支持支持
堆外内存不支持支持(off-heap 指定大小)不支持
淘汰算法TinyLFU(W-TinyLFU,接近最优命中率)LRU / LFU / FIFO(可配置)基于访问顺序的 LRU(基础版)
统计指标丰富:命中率、加载时间、驱逐计数、容量感知中等:命中/未命中计数、持久化统计基础:hitCount/missCount,缺少驱逐详情
Spring 集成原生支持 Spring Cache 注解(自动配置)Spring Boot Starter 认证完善需手动封装 CacheManager
JCache(JSR 107)不完全实现官方实现不支持
持久化不提供支持磁盘持久化(通过缓存管理器)不提供
性能(吞吐量)极高(设计上针对高并发优化)较高(IO 线程池可调优)一般(同步锁较重)
关键判断:若需要堆外内存以规避 GC 压力,或需要磁盘持久化,Ehcache 是唯一选项;若追求极致性能与高效淘汰,Caffeine 是首选;Guava Cache 适合旧项目迁移或 JCache 绑定。

数据流与信任链路

如下图所示,三款组件的核心数据流均由应用层驱动,差异主要体现在淘汰算法和内存管理方式。

本地缓存数据流与信任链路(Caffeine / Ehcache / Guava Cache) 业务应用 Spring Cache / JCache Caffeine Ehcache Guava Cache 数据库 / 远程服务 回源加载 淘汰算法差异 Caffeine: TinyLFU(频率+衰减) Ehcache: LRU / LFU / FIFO Guava: 基础 LRU

信任链路方面:应用层通过缓存抽象接口直接调用组件实例,不信任本地缓存的持久性(允许丢失),因此回源策略必须由业务保证一致性。Ehcache 的堆外内存和持久化可选地提供更高可靠性,但代价是引入 IO 边界。

选型权衡

从架构角度,推荐按照以下优先级做决策:

  1. 是否需要堆外内存或持久化? → 选 Ehcache。
  2. 是否已有 Guava Cache 代码且无法重构? → 维持 Guava Cache,但考虑逐步替换为 Caffeine(性能收益明显)。
  3. 新项目 / 追求吞吐量 → 选 Caffeine,Spring 集成近乎零配置。
  4. 需要 JCache 标准兼容? → 选 Ehcache(官方 JCache 实现)。
注意:Guava Cache 的淘汰算法在热点数据频繁更新的场景下命中率下降明显,且缺少驱逐回调与权重控制。不推荐在要求高命中率的中间件中使用。

风险点

  • 堆内 OOM:Caffeine 和 Guava Cache 仅限制元素个数或权重,若每个元素体积巨大,仍可能撑爆堆内存。需配合监控限制最大权重。
  • 堆外内存泄漏:Ehcache 堆外内存管理不当可能导致 Direct Memory 溢出,需设置合理的 maxBytesLocalOffHeap 并手动触发 clear。
  • Spring 双缓存冲突:当同时引入 Caffeine 和 Ehcache 的 Starter 时,默认 CacheManager 可能导致注解指向混乱,需通过 spring.cache.type 显式指定。
  • 淘汰延迟:Guava Cache 的淘汰是懒惰式的(在 get 时检查),大量过期条目堆积可能引起瞬时性能抖动。

落地步骤

  1. 选择组件并引入依赖(如适用):
    // Maven 示例 - Caffeine
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
    </dependency>
    
    // Spring Boot 自动配置
    spring.cache.type=caffeine
    spring.cache.caffeine.spec=maximumSize=10000,expireAfterWrite=5m
  2. 配置缓存规范:通过 CaffeineSpec 或 Ehcache XML 定义最大容量、过期策略、统计收集开关。
  3. 集成 Spring Cache:使用 @Cacheable / @CacheEvict 注解,并注册对应的 CacheManager Bean。
  4. 监控与告警:开启统计指标 exporter(如 Micrometer),配置命中率阈值告警。
  5. 压测验证:用 JMH 或 Gatling 对比淘汰算法命中率与 GC 影响。

验收标准

验收项指标方式
缓存可被正确命中同 key 第二次访问不执行回源方法单元测试 + 日志
淘汰策略生效达到最大容量后,继续 put 不会 OOM压力测试(循环写入)
堆外内存(如适用)Direct Buffer 使用量不超过设定值jconsole / NMT
统计指标暴露命中率、驱逐计数可通过 Actuator 读取GET /actuator/metrics/cache.gets
Spring 注解兼容多 Service 共用同一缓存区域,key 隔离正常功能回归
最佳实践:在灰度环境中,先使用 Caffeine 的 recordStats() 采集一周实际命中率,再与 Ehcache 对比分析,避免无依据的选型决策。