日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Guava自加載緩存LoadingCache

作者:線粒體會放屁 更新時間: 2024-07-14 編程語言

LoadingCache基礎

LoadingCacheDemo

LoadingCache是Guava中一個提供自動加載功能的緩存接口。它允許咱們通過一個CacheLoader來指定如何加載緩存。這就意味著,當咱們嘗試從緩存中讀取一個值,如果這個值不存在,LoadingCache就會自動調用預定義的加載機制去獲取數據,然后將其加入到緩存中。

public class LoadingCacheDemo {
    public static void main(String[] args) throws ExecutionException {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(100) // 最大緩存項數
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        return "Hello, " + key; // 定義緩存加載的方式
                    }
                });

        // 使用緩存
        printCacheContents(cache);
        System.out.println(cache.getUnchecked("hello")); // 輸出: HELLO
        System.out.println(cache.getUnchecked("guava")); // 輸出: GUAVA
        printCacheContents(cache);
    }

    public static void printCacheContents(LoadingCache<String, String> cache) {
        Map<String, String> cacheMap = cache.asMap();
        if (cacheMap.isEmpty()) {
            System.out.println("現在緩存里還沒有內容====");
        } else {
            System.out.println("緩存內容如下內容====");
            for (Map.Entry<String, String> entry : cacheMap.entrySet()) {
                System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
            }
        }
    }
}

?運行內容如下

直接打印緩存內容
現在緩存里還沒有內容====
Hello, hello
Hello, guava
使用后打印緩存內容
緩存內容如下內容====
Key: hello, Value: Hello, hello
Key: guava, Value: Hello, guava

解釋:在這個例子里,創建了一個簡單的LoadingCache實例。通過getUnchecked方法獲取一個緩存項時,如果這個項不存在,CacheLoader會自動加載一個新值。在這里是簡單地返回一個字符串。

LoadingCache詳解

Google Guava 緩存工具使用詳解_google loadingcache-CSDN博客

CacheBuilder類

CacheBuilder類是用于創建Guava緩存的構建器。可以使用該類的newBuilder()方法創建一個構建器實例,并通過一系列方法設置緩存的屬性,例如最大容量、過期時間等。最后可以通過build()方法構建一個Cache實例。
部分方法詳解:

?

initialCapacity:設置緩存的初始容量

這個方法將通過一個整數值設置緩存的初始大小。它是一個可選的方法,如果沒有指定,緩存將采用默認的初始容量。

concurrencyLevel:設置并發級別

用于估計同時寫入的線程數。這個方法將通過一個整數值設置并發級別,用于內部數據結構的調整,以提高并發寫入的性能。它是一個可選的方法,缺省值為 4。

maximumSize:設置緩存的最大容量

這個方法將通過一個 long 類型的值設置緩存的最大容量。當緩存的條目數達到這個容量時,會觸發緩存清除策略來移除一些條目以騰出空間。它是一個可選的方法,如果沒有指定最大容量,緩存將不會有大小限制。

maximumWeight:設置緩存的最大權重

這個方法將通過一個 long 類型的值設置緩存的最大權重。權重可以根據緩存條目的大小計算,通常用于緩存對象大小不同的場景。當緩存的總權重達到這個值時,會觸發緩存清除策略來移除一些條目以騰出空間。它是一個可選的方法,如果沒有指定最大權重,緩存將不會有權重限制。

weigher:設置緩存的權重計算器

這個方法將通過一個實現了 Weigher 接口的對象設置緩存的權重計算器。通過權重計算器,可以根據緩存條目的鍵和值來計算它們的權重,以便在達到最大權重時觸發緩存清除策略。它是一個可選的方法,如果沒有設置權重計算器,緩存將不會有權重限制。

expireAfterWrite:設置寫入后過期時間

這個方法通過一個 java.time.Duration 對象設置緩存條目的寫入后過期時間。過期時間從最后一次寫入條目開始計算。一旦超過指定的時間,條目將被認為是過期的并被清除。這是一個可選的方法,如果沒有指定過期時間,條目將不會主動過期。

expireAfterAccess:設置訪問后過期時間

這個方法通過一個 java.time.Duration 對象設置緩存條目的訪問后過期時間。過期時間從最后一次訪問條目開始計算。一旦超過指定的時間,條目將被認為是過期的并被清除。這是一個可選的方法,如果沒有指定過期時間,條目將不會主動過期。

