Guava Cache 高性能本地缓存库详解与使用案例

Guava Cache 高性能本地缓存库详解与使用案例


一、Guava Cache 简介

1.1 什么是 Guava Cache?

Guava Cache 是 Google 提供的 Java 本地缓存库,是 Google Guava 工具库的一部分。它提供了灵活且高效的缓存管理功能,适用于需要高性能、低延迟的本地缓存场景。核心特性包括:

  • 基于大小和时间的缓存驱逐
  • 软引用/弱引用支持
  • 缓存统计(命中率、加载次数等)
  • 手动刷新与异步加载
  • 缓存监听器

Guava Cache 适用于单体应用、微服务中的本地缓存需求,尤其适合对缓存性能要求较高的场景。


二、核心特性详解

2.1 依赖引入

Maven
xml 复制代码
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
Gradle
groovy 复制代码
implementation 'com.google.guava:guava:31.1-jre'

2.2 缓存构建器 CacheBuilder

Guava Cache 的核心是通过 CacheBuilder 构建缓存实例,支持链式配置。

2.2.1 基本缓存创建
java 复制代码
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)// 最大缓存大小
.build();
2.2.2 基于时间的过期策略
  • 写入后过期(expireAfterWrite):元素在写入后指定时间过期。
  • 访问后过期(expireAfterAccess):元素在最后一次访问后指定时间过期。
java 复制代码
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)// 写入后10分钟过期
.expireAfterAccess(5, TimeUnit.MINUTES)// 最后一次访问后5分钟过期
.build();
2.2.3 基于大小的驱逐策略

Guava 使用 LRU(Least Recently Used) 算法管理缓存大小。

java 复制代码
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(1000)// 缓存最多存储1000个元素
.build();
2.2.4 软引用/弱引用
  • 软引用(Soft References):在 JVM 内存不足时回收。
  • 弱引用(Weak References):在下一次 GC 时回收。
java 复制代码
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.softValues()// 值使用软引用
.weakKeys()// 键使用弱引用
.build();
2.2.5 缓存统计

启用统计功能可获取命中率、加载次数等信息。

java 复制代码
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.recordStats()// 启用统计
.build();

// 获取统计信息
CacheStats stats = cache.stats();
System.out.println("命中率: " + stats.hitRate());
2.2.6 手动刷新与异步加载
  • 手动刷新refresh(key) 强制刷新指定缓存项。
  • 异步加载 :结合 LoadingCache 实现异步加载。
java 复制代码
LoadingCache<String, String> loadingCache = CacheBuilder.newBuilder()
.maximumSize(100)
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return fetchFromDB(key);// 从数据库加载
}
});

// 异步加载
String value = loadingCache.get("key");

2.3 缓存监听器

通过 RemovalListener 监听缓存项被驱逐或过期。

java 复制代码
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.removalListener((RemovalListener<String, String>) notification -> {
System.out.println("Key: " + notification.getKey() + " 被移除,原因: " + notification.getCause());
})
.build();

三、使用案例详解

3.1 单体应用缓存

场景:用户信息缓存
java 复制代码
public class UserService {
private final LoadingCache<String, User> userCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<String, User>() {
@Override
public User load(String userId) throws Exception {
return fetchUserFromDB(userId);// 从数据库加载
}
});

public User getUser(String userId) {
try {
return userCache.get(userId);// 自动加载或从缓存获取
} catch (ExecutionException e) {
throw new RuntimeException("缓存加载失败", e);
}
}

private User fetchUserFromDB(String userId) {
// 模拟数据库查询
return new User(userId, "User_" + userId);
}
}
优势:
  • 减少数据库压力:高频查询直接命中缓存。
  • 自动刷新:Guava 自动管理缓存更新。

3.2 分布式系统中的本地缓存

场景:结合 Redis 的二级缓存

Guava Cache 作为本地缓存,Redis 作为分布式缓存。

java 复制代码
public class DistributedCache {
private final Cache<String, String> localCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();

public String get(String key) {
String value = localCache.getIfPresent(key);
if (value == null) {
value = fetchFromRedis(key);
if (value == null) {
value = fetchFromDB(key);
saveToRedis(key, value);
}
localCache.put(key, value);
}
return value;
}
}
优势:
  • 本地缓存加速:减少对 Redis 的直接访问。
  • 降级容错:Redis 不可用时可直接访问数据库。

3.3 Spring Boot 集成

