1. 程式人生 > 程式設計 >如何基於LoadingCache實現Java本地快取

如何基於LoadingCache實現Java本地快取

這篇文章主要介紹瞭如何基於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

LoadingCache<Long,String> cache = CacheBuilder.newBuilder()
        //快取池大小,在快取項接近該大小時, Guava開始回收舊的快取項
        .maximumSize(GUAVA_CACHE_SIZE)
        //設定時間物件沒有被讀/寫訪問則物件從記憶體中刪除(在另外的執行緒裡面不定期維護)
        .expireAfterAccess(10,TimeUnit.MINUTES)
        //移除監聽器,快取項被移除時會觸發
        .removalListener(new RemovalListener <Long,String>() {
          @Override
          public void onRemoval(RemovalNotification<Long,String> rn) {
            //執行邏輯操作
          }
        })
        //開啟Guava Cache的統計功能
        .recordStats()
        .build(cacheLoader);

三.個人封裝的工具類

package com.xxx;

import com.google.common.cache.*;
import org.slf4j.Logger;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

public class CacheManager {

  private static Logger log = Log.get();

  /** 快取項最大數量 */
  private static final long GUAVA_CACHE_SIZE = 100000;

  /** 快取時間:天 */
  private static final long GUAVA_CACHE_DAY = 10;

  /** 快取操作物件 */
  private static LoadingCache<Long,String> GLOBAL_CACHE = null;

  static {
    try {
      GLOBAL_CACHE = loadCache(new CacheLoader <Long,String>() {
        @Override
        public String load(Long key) throws Exception {
          // 處理快取鍵不存在快取值時的處理邏輯
          return "";
        }
      });
    } catch (Exception e) {
      log.error("初始化Guava Cache出錯",e);
    }
  }

  /**
   * 全域性快取設定
   *
   * 快取項最大數量:100000
   * 快取有效時間(天):10
   *
   *
   * @param cacheLoader
   * @return
   * @throws Exception
   */
  private static LoadingCache<Long,String> loadCache(CacheLoader<Long,String> cacheLoader) throws Exception {
    LoadingCache<Long,String> cache = CacheBuilder.newBuilder()
        //快取池大小,在快取項接近該大小時, Guava開始回收舊的快取項
        .maximumSize(GUAVA_CACHE_SIZE)
        //設定時間物件沒有被讀/寫訪問則物件從記憶體中刪除(在另外的執行緒裡面不定期維護)
        .expireAfterAccess(GUAVA_CACHE_DAY,TimeUnit.DAYS)
        // 設定快取在寫入之後 設定時間 後失效
        .expireAfterWrite(GUAVA_CACHE_DAY,TimeUnit.DAYS)
        //移除監聽器,String> rn) {
            //邏輯操作
          }
        })
        //開啟Guava Cache的統計功能
        .recordStats()
        .build(cacheLoader);
    return cache;
  }

  /**
   * 設定快取值
   * 注: 若已有該key值,則會先移除(會觸發removalListener移除監聽器),再新增
   *
   * @param key
   * @param value
   */
  public static void put(Long key,String value) {
    try {
      GLOBAL_CACHE.put(key,value);
    } catch (Exception e) {
      log.error("設定快取值出錯",e);
    }
  }

  /**
   * 批量設定快取值
   *
   * @param map
   */
  public static void putAll(Map<? extends Long,? extends String> map) {
    try {
      GLOBAL_CACHE.putAll(map);
    } catch (Exception e) {
      log.error("批量設定快取值出錯",e);
    }
  }

  /**
   * 獲取快取值
   * 注:如果鍵不存在值,將呼叫CacheLoader的load方法載入新值到該鍵中
   *
   * @param key
   * @return
   */
  public static String get(Long key) {
    String token = "";
    try {
      token = GLOBAL_CACHE.get(key);
    } catch (Exception e) {
      log.error("獲取快取值出錯",e);
    }
    return token;
  }

    /**
   * 移除快取
   *
   * @param key
   */
  public static void remove(Long key) {
    try {
      GLOBAL_CACHE.invalidate(key);
    } catch (Exception e) {
      log.error("移除快取出錯",e);
    }
  }

  /**
   * 批量移除快取
   *
   * @param keys
   */
  public static void removeAll(Iterable<Long> keys) {
    try {
      GLOBAL_CACHE.invalidateAll(keys);
    } catch (Exception e) {
      log.error("批量移除快取出錯",e);
    }
  }

  /**
   * 清空所有快取
   */
  public static void removeAll() {
    try {
      GLOBAL_CACHE.invalidateAll();
    } catch (Exception e) {
      log.error("清空所有快取出錯",e);
    }
  }

  /**
   * 獲取快取項數量
   *
   * @return
   */
  public static long size() {
    long size = 0;
    try {
      size = GLOBAL_CACHE.size();
    } catch (Exception e) {
      log.error("獲取快取項數量出錯",e);
    }
    return size;
  }
}

總結

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()。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。