refreshAfterWrite:設置寫入后自動刷新時間

這個方法通過一個 java.time.Duration 對象設置緩存條目的自動刷新時間。自動刷新時間從最后一次寫入條目開始計算。一旦超過指定的時間,當條目被訪問時,緩存將自動刷新該條目,即會調用 CacheLoader 的 load 方法重新加載該條目。這是一個可選的方法,如果沒有設置自動刷新時間,條目將不會自動刷新。

recordStats():開啟緩存統計信息記錄

這個方法用于開啟緩存的統計信息記錄功能。一旦開啟,可以通過 Cache.stats() 方法獲取緩存的統計信息,如命中率、加載次數、平均加載時間等。這是一個可選的方法,如果不開啟統計信息記錄,將無法獲取緩存的統計信息。

注意事項:

maximumSize與maximumWeight不能同時設置

設置maximumWeight時必須設置weigher

當緩存失效后,refreshAfterWrite設置的寫入后自動刷新時間不會再有用

注意:expireAfterWrite、expireAfterAccess、refreshAfterWrite三個值的使用

開啟recordStats后,才進行統計

CacheLoader類


CacheLoader 可以被視為一種從存儲系統(如磁盤、數據庫或遠程節點)中加載數據的方法。CacheLoader 通常搭配refreshAfterWrite使用,在寫入指定的時間周期后會調用CacheLoader 的load方法來獲取并刷新為新值。

load 方法在以下情況下會被觸發調用:

  1. 當設置了refreshAfterWrite(寫入后自動刷新時間),達到自動刷新時間時,會調用reload 方法來重新加載該鍵的值,如果reload 方法違背重寫,reload 的默認實現會調用 load 方法來重新加載該鍵的值。
  2. 調用 Cache.get(key) 方法獲取緩存中指定鍵的值時,如果該鍵的值不存在,則會調用 load 方法來加載該鍵的值。
  3. 調用 Cache.get(key, callable) 方法獲取緩存中指定鍵的值時,如果該鍵的值存在,則直接返回;如果該鍵的值不存在,則會調用 callable 參數指定的回調函數來計算并加載該鍵的值。
  4. 調用 Cache.getUnchecked(key) 方法獲取緩存中指定鍵的值時,無論該鍵的值存在與否,都會調用 load 方法來加載該鍵的值。

需要注意的是:

當調用 load 方法加載緩存值時,可能會發生 IO 操作或其他耗時操作,因此建議在加載操作中使用異步方式來避免阻塞主線程。另外,加載操作的實現要考慮緩存的一致性和并發性,避免多個線程同時加載同一個鍵的值。

關于reload 方法:

reload 方法用于異步地刷新緩存值。它接收兩個參數:key 和 oldValue,分別表示需要刷新的鍵以及該鍵之前對應的舊值。實現者需要通過在 reload 方法體內通過新的數據源或其他方式來重新加載和構建緩存數據,并在加載完成之后返回新的緩存值即可。通過異步地來更新緩存數據,讓余下的請求可以同時從舊值中訪問數據。

相對于 load 方法,reload 方法多了一個參數 oldValue。這是因為 reload 方法在執行時,緩存項可能已經過期了,它需要使用舊值來保證其它線程可以繼續獲得前一緩存項的值,避免出現緩存穿透的情況。同時,reload 方法需要保證異步刷新緩存的情況下線程安全。

CacheStats類

CacheStats 對象提供了諸如緩存命中率、加載緩存項數、緩存項回收數等統計信息的訪問。

它可以通過 Cache.stats() 方法來獲取,從而方便開發者監控緩存狀態。

主要屬性:

RemovalListener類

RemovalListener 用于在緩存中某個值被移除時執行相應的回調操作。

可以使用 CacheBuilder.removalListener() 方法為緩存設置 RemovalListener。

RemovalListener 的使用:

創建一個實現 RemovalListener 接口的類,實現 onRemoval 方法。這個方法會在緩存項被移除時被調用,接受兩個參數: key 和 value。key 是被移除的緩存項的鍵,value 是被移除的緩存項的值。你可以根據需要在 onRemoval 方法中實現自定義的邏輯。

使用 CacheBuilder 的 removalListener 方法,將創建的 RemovalListener 對象傳遞給它。

