库 / SDK
ben-manes/caffeine avatar
ben-manes/caffeine

Caffeine 3.2.4 评测:Java 进程内缓存的吞吐与命中率如何兼得

Java 的高性能缓存库。 Cache Caffeine 使用受 Google Guava 启发的 API 提供内存缓存。

17,866 个 Star1,714 个 ForkJavaApache-2.0
GitHub

秒懂

它是什么?
Caffeine 是一个基于 TinyLFU 与窗口自适应策略的 Java 进程内缓存库,API 沿袭 Guava 风格。本文从机制、用法、局限与替代方案四个角度评估其 3.2.4 版本,回答它是否值得替换你现有的缓存实现。
适合谁用?
Caffeine 适合对读吞吐和命中率敏感、且愿意接受进程内缓存固有局限的 Java 服务,尤其是已经使用 Guava Cache 但遇到命中率瓶颈的团队。如果你的缓存数据超过单机内存、需要跨节点失效,或者要求强一致性的写后读,Caffeine 不是正确工具,请转向 Redis 或 Hazelcast 这类分布式方案。
能商用吗?
可以。Apache-2.0 是宽松许可证:你可以使用、修改并销售基于它的软件,只需保留版权和许可证声明。
还在维护吗?
在维护。仓库在最近一天内有新的提交。
用什么语言写的?
主要是 Java(依据 GitHub 的语言统计)。

以上回答依据项目的 GitHub 数据(最近同步于 2026年9月15日)和我们的分析,不构成法律意见。

开源项目深度解析

它解决什么问题,谁该关注

Caffeine 解决的是 Java 进程内缓存的两个核心矛盾:高并发读写下的吞吐,以及有限内存下的命中率。文档自述为 high performance, near optimal 的缓存库,这个定位直接指向那些把缓存放在应用堆内、追求微秒级延迟的场景。典型使用者是 Web 服务、数据访问层、以及任何需要缓存热点数据的 JVM 应用。它不解决分布式缓存问题,不提供跨节点一致性,这些边界在 README 中并未提及,但仓库布局和扩展列表已经暗示,它只负责单机内存。

TinyLFU 与窗口自适应:驱逐策略的实质

Caffeine 的驱逐策略基于 TinyLFU 论文,该论文由 Ben Manes 参与撰写,仓库 README 直接链接了论文。TinyLFU 用频率草图记录访问频次,而非维护精确的 LRU 链表,这使得在 O(1) 时间内近似判断一个条目是否值得保留。但固定频率统计对突发流量不敏感,所以 Caffeine 引入了自适应窗口,即一个小的窗口缓存区专门捕获近期热点。这个设计在 HighScalability 的文章中被描述为 Adaptive Window。实际效果是,Caffeine 在扫描型工作负载下能保持较高命中率,而传统 LRU 遇到全表扫描时会迅速污染缓存。这个机制值得注意:它不是精确 LRU,而是概率性保留高频项,因此如果你的业务要求严格按最近访问顺序淘汰,Caffeine 的行为可能与预期不符。

从 Guava 到 Caffeine:API 迁移的真实成本

Caffeine 的 API 刻意模仿 Guava Cache,README 明确说使用 Google Guava inspired API。这意味着迁移时大部分调用点可以机械替换。但差异在于构建器选项更丰富:maximumSize 之外还有 expireAfterWrite、refreshAfterWrite、weakKeys、softValues 等。一个关键区别是 refreshAfterWrite 的行为,它会在首次过期请求时异步刷新,而不是同步阻塞。这比 Guava 的 expireAfterWrite 更平滑,但代价是读取线程可能拿到旧值。README 中的示例展示了典型用法:maximumSize(10_000) 配合 expireAfterWrite(Duration.ofMinutes(5)) 和 refreshAfterWrite(Duration.ofMinutes(1)),这种组合适合读多写少且能容忍短暂陈旧的数据。迁移时你需要逐项检查每个缓存配置,因为 Guava 的 removalListener 和 Caffeine 的 evictionListener 在触发时机上有细微差别。

上手与配置:三个依赖,两个版本分支

Maven Central 上发布的主包是 com.github.ben-manes.caffeine:caffeine:3.2.4,Gradle 坐标在 README 中直接给出。可选扩展 guava 和 jcache 分别对应 Guava 适配器和 JSR-107 实现。版本选择有硬性约束:Java 11 以上用 3.x,否则用 2.x。这个分支意味着如果你的生产环境还在 Java 8,你无法获得 3.x 的后续修复。构建缓存只需一个 build 方法,传入加载函数即可。对于异步加载,文档提到 autoloading 可异步执行,但具体 API 需要查阅 Javadoc。统计功能通过 recordStats 启用,可输出命中率、加载耗时等指标,这对评估是否值得保留缓存很有用。配置上没有复杂的 XML 或 YAML,全部通过 builder 链式调用完成。

