CacheBuilder
Cache Builder
基础使用
使用
Cache<String, User> cache = CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES);
user = cache.get(name, () -> {
User value = query(key); // from databse, disk, etc.
return value;
});
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(MY_LISTENER)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
Null 处理
不过需要注意一点的是,
LoadingCache<String, Optional<User>> cache = CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build(
new CacheLoader<String, Optional<User>>() {
@Override
public Optional<User> load(String name) throws Exception {
User value = query(key);//from databse, disk, etc.
return Optional.ofNullable(value);
}
}
);
这样我们保证了
Callable
所有类型的
Cache<Key, Graph> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(); // look Ma, no CacheLoader
...
try {
// If the key wasn't in the "easy to compute" group, we need to
// do things the hard way.
cache.get(key, new Callable<Key, Graph>() {
@Override
public Value call() throws AnyException {
return doThingsTheHardWay(key);
}
});
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}
显式插入
使用
缓存回收
一个残酷的现实是,我们几乎一定没有足够的内存缓存所有数据。你你必须决定:什么时候某个缓存项就不值得保留了?
基于容量的回收(size-based eviction)
如果要规定缓存项的数目不超过固定值,只需使用
另外,不同的缓存项有不同的“权重”(weights)——例如,如果你的缓存值,占据完全不同的内存空间,你可以使用
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumWeight(100000)
.weigher(new Weigher<Key, Graph>() {
public int weigh(Key k, Graph g) {
return g.vertices().size();
}
})
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) { // no checked exception
return createExpensiveGraph(key);
}
});
定时回收(Timed Eviction)
- expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读
/ 写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。 - expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖
) ,则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。
显式清除
任何时候,你都可以显式地清除缓存项,而不是等到它被回收:
- 个别清除:Cache.invalidate(key)
- 批量清除:Cache.invalidateAll(keys)
- 清除所有缓存项:Cache.invalidateAll()
刷新
刷新和回收不太一样。正如
如果刷新过程抛出异常,缓存将保留旧值,而异常会在记录到日志后被丢弃
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) { // no checked exception
return getGraphFromDatabase(key);
}
public ListenableFuture<Key, Graph> reload(final Key key, Graph prevGraph) {
if (neverNeedsRefresh(key)) {
return Futures.immediateFuture(prevGraph);
}else{
// asynchronous!
ListenableFutureTask<Key, Graph> task=ListenableFutureTask.create(new Callable<Key, Graph>() {
public Graph call() {
return getGraphFromDatabase(key);
}
});
executor.execute(task);
return task;
}
}
});
其他特性
统计
- hitRate():缓存命中率;
- averageLoadPenalty():加载新值的平均时间,单位为纳秒;
- evictionCount():缓存项被回收的总数,不包括显式清除。
此外,还有其他很多统计信息。这些统计信息对于调整缓存设置是至关重要的,在性能要求高的应用中我们建议密切关注这些数据。
asMap 视图
cache.asMap() 包含当前所有加载到缓存的项。因此相应地,cache.asMap().keySet() 包含当前所有已加载键; asMap().get(key) 实质上等同于cache.getIfPresent(key) ,而且不会引起缓存项的加载。这和Map 的语义约定一致。
所有读写操作都会重置相关缓存项的访问时间,包括
中断
缓存加载方法(如
如果用户提供的
原则上,我们可以拆除包装,把
对于这个决定,我们的指导原则是让缓存始终表现得好像是在当前线程加载值。这个原则让使用缓存或每次都计算值可以简单地相互切换。如果老代码(加载值的代码)是不可中断的,那么新代码(使用缓存加载值的代码)多半也应该是不可中断的。
如上所述,