天天看點

Java Cache 緩存方案詳解及代碼-Guava Cache

作者:Doker多克

一、 概念

Google Guava Cache是一種非常優秀本地緩存解決方案,提供了基于容量,時間和引用的緩存回收方式。guava cache是運作在JVM的本地緩存,并不能把資料存放到外部伺服器上。如果有這樣的要求,因該嘗試Memcached或 Redis這類分布式緩存。

  • 基于容量的方式内部實作采用LRU算法,
  • 基于引用回收很好的利用了Java 虛拟機的垃圾回收機制。

Guava Cache與ConcurrentMap很相似,但也不完全一樣。最基本的差別是ConcurrentMap會一直儲存所有添加的元素,直到顯式地移除。Guava Cache為了限制記憶體占用,通常都設定為自動回收元素。Guava Cache 的優點是封裝了get,put操作;提供線程安全的緩存操作;提供過期政策;提供回收政策;緩存監控。當緩存的資料超過最大值時,使用LRU算法替換。

二、Guava Cache 加載方式:

加載方式1 - CacheLoader

LoadingCache是附帶CacheLoader建構而成的緩存實作。建立自己的CacheLoader通常隻需要簡單地實作V load(K key) throws Exception方法

加載方式2 - Callable

所有類型的Guava Cache,不管有沒有自動加載功能,都支援get(K,Callable<V>)方法。這個方法傳回緩存中相應的值,或者用給定的Callable運算并把結果加入到緩存中。在整個加載方法完成前,緩存項相關的可觀察狀态都不會更改。這個方法簡便地實作了模式"如果有緩存則傳回;否則運算、緩存、然後傳回"

三、開發執行個體:

依賴引入:

<dependencies>
        <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.1-jre</version>
        </dependency>
    </dependencies>           

CacheLoader方式使用:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) throws AnyException {
               return createExpensiveGraph(key);
             }
           });

...
try {
  return graphs.get(key);
} catch (ExecutionException e) {
  throw new OtherException(e.getCause());
}           

Callable方式使用:

Cache<Key, Value> cache = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .build(); // look Ma, no CacheLoader
...
try {
  // If the key wasn't in the "easy to compute" group, we need to
  // do things the hard way.
  cache.get(key, new Callable<Value>() {
    @Override
    public Value call() throws AnyException {
      return doThingsTheHardWay(key);
    }
  });
} catch (ExecutionException e) {
  throw new OtherException(e.getCause());
}           

緩存回收-基于容量回收:

基于容量回收

如果要規定緩存項的數目不超過固定值,隻需使用CacheBuilder.maximumSize(long)在緩存項的數目達到限定值之前,緩存就可能進行回收操作,通常發生在緩存項的數目逼近限定值時另外不同的緩存項有不同的“權重”(weights)-例如:如果你的緩存值,占據完全不同的記憶體空間,可以使用CacheBuilder.weigher(Weigher)指定一個權重函數,并且用acheBuilder.maximumWeight(long)指定最大總重

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumWeight(100000)
       .weigher(new Weigher<Key, Graph>() {
          public int weigh(Key k, Graph g) {
            return g.vertices().size();
          }
        })
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) { // no checked exception
               return createExpensiveGraph(key);
             }
           });           

Guava Cache緩存回收-定時回收

CacheBuilder提供兩種定時回收的方法:

expireAfterAccess(long,TimeUnit):緩存項在給定時間内沒有被讀/寫通路,則回收

expireAfterWriter(long,TimeUnit):緩存項在給定時間内沒有被寫通路(建立或覆寫)

Guava Cache添加移除監聽器

可以為Cache對象添加一個移除監聽器,以便緩存被移除時做一些額外操作。

緩存項被移除時,RemovalListener會擷取通知RemovalNotification,其中包含移除原因RemovalCause、鍵和值

CacheLoader<Key, DatabaseConnection> loader = new CacheLoader<Key, DatabaseConnection> () {
  public DatabaseConnection load(Key key) throws Exception {
    return openConnection(key);
  }
};
RemovalListener<Key, DatabaseConnection> removalListener = new RemovalListener<Key, DatabaseConnection>() {
  public void onRemoval(RemovalNotification<Key, DatabaseConnection> removal) {
    DatabaseConnection conn = removal.getValue();
    conn.close(); // tear down properly
  }
};

return CacheBuilder.newBuilder()
  .expireAfterWrite(2, TimeUnit.MINUTES)
  .removalListener(removalListener)
  .build(loader);           

Guava Cache重新整理

重新整理和回收不太一樣。正如LoadingCache.refresh(K)所聲明,重新整理表示為鍵加載新值,這個過程可以是異步的。在重新整理操作進行時,緩存仍然可以向其他線程傳回舊值,而不像回收操作,讀緩存的線程必須等待新值加載完成。在進行緩存定時重新整理時,需要指定緩存的重新整理間隔,和一個用來加載緩存的CacheLoader,當達到重新整理時間間隔後,下一次擷取緩存時,會調用CacheLoader的load方法重新整理緩存

// Some keys don't need refreshing, and we want refreshes to be done asynchronously.
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .refreshAfterWrite(1, TimeUnit.MINUTES)
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) { // no checked exception
               return getGraphFromDatabase(key);
             }

             public ListenableFuture<Graph> reload(final Key key, Graph prevGraph) {
               if (neverNeedsRefresh(key)) {
                 return Futures.immediateFuture(prevGraph);
               } else {
                 // asynchronous!
                 ListenableFutureTask<Graph> task = ListenableFutureTask.create(new Callable<Graph>() {
                   public Graph call() {
                     return getGraphFromDatabase(key);
                   }
                 });
                 executor.execute(task);
                 return task;
               }
             }
           });           

官網:https://github.com/google/guava/wiki/CachesExplained

大家好,我是Doker品牌的Sinbad,歡迎點贊和評論,您的鼓勵是我們持續更新的動力!更多資料請前往Doker 多克