Java內存緩存工具實現 - Guava LoadingCache

一、Guava介紹

  1. Guava是Google guava中的一個內存緩存模塊,用於將數據緩存到JVM內存中。實際項目開發中經常將一些公共或者常用的數據緩存起來方便快速訪問。

  2. Guava Cache是單個應用運行時的本地緩存。它不把數據存放到文件或外部服務器。如果不符合需求,可以選擇Memcached、Redis等工具。

二、代碼示例

1. POM引入

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

2. 封裝工具類

package com.soyoung.ad.engine.util;

import com.google.common.cache.*;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 功能描述
 *
 * @author 馬振全 2020/1/13 16:18
 */
@Slf4j
public class CacheManager {

    /** 緩存項最大數量 */
    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)
                //移除監聽器,緩存項被移除時會觸發
                .removalListener(new RemovalListener<Long, String>() {
                    @Override
                    public void onRemoval(RemovalNotification<Long, 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構建的緩存不會在特定時間自動執行清理和回收工作,也不會在某個緩存項過期後馬上清理,它不會啓動一個線程來進行緩存維護,因爲a)線程相對較重,b)某些環境限制線程的創建。它會在寫操作時順帶做少量的維護工作,或者偶爾在讀操作時做。當然,也可以創建自己的維護線程,以固定的時間間隔調用Cache.cleanUp()。

3. Cache提供哪些方法

/**
* 該接口的實現被認爲是線程安全的,即可在多線程中調用
* 通過被定義單例使用
*/
public interface Cache<K, V> {

/**
* 通過key獲取緩存中的value,若不存在直接返回null
*/
V getIfPresent(Object key);

/**
* 通過key獲取緩存中的value,若不存在就通過valueLoader來加載該value
* 整個過程爲 "if cached, return; otherwise create, cache and return"
* 注意valueLoader要麼返回非null值,要麼拋出異常,絕對不能返回null
*/
V get(K key, Callable<? extends V> valueLoader) throws ExecutionException;

/**
* 添加緩存,若key存在,就覆蓋舊值
*/
void put(K key, V value);

/**
* 刪除該key關聯的緩存
*/
void invalidate(Object key);

/**
* 刪除所有緩存
*/
void invalidateAll();

/**
* 執行一些維護操作,包括清理緩存
*/
void cleanUp();
}

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章