Guava - LoadingCache實現Java本地緩存


前言

Guava是Google開源出來的一套工具庫。其中提供的cache模塊非常方便,是一種與ConcurrentMap相似的緩存Map。

官方地址:https://github.com/google/guava/wiki/CachesExplained

 

開始構建

一. 添加依賴

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

二.創建 CacheLoader

 1 LoadingCache<Long, String> cache = CacheBuilder.newBuilder()
 2                 //緩存池大小,在緩存項接近該大小時, Guava開始回收舊的緩存項
 3                 .maximumSize(GUAVA_CACHE_SIZE)
 4                 //設置時間對象沒有被讀/寫訪問則對象從內存中刪除(在另外的線程里面不定期維護)
 5                 .expireAfterAccess(10, TimeUnit.MINUTES)
 6                 //移除監聽器,緩存項被移除時會觸發
 7                 .removalListener(new RemovalListener <Long, String>() {
 8                     @Override
 9                     public void onRemoval(RemovalNotification<Long, String> rn) {
10                         //執行邏輯操作
11                     }
12                 })
13                 //開啟Guava Cache的統計功能
14                 .recordStats()
15                 .build(cacheLoader);

三.個人封裝的工具類

  1 package com.xxx;
  2 
  3 import com.google.common.cache.*;
  4 import org.slf4j.Logger;
  5 
  6 import java.util.ArrayList;
  7 import java.util.List;
  8 import java.util.Map;
  9 import java.util.concurrent.ConcurrentMap;
 10 import java.util.concurrent.TimeUnit;
 11 
 12 public class CacheManager {
 13 
 14     private static Logger log = Log.get();
 15 
 16     /** 緩存項最大數量 */
 17     private static final long GUAVA_CACHE_SIZE = 100000;
 18 
 19     /** 緩存時間:天 */
 20     private static final long GUAVA_CACHE_DAY = 10;
 21 
 22     /** 緩存操作對象 */
 23     private static LoadingCache<Long, String> GLOBAL_CACHE = null;
 24 
 25     static {
 26         try {
 27             GLOBAL_CACHE = loadCache(new CacheLoader <Long, String>() {
 28                 @Override
 29                 public String load(Long key) throws Exception {
 30                     // 處理緩存鍵不存在緩存值時的處理邏輯
 31                     return "";
 32                 }
 33             });
 34         } catch (Exception e) {
 35             log.error("初始化Guava Cache出錯", e);
 36         }
 37     }
 38 
 39     /**
 40      * 全局緩存設置
 41      *
 42      * 緩存項最大數量:100000
 43      * 緩存有效時間(天):10
 44      *
 45      *
 46      * @param cacheLoader
 47      * @return
 48      * @throws Exception
 49      */
 50     private static LoadingCache<Long, String> loadCache(CacheLoader<Long, String> cacheLoader) throws Exception {
 51         LoadingCache<Long, String> cache = CacheBuilder.newBuilder()
 52                 //緩存池大小,在緩存項接近該大小時, Guava開始回收舊的緩存項
 53                 .maximumSize(GUAVA_CACHE_SIZE)
 54                 //設置時間對象沒有被讀/寫訪問則對象從內存中刪除(在另外的線程里面不定期維護)
 55                 .expireAfterAccess(GUAVA_CACHE_DAY, TimeUnit.DAYS)
 56                 // 設置緩存在寫入之后 設定時間 后失效
 57                 .expireAfterWrite(GUAVA_CACHE_DAY, TimeUnit.DAYS)
 58                 //移除監聽器,緩存項被移除時會觸發
 59                 .removalListener(new RemovalListener <Long, String>() {
 60                     @Override
 61                     public void onRemoval(RemovalNotification<Long, String> rn) {
 62                         //邏輯操作
 63                     }
 64                 })
 65                 //開啟Guava Cache的統計功能
 66                 .recordStats()
 67                 .build(cacheLoader);
 68         return cache;
 69     }
 70 
 71     /**
 72      * 設置緩存值
 73      * 注: 若已有該key值,則會先移除(會觸發removalListener移除監聽器),再添加
 74      *
 75      * @param key
 76      * @param value
 77      */
 78     public static void put(Long key, String value) {
 79         try {
 80             GLOBAL_CACHE.put(key, value);
 81         } catch (Exception e) {
 82             log.error("設置緩存值出錯", e);
 83         }
 84     }
 85 
 86     /**
 87      * 批量設置緩存值
 88      *
 89      * @param map
 90      */
 91     public static void putAll(Map<? extends Long, ? extends String> map) {
 92         try {
 93             GLOBAL_CACHE.putAll(map);
 94         } catch (Exception e) {
 95             log.error("批量設置緩存值出錯", e);
 96         }
 97     }
 98 
 99     /**
100      * 獲取緩存值
101      * 注:如果鍵不存在值,將調用CacheLoader的load方法加載新值到該鍵中
102      *
103      * @param key
104      * @return
105      */
106     public static String get(Long key) {
107         String token = "";
108         try {
109             token = GLOBAL_CACHE.get(key);
110         } catch (Exception e) {
111             log.error("獲取緩存值出錯", e);
112         }
113         return token;
114     }
115 
116         /**
117      * 移除緩存
118      *
119      * @param key
120      */
121     public static void remove(Long key) {
122         try {
123             GLOBAL_CACHE.invalidate(key);
124         } catch (Exception e) {
125             log.error("移除緩存出錯", e);
126         }
127     }
128 
129     /**
130      * 批量移除緩存
131      *
132      * @param keys
133      */
134     public static void removeAll(Iterable<Long> keys) {
135         try {
136             GLOBAL_CACHE.invalidateAll(keys);
137         } catch (Exception e) {
138             log.error("批量移除緩存出錯", e);
139         }
140     }
141 
142     /**
143      * 清空所有緩存
144      */
145     public static void removeAll() {
146         try {
147             GLOBAL_CACHE.invalidateAll();
148         } catch (Exception e) {
149             log.error("清空所有緩存出錯", e);
150         }
151     }
152 
153     /**
154      * 獲取緩存項數量
155      *
156      * @return
157      */
158     public static long size() {
159         long size = 0;
160         try {
161             size = GLOBAL_CACHE.size();
162         } catch (Exception e) {
163             log.error("獲取緩存項數量出錯", e);
164         }
165         return size;
166     }
167 }

 

