项目中经常使用Guava Cache,根据经验总结了一些最佳实践。
示例代码
快速有效的使用示例如下:
LoadingCache<Integer, Model> modelCache = CacheBuilder.newBuilder()
//限制缓存大小,防止OOM
.maximumSize(1000)
//提供过期策略
.expireAfterAccess(100, TimeUnit.MINUTES)
//缓存不存在的时候,自动加载
.build(new CacheLoader<Integer, Model>() {
@Override
public Model load(@Nonnull Integer key) {
Model model = doGetModel(key);
if (model == null) {
//guava不支持null,会抛出异常InvalidCacheLoadException,最佳办法是抛出自定义异常
throw new ModelNotFoundException();
}
return model;
}
});
最佳实践
自动加载
如果缓存不存在,则自动去数据源加载数据到缓存
.build(new CacheLoader<Integer, Model>() {
@Override
public Model load(@Nonnull Integer key) {
return doGetModel(key);
}
)
内存控制
使用缓存一定要防止缓存占用过多的内存,导致程序OOM。需要对缓存的内存使用量进行限制,同时还需要设置过期或刷新策略。
//限制缓存大小,防止OOM
.maximumSize(1000)
//提供过期策略
.expireAfterAccess(100, TimeUnit.MINUTES)
上面是使用得最多的两个选项,其他选项还有:
-
maximumWeight
:限制最大权重(权重的计算方式需要传递Weigher
) -
expireAfterWrite
:写入后多长时间过期 -
refreshAfterWrite
:写入后多长时间刷新
removal listener
在一些场景下,监控cache的换出结果,方便做出响应,比如在集群本地缓存同步的时候,可以监听后同步给集群内其他机器。参见:本地缓存同步的一个简单方案
.removalListener((RemovalListener<Integer, Model>) notification -> {
log.info("remove taskbot from guava cache: key[{}], cause[{}]", notification.getKey(), notification.getCause());
final RemovalCause cause = notification.getCause();
switch (cause) {
case EXPIRED:
log.info("model evicted because of expiration in guava cache: {}", notification.getKey());
break;
case SIZE:
log.info("model evicted because of size in guava cache: {}", notification.getKey());
break;
case COLLECTED:
//如果是缓存到期等原因被删除,则需要通知分布式环境下的其他机器也要删除
log.info("model evicted because of gc in guava cache: {}", notification.getKey());
break;
case EXPLICIT:
log.info("model evicted because of explicit in guava cache: {}", notification.getKey());
break;
case REPLACED:
log.info("model updated because of replaced in guava cache: {}", notification.getKey());
break;
default:
log.error("there should not be [{}]", cause);
}
})
查看缓存统计值
可以了解缓存使用的特性,比如命中率等
CacheStats Cache#stats();
public final class CacheStats {
//命中次数
private final long hitCount;
//击穿次数
private final long missCount;
//加载成功次数
private final long loadSuccessCount;
//加载发生异常的次数
private final long loadExceptionCount;
//加载时间总机
private final long totalLoadTime;
//换出的次数
private final long evictionCount;
}
不常用功能
weakKey, weakValue, softValue
使用这些值可以把内存的使用量交给JVM来控制,一般不太实用
NULL值的处理
GauvaCache不支持null值的缓存,而且会抛出异常InvalidCacheLoadException,最佳办法是抛出自定义异常,然后在Cache#get的时候捕捉定义异常。示例如下:
@Override
public Model load(@Nonnull Integer key) {
Model model = doGetModel(key);
if (model == null) {
//guava不支持null,会抛出异常InvalidCacheLoadException,最佳办法是抛出自定义异常
throw new ModelNotFoundException();
}
return model;
}
try {
modelCache.get(key);
} catch (ExecutionException e) {
//TODO: handle exception
} catch (ModelNotFoundException e) {
//TODO: handle exception
}
注意事项
- GauvaCache异步刷新缓存,不会阻塞线程获取缓存内容(老的内容)
- GauvaCache不支持缓存null值