1. 配置缓存 Bean
java 复制代码
@Configuration
public class CacheConfig {
@Bean
public Cache<String, String> myCache() {
return CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
}
}
2. 使用缓存
java 复制代码
@Service
public class MyService {
@Autowired
private Cache<String, String> myCache;

public String getData(String key) {
String value = myCache.getIfPresent(key);
if (value == null) {
value = fetchFromExternalService(key);
myCache.put(key, value);
}
return value;
}
}

四、性能优化技巧

4.1 避免缓存雪崩

  • 随机过期时间:为缓存设置随机过期时间,避免大量缓存同时失效。
  • 热点数据预加载:对高频访问的数据主动加载到缓存。
java 复制代码
// 随机过期时间(需结合自定义逻辑)
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build();

4.2 缓存命中率优化

  • 合理设置大小 :根据业务访问模式调整 maximumSize
  • 监控统计 :定期分析 hitRate()evictionCount(),调整策略。
java 复制代码
CacheStats stats = cache.stats();
System.out.println("命中率: " + stats.hitRate());

4.3 内存管理

  • 软引用值(softValues):在 JVM 内存不足时自动回收缓存。
  • 定期清理 :使用 cleanUp() 手动触发清理。
java 复制代码
cache.cleanUp(); // 手动清理过期缓存

五、常见问题与解决方案

5.1 缓存穿透(Cache Penetration)

问题 :查询不存在的数据导致频繁访问数据库。
解决方案

  • 布隆过滤器(Bloom Filter):拦截不存在的键。
  • 空值缓存:对查询结果为空的键设置短暂缓存。
java 复制代码
String value = cache.get(key, () -> {
String result = fetchFromDB(key);
if (result == null) {
cache.put(key, "");// 缓存空值
}
return result;
});

5.2 缓存击穿(Cache Breakdown)

问题 :热点数据过期后大量请求直接访问数据库。
解决方案

  • 互斥锁(Mutex Lock):仅允许一个线程重建缓存。
  • 永不过期:热点数据设置永不过期,定时异步更新。
java 复制代码
String value = cache.get(key, () -> {
synchronized (key.intern()) {
if (cache.getIfPresent(key) == null) {
String result = fetchFromDB(key);
cache.put(key, result);
return result;
}
}
return cache.getIfPresent(key);
});

5.3 缓存雪崩(Cache Avalanche)

问题 :大量缓存同时失效,导致数据库压力激增。
解决方案

  • 随机过期时间:为缓存设置随机过期时间。
  • 分层缓存:本地缓存 + Redis 分布式缓存。

六、Guava Cache 与 Caffeine 对比

特性 Guava Cache Caffeine
算法 LRU(较简单) Window TinyLFU(更高命中率)
异步加载 不支持(需手动实现) 支持 AsyncLoadingCache
统计功能 基本统计 详细统计(命中率、加载次数等)
性能 一般(适合中等规模应用) 更高吞吐量和更低延迟
维护状态 已停止更新(推荐使用 Caffeine) 活跃维护

七、总结

Guava Cache 是 Java 生态中经典的本地缓存库,其 灵活配置、稳定性和易用性 使其成为许多项目的首选。通过合理配置缓存策略,开发者可以显著提升系统性能,减少对数据库的依赖。尽管 Caffeine 在性能上更优,但 Guava Cache 仍然是许多遗留项目和中等规模应用的可靠选择。


八、参考资料

相关推荐
我真的是大笨蛋3 小时前
Redis的String详解
java·数据库·spring boot·redis·spring·缓存
心态特好3 小时前
Jwt非对称加密的应用场景
java
敢敢J的憨憨L4 小时前
GPTL(General Purpose Timing Library)使用教程
java·服务器·前端·c++·轻量级计时工具库
4 小时前
JUC专题 - 并发编程带来的安全性挑战之同步锁
后端
凯哥19704 小时前
迁移PostgreSQL数据库教程
后端
Ray664 小时前
单例模式
后端
用户8356290780514 小时前
掌控PDF页面:使用Python轻松实现添加与删除
后端·python
无责任此方_修行中4 小时前
谁动了我的数据?一个 Bug 背后的“一行代码”真凶
后端·node.js·debug
sg_knight4 小时前
Spring Cloud与RabbitMQ深度集成:从入门到生产级实战
java·spring boot·spring·spring cloud·消息队列·rabbitmq·stream