记录工作中使用到的异步刷新缓存方法。
Guava cache是Java项目中很常用的本地缓存。CacheLoader是Guava cache常用的缓存加载方式。
1、定时过期
LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(1, TimeUnit.SECONDS) //根据写入时间过期
.build(new CacheLoader<String, Object>() {
@Override public Object load(String key) {
return generateValueByKey(key);
}
});
按照expireAfterWrite方式来让已写入的缓存过期。这种方式存在一个问题:当高并发同时get同一个缓存值,而此时该缓存过期,就会有一个线程进入load方法,而其他线程则阻塞等待,直到缓存值被生成。虽然这样避免了缓存击穿的危险,但还是有大量线程阻塞等待生成缓存值。
guava源码:
V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
try {
if (this.count != 0) {
LocalCache.ReferenceEntry<K, V> e = this.getEntry(key, hash);
if (e != null) {
long now = this.map.ticker.read();
V value = this.getLiveValue(e, now);
if (value != null) {
this.recordRead(e, now);
this.statsCounter.recordHits(1);
// 缓存值存在,判断是否需要刷新,并获取新的缓存值
Object var17 = this.scheduleRefresh(e, key, hash, value, now, loader);
return var17;
}
LocalCache.ValueReference<K, V> valueReference = e.getValueReference();
if (valueReference.isLoading()) {
//发现该缓存值正在被加载,则等待直到缓存值被生成
Object var9 = this.waitForLoadingValue(e, key, valueReference);
return var9;
}
}
}
Object var15 = this.lockedGetOrLoad(key, hash, loader);
//第一次get,会进去lockedGetOrLoad方法去加载缓存值
return var15;
} catch(Exception e){...}
2、定时刷新
LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.refreshAfterWrite(1, TimeUnit.SECONDS) //根据写入时间去刷新缓存值
.build(new CacheLoader<String, Object>() {
@Override public Object load(String key) {
return generateValueByKey(key);
}
});
当到达刷新时间是,会有一个用户线程去刷新缓存值,其他线程仍获取旧值。虽然这样每个key只会阻塞一个用户线程,但高并发请求不同key时,仍然会造成大量线程阻塞,并对数据库压力过大。
guava源码:
V scheduleRefresh(LocalCache.ReferenceEntry<K, V> entry, K key, int hash, V oldValue, long now, CacheLoader<? super K, V> loader) {
if (this.map.refreshes() && now - entry.getWriteTime() > this.map.refreshNanos) {
V newValue = this.refresh(key, hash, loader);
if (newValue != null) {
return newValue;
}
}
return oldValue; //当newValue为null,就返回oldValue
}
V refresh(K key, int hash, CacheLoader<? super K, V> loader) {
LocalCache.LoadingValueReference<K, V> loadingValueReference = this.insertLoadingValueReference(key, hash);
if (loadingValueReference == null) {
return null; //若正在加载该缓存值,就返回null
} else {
//重新加载缓存值
ListenableFuture<V> result = this.loadAsync(key, hash, loadingValueReference, loader);
// 默认result是调用load方法,并将其结果构造成ListenableFuture返回,所以是同步加载调用load
if (result.isDone()) {
try {
return result.get();
} catch (Throwable var7) {
}
}
return null;
}
}
3、异步刷新
ExecutorService executorService = Executors.newFixedThreadPool(10);
LoadingCache<String, Object> cache = CacheBuilder.newBuilder().maximumSize(100)
.refreshAfterWrite(1, TimeUnit.SECONDS)
.build(new CacheLoader<String, Object>() {
@Override public Object load(String key) {
return generateValueByKey(key);
}
@Override public ListenableFuture<Object> reload(String key, Object oldValue) {
ListenableFutureTask<Object> task =
new ListenableFutureTask<Object>(new Callable<Object>() {
@Override public Object call() {
return generateValueByKey(key);
}
});
executorService.submit(task);
return task;
}
});
上面代码中是使用executorService线程池去异步的刷新缓存值。