简单高效的缓存解决方案--Guava Cache
目录
简单高效的缓存解决方案--Guava Cache
在构建高性能Java应用时,缓存是提升系统性能的关键技术之一。虽然 ConcurrentHashMap 能够提供基础的键值存储,但在实际的缓存场景中,我们往往需要更多专业特性。这就是 com.google.common.cache 大显身手的地方。
什么是 Guava Cache?
Guava Cache 是 Google Guava 库中的一个本地缓存实现,它提供了比普通 Map 更丰富的缓存特性,包括自动驱逐、过期策略、统计信息等,专门为缓存场景深度优化。
Guava Cache 是超越普通Map的高性能本地缓存。
从本质上讲,它确实就是一个 Map。
但更准确地说,它是一个「智能的、自带管理功能的 Map」。
<dependencies>
<!-- Guava 核心依赖 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
<!-- 或者使用 Android 版本 -->
<!-- <version>32.1.3-android</version> -->
<!-- 别的版本 -->
<!-- <version>19.0</version> -->
</dependency>
</dependencies>
核心优势
1. 自动驱逐策略
普通 Map 需要手动管理内存,而 Guava Cache 提供了多种自动驱逐机制:
// 基于大小的驱逐
Cache<String, Object> sizeBasedCache = CacheBuilder.newBuilder()
.maximumSize(1000) // 最多1000个条目
.build();
// 基于权重的驱逐
Cache<String, Object> weightBasedCache = CacheBuilder.newBuilder()
.maximumWeight(1000000) // 总权重最大100万
.weigher((String key, Object value) -> getWeight(value))
.build();
2. 时间-based 过期
Cache<String, Object> timeBasedCache = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期
.expireAfterAccess(5, TimeUnit.MINUTES) // 5分钟未被访问则过期
.build();
3. 自动加载数据
最强大的特性之一:当缓存未命中时自动加载数据。
LoadingCache<String, User> userCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(new CacheLoader<String, User>() {
@Override
public User load(String userId) throws Exception {
// 当缓存不存在时,自动调用此方法加载数据
return userService.getUserById(userId);
}
});
// 使用 - 如果不存在会自动加载
User user = userCache.get("123");
实战示例
1. 配置缓存实例
public class ProductCache {
private final LoadingCache<String, Product> cache;
public ProductCache(ProductService productService) {
this.cache = CacheBuilder.newBuilder()
.maximumSize(500)
.expireAfterWrite(30, TimeUnit.MINUTES)
.refreshAfterWrite(10, TimeUnit.MINUTES) // 定时刷新
.recordStats() // 开启统计
.build(new CacheLoader<String, Product>() {
@Override
public Product load(String productId) {
return productService.getProduct(productId);
}
@Override
public ListenableFuture<Product> reload(String key, Product oldValue) {
// 异步刷新
return productService.getProductAsync(key);
}
});
}
public Product getProduct(String productId) {
try {
return cache.get(productId);
} catch (ExecutionException e) {
throw new RuntimeException("Failed to load product: " + productId, e);
}
}
}
2. 批量操作
// 批量获取
Map<String, User> users = userCache.getAll(Arrays.asList("1", "2", "3"));
// 批量失效
cache.invalidateAll(Arrays.asList("key1", "key2"));
// 清空缓存
cache.invalidateAll();
3. 缓存统计与监控
CacheStats stats = cache.stats();
logger.info("缓存命中率: {}%", stats.hitRate() * 100);
logger.info("平均加载时间: {}ms", stats.averageLoadPenalty() / 1000000);
logger.info("缓存命中次数: {}", stats.hitCount());
logger.info("缓存未命中次数: {}", stats.missCount());
logger.info("驱逐次数: {}", stats.evictionCount());
高级特性
1. 缓存刷新
LoadingCache<String, Config> configCache = CacheBuilder.newBuilder()
.refreshAfterWrite(1, TimeUnit.MINUTES) // 1分钟后刷新
.build(new CacheLoader<String, Config>() {
@Override
public Config load(String key) {
return loadConfig(key);
}
});
2. 移除监听器
Cache<String, Data> cache = CacheBuilder.newBuilder()
.removalListener((RemovalNotification<String, Data> notification) -> {
RemovalCause cause = notification.getCause();
String key = notification.getKey();
Data value = notification.getValue();
logger.info("Key {} 被移除,原因: {}", key, cause);
if (cause == RemovalCause.SIZE) {
// 处理因大小限制被移除的情况
metrics.recordEviction();
}
})
.build();
3. 异步刷新
LoadingCache<String, HeavyObject> asyncCache = CacheBuilder.newBuilder()
.refreshAfterWrite(10, TimeUnit.MINUTES)
.build(CacheLoader.asyncReloading(
new CacheLoader<String, HeavyObject>() {
@Override
public HeavyObject load(String key) {
return computeHeavyObject(key);
}
},
executorService // 使用线程池异步刷新
));
最佳实践
1. 合理设置缓存大小
// 根据应用内存情况设置合理的大小
CacheBuilder.newBuilder()
.maximumSize(calculateOptimalSize()) // 动态计算
.build();
2. 处理加载异常
LoadingCache<String, Data> safeCache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, Data>() {
@Override
public Data load(String key) throws Exception {
try {
return externalService.getData(key);
} catch (Exception e) {
// 返回默认值或抛出特定异常
return getDefaultData();
}
}
});
3. 结合 Spring 使用
@Component
public class CacheManager {
@Autowired
private UserRepository userRepository;
private LoadingCache<Long, User> userCache;
@PostConstruct
public void init() {
userCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterAccess(2, TimeUnit.HOURS)
.recordStats()
.build(CacheLoader.from(userRepository::findById));
}
public User getUser(Long id) {
try {
return userCache.get(id);
} catch (ExecutionException e) {
throw new RuntimeException("Cache load failed", e);
}
}
}
性能考虑
1. 选择合适的驱逐策略
- maximumSize(): 适用于条目大小相近的场景
- maximumWeight(): 适用于条目大小差异较大的场景
- expireAfterWrite(): 适用于数据不经常变化的场景
- expireAfterAccess(): 适用于需要保留热门数据的场景
2. 监控与调优
// 定期输出缓存统计
scheduledExecutorService.scheduleAtFixedRate(() -> {
CacheStats stats = cache.stats();
if (stats.requestCount() > 0) {
logger.info("缓存统计: 命中率={}%, 加载次数={}, 平均加载时间={}ms",
String.format("%.2f", stats.hitRate() * 100),
stats.loadCount(),
String.format("%.2f", stats.averageLoadPenalty() / 1000000.0));
}
}, 1, 1, TimeUnit.MINUTES);
总结
Guava Cache 提供了一个功能完整、性能优异的本地缓存解决方案。与普通 Map 相比,它在以下方面表现出色:
- ✅ 自动内存管理 - 无需手动清理
- ✅ 灵活的过期策略 - 时间和访问频率控制
- ✅ 丰富的统计信息 - 便于监控和调优
- ✅ 线程安全 - 内置并发控制
- ✅ 自动加载 - 简化缓存未命中处理
对于大多数Java应用的本地缓存需求,Guava Cache 都是一个值得信赖的选择。它既保持了使用的简洁性,又提供了企业级缓存所需的强大功能。
适用场景:数据量不大、访问频繁、计算成本高的数据缓存,如配置信息、用户会话、数据库查询结果等。
不适用场景:分布式环境、超大容量缓存(考虑 Redis 等分布式缓存)。
Google Guava 库中的 Cache 类型详解
Guava 提供了两种核心的 Cache 接口,分别针对不同的使用场景。
// CacheBuilder类源码
public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build(CacheLoader<? super K1, V1> loader) {
this.checkWeightWithWeigher();
return new LocalLoadingCache(this, loader);
}
public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
this.checkWeightWithWeigher();
this.checkNonLoadingCache();
return new LocalManualCache(this);
}
1. Cache<K, V> - 手动缓存
特点
- 手动加载:需要显式调用
put()方法插入数据 - 灵活控制:可以手动管理缓存项的插入和失效
- 基础功能:提供基本的缓存操作
- 适合需要精确控制缓存时机的场景
public class ManualCacheExample {
public static void main(String[] args) {
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(); // 注意:没有 CacheLoader
// 1. 存入数据
cache.put("key1", "value1");
cache.put("key2", "value2");
// 2. 获取数据 - 如果不存在返回 null
String value = cache.getIfPresent("key1");
System.out.println("key1: " + value); // value1
// 3. 获取或默认值
String value3 = cache.getIfPresent("key3");
System.out.println("key3: " + value3); // null
// 4. 批量获取
Map<String, String> allValues = cache.getAllPresent(Arrays.asList("key1", "key2"));
// 5. 手动失效
cache.invalidate("key1"); // 单个失效
cache.invalidateAll(Arrays.asList("key1", "key2")); // 批量失效
cache.invalidateAll(); // 全部失效
// 6. 统计信息
cache.stats().hitCount();
cache.stats().missCount();
}
}
2. LoadingCache<K, V> - 自动加载缓存
特点
- 自动加载:当缓存不存在时自动调用
CacheLoader加载数据 - 简化代码:避免了 "检查-加载-存入" 的模式
- 批量操作:支持批量加载
- 适合数据访问模式固定的场景
public class LoadingCacheExample {
private LoadingCache<String, Product> productCache;
public LoadingCacheExample() {
this.productCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build(new CacheLoader<String, Product>() {
@Override
public Product load(String productId) {
System.out.println("Loading product: " + productId);
return loadProductFromDB(productId);
}
@Override
public Map<String, Product> loadAll(Iterable<? extends String> keys) {
System.out.println("Batch loading products");
return loadProductsFromDB(keys);
}
});
}
public void demo() throws ExecutionException {
// 1. 获取单个 - 自动加载
Product p1 = productCache.get("123"); // 如果不存在会调用load方法
// 2. 获取或计算
Product p2 = productCache.get("456", () -> createDefaultProduct());
// 3. 批量获取
Map<String, Product> products = productCache.getAll(Arrays.asList("123", "456", "789"));
// 4. 刷新缓存(异步)
productCache.refresh("123"); // 在后台重新加载
// 5. 手动存入(覆盖自动加载)
productCache.put("999", new Product("999", "Special Product"));
// 6. 查看缓存状态
System.out.println("Cache stats: " + productCache.stats());
}
private Product loadProductFromDB(String productId) {
// 模拟数据库查询
return new Product(productId, "Product " + productId);
}
private Map<String, Product> loadProductsFromDB(Iterable<? extends String> keys) {
Map<String, Product> result = new HashMap<>();
for (String key : keys) {
result.put(key, loadProductFromDB(key));
}
return result;
}
private Product createDefaultProduct() {
return new Product("default", "Default Product");
}
}
多级缓存策略
public class MultiLevelCacheManager {
// 一级缓存:高频小数据 - 快速访问
private final LoadingCache<String, HotData> l1Cache;
// 二级缓存:全量数据 - 较大容量
private final LoadingCache<String, FullData> l2Cache;
public MultiLevelCacheManager() {
this.l1Cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.recordStats()
.build(new CacheLoader<String, HotData>() {
@Override
public HotData load(String key) {
// 先尝试从 L2 缓存获取
FullData fullData = l2Cache.getUnchecked(key);
return extractHotData(fullData);
}
});
this.l2Cache = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(1, TimeUnit.HOURS)
.recordStats()
.build(new CacheLoader<String, FullData>() {
@Override
public FullData load(String key) {
return loadFromDataSource(key);
}
});
}
public HotData getHotData(String key) {
return l1Cache.getUnchecked(key);
}
public FullData getFullData(String key) {
return l2Cache.getUnchecked(key);
}
public void refreshL1(String key) {
l1Cache.refresh(key);
}
public void printStats() {
System.out.println("L1 Cache Stats: " + l1Cache.stats());
System.out.println("L2 Cache Stats: " + l2Cache.stats());
}
private HotData extractHotData(FullData fullData) {
// 提取热点数据逻辑
return new HotData();
}
private FullData loadFromDataSource(String key) {
// 从数据源加载完整数据
return new FullData();
}
}
总结
| 缓存类型 | 特点 | 适用场景 |
|---|---|---|
| Cache<K,V> | 手动管理,灵活控制 | 需要精确控制缓存时机,数据来源多样 |
| LoadingCache<K,V> | 自动加载,简化代码 | 数据访问模式固定,有明确的加载逻辑 |
选择建议:
- 大多数情况下推荐使用
LoadingCache,代码更简洁 - 只有在需要特殊控制逻辑时才使用
Cache - 两者都基于相同的
CacheBuilder配置体系
浙公网安备 33010602011771号