源:https://github.com/google/guava/wiki/CachesExplained
适用性
缓存及其有用。blala
Cache
类似于一个ConcurrentMap
,但并不完全一样。最基础的不同是ConcurrentMap
保存所有的元素知道它们被明确删除。而Cache
是会安排配置自动删除元素的以便于限制内存占用。在某些用例中,LoadingCache
即便不使用它的删除功能,任然可以利用它的自动加载功能。
加载数据
使用CacheLoader加载
LoadingCache
是一个附加着CacheLoader
的Cache
。
在创建LoadingCache
的时候传入CacheLoader
的实现,主要是实现V load(K key) throws Exception
方法,既怎么去加载一个key的值。
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
...
try {
return graphs.get(key);
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}
当我们通过get(K)
去查询时,会返回已经被缓存的值,或者使用CacheLoader
去 “原子性” 的加载一个新的值到缓存。因为load
方法本身会抛异常,所以get
方法也会抛对应的ExecutionException
异常表示load失败。或者直接使用getUncheked(k)
方法,所有异常都会被包裹成UncheckedException
丢出。
当使用loadAll
加载大量key时,如果遇到key加载,loadAll
是迭代去取值的,如果需要更有效率的取法,自己实现。
使用Callable加载
CacheLoader
是创建Cache
的时候的默认加载器,get(K,Callable<V>)
则是get的时候单独传入一个针对本次调用的加载逻辑。
Cache<Key, Value> 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<Value>() {
@Override
public Value call() throws AnyException {
return doThingsTheHardWay(key);
}
});
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}
直接手动插入
cache.put(key, value)
删除数据
根据容量删除
如果对Cache
的大小在意。创建的时候配置CacheBuilder.maxmumSize(long)
配置缓存大小。
当"接近容量"大小的时候会自动删除元素。元素也可以有Weight比重,但注意是在元素创建的时候计算出来静态放置的。后面不会改变。
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);
}
});
根据时间删除
2种策略,见名知意:
expireAfterAccess(long, TimeUnit)
expireAfterWrite(long, TimeUnit)
guava提供测试工具模拟时间流逝Ticker
和CacheBuilder.ticker(Ticker)
根据引用删除
最好使用配置明显的Cache size去解决内存大小,用折后在哪个策略,是依赖于JVM,而且会导致缓存使用==
去代替'compare`在内部逻辑中的比较。因为jvm的判断是基于引用的。
手动删除
Cache.invalidate(key)
Cache.invalidateAll(keys)
Cache.invalidateAll()
删除的时候得到通知Removal Listeners
不要进行耗性能和超时的动作,如果需要,考虑RemovalListeners.asynchronous(RemovalListener,Executor)
包裹。
删除数据的时候到底发生了什么?
延迟删除,只在读和写的时候才真正去做删除维护,所以remove listener才不能过长,因为是同步执行的,会阻塞对应的操作。需要频繁删除维护,需要用户自己去调用cleanUp()
.
刷新数据
LoadingCahe.refresh(K)
异步调用底层的CacheLoader.reload(K,V)
刷新值。如果异常,保留原来的值,只打印日志。
自动定时刷新可以在CacheBuilder中配置CacheBuilder.refreshAfterWrite(long,TimeUnit)
.
值得注意的是refreshAfterWrite
仅仅只在到时后,标记某个key可以被更新了,只有在查询来临的时候才会真正触发更新。在同时有定时过期和定时更新的Cache
上,只有在标记的key在更新时间到后,查询没有来临,更新未触发。才会被过期策略考虑入删除。
注意Guava提供的demo,CacheLoader.reload
的实现不仅可以判断哪些可以key应该被更新,而且是异步的。
// Some keys don't need refreshing, and we want refreshes to be done asynchronously.
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<Graph> reload(final Key key, Graph prevGraph) {
if (neverNeedsRefresh(key)) {
return Futures.immediateFuture(prevGraph);
} else {
// asynchronous!
ListenableFutureTask<Graph> task = ListenableFutureTask.create(new Callable<Graph>() {
public Graph call() {
return getGraphFromDatabase(key);
}
});
executor.execute(task);
return task;
}
}
});
统计功能
CacheBuilder.recordStats
开启缓存的辅助统计功能。
Cache.stats
方法可以返回一个CacheStats
对象,包含很多统计信息。
网友评论