Guava 記憶體快取的使用
一、概述
guava⽬前有三種重新整理本地快取的機制:
- expireAfterAccess:當快取項在指定的時間段內沒有被讀或寫就會被回收。
- expireAfterWrite:當快取項在指定的時間段內沒有更新就會被回收。-- 常用
- refreshAfterWrite:當快取項上一次更新操作之後的多久會被重新整理。 -- 常用
二、原理
expireAfterWrite 為了避免快取雪崩,guava 會限制只有一個載入操作時進行加鎖,其他請求必須阻塞等待這個載入操作完成。而且,在載入完成之後,其他請求的執行緒會逐一獲得鎖,去判斷是否已被載入完成,每個執行緒必須輪流地走一個“獲得鎖,獲得值,釋放鎖”的過程,這樣效能會有一些損耗。
refreshAfterWrite 當快取項上一次更新操作之後的多久會被重新整理。在 refresh 的過程中,guava 會限制只有一個載入操作時進行加鎖,而其他查詢先返回舊值,這樣能有效減少等待和鎖爭用,所以 refreshAfterWrite 會比 expireAfterWrite 效能好。
Load 加鎖是從從 expire 到 load 到新值為⽌,⽽ refresh->reload 的過程,⼀旦 get 發現需要 refresh,會先判斷是否有 loading,再去獲得鎖,然後釋放鎖之後再去reload,阻塞的範圍只是 insertLoadingValueReference 的⼀個⼩物件的 new 和 set 操作,⼏乎可以忽略不計。
三、實踐
首先了解⼀個機制,guava 不會⾃動清除清除資料,只有在訪問時候再去判斷 expire。
設定合理的 expireAfterWrite 和 refreshAfterWrite 時間來保證快取不會被瞬間擊垮。根據合理的場景設定合理的引數。比如 expireAfterWrite=60 和 refreshAfterWrite=30,保證每隔 30 秒重新整理⼀次資料。但是超過 60 秒⼀直沒有訪問量,突然間訪問,還是會觸發 load 操作。
expireAfterWrite 是為了保證在很久沒有訪問量,⾸次訪問不再訪問舊值。⽐如已經間隔⼀天,首次訪問希望獲取最新值,而不是先獲取舊值再去重新整理新值,這種就可⽤通過設定expireAfterWrite來保證。
其實如果極端情況下,即新舊值基本不會變更的,直接不採⽤ expireAfterWrite,⽽直接採⽤ refreshAfterWrite 來執⾏ load 也是可以的,效能會更優。
public class CacheBuilderTest {
private static AtomicInteger atomicInteger = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
LoadingCache<String, String> loadingCache = CacheBuilder.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.refreshAfterWrite(30, TimeUnit.SECONDS)
.maximumSize(10)
.removalListener(new RemovalListener() {
@Override
public void onRemoval(RemovalNotification notification) {
System.out.println("key:" + notification.getKey() + ", remove!");
}
})
.recordStats()
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
System.out.println("key:" + key + ", load");
return "Hello Guava Cache " + key + "load";
}
@Override
public ListenableFuture<String> reload(String key, String oldValue) throws Exception {
ListenableFutureTask<String> listenableFutureTask = ListenableFutureTask.create(() -> {
System.out.println("key:" + key + ", reload");
return "Hello Guava Cache " + key + "reload" + atomicInteger.incrementAndGet();
});
CompletableFuture.runAsync(listenableFutureTask);
return listenableFutureTask;
}
});
while (true) {
String lucky = loadingCache.getUnchecked("lucky");
System.out.println(lucky);
CacheStats stats = loadingCache.stats();
System.out.println("快取命中率:" + stats.hitCount());
System.out.println("載入新值的平均時間,單位為毫秒:" + stats.averageLoadPenalty() / 10000);
System.out.println("快取項被回收的總數,不包括顯式清除:" + stats.evictionCount());
Thread.sleep(65000);
System.out.println("---------------------------------------------------------");
}
}
}