一、简介
Caffeine 是一个基于 Java 8 开发的高性能、近乎最优的本地缓存库,由 Google 工程师 Ben Manes 受 Guava Cache 启发而设计,并在其基础上大幅优化了命中率、内存效率和并发性能。它被广泛用于替代 Guava Cache,成为 Spring Boot 2.x 默认的本地缓存实现(spring-boot-starter-cache 中的 CaffeineCacheManager)。
二、Caffeine 的核心特点
- 高性能:读写性能接近 ConcurrentHashMap,命中率和淘汰策略经过精心调优。
- 丰富的驱逐策略:基于大小、时间、引用(软/弱)以及自定义权重。
- 异步加载:支持 CacheLoader 和 AsyncCacheLoader,可以异步加载缓存值。
- 统计功能:提供命中率、加载成功率、平均加载耗时等指标。
- 灵活过期:支持创建后固定时间过期、最后一次访问后过期、最后一次写入后过期、自定义过期策略。
- 弱/软引用:允许键或值使用软引用、弱引用,便于 GC 回收。
- 与 Spring 无缝集成:通过 @Cacheable 注解即可使用。
三、快速入门
3.1 Maven 依赖
xml
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
3.2 基本用法
java
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
public class CaffeineDemo {
public static void main(String[] args) {
// 构建缓存
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(100) // 最大条目数
.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期
.recordStats() // 开启统计
.build();
// 存入
cache.put("key1", "value1");
// 获取(若不存在则返回 null)
String value = cache.getIfPresent("key1");
// 获取(若不存在则调用函数加载)
String value2 = cache.get("key2", k -> loadFromDB(k));
}
private static String loadFromDB(String key) {
// 模拟从数据库加载
return "dbValue";
}
}
四、核心配置详解
4.1 容量限制
| 方法 | 说明 |
|---|---|
| maximumSize(long size) | 限制缓存最大条目数,采用 W-TinyLFU 淘汰算法 |
| maximumWeight(long weight) | 配合 weigher 设置最大权重(例如不同条目权重不同) |
| weigher(Weigher<K, V> weigher) | 自定义权重计算函数 |
示例:
java
Cache<String, String> cache = Caffeine.newBuilder()
.maximumWeight(1000)
.weigher((String key, String value) -> value.length())
.build();
4.2 过期策略
| 方法 | 说明 |
|---|---|
| expireAfterWrite(long, TimeUnit) | 写入后经过固定时间过期 |
| expireAfterAccess(long, TimeUnit) | 最后一次访问(读或写)后经过固定时间过期 |
| expireAfter(Expiry<K, V>) | 自定义过期策略,可针对每个条目单独计算过期时间 |
| refreshAfterWrite(long, TimeUnit) | 写入后经过固定时间,下一次访问时异步刷新(不阻塞读取) |
注意:
- expireAfterWrite 和 expireAfterAccess 互斥,不能同时使用。
- refreshAfterWrite 仅当缓存被访问时触发刷新,且刷新是异步的(需要搭配 buildAsync 或 CacheLoader)。
自定义过期示例:
java
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfter(new Expiry<String, String>() {
@Override
public long expireAfterCreate(String key, String value, long currentTime) {
return TimeUnit.SECONDS.toNanos(30); // 创建后30秒过期
}
@Override
public long expireAfterUpdate(String key, String value, long currentTime, long currentDuration) {
return currentDuration; // 更新后不重置
}
@Override
public long expireAfterRead(String key, String value, long currentTime, long currentDuration) {
return currentDuration; // 读取后不重置
}
})
.build();
4.3 刷新策略(异步加载)
java
LoadingCache<String, String> cache = Caffeine.newBuilder()
.refreshAfterWrite(1, TimeUnit.MINUTES) // 写入1分钟后,下次访问触发异步刷新
.build(key -> loadFromDB(key)); // 同步加载函数
注意:刷新不会抛出异常到调用方,如果刷新失败,会继续返回旧值,并在后台重试。
4.4 统计功能
java
Cache<String, String> cache = Caffeine.newBuilder()
.recordStats() // 开启统计
.build();
// 使用一段时间后...
CacheStats stats = cache.stats();
System.out.println(stats.hitRate()); // 命中率
System.out.println(stats.loadSuccessCount()); // 成功加载次数
System.out.println(stats.averageLoadPenalty()); // 平均加载耗时(纳秒)
五、缓存加载方式
Caffeine 提供了三种缓存接口:
5.1 Cache(手动加载)
java
Cache<K, V> cache = Caffeine.newBuilder().build();
// 手动 put/get
V value = cache.getIfPresent(key);
cache.put(key, value);
cache.invalidate(key);
5.2 LoadingCache(同步加载)
需要提供 CacheLoader,当 key 不存在时自动调用 load 方法。
java
LoadingCache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1000)
.build(key -> loadFromDB(key)); // 或实现 CacheLoader
String value = cache.get("key"); // 若不存在则调用 load
Map<String, String> all = cache.getAll(keys); // 批量加载
5.3 AsyncLoadingCache(异步加载)
异步加载不会阻塞调用线程,适用于加载耗时较长的场景。
java
AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1000)
.buildAsync(key -> CompletableFuture.supplyAsync(() -> loadFromDB(key)));
CompletableFuture<String> future = cache.get("key");
future.thenAccept(System.out::println);
六、驱逐算法:W-TinyLFU
Caffeine 采用 W-TinyLFU(Window Tiny Least Frequently Used)算法,结合了 LRU 和 LFU 的优点,同时解决了传统算法的痛点:
- LRU (最近最少使用):只看时间,不看频率。容易被冷数据扫描污染,热点易被冲掉。
- LFU (最不经常使用):只看频率,不看时间。新数据冷启动困难(刚加入频率低易被删),历史高频数据长期占内存。
核心思想:新数据靠 Window 保护,老数据靠频率留存,冷热智能分离。
-
窗口缓存(Window Cache):
- 小容量(约总容量 1%),纯 LRU 策略
- 作用:接纳新数据,解决冷启动问题,避免新数据刚进就被淘汰
-
Main Cache (主缓存,TinyLFU)
- 大容量,核心存储区
- 用 Count-Min Sketch 概率结构极低内存开销记录访问频率
- 定期频率衰减(如每小时减半),让旧高频数据自然降温
- 淘汰时:频率最低 + 最久未用 者优先
-
PD (Probation Denominator) 候选区
- 临时缓冲区,用于 Window 与 Main 之间的替换权衡
七、高级特性
7.1 异步监听与移除监听
java
Cache<String, String> cache = Caffeine.newBuilder()
.removalListener((String key, String value, RemovalCause cause) -> {
System.out.println("Removed " + key + " because " + cause);
})
.build();
// 手动移除
cache.invalidate("key1");
// 移除原因:EXPLICIT(手动)、REPLACED(替换)、COLLECTED(GC回收)、EXPIRED(过期)、SIZE(容量淘汰)
7.2 弱引用与软引用
java
// 键使用弱引用(允许 GC 回收键)
Cache<String, String> cache = Caffeine.newBuilder()
.weakKeys() // 键使用 WeakReference
.weakValues() // 值使用 WeakReference
.build();
// 值使用软引用(适合内存敏感的缓存)
Cache<String, String> cache2 = Caffeine.newBuilder()
.softValues()
.build();
7.3 写入外部存储(Write-Through)
可以通过 CacheWriter 实现同步写入外部存储(如数据库),确保缓存和存储的一致性。
java
Cache<String, String> cache = Caffeine.newBuilder()
.writer(new CacheWriter<String, String>() {
@Override
public void write(String key, String value) {
// 写入数据库
db.put(key, value);
}
@Override
public void delete(String key, String value, RemovalCause cause) {
// 从数据库删除
db.delete(key);
}
})
.build();
7.4 与 Spring Cache 集成
Spring Boot 2.x 默认的 CacheManager 实现为 CaffeineCacheManager。
yaml
# application.yml
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=500,expireAfterWrite=60s
或在 Java 配置中:
java
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("users", "products");
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES));
return cacheManager;
}
}
使用时:
java
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User getUser(Long id) {
return userRepository.findById(id);
}
}
八、其他本地缓存实现方案
8.1 Spring Cache
Spring 从 3.1 开始提供了 Cache 和 CacheManager 接口,统一了缓存编程模型。通过注解(@Cacheable、@CacheEvict、@CachePut、@Caching、@CacheConfig)可以透明地使用不同缓存技术,而无需修改业务代码。
核心注解:
| 注解 | 作用 |
|---|---|
| @Cacheable | 方法执行前先查缓存,有则返回;无则执行方法并将结果存入缓存 |
| @CacheEvict | 触发时清除一条或多条缓存(常用于更新/删除操作) |
| @CachePut | 总是执行方法,并将结果存入缓存(用于更新缓存) |
| @Caching | 组合多个缓存操作 |
| @CacheConfig | 类级别共享缓存配置(如 cacheNames、keyGenerator) |
使用步骤:
- 启用缓存:@EnableCaching
- 声明 CacheManager Bean(根据具体实现)
- 在业务方法上添加缓存注解
8.2 Guava Cache
Guava 是 Google 提供的 Java 核心库,其中的 Cache 实现是 Caffeine 的前身,使用 LRU 淘汰算法。在 Spring Boot 1.x 中是默认本地缓存,2.x 后被 Caffeine 取代。
特点:
- 成熟稳定,文档丰富
- 支持过期、大小限制、移除监听器
- 性能逊于 Caffeine(约 3~5 倍差距)
集成方式:
xml
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
配置示例:
java
@Bean
public CacheManager cacheManager() {
GuavaCacheManager cacheManager = new GuavaCacheManager("users");
cacheManager.setCacheBuilder(CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats());
return cacheManager;
}
8.3 EhCache
EhCache 是一个纯 Java 的分布式缓存框架,历史悠久,功能非常全面:支持磁盘溢出、多级缓存、事务、JMX 监控等。它既可以作为本地缓存,也支持分布式(需配合 Terracotta)。
特点:
- 支持内存+磁盘两级存储(适合超大缓存)
- 支持缓存事件监听器
- 支持 XML 或 Java 配置
- 性能中等,略低于 Caffeine
集成方式:
xml
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<!-- Spring Boot 对 EhCache 的自动配置支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
配置文件(ehcache.xml):
xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.ehcache.org/v3"
xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd">
<cache alias="users">
<resources>
<heap unit="entries">1000</heap>
<offheap unit="MB">10</offheap>
</resources>
<expiry>
<ttl unit="minutes">10</ttl>
</expiry>
</cache>
</config>
Spring 配置:
yaml
spring:
cache:
type: ehcache
ehcache:
config: classpath:ehcache.xml
8.4 Simple Cache(基于 ConcurrentHashMap)
Spring Boot 自动配置的"简单缓存",当没有添加其他缓存库时,默认使用 ConcurrentHashMap 作为缓存存储。仅适合开发测试,生产环境不推荐,因为没有过期、淘汰策略,内存会无限增长。
特点:
- 无过期、无淘汰、无统计
- 基于 ConcurrentHashMap,性能高
- 无需额外依赖
配置(默认即启用):
java
// 自动配置 SimpleCacheManager
@Bean
public CacheManager cacheManager() {
return new SimpleCacheManager();
}
8.5 自定义缓存实现
如果上述方案均不满足需求(例如需要 TTL 分桶、异步刷新等特殊逻辑),可以自行实现 Cache 和 CacheManager 接口,或继承 AbstractValueAdaptingCache。
示例(基于 Caffeine 定制):
java
public class CustomCaffeineCache extends CaffeineCache {
public CustomCaffeineCache(String name, Caffeine<Object, Object> caffeine) {
super(name, caffeine.build());
}
// 可覆盖 get、put 等方法添加额外逻辑
}
九、选型标准
- 90% Spring Boot 项目 → Spring Cache + Caffeine
- 轻量小工具 → ConcurrentHashMap
- 需要磁盘持久化 → Ehcache
- 高并发、精细控制 → Caffeine 原生 LoadingCache
- 老项目维护 → Guava
Spring Boot 本地缓存首选:Spring Cache + Caffeine
简单场景用 ConcurrentHashMap,复杂高性能场景用 Caffeine 原生 API。