總結

1.移除機制

guava做cache時候數據的移除分為被動移除主動移除兩種。

被動移除分為三種:1).基於大小的移除:數量達到指定大小,會把不常用的鍵值移除

         2).基於時間的移除:expireAfterAccess(long, TimeUnit) 根據某個鍵值對最后一次訪問之后多少時間后移除 
                   expireAfterWrite(long, TimeUnit) 根據某個鍵值對被創建或值被替換后多少時間移除 

            3).基於引用的移除:主要是基於java的垃圾回收機制,根據鍵或者值的引用關系決定移除 

主動移除分為三種:1).單獨移除:Cache.invalidate(key) 

         2).批量移除:Cache.invalidateAll(keys) 

         3).移除所有:Cache.invalidateAll() 

如果配置了移除監聽器RemovalListener,則在所有移除的動作時會同步執行該listener下的邏輯。

如需改成異步,使用:RemovalListeners.asynchronous(RemovalListener, Executor)

2.遇到的問題

1). 在put操作之前,如果已經有該鍵值,會先觸發removalListener移除監聽器,再添加

2). 配置了expireAfterAccess和expireAfterWrite,但在指定時間后沒有被移除。

 解決方案:CacheBuilder在文檔上有說明:If expireAfterWrite or expireAfterAccess is requested entries may be evicted on each cache modification, on occasional cache accesses, or on calls to Cache.cleanUp(). Expired entries may be counted in Cache.size(), but will never be visible to read or write operations. 翻譯過來大概的意思是:CacheBuilder構建的緩存不會在特定時間自動執行清理和回收工作,也不會在某個緩存項過期后馬上清理,它不會啟動一個線程來進行緩存維護,因為a)線程相對較重,b)某些環境限制線程的創建。它會在寫操作時順帶做少量的維護工作,或者偶爾在讀操作時做。當然,也可以創建自己的維護線程,以固定的時間間隔調用Cache.cleanUp()。

 


免責聲明!

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



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