Guava Cache 官方文档直通车:github.com/google/guav…
近日在使用 Guava Cache 做缓存池时产生了一些疑问,例如过期时机是什么时候?遂决定深入了解这玩意。
使用示例:
/**
* 把 RestHighLevelClient 池化,避免每次都创建连接
*/
LoadingCache<EngineDatasourceQuery, RestHighLevelClientHolder> externalEsClientCache = CacheBuilder
.newBuilder()
.expireAfterAccess(4, TimeUnit.HOURS)
.removalListener((RemovalListener<EngineDatasourceQuery, RestHighLevelClientHolder>) n -> {
log.info("close es client, engineId: {}", n.getValue().getEngineId());
RestHighLevelClientFactory.close(n.getValue().getClient());
})
.build(new CacheLoader<EngineDatasourceQuery, RestHighLevelClientHolder>() {
@Override
public RestHighLevelClientHolder load(EngineDatasourceQuery engineDatasourceQuery) {
int engineId = engineDatasourceQuery.getEngineId();
KpiDatasource datasource = engineDatasourceQuery.getDatasource();
log.info("load datasource, engineId: {}, code: {}", engineId, datasource.getUnicode());
RestHighLevelClient restHighLevelClient = RestHighLevelClientFactory.create(
new String[]{datasource.getUrl()},
StringUtils.defaultString(datasource.getUserName()),
StringUtils.defaultString(datasource.getPassword()));
return new RestHighLevelClientHolder(engineId, datasource.getUnicode(), restHighLevelClient);
}
});
-
刷新数据,LoadingCache.refresh(K),刷新指定key的值,刷新过程内部是异步操作(当前线程仍然会阻塞直到刷新完成),在刷新完成前拿到的还是旧值。in contrast to eviction, which forces retrievals to wait until the value is loaded anew. 可以通过重写 CacheLoader.reload(K, V) 方法来达到刷新目的,该方法可以在刷新时根据旧值计算新值,下面贴出官方示例:
// 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; } } });
-
手动的删除数据
- 删除单个 key,使用 Cache.invalidate(key)
- 批量删除 Key,使用 Cache.invalidateAll(keys)
- 删除全部 key,时候用 Cache.invalidateAll()
-
监听数据删除,实现接口 RemovalListener,默认该监听器的操作时同步的,如果该操作比较慢,可以使操作异步化,使用 RemovalListeners.asynchronous(RemovalListener, Executor) 来装饰 RemovalListener
-
执行删除过期元素的操作并不是实时的,也就是说并不是元素一过期就从缓存中去掉,下面贴一下原因:
Caches built with CacheBuilder do not perform cleanup and evict values "automatically," or instantly after a value expires, or anything of the sort. Instead, it performs small amounts of maintenance during write operations, or during occasional read operations if writes are rare.
The reason for this is as follows: if we wanted to perform Cache maintenance continuously, we would need to create a thread, and its operations would be competing with user operations for shared locks. Additionally, some environments restrict the creation of threads, which would make CacheBuilder unusable in that environment.
Instead, we put the choice in your hands. If your cache is high-throughput, then you don't have to worry about performing cache maintenance to clean up expired entries and the like. If your cache does writes only rarely and you don't want cleanup to block cache reads, you may wish to create your own maintenance thread that calls Cache.cleanUp() at regular intervals.
所以过期数据不会立即调用 RemovalListener 的监听方法,只有固定时机才会执行真实的删除缓存操作,这时才会真的调用 RemovalListener 的监听方法,甚至有可能一直不删除也就一直不调用,所以如果想要通过使用该方法来实现对删除数据进行一些即时的处理的话就要好好注意一下了。Guava Cache 也给出了一个补充的方法,就是由我们来手动执行 Cache.cleanUp() 方法,此方法会把过期的元素全部清除掉。
目前我尝试过的会立即触发删除元素并执行 RemovalListener 的监听方法的方式有:
- 手动删除数据
- 刷新数据
- cache 设置了 maximumSize,并且元素数量超过了 maximumSize
- 调用 Cache.cleanUp() 方法,并且存在过期元素
- 调用 cache.put() 或 cache.asMap().put() 方法,覆盖已有key的值
- 调用 cache.asMap().remove 方法
完~