最近正在重构公司平台中的一些组件,有一些涉及到缓存的处理。发现Guava的缓存还是非常不错的,所以把CachesExplained翻译了一下,供大家一起参考。
首先,看一下使用范例:
- LoadingCache<Key,Graph> graphs =CacheBuilder.newBuilder()
- .maximumSize(1000)
- .expireAfterWrite(10,TimeUnit.MINUTES)
- .removalListener(MY_LISTENER)
- .build(
- newCacheLoader<Key,Graph>(){
- publicGraph load(Key key)throwsAnyException{
- return createExpensiveGraph(key);
- }
- });
适用性
- 你愿意花费一些内存来换取性能提升;
- 你预测到某些键会多次进行查询;
- 你的缓存数据不超过内存(Guava缓存是单个应用中的本地缓存。它不会将数据存储到文件中,或者外部服务器。如果不适合你,可以考虑一下 Memcached)。
加载
From a CacheLoader
LoadingCache 缓存是通过一个CacheLoader来构建缓存。创建一个CacheLoader仅需要实现V load(K key) throws Exception方法即可。下面的范例就是如何创建一个LoadingCache:
- LoadingCache<Key,Graph> graphs =CacheBuilder.newBuilder()
- .maximumSize(1000)
- .build(
- newCacheLoader<Key,Graph>(){
- publicGraph load(Key key)throwsAnyException{
- return createExpensiveGraph(key);
- }
- });
- ...
- try{
- return graphs.get(key);
- }catch(ExecutionException e){
- thrownewOtherException(e.getCause());
- }
通过方法get(K)可以对LoadingCache进行查询。该方法要不返回已缓存的值,要不通过CacheLoader来自动加载相应的值到缓存中。这里需要注意的是:CacheLoader可能会抛出Exception,LoaderCache.get(K)则可能会抛出ExecutionException。假如你定义的CacheLoader没有声明检查型异常,那么可以通过调用getUnchecked(K)来获取缓存值;但是一旦当CacheLoader中声明了检查型异常,则不可以调用getUnchecked。
- LoadingCache<Key,Graph> graphs =CacheBuilder.newBuilder()
- .expireAfterAccess(10,TimeUnit.MINUTES)
- .build(
- newCacheLoader<Key,Graph>(){
- publicGraph load(Key key){// no checked exception
- return createExpensiveGraph(key);
- }
- });
- ...
- return graphs.getUnchecked(key);
批量查询可以使用getAll(Iterable<? extends K>)方法。缺省,getAll方法将循环每一个键调用CacheLoader.load方法获取缓存值。当缓存对象的批量获取比单独获取更有效时,可以通过复写CacheLoader.loadAll方法实现缓存对象的加载。此时当调用getAll(Iterable)方法时性能也会提升。
From a Callable
- 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,newCallable<Value>(){
- @Override
- publicValue call()throwsAnyException{
- return doThingsTheHardWay(key);
- }
- });
- }catch(ExecutionException e){
- thrownewOtherException(e.getCause());
- }
直接插入
缓存回收
基于容量回收策略
- LoadingCache<Key,Graph> graphs =CacheBuilder.newBuilder()
- .maximumWeight(100000)
- .weigher(
- newWeigher<Key,Graph>(){
- publicint weigh(Key k,Graph g){
- return g.vertices().size();
- }
- })
- .build(
- newCacheLoader<Key,Graph>(){
- publicGraph load(Key key){// no checked exception
- return createExpensiveGraph(key);
- }
- });
基于时间回收策略
- expireAfterAccess(long, TimeUnit) 当缓存项在指定的时间段内没有被读或写就会被回收。这种回收策略类似于基于容量回收策略;
- expireAfterWrite(long, TimeUnit) 当缓存项在指定的时间段内没有更新就会被回收。如果我们认为缓存数据在一段时间后数据不再可用,那么可以使用该种策略。
测试定时回收
基于引用回收策略
- CacheBuilder.weakKeys() 使用弱引用存储键。当没有(强或软)引用到该键时,相应的缓存项将可以被垃圾回收。由于垃圾回收是依赖==进行判断,因此这样会导致整个缓存也会使用==来比较键的相等性,而不是使用equals();
- CacheBuilder.weakValues() 使用弱引用存储缓存值。当没有(强或软)引用到该缓存项时,将可以被垃圾回收。由于垃圾回收是依赖==进行判断,因此这样会导致整个缓存也会使用==来比较缓存值的相等性,而不是使用equals();
- CacheBuilder.softValues() 使用软引用存储缓存值。当响应需要时,软引用才会被垃圾回收通过最少使用原则回收掉。由于使用软引用造成性能上的影响,我们强烈建议使用可被预言的maximum cache size的策略来代替。同样使用softValues()缓存值的比较也是使用==,而不是equals()。
显示移除
- 使用Cache.invalidate(key)单个移除;
- 使用Cache.invalidteAll(keys)批量移除;
- 使用Cache.invalidateAll()移除全部。
移除监听器
- CacheLoader<Key,DatabaseConnection> loader = newCacheLoader<Key,DatabaseConnection>(){
- publicDatabaseConnection load(Key key)throwsException{
- return openConnection(key);
- }
- };
- RemovalListener<Key,DatabaseConnection> removalListener = newRemovalListener<Key,DatabaseConnection>(){
- publicvoid onRemoval(RemovalNotification<Key,DatabaseConnection> removal){
- DatabaseConnection conn = removal.getValue();
- conn.close();// tear down properly
- }
- };
- returnCacheBuilder.newBuilder()
- .expireAfterWrite(2,TimeUnit.MINUTES)
- .removalListener(removalListener)
- .build(loader);
Cleanup什么时候会发生?
刷新
- // 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(
- newCacheLoader<Key,Graph>(){
- publicGraph load(Key key){// no checked exception
- return getGraphFromDatabase(key);
- }
- publicListenableFuture<Graph> reload(finalKey key,Graph prevGraph){
- if(neverNeedsRefresh(key)){
- returnFutures.immediateFuture(prevGraph);
- }else{
- // asynchronous!
- ListenableFutureTask<Graph> task =ListenableFutureTask.create(newCallable<Graph>(){
- publicGraph call(){
- return getGraphFromDatabase(key);
- }
- });
- executor.execute(task);
- return task;
- }
- }
- });
其它特性
统计
- hitRate(),提供了请求命中率;
- averageLoadZPenalty(),加载平均耗时,单位为纳秒;
- evictionCount(),缓存移除总数
asMap
- cache.asMap()包含了当前缓存中所加载的所有条目。也就是说,cache.asMap().keySet包含了当前缓存中已经加载的键。
- asMap().get(key)等同于cache.getIfPresent(key),不会调用到加载方法。这一点和Map的语义是一致的。
- 缓存的读写操作(包含了Cache.asMap().get(Object)和Cache.asMap().put(K,V))都会重置缓存的访问时间,但containsKey(Object)方法不会重置访问时间,Cache.asMap()方法也不会。因此,通过cache.entrySet()迭代访问是不会重置缓存条目的访问时间。