緩存項被移除時,onRemoval 方法會自動被調用,方法會傳入一個RemovalNotification 類型的參數,里面包含相應的 key 和 value等信息。你可以在這個方法中執行自定義的業務邏輯,例如日志記錄、資源清理等操作。

RemovalNotification:

RemovalNotification 是 Guava 中用于表示緩存項被移除的通知的類。當在 Guava Cache 中注冊了 RemovalListener 后,RemovalNotification 對象會在緩存項被移除時傳遞給 RemovalListener 的 onRemoval 方法。

RemovalNotification 包含了有關被移除緩存項的一些重要信息,例如鍵、值以及移除原因。下面是 RemovalNotification 類中常用的屬性和方法:

getKey():獲取被移除的緩存項的鍵。

getValue():獲取被移除的緩存項的值。

getCause():獲取移除原因,它是一個枚舉類型RemovalCause,表示緩存項被移除的原因。

RemovalCause的可選值:

EXPLICIT:條目被顯式刪除,例如通過調用 Cache.invalidate(key) 方法。
REPLACED:條目被替換,例如通過調用 Cache.put(key, value) 方法重復放入相同的鍵。
EXPIRED:緩存條目由于達到了指定的過期時間而被移除。
SIZE:緩存條目由于超過了指定的大小限制而被移除。
COLLECTED:緩存條目被垃圾回收移除。這是在啟用了緩存值的弱引用或軟引用時發生的。
使用 RemovalNotification 可以讓你在緩存項被移除時獲取相關信息,并根據移除原因采取適當的處理措施。例如,你可以根據移除原因記錄日志、執行清理操作、發送通知等。這樣能夠增強緩存的功能和可觀察性。

注意事項:

RemovalListener 的 onRemoval 方法會在移除操作發生時同步調用,因此請確保不要在此方法中做耗時的操作,以免阻塞緩存的性能。
如果在緩存移除過程中拋出任何異常,它將被捕獲并記錄,不會影響緩存的正常運行。
需要注意的是,RemovalListener 只會在通過 Cache 的操作(如 invalidate、invalidateAll、put 進行替換)觸發移除時被調用,并不會在緩存項因為過期失效而自動移除時被調用。
使用 RemovalListener 可以方便地在緩存項被移除時執行一些自定義的操作,例如清理相關資源、更新其他緩存或發送通知等。根據實際需要,合理利用 RemovalListener 可以增強緩存的功能和靈活性。

實際使用

自動加載和刷新機制

對于LoadingCache,當請求某個鍵的值時,如果這個值不存在或者需要刷新,LoadingCache會自動調用CacheLoader去加載或刷新數據。

public class AutoRefreshCache {
    public static void main(String[] args) throws Exception {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .refreshAfterWrite(1, TimeUnit.MINUTES) // 設置1分鐘后刷新
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) {
                        return fetchDataFromDatabase(key); // 模擬從數據庫加載數據
                    }
                });

        System.out.println("First load: " + cache.get("key")); // 第一次加載


        System.out.println("Waiting for 2 minute...");
        Thread.sleep(TimeUnit.MINUTES.toMillis(2));

        // 2分鐘后,嘗試再次獲取,將觸發刷新操作
        System.out.println("Second load after refresh: " + cache.get("key"));
    }

    private static String fetchDataFromDatabase(String key) {
        // 模擬數據庫操作
        return System.currentTimeMillis() + key;
    }
}

運行結果

First load: 1720669175847key
Waiting for 2 minute...
Second load after refresh: 1720669295853key

異常處理

public class ExceptionHandlingCache {
    public static void main(String[] args) throws Exception {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        if (!checkKey(key)) {
                            throw new Exception("Loading error");
                        }
                        return "Data for " + key;
                    }
                });
      getByKey(cache,"key");
       getByKey(cache,"errorKey");

    }


    public static String getByKey(LoadingCache<String, String> cache, String key) {
        try {
            String value = cache.get(key);
            System.out.println("Value for key \"" + key + "\": " + value);
            return value;
        } catch (Exception e) {
            System.out.println("Error during cache load for key \"" + key + "\": " + e.getMessage());
            return null;
        }
    }
    public static boolean checkKey(String key) {
        if ("errorKey".equals(key)){
            return false;
        }
        return true;
    }
}

輸出結果

Value for key "key": Data for key
Error during cache load for key "errorKey": java.lang.Exception: Loading error

