天天看點

谷歌Guava LoadingCache介紹

谷歌Guava LoadingCache介紹

  在工作中,加Cache是非常常見的一種性能優化手段,作業系統底層、計算機硬體層為了性能優化加了各種各樣的Cache,當然大多數都是對應用層透明的。但如果你想在應用層加Cache的話,可能就需要你自己實作了。

  其實在Java環境下,Cache有各種各樣的選擇,比如最初級的你可以直接用HashMap實作一個Cache,不過你得自己關注下資料加載和淘汰的政策。更進階的有像spring-cache,代碼都不需要改,隻需要簡單加幾個注解就可以實作對關鍵資料的緩存,相當友善(後續我也會出一篇部落格介紹下spring-cache)。 今天我們要介紹的是谷歌guava包中的LoadingCache, 也是功能完善,簡單好用。

  LoadingCache是Guava包中提供一個一種本地Cache,本地Cache的優勢就是沒有網絡IO,速度快。但劣勢也很明顯,Cache容量受限于本地記憶體大小,Cache中的資料沒法共享。是以它就隻适合少量熱點資料的緩存,其使用方法也很簡單,我們拿maven為例,你隻需要添加一下Maven依賴即可引入guava包:

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

使用代碼也非常簡單,如下:

private static LoadingCache<String, String> cache =
            CacheBuilder.newBuilder()
                        // 初始化容量
                        .initialCapacity(4)
                        // 緩存池大小,在緩存數量到達該大小時, Guava開始回收舊的資料
                        .maximumSize(8)
                        // 設定時間對象沒有被讀/寫通路則對象從記憶體中删除(在另外的線程裡面不定期維護)
                        .expireAfterAccess(5, TimeUnit.SECONDS)
                        // 設定緩存在寫入之後 設定時間 後失效
                        .expireAfterWrite(5, TimeUnit.SECONDS)
                        // 資料被移除時的監聽器, 緩存項被移除時會觸發執行
                        .removalListener((RemovalListener<String, String>) rn -> {
                            System.out.println(String.format("資料key:%s value:%s 因為%s被移除了", rn.getKey(), rn.getValue(),
                                    rn.getCause().name()));
                        })
                        // 開啟Guava Cache的統計功能
                        .recordStats()
                        // 資料寫入後被多久重新整理一次
                        .refreshAfterWrite(5, TimeUnit.SECONDS)
                        // 資料并發級别
                        .concurrencyLevel(16)
                        // 當緩存中沒有資料時的資料加載器
                        .build(new CacheLoader<String, String>() {
                            @Override
                            public String load(String key) throws Exception {
                                return key + "_" + System.currentTimeMillis();
                            }
                        });      

  然後我們就可以直接在代碼的其他地方用​

​cache.get("myKey")​

​ 來愉快地使用LoadingCache了,它會主動加載資料,并在存儲空間不夠或者資料過期時清理掉不需要的資料,非常省心且友善。

這裡有些重點參數,下面詳細介紹下:

參數 作用 注意事項
maximumSize 緩存的k-v最大資料,當總緩存的資料量達到或者快達到這個值時,就會淘汰它認為不太用的一份資料,近似LRU或者LFU政策 并不一定是達到這個值才開始淘汰舊資料,可能接近時就會開始淘汰
expireAfterAccess 資料被通路後多久就會過期,這個政策主要是為了淘汰長時間不被通路的資料 資料過期不是立即淘汰,而是有資料通路時才會觸發
expireAfterWrite 資料寫入後多久過期,這個政策是為了防止舊資料被緩存過久 同上
refreshAfterWrite 資料寫入後多久重新整理一次,這個類似于expireAfterWrite,但它會主動更新資料 同上
concurrencyLevel 資料的并發級别,LoadingCache為了實作線程安全,它裡面采用了類似Java7中ConcurrentHashMap的實作,采用了分段加鎖的方式,分段數影響了它的最大并發量
recordStats 開啟Cache的狀态統計(預設是開啟的) 開啟這個是會影響到性能的,如果要求極緻性能的話關注下個

  我們來重點介紹下CacheLoader CacheStats和RemovalListener,因為這三者涉及到了資料的加載、使用和删除的完整生命周期,先來看下CacheLoader。

CacheLoader

  CacheLoader的作用就是為了在Cache中資料缺失時加載資料,其中最重要的方法就是load()方法,你可以在load() 方法中實作對應key加載資料的邏輯。在調用LoadingCache的get(key)方法時,如果key對應的value不存在,LoadingCache就會調起你在建立cache時傳入的CacheLoader的load方法。

CacheStats

谷歌Guava LoadingCache介紹

  使用​

​CacheStats cacheStats = cache.stats();​

​ 我就可以擷取到cache的stats資料。從cacheStats中我們可以看到cache的命中率、命中數、異常率、加載時延……等資料,通過這些資料就可以直覺地看出我們cache的一些性能名額,如果做出一些參數調整。 比如如果命中率過低,我們是不是可以調整大下maximumSize,或者調整下資料的過期政策?

RemovalListener

  RemovalListener會在LoadingCache中資料被清理時調起,其實就是個監聽器模式,這樣你可以通過Listener實作對資料淘汰事件的監聽,比如在資料淘汰時打一行日志啥的。使用方法也很簡單,在Java8+上你可以直接使用lambda表達式,或者也可以自己實作RemovalListener接口,并在建構Cache時注冊進去即可。

public enum RemovalCause {
  EXPLICIT {
    @Override
    boolean wasEvicted() {
      return false;
    }
  },
  REPLACED {
    @Override
    boolean wasEvicted() {
      return false;
    }
  },

  COLLECTED {
    @Override
    boolean wasEvicted() {
      return true;
    }
  },
  EXPIRED {
    @Override
    boolean wasEvicted() {
      return true;
    }
  },

  SIZE {
    @Override
    boolean wasEvicted() {
      return true;
    }
  };
  abstract boolean wasEvicted();
}