Guava Cache 介紹

来源:https://www.cnblogs.com/wuyongyin/archive/2023/11/12/17788830.html
-Advertisement-
Play Games

Guava 是 Google 提供的一套 Java 工具包,而 Guava Cache 是該工具包中提供的一套完善的 JVM 級別高併發緩存框架;本文主要介紹它的相關功能及基本使用,文中所使用到的軟體版本:Java 1.8.0_341、Guava 32.1.3-jre。 1、簡介 緩存在很多情況下非 ...


Guava 是 Google 提供的一套 Java 工具包,而 Guava Cache 是該工具包中提供的一套完善的 JVM 級別高併發緩存框架;本文主要介紹它的相關功能及基本使用,文中所使用到的軟體版本:Java 1.8.0_341、Guava 32.1.3-jre。

1、簡介

緩存在很多情況下非常有用。例如,當某個值的計算或檢索代價很高,並且你需要在特定輸入下多次使用該值時,就應該考慮使用緩存。

Guava Cache 與 ConcurrentMap 類似,但並不完全相同。最基本的區別在於,ConcurrentMap 會一直保存所有添加到其中的元素,直到顯式地將它們刪除。而 Guava Cache 通常會配置自動刪除條目,以限制其記憶體占用。在某些情況下,即使不刪除條目,LoadingCache 也很有用,因為它具有自動載入條目的功能。

通常情況下,Guava Cache 適用於以下情況:

  • 你願意花費一些記憶體來提高速度。
  • 你預期某些鍵有時會被多次查詢。
  • 你的緩存不需要存儲超過記憶體容量的數據。(Guava Cache 是局限於應用程式運行期間的本地緩存。它們不會將數據存儲在文件或外部伺服器上。如果這不符合你的需求,可以考慮使用像Memcached 這樣的工具。)

如果你的情況符合上述每一點,那麼 Guava Cache 可能適合你。

註意:如果你不需要緩存的特性,ConcurrentHashMap 在記憶體效率方面更高——但是使用任何 ConcurrentMap 幾乎不可能複製大多數 Guava Cache 的特性。

2、數據載入

使用 Guava Cache 時,首先要問自己一個問題:是否有合理的預設函數來載入或計算需緩存的數據?如果是這樣,應該使用 CacheLoader。如果沒有,或者需要覆蓋預設函數,但仍然希望具有原子的“如果不存在則計算並獲取”語義,你應該將一個 Callable 對象傳遞給 get 方法。可以直接使用 Cache.put 方法插入元素,但更推薦自動載入數據,因為這樣可以更容易地推斷所有緩存內容的一致性。

2.1、CacheLoader

LoadingCache 是一個帶有 CacheLoader 的緩存。創建 CacheLoader 很容易,只需要實現方法 V load(K key) throws Exception 即可。

LoadingCache<Long, String> loadingCache = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .build(new CacheLoader<Long, String>() {
            @Override
            public String load(Long key) throws Exception {
                //TODO: 根據業務載入數據
                return RandomStringUtils.randomAlphanumeric(10);
            }
        });
try {
    log.info(loadingCache.get(1L));
} catch (ExecutionException e) {
    e.printStackTrace();
}

LoadingCache 使用 get(K) 方法來獲取數據。該方法要麼返回已緩存的值,要麼使用 CacheLoader 來原子地載入一個新值到緩存中。由於 CacheLoader 可能會拋出異常,LoadingCache.get(K) 方法會拋出 ExecutionException 異常。(如果 CacheLoader 拋出未經檢查異常,get(K) 方法將拋出包裝異常 UncheckedExecutionException)。也可以選擇使用 getUnchecked(K) 方法,它將所有異常都包裝在UncheckedExecutionException 中,但如果底層的 CacheLoader 拋出已檢查異常,這可能導致意外行為。

LoadingCache<Long, String> loadingCache = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .build(new CacheLoader<Long, String>() {
            @Override
            public String load(Long key) {//拋出未檢查異常
                //TODO: 根據業務載入數據
                return RandomStringUtils.randomAlphanumeric(10);
            }
        });
log.info(loadingCache.getUnchecked(1L));

可以使用 getAll(Iterable<? extends K>)方法執行批量查詢。預設情況下,getAll 會為緩存中不存在的每個鍵單獨調用 CacheLoader.load 方法。當批量檢索比多個單獨查找更高效時,可以重寫CacheLoader.loadAll 以利用此功能。getAll(Iterable)的性能將相應提高。

2.2、Callable

所有 Guava Cache,無論是 LoadingCache 還是非 LoadingCache,都支持 get(K, Callable<V>) 方法。該方法返回與緩存中鍵相關聯的值,或者從指定的 Callable 計算它並將其添加到緩存中。在載入完成之前,與此緩存關聯的任何可觀察狀態都不會被修改。該方法為傳統的“如果有緩存,則返回;否則創建、緩存並返回”模式提供了一個簡單的替代方案。

Cache<Long, String> cache = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .build();
try {
    String s = cache.get(1L, new Callable<String>() {
        @Override
        public String call() throws Exception {
            //TODO: 根據業務載入數據
            return RandomStringUtils.randomAlphanumeric(10);
        }
    });
    log.info(s);
} catch (ExecutionException e) {
    e.printStackTrace();
}

2.3、直接插入

可以使用 cache.put(key, value) 方法直接將值插入到緩存中。這會覆蓋緩存中指定鍵的的任何先前條目。還可以使用 Cache.asMap() 視圖公開的任何 ConcurrentMap 方法更改緩存。請註意,asMap 視圖上的任何方法都不會自動將條目載入到緩存中。此外,視圖上的原子操作在緩存自動載入的範圍之外運行,因此在使用 CacheLoader 或 Callable 載入值的緩存中,始終應優先選擇 Cache.get(K, Callable<V>) 而不是 Cache.asMap().putIfAbsent()。

3、數據淘汰

現實情況是,我們幾乎肯定沒有足夠的記憶體來緩存所有可能的內容。你必須決定:什麼時候不值得保留緩存條目?Guava提供了三種數據淘汰方式:基於大小的淘汰、基於時間的淘汰和基於引用的淘汰。

3.1、基於容量的淘汰

 如果你的緩存不應該超過一定大小,只需使用 CacheBuilder.maximumSize(long) 。緩存將嘗試淘汰最近未被使用或使用頻率很低的條目。警告:在達到限制之前,緩存可能會淘汰條目,通常是在緩存大小接近限制時。

或者,如果不同的緩存條目具有不同的“權重”——例如,如果你的緩存值具有截然不同的記憶體占用,你可以使用 CacheBuilder.weigher(Weigher) 來指定一個權重函數,並使用CacheBuilder.maximumWeight(long) 來設置最大的緩存權重。除了與 maximumSize 相同的註意事項外,請註意權重是在條目創建時計算的,並且在此後是靜態的。

Cache<Long, String> cache = CacheBuilder.newBuilder()
        .maximumWeight(100000)
        .weigher(new Weigher<Long, String>() {
            @Override
            public int weigh(Long key, String value) {
                return value.getBytes().length;
            }
        }).build();

3.2、基於時間的淘汰

CacheBuilder 提供了兩種基於時間淘汰數據的方法:

expireAfterAccess(long, TimeUnit):在最後一次讀取或寫入條目後,僅在指定的持續時間過去後才淘汰條目。需要註意的是,條目的淘汰順序類似於基於大小的淘汰策略。
expireAfterWrite(long, TimeUnit):在條目創建或最近一次替換值之後,僅在指定的持續時間過去後才淘汰條目。如果緩存數據在一段時間後變得過時,這種方式可能是可取的。

3.3、基於引用的淘汰

Guava 允許通過對鍵或值使用弱引用和對值使用軟引用來設置緩存,從而利用垃圾回收來淘汰數據。

  • CacheBuilder.weakKeys() 使用弱引用來存儲鍵。這意味著當鍵沒有其他(強或軟)引用時,條目可以被垃圾回收。由於垃圾回收僅依賴於記憶體地址相等性,這導致整個緩存使用(==)來比較鍵,而不是equals()方法。
  • CacheBuilder.weakValues() 使用弱引用來存儲值。這意味著當值沒有其他(強或軟)引用時,條目可以被垃圾回收。由於垃圾回收僅依賴於記憶體地址相等性,這導致整個緩存使用(==)來比較值,而不是equals()方法。
  • CacheBuilder.softValues() 使用軟引用來存儲值。以軟引用方式引用的對象會根據記憶體需求以全局最近最少使用的方式進行垃圾回收。由於使用軟引用可能會影響性能,我們通常建議使用更可預測的 maximum cache size 替代。使用 softValues() 將導致值使用(==)來比較,而不是 equals() 方法。

3.4、顯式刪除

在任何時候,你可以顯式地使緩存條目失效,而不是等待條目被淘汰。可以通過以下方式實現:

單個失效:使用 Cache.invalidate(key)
批量失效:使用 Cache.invalidateAll(keys)
全部失效:使用 Cache.invalidateAll()

3.5、刪除監聽器

你可以為緩存指定一個刪除監聽器(RemovalListener),以在條目被移除時執行某些操作,通過 CacheBuilder.removalListener(RemovalListener) 方法指定刪除監聽器。RemovalListener 會接收到一個RemovalNotification 對象,其中包含了 RemovalCause、鍵和值的信息。

需要註意的是,任何由 RemovalListener 拋出的異常都會被記錄(使用 Logger 時)並被忽略。

Cache<Long, String> cache = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .removalListener(new RemovalListener<Long, String>() {
            @Override
            public void onRemoval(RemovalNotification<Long, String> notification) {
                log.info(notification.toString());
            }
        })
        .build();

警告:預設情況下,移除監聽器操作是同步執行的。由於緩存維護通常在正常緩存操作期間執行,因此移除監聽器可能會降低緩存的速度!如果需要移除監聽器,請使用RemovalListeners.asynchronous(RemovalListener, Executor) 方法來裝飾 RemovalListener,這樣可以以非同步的方式運行。

3.6、數據清理時機

使用 CacheBuilder 構建的緩存不會“自動”執行清理和逐出值,也不會在值過期後立即執行清理和逐出值,也不會執行任何類似操作。相反,它會在寫入操作期間執行少量維護,或者在偶爾的讀取操作期間(如果寫入很少)執行少量維護。

原因是:如果我們想要連續執行緩存維護,我們需要創建一個線程,它的操作將與用戶操作競爭共用鎖。此外,某些環境限制了線程的創建,這將使 CacheBuilder 在該環境中無法使用。

相反,我們將選擇權交到你手中。如果你的緩存是高吞吐量的,那麼你無需擔心執行緩存維護來清除過期條目等問題。如果你的緩存只偶爾進行寫操作,並且不想讓清理阻塞緩存讀取,你可以創建自己的維護線程,定期調用 Cache.cleanUp() 方法。

如果要為很少進行寫入操作的緩存安排定期緩存維護,請使用 ScheduledExecutorService

3.7、刷新

刷新(Refreshing)與淘汰(Eviction)並不完全相同。根據 LoadingCache.refresh(K) 的定義,刷新一個鍵會載入該鍵的新值,這可能是非同步的。在鍵正在刷新的過程中,舊值(如果存在)仍然會被返回,這與淘汰操作不同,淘汰操作會導致獲取操作等待直到新值載入完成。

如果在刷新過程中發生異常,舊值將被保留,異常將被記錄並忽略。

可以根據業務需要,重寫 CacheLoader 的 CacheLoader.reload(K, V) 方法來重新定義刷新操作;這允許你在計算新值時使用舊值。

LoadingCache<Integer, String> loadingCache = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .refreshAfterWrite(1, TimeUnit.MINUTES)
        .build(new CacheLoader<Integer, String>() {
            @Override
            public String load(Integer key) throws Exception {
                //TODO: 根據業務載入數據
                return RandomStringUtils.randomAlphanumeric(10);
            }

            @Override
            public ListenableFuture<String> reload(Integer key, String oldValue) throws Exception {
                if (neverNeedsRefresh(key)) {//不需要刷新
                    return Futures.immediateFuture(oldValue);
                } else {
                    //非同步刷新
                    ListenableFutureTask<String> task = ListenableFutureTask.create(new Callable<String>() {
                        public String call() {
                            return RandomStringUtils.randomAlphanumeric(10);
                        }
                    });
                    executorService.execute(task);
                    return task;
                }
            }
        });

可以使用 CacheBuilder.refreshAfterWrite(long, TimeUnit) 為緩存添加自動定時刷新。與 expireAfterWrite 不同,refreshAfterWrite 會使一個鍵在指定的時間後變為可刷新狀態,但只有在查詢該條目時才會實際啟動刷新(如果 CacheLoader.reload 被實現為非同步,則查詢不會因刷新而變慢)。因此,可以在同一個緩存上同時指定 refreshAfterWrite 和 expireAfterWrite,以便在條目變為可刷新狀態時不會盲目地重置過期計時器,如果一個條目在變為可刷新狀態後沒有被查詢,它就允許過期。

4、特點

4.1、統計信息

使用 CacheBuilder.recordStats() 可以為 Guava Cache 打開統計信息收集功能。Cache.stats() 方法返回一個 CacheStats 對象,該對象提供了諸如以下統計信息:

還有許多其他的統計信息。這些統計信息在緩存調優中非常重要,我們建議在性能關鍵的應用程式中密切關註這些統計信息。

4.2、asMap

可以使用 Cache 的 asMap 視圖將任何緩存視為 ConcurrentMap,但是 asMap 視圖與緩存的交互需要一些說明。

  • cache.asMap() 包含當前載入在緩存中的所有條目。例如,cache.asMap().keySet() 包含當前載入的所有鍵。
  • asMap().get(key) 基本等同於 cache.getIfPresent(key),並且不會導致值被載入。這與 Map 的約定一致。
  • 訪問時間會被讀取和寫入操作重置(包括 Cache.asMap().get(Object) 和 Cache.asMap().put(K, V)),但不會被 containsKey(Object) 或其他操作所重置。因此,遍歷 cache.asMap().entrySet() 不會重置條目的訪問時間。

5、簡單使用

5.1、引入依賴

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.3-jre</version>
</dependency>

5.2、簡單使用

public static void main(String[] args) {
    LoadingCache<Long, String> loadingCache = CacheBuilder.newBuilder()
            .initialCapacity(1000)
            .maximumSize(10000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .refreshAfterWrite(3, TimeUnit.MINUTES)
            .recordStats()
            .build(new CacheLoader<Long, String>() {
                @Override
                public String load(Long key) throws Exception {//拋出已檢查異常
                    //TODO: 根據業務載入數據
                    return RandomStringUtils.randomAlphanumeric(10);
                }
            });
    try {
        log.info(loadingCache.get(1L));
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

 

 

參考:https://github.com/google/guava/wiki/CachesExplained


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 六、判斷(一) 1、關係運算符 1)每一個關係運算符得出的結果都是一個布爾類型值(true、flase或真、假或1、0) 關係運算符 說明 關係運算符 說明 > 大於 >= 大於等於 < 小於 <= 小於等於 == 等於 != 不等於 #include <iostream> int main() { ...
  • 常見鎖介紹 synchronized鎖的八中情況 package com.shaonian.juc.more_thread_lock; import java.util.concurrent.TimeUnit; class Phone { public static synchronized voi ...
  • Python MySQL 限制結果 限制結果數量 示例 1: 獲取您自己的 Python 伺服器 選擇 "customers" 表中的前 5 條記錄: import mysql.connector mydb = mysql.connector.connect( host="localhost", u ...
  • 操作系統 :Windows 10_x64 python版本 :3.9.2 pymysql版本: 1.0.2 MySQL版本: 5.7.38 之前寫過一篇關於python操作mysql資料庫的文章: https://www.cnblogs.com/MikeZhang/p/pythonOptMysql2 ...
  • 寫在前面 目前已經上班快兩個月了,對現在的工作很滿意,甚至更喜歡這的氛圍吧。 如題所示,從今年5月開始,發生的所有事,都完全超出了我自己可以承受的範圍,好在這一切都過去了,真的感謝上天安排,讓我能更加確信自己要的是什麼,以後該怎麼生活。 爸爸被診斷為肺癌 我每年都會帶父母去做體檢,因為去年疫情全面放 ...
  • 本文適用範圍 主要適用於debug python 程式,尤其是深度學習剛入門需要使用remote 連接到linux進行程式運行,想調試一下的同學。 當然非深度學習也可以參考食用本文哈哈哈。 極速入門版 提前準備:代碼倉庫已經拉取到linux上面,且已經知道運行的方式。 比如: 項目的啟動命令為:py ...
  • 五、位運算 ​ 位運算主要計算記憶體中每個小格的數據 1、輸出二進位內容 頭文件調用 語法 示例 include <bitser> std::bitset<要顯示的二進位位數>(要顯示的變數) std::cout << std::bitset<16>(a); //二進位內容輸出 #include <i ...
  • 在 Java 中,有四種方法可以獲取當前正在執行方法體的方法名稱,分別是: 使用 Thread.currentThread().getStackTrace() 方法 使用異常對象的 getStackTrace() 方法 使用匿名內部類的 getClass().getEnclosingMethod() ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...