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("---------------------------------------------------------");
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章