統計和監聽功能

LoadingCache還提供了緩存統計和監聽功能,這對于監控緩存性能和行為非常有用。

public class CacheMonitoring {
    public static void main(String[] args) throws Exception {
        // 創建Cache實例
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .initialCapacity(2)                                         // 設置初始容量
                .concurrencyLevel(4)                                        // 設置并發級別
                .maximumSize(5)                                             // 設置最大容量
//                .maximumWeight(1000)                                        // 設置最大權重
//                .weigher((Weigher<String, String>) (k, v) -> v.length())    // 設置權重計算器
                .expireAfterWrite(10, TimeUnit.SECONDS)                    // 寫入后x秒過期
                .expireAfterAccess(20, TimeUnit.SECONDS)                  // 訪問x秒過期
                .refreshAfterWrite(5, TimeUnit.SECONDS)                  // 寫入后自動刷新,
                .recordStats()                                              // 開啟統計信息記錄
                .removalListener(notification -> {                          // 設置移除監聽
                    // 緩存Key被移除時觸發
                    String cause = "";
                    if (RemovalCause.EXPLICIT.equals(notification.getCause())) {
                        cause = "被顯式移除";
                    } else if (RemovalCause.REPLACED.equals(notification.getCause())) {
                        cause = "被替換";
                    } else if (RemovalCause.EXPIRED.equals(notification.getCause())) {
                        cause = "被過期移除";
                    } else if (RemovalCause.SIZE.equals(notification.getCause())) {
                        cause = "被緩存條數超上限移除";
                    } else if (RemovalCause.COLLECTED.equals(notification.getCause())) {
                        cause = "被垃圾回收移除";
                    }
                    System.out.println(getCurrentFormattedTime() + " Key: " + notification.getKey() + " 移除了, 移除原因: " + cause);
                })
                .build(new CacheLoader<String, String>() {                  // 設置緩存重新加載邏輯
                    @Override
                    public String load(String key) {
                        // 重新加載指定Key的值
                        String newValue = "value" + (int)Math.random()*100;
                        System.out.println(getCurrentFormattedTime() + " Key: " + key + " 重新加載,新value:" + newValue);
                        return newValue;
                    }
                });
        // 將數據放入緩存
        cache.put("key0", "value0");
        cache.invalidate("key0");
        cache.put("key1", "value1");
        cache.put("key1", "value11");
        cache.put("key2", "value22");
        cache.put("key3", "value3");
        cache.put("key4", "value4");
        cache.put("key5", "value5");
        cache.put("key6", "value6");
        cache.put("key7", "value7");
        cache.put("key8", "value8");

        while (true) {
            // 獲取數據
            System.out.println(getCurrentFormattedTime() + " get key1 value: " + cache.get("key1"));
            // 統計信息
            System.out.println(getCurrentFormattedTime() + " get stats: " + cache.stats());
            Thread.sleep(3000);
        }
    }
    public static String getCurrentFormattedTime() {
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return now.format(formatter);
    }
}

打印日志如下

2024-07-11 12:09:01 Key: key0 移除了, 移除原因: 被顯式移除
2024-07-11 12:09:01 Key: key1 移除了, 移除原因: 被替換
2024-07-11 12:09:01 Key: key1 移除了, 移除原因: 被緩存條數超上限移除
2024-07-11 12:09:01 Key: key2 移除了, 移除原因: 被緩存條數超上限移除
2024-07-11 12:09:01 Key: key3 移除了, 移除原因: 被緩存條數超上限移除
2024-07-11 12:09:01 Key: key1 重新加載,新value:value0
2024-07-11 12:09:01 Key: key4 移除了, 移除原因: 被緩存條數超上限移除
2024-07-11 12:09:01 get key1 value: value0
2024-07-11 12:09:01 get stats: CacheStats{hitCount=0, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=951083, evictionCount=4}
2024-07-11 12:09:04 get key1 value: value0
2024-07-11 12:09:04 get stats: CacheStats{hitCount=1, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=951083, evictionCount=4}
2024-07-11 12:09:07 Key: key1 重新加載,新value:value0
2024-07-11 12:09:07 Key: key1 移除了, 移除原因: 被替換

原文鏈接:https://blog.csdn.net/weixin_43851023/article/details/140346730

  • 上一篇:沒有了
  • 下一篇:沒有了
欄目分類
最近更新