扩展生态:JCache、Guava 适配器与社区集成

Caffeine 不只是一个孤立库,它提供了 JSR-107 JCache 实现和 Guava 适配器,前者让标准缓存接口可以切换到 Caffeine 后端,后者帮助现有 Guava 用户平滑迁移。社区集成列表很长,包括 Spring Cache、Play Framework、Micronaut、Quarkus、Camel、Scala 的 Scaffeine 和 Kotlin 的 Aedile。这意味着如果你已经在用 Spring 的 @Cacheable,可以只改配置类就替换底层缓存。但要注意,这些集成通常是薄封装,Caffeine 的高级特性如异步刷新、引用包装,在 Spring Cache 抽象下不一定全部暴露。依赖这些集成时,你需要检查对应版本的适配层是否跟进 Caffeine 的 API 变化。另外,README 列出了 Cassandra、Kafka、HBase 等大型项目使用 Caffeine,这可以作为生产验证的参考,但不能直接等同于你的场景。

局限与误用场景:何时不该用 Caffeine

Caffeine 是进程内缓存,这意味着每个 JVM 实例各自维护一份数据副本。如果你的服务水平扩展部署了多个实例,缓存一致性需要自己解决,Caffeine 不提供任何跨节点同步机制。其次,引用包装功能(weakKeys、weakValues、softValues)虽然能防止内存泄漏,但会让缓存行为依赖 GC 状态,导致条目可能在任何时刻被回收,这会让命中率变得不可预测。文档中提及这些选项,但没有承诺回收时机。另一个容易被忽略的点是 expireAfterWrite 与 refreshAfterWrite 同时配置时,refresh 是异步的,如果加载函数很慢,高并发下可能积累大量刷新任务,拖累 CPU。最后,Caffeine 不适用于缓存值大于堆内存的场景,因为它是纯内存实现,没有磁盘或外部存储的溢出路径。

替代方案:Guava Cache、Couchbase 与 Caffeine 的定位差异

最直接的替代是 Guava Cache,Caffeine 的 README 明确说它的改进来自设计 Guava Cache 的经验。Guava 的驱逐策略是 LRU 近似,而 Caffeine 使用 TinyLFU,两者在扫描型负载下的命中率差异明显,这也是 Caffeine 宣称 near optimal 的依据。如果你的缓存命中率需求不高,或者代码库已经深度依赖 Guava 的 API 细节,继续用 Guava 更省事。另一个极端是分布式缓存如 Redis 或 Hazelcast,它们解决多实例共享问题,但引入网络延迟和序列化开销。Caffeine 适合作为一级缓存,分布式缓存作为二级,但这需要你自行设计两级架构。Caffeine 的 Simulator 扩展可以模拟不同策略的命中率,官方用它来验证算法,你也可以用它来对比你的工作负载。

维护成本与许可证:Apache-2.0 下的长期可用性

Caffeine 的许可证是 Apache-2.0,允许商用和修改,没有传染性条款,这对企业采用是低风险的。项目当前活跃,最近一次发布是 2026 年 5 月的 v3.2.4,距离上一个版本 v3.2.3 约半年,说明维护节奏稳定。但注意版本分支策略:3.x 只支持 Java 11+,如果你停留在 Java 8,只能使用 2.x,而 2.x 的维护状态未在 README 中说明,你需要自行查看 release notes 确认是否还有安全修复。升级成本方面,Caffeine 的 API 相对稳定,但扩展包(guava、jcache)需要与主包版本匹配,升级时最好同时更新。文档中提到 release notes 记录变更,建议每次升级前阅读。没有看到迁移指南,但 API 的 Guava 风格意味着大部分变更可能是新增而非破坏。

编辑结论

Caffeine 适合对读吞吐和命中率敏感、且愿意接受进程内缓存固有局限的 Java 服务,尤其是已经使用 Guava Cache 但遇到命中率瓶颈的团队。如果你的缓存数据超过单机内存、需要跨节点失效,或者要求强一致性的写后读,Caffeine 不是正确工具,请转向 Redis 或 Hazelcast 这类分布式方案。在采用前,先确认你的 JDK 版本:Java 11 以上用 3.x,否则必须停留在 2.x。同时验证 expireAfterWrite 与 refreshAfterWrite 的组合行为,因为后者是异步触发,不会阻塞读取线程,但可能返回旧值。若你的淘汰策略依赖严格 LRU,注意 Caffeine 的 TinyLFU 是近似频率统计,不保证最近最少使用项一定被逐出。

官方来源

  1. Official README
  2. Project repository
  3. Release notes
社区笔记

社区笔记