Guava Cache詳解


適用性

  緩存在很多場景下都是相當有用的。例如,計算或檢索一個值的代價很高,並且對同樣的輸入需要不止一次獲取值的時候,就應當考慮使用緩存

  Guava Cache與ConcurrentMap很相似,但也不完全一樣。最基本的區別是ConcurrentMap會一直保存所有添加的元素,直到顯式地移除。相對地,Guava Cache為了限制內存占用,通常都設定為自動回收元素。在某些場景下,盡管LoadingCache 不回收元素,它也是很有用的,因為它會自動加載緩存

  通常來說,Guava Cache適用於:

    • 你願意消耗一些內存空間來提升速度。
    • 你預料到某些鍵會被查詢一次以上。
    • 緩存中存放的數據總量不會超出內存容量。(Guava Cache是單個應用運行時的本地緩存。它不把數據存放到文件或外部服務器。如果這不符合你的需求,請嘗試Redis這類工具)

  如果你的場景符合上述的每一條,Guava Cache就適合你。

  如果你不需要Cache中的特性,使用ConcurrentHashMap有更好的內存效率——但Cache的大多數特性都很難基於舊有的ConcurrentMap復制,甚至根本不可能做到

代碼詳解

  1 /**
  2  * @author LiuHuan
  3  * @date 2020-06-17 15:52
  4  * @desc Guava Cache學習
  5  */
  6 public class GuavaCacheTest {
  7 
  8     public static void main(String[] args) throws ExecutionException {
  9         GuavaCacheTest test = new GuavaCacheTest();
 10         Cache<String, String> cache = test.getGuavaCache();
 11         // 放入/覆蓋一個緩存
 12         cache.put("key", "value");
 13         // 獲取一個緩存,如果該緩存不存在則返回一個null值
 14         cache.getIfPresent("");
 15         // 獲取緩存,當緩存不存在時,則通Callable進行加載並返回。該操作是原子
 16         cache.get("key", () -> loadingValue("key"));
 17         // 回收key為k1的緩存
 18         cache.invalidate("key");
 19         // 使用Map的put方法進行覆蓋刷新
 20         cache.asMap().put("key", "value");
 21         // 使用ConcurrentMap的replace方法進行覆蓋刷新
 22         cache.asMap().replace("key", "value1");
 23         // 使用Map的putAll方法進行批量覆蓋刷新
 24         Map<String,String> needRefresh = ImmutableMap.of("key1","value1", "key2", "value2");
 25         cache.asMap().putAll(needRefresh);
 26         // 批量回收key為key1、key2的緩存
 27         List<String> needInvalidateKeys = Arrays.asList("key1", "key2");
 28         cache.invalidateAll(needInvalidateKeys);
 29         // 回收所有緩存
 30         cache.invalidateAll();
 31 
 32         // 用來開啟Guava Cache的統計功能。統計打開后,Cache.stats()方法會返回CacheStats對象
 33         CacheStats stats = cache.stats();
 34         // 緩存命中率
 35         double hitRate = stats.hitRate();
 36         // 加載新值的平均時間,單位為納秒
 37         double averageLoadPenalty = stats.averageLoadPenalty();
 38         // 緩存項被回收的總數,不包括顯式清除
 39         long evictionCount = stats.evictionCount();
 40 
 41         LoadingCache<String, String> loadingCache = test.getGuavaLoadingCache();
 42         // loadingCache 在進行刷新時無需顯式的傳入value
 43         loadingCache.refresh("key");
 44     }
 45 
 46     /**
 47      * 獲取GuavaCache實例
 48      * @return
 49      */
 50     public Cache<String, String> getGuavaCache(){
 51         // 異步觸發監聽器
 52         RemovalListener<Object, Object> removalListener = RemovalListeners.asynchronous(removal -> {
 53             // 如果被顯示移除這里為true
 54             boolean wasEvicted = removal.wasEvicted();
 55             // 移除的原因
 56             RemovalCause cause = removal.getCause();
 57         }, Executors.newSingleThreadExecutor());
 58         
 59         // 通過CacheBuilder構建一個緩存實例
 60         Cache<String, String> cache = CacheBuilder.newBuilder()
 61             // 由於Guava的緩存使用了分離鎖的機制,擴容的代價非常昂貴,所以合理的初識容量能夠減少擴容次數
 62             .initialCapacity(100)
 63             // 設置緩存的最大條數
 64             .maximumSize(100)
 65             // maximumWeight邏輯上用來表示一種“權重”,這里與maximumSize沖突,設置一個即可
 66             // 這里我們將key和value所占的字節數,作為weight,當cache中所有的“weight”總和達到maximumWeight時,將會觸發“剔除策略”
 67             .maximumWeight(1024 * 1024)
 68             .weigher((Weigher<String, String>)(key, value) -> key.getBytes().length + value.getBytes().length)
 69             // 使用弱引用存儲鍵。當鍵沒有其它(強或軟)引用時,該緩存可能會被回收
 70             .weakKeys()
 71             // 使用弱引用存儲值。當值沒有其它(強或軟)引用時,該緩存可能會被回收
 72             .weakValues()
 73             // 使用軟引用存儲值。當內存不足並且該值其它強引用引用時,該緩存就會被回收
 74             // 通過軟/弱引用的回收方式,相當於將緩存回收任務交給了GC,使得緩存的命中率變得十分的不穩定,在非必要的情況下,還是推薦基於數量和容量的回收
 75             .softValues()
 76             // 設置緩存在寫入一分鍾后失效
 77             .expireAfterWrite(1, TimeUnit.MINUTES)
 78             // 設置緩存最后一次訪問10分鍾之后失效,與session類似。與expireAfterWrite沖突,設置一個即可
 79             .expireAfterAccess(Duration.ofMinutes(10))
 80             // 開啟緩存統計
 81             .recordStats()
 82             // 設置並發級別為CPU核心數
 83             .concurrencyLevel(Runtime.getRuntime().availableProcessors())
 84             // 移除監聽器,這里是移除緩存時同步調用,會拖慢正常的請求
 85             .removalListener(removal -> {
 86                 // 如果被顯示移除這里為true
 87                 boolean wasEvicted = removal.wasEvicted();
 88                 // 移除的原因
 89                 RemovalCause cause = removal.getCause();
 90             })
 91             // 異步觸發監聽器
 92             .removalListener(removalListener)
 93             .build();
 94         return cache;
 95     }
 96 
 97     /**
 98      * 獲取GuavaLoadingCache實例,會有默認的緩存加載策略
 99      * @return
100      */
101     public LoadingCache<String, String> getGuavaLoadingCache(){
102         // 通過CacheBuilder構建一個緩存實例
103         LoadingCache<String, String> cache = CacheBuilder.newBuilder()
104             // 設置緩存在寫入10分鍾后,通過CacheLoader的load方法進行刷新
105             .refreshAfterWrite(Duration.ofMinutes(10))
106             .build(new CacheLoader<String, String>() {
107                 @Override
108                 public String load(String key) throws Exception {
109                     // 緩存加載策略
110                     return loadingValue(key);
111                 }
112 
113             });
114         return cache;
115     }
116 
117     /**
118      * 緩存加載策略
119      * @param key
120      * @return
121      */
122     private static String loadingValue(String key){
123         return null;
124     };
125 
126 }

緩存清理  

  使用CacheBuilder構建的緩存不會"自動"執行清理和回收工作,也不會在某個緩存項過期后馬上清理,也沒有諸如此類的清理機制。相反,它會在寫操作時順帶做少量的維護工作,如果寫操作實在太少的話,偶爾在讀操作時做這個操作。這樣做的原因在於:如果要自動地持續清理緩存,就必須有一個線程,這個線程會和用戶操作競爭共享鎖。此外,某些環境下線程創建可能受限制,這樣CacheBuilder就不可用了。

  如果你的緩存是高吞吐的,那就無需擔心緩存的維護和清理等工作。如果你的緩存只會偶爾有寫操作,而你又不想清理工作阻礙了讀操作,那么可以創建自己的維護線程,以固定的時間間隔調用Cache.cleanUp()ScheduledExecutorService可以幫助你很好地實現這樣的定時調度

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM