Caffeine 是 Guava Cache 的「现代替代方案」—— 由 Guava Cache 核心贡献者 Ben Manes 开发,完全兼容 Guava Cache 的 API 设计,同时通过更优的算法和并发模型实现了性能碾压。本文从核心原理、性能表现、功能特性、使用示例、选型建议 五个维度,全面对比两者的差异,帮助开发者快速选择适配场景的本地缓存方案。
一、核心背景与设计理念
1. Guava Cache
- 定位:Google 出品的 Java 本地缓存实现,2010 年发布,是早期 Java 本地缓存的「事实标准」;
- 核心目标:解决 HashMap 手动管理缓存的痛点(如过期、淘汰、并发),提供轻量、易用的本地缓存能力;
- 底层算法:基于「分段 LRU(Segmented LRU)」实现缓存淘汰,通过分段锁(Segment)保证并发安全。
2. Caffeine
- 定位:2014 年发布,专为高并发、高命中率场景设计,是 Guava Cache 的「升级版」;
- 核心目标:在兼容 Guava API 的前提下,大幅提升性能和缓存命中率,降低内存占用;
- 底层算法:基于「W-TinyLFU(Window-TinyLFU)」淘汰策略(结合 LFU 高命中率和 LRU 低缓存污染的优点),是目前业界公认的最优本地缓存淘汰算法。
二、核心维度对比(表格版)
|
对比维度 |
Guava Cache |
Caffeine |
|
核心算法 |
分段 LRU(Segmented LRU) |
W-TinyLFU(LFU+LRU 融合) |
|
缓存命中率 |
约 70%-80%(易受缓存污染影响) |
约 85%-95%(W-TinyLFU 抗污染) |
|
性能(吞吐量) |
读:100 万 QPS;写:50 万 QPS |
读:700 万 QPS;写:300 万 QPS(官方基准) |
|
并发模型 |
分段锁(Segment),concurrencyLevel 固定分段数 |
Striped Lock + CAS,无固定分段数,动态适配并发 |
|
过期策略 |
支持:访问过期、写入过期、引用过期 |
兼容 Guava 所有过期策略 + 更灵活的时间精度 |
|
刷新机制 |
同步刷新(刷新时阻塞读请求) |
异步刷新(基于 CompletableFuture,无阻塞) |
|
内存占用 |
较高(分段结构冗余) |
更低(紧凑的数据结构设计) |
|
统计功能 |
基础统计(命中率、加载数、过期数) |
详细统计(含耗时、驱逐原因、内存占用) |
|
API 兼容性 |
—— |
完全兼容 Guava Cache API(迁移成本 ≈ 0) |
|
Spring 集成 |
需手动配置 |
Spring Cache 5.0+ 官方默认本地缓存实现 |
|
JDK 依赖 |
兼容 JDK 6+ |
依赖 JDK 8+(利用 Lambda/CompletableFuture) |
三、关键差异深度解析
1. 缓存淘汰策略:LRU vs W-TinyLFU(核心性能差距)
Guava Cache 的 LRU 痛点
LRU(最近最少使用)是最基础的缓存淘汰策略,但存在两大问题:
- 缓存污染:一次性热点数据(如秒杀活动的临时流量)会占据缓存,挤走长期热点数据;
- 命中率低:无法区分「偶尔访问」和「长期高频访问」的缓存项。
Guava 虽通过「分段 LRU」优化并发,但未解决 LRU 本身的缺陷,在高并发、数据分布复杂的场景下命中率显著下降。
Caffeine 的 W-TinyLFU 优势
W-TinyLFU 结合了三种机制,完美解决 LRU 痛点:
- Window LRU:缓存新数据,过滤一次性热点(短期数据先进入窗口,只有持续访问才进入核心缓存);
- TinyLFU:用极小的内存(布隆过滤器)记录访问频率,筛选长期高频数据;
- LRU 淘汰:核心缓存满时,淘汰「频率低且最近未访问」的项。
效果:在电商秒杀、风控规则缓存等场景,Caffeine 的命中率比 Guava 高 10%-20%,大幅减少缓存穿透到数据库的请求。
2. 并发控制:分段锁 vs Striped Lock + CAS
Guava Cache 的分段锁
Guava 将缓存分为 concurrencyLevel 个 Segment(默认 16),每个 Segment 是一个独立的 LRU 缓存,通过锁隔离并发。
- 缺点:
-
- 分段数固定,无法适配动态并发(如 16 分段无法利用 32 核 CPU);
- 分段间数据无法共享,内存冗余;
- 高并发下,单个 Segment 锁竞争激烈。
Caffeine 的 Striped Lock + CAS
Caffeine 放弃了固定分段,采用「Striped Lock(条纹锁)+ CAS」实现并发控制:
- 对缓存项的读写优先用 CAS 无锁操作;
- 仅当 CAS 失败时,才对「缓存项所在的哈希桶」加锁(锁粒度更小);
- 无固定分段数,动态适配 CPU 核心数和并发量。
效果:高并发下(如 10 万+ QPS 写),Caffeine 的锁竞争延迟比 Guava 低一个数量级。
3. 刷新机制:同步 vs 异步
缓存刷新(如 refreshAfterWrite)是「缓存过期前主动更新数据」的机制,两者的实现差异直接影响业务响应速度:
Guava Cache 同步刷新
// Guava 同步刷新:刷新时,所有读请求阻塞,直到刷新完成
LoadingCache<String, String> guavaCache = CacheBuilder.newBuilder()
.refreshAfterWrite(1, TimeUnit.MINUTES) // 1 分钟后刷新
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
// 同步加载数据(如查数据库,耗时 100ms)
return queryFromDB(key);
}
});
- 问题:刷新期间,所有访问该 key 的请求都会阻塞,直到
load方法执行完成,易导致接口超时。
Caffeine 异步刷新
// Caffeine 异步刷新:刷新不阻塞读请求,返回旧值 + 异步更新
AsyncLoadingCache<String, String> caffeineCache = Caffeine.newBuilder()
.refreshAfterWrite(1, TimeUnit.MINUTES)
.buildAsync((key, executor) -> {
// 异步加载数据(CompletableFuture 非阻塞)
return CompletableFuture.supplyAsync(() -> queryFromDB(key), executor);
});
- 优势:
-
- 刷新时,读请求直接返回旧缓存值,无阻塞;
- 刷新任务异步执行,完成后自动更新缓存;
- 支持自定义线程池,避免占用业务线程。
4. 过期策略与内存管理
两者均支持以下过期策略,但 Caffeine 实现更高效:
|
过期策略 |
Guava Cache 实现 |
Caffeine 实现 |
|
写入过期 |
分段定时清理(精度秒级) |
惰性清理 + 定时清理(精度毫秒级) |
|
访问过期 |
访问时校验(无定时清理,可能内存泄漏) |
惰性清理 + 后台线程定期清理(可控) |
|
引用过期 |
支持软引用/弱引用(JVM 内存不足时回收) |
兼容 + 更高效的引用队列处理 |
Caffeine 优化点:Guava 的访问过期依赖「下次访问」触发清理,若缓存项长期不访问,会一直占用内存;Caffeine 新增后台清理线程,定期清理过期项,避免内存泄漏。
四、代码示例对比(API 兼容性与差异)
1. 基础缓存使用(API 几乎一致)
Guava Cache 示例
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
public class GuavaCacheDemo {
public static void main(String[] args) {
// 构建 Guava 缓存
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(10000) // 最大缓存数
.expireAfterWrite(5, TimeUnit.MINUTES) // 写入后 5 分钟过期
.concurrencyLevel(16) // 并发级别(分段数)
.recordStats() // 开启统计
.build();
// 存/取/删
cache.put("key1", "value1");
String value = cache.getIfPresent("key1");
cache.invalidate("key1");
// 查看统计
System.out.println("命中率:" + cache.stats().hitRate());
}
}
Caffeine 示例(API 兼容,仅构建器不同)
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
public class CaffeineCacheDemo {
public static void main(String[] args) {
// 构建 Caffeine 缓存(API 与 Guava 一致)
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(10000) // 最大缓存数
.expireAfterWrite(5, TimeUnit.MINUTES) // 写入后 5 分钟过期
.recordStats() // 开启统计
.build();
// 存/取/删(与 Guava 完全一致)
cache.put("key1", "value1");
String value = cache.getIfPresent("key1");
cache.invalidate("key1");
// 查看更详细的统计
System.out.println("命中率:" + cache.stats().hitRate());
System.out.println("平均加载耗时:" + cache.stats().averageLoadTime());
}
}
2. 异步加载缓存(Caffeine 独有)
// Caffeine 异步加载缓存(Guava 无此特性)
AsyncLoadingCache<String, String> asyncCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
// 异步加载数据,支持 CompletableFuture
.buildAsync((key) -> {
// 异步查询数据库/远程接口
return CompletableFuture.supplyAsync(() -> queryFromDB(key));
});
// 异步获取(非阻塞)
CompletableFuture<String> future = asyncCache.get("key1");
future.thenAccept(value -> System.out.println("异步获取值:" + value));
3. Spring Cache 集成(Caffeine 更便捷)
Guava Cache 集成 Spring
@Configuration
@EnableCaching
public class GuavaCacheConfig {
@Bean
public CacheManager cacheManager() {
GuavaCacheManager cacheManager = new GuavaCacheManager();
cacheManager.setCacheBuilder(
CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
);
return cacheManager;
}
}
Caffeine 集成 Spring(默认支持)
@Configuration
@EnableCaching
public class CaffeineCacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(
Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.recordStats()
);
return cacheManager;
}
}
五、选型建议(按场景适配)
优先选 Caffeine 的场景
- 高并发/高命中率需求:如电商秒杀、风控规则、热点商品缓存(QPS > 1 万);
- 异步场景:需要非阻塞加载/刷新缓存(如微服务接口缓存);
- Spring 生态:Spring 5.0+ 项目(官方默认,集成成本最低);
- JDK 8+ 环境:能利用 Lambda/CompletableFuture 特性;
- 内存敏感场景:需要更低的内存占用和更高的缓存利用率。
保留 Guava Cache 的场景
- 老项目维护:JDK 6/7 环境,无法升级到 JDK 8;
- 轻量场景:低并发(QPS < 1 万)、简单缓存需求(无需异步/高精度过期);
- 无依赖新增限制:项目已强依赖 Guava,无需引入 Caffeine 新依赖。
迁移建议(Guava → Caffeine)
- API 层:仅需替换
CacheBuilder为Caffeine,核心方法(put/get/invalidate)无需修改; - 特性层:若使用 Guava 的软引用/弱引用,Caffeine 完全兼容;若需异步能力,可逐步替换为
AsyncLoadingCache; - 监控层:Caffeine 的统计指标更丰富,可扩展监控维度(如命中率、加载耗时)。
六、总结
|
特性 |
结论 |
|
性能 |
Caffeine 全面碾压 Guava Cache(吞吐量 ≈ 6-7 倍,命中率 ≈ 1.2 倍) |
|
易用性 |
API 完全兼容,迁移成本极低 |
|
功能 |
Caffeine 新增异步加载、更细粒度的过期、更详细的统计 |
|
生态 |
Spring 5.0+ 官方默认,社区支持更活跃 |
最终建议:
- 新项目直接使用 Caffeine,无需考虑 Guava Cache;
- 老项目若有性能瓶颈(如缓存命中率低、并发高时响应慢),优先迁移到 Caffeine;
- 仅在 JDK 6/7 等老旧环境下,保留 Guava Cache。
Caffeine 是 Guava Cache 的「超集」—— 既兼容原有使用习惯,又在性能、功能、内存占用上全面优化,是目前 Java 本地缓存的最优选择。
1489

被折叠的 条评论
为什么被折叠?



