Guava Cache(LoadingCache)

  • Post author:
  • Post category:其他



Guava Cache

guava cache是一个本地缓存。



优点

  • 线程安全的缓存,与ConcurrentMap相似,但前者增加了更多的元素失效策略,后者只能显示的移除元素。
  • 提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。定时回收有两种:按照写入时间,最早写入的最先回收;按照访问时间,最早访问的最早回收。
  • 监控缓存加载/命中情况。
  • 集成了多部操作,调用get方式,可以在未命中缓存的时候,从其他地方获取数据源(DB,redis),并加载到缓存中。



缺点

  • Guava Cache的超时机制不是精确的。
public static void main(String[] args) throws ExecutionException, InterruptedException{
        //缓存接口这里是LoadingCache,LoadingCache在缓存项不存在时可以自动加载缓存
        LoadingCache<Integer,Student> studentCache
                //CacheBuilder的构造函数是私有的,只能通过其静态方法newBuilder()来获得CacheBuilder的实例
                = CacheBuilder.newBuilder()
                //设置并发级别为8,并发级别是指可以同时写缓存的线程数
                .concurrencyLevel(8)
                //设置写缓存后8秒钟过期
                .expireAfterWrite(8, TimeUnit.SECONDS)
                //设置缓存容器的初始容量为10
                .initialCapacity(10)
                //设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
                .maximumSize(100)
                //设置要统计缓存的命中率
                .recordStats()
                //设置缓存的移除通知
                .removalListener(new RemovalListener<Object, Object>() {
                    @Override
                    public void onRemoval(RemovalNotification<Object, Object> notification) {
                        System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause());
                    }
                })
                //build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
                .build(
                        new CacheLoader<Integer, Student>() {
                            @Override
                            public Student load(Integer key) throws Exception {
                                System.out.println("load student " + key);
                                Student student = new Student();
                                student.setId(key);
                                student.setName("name " + key);
                                return student;
                            }
                        }
                );

        for (int i=0;i<20;i++) {
            //从缓存中得到数据,由于我们没有设置过缓存,所以需要通过CacheLoader加载缓存数据
            Student student = studentCache.get(1);
            System.out.println(student);
            //休眠1秒
            TimeUnit.SECONDS.sleep(1);
        }

        System.out.println("cache stats:");
        //最后打印缓存的命中率等 情况
        System.out.println(studentCache.stats().toString());
    }

输出结果:

cache stats:
CacheStats{hitCount=17, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=1348802, evictionCount=2}

原因:看看到在20此循环中命中次数是17次,未命中3次,这是因为我们设定缓存的过期时间是写入后的8秒,所以20秒内会失效两次,另外第一次获取时缓存中也是没有值的,所以才会未命中3次,其他则命中。

  • 不能持久化本地缓存



常用方法

  • V getIfPresent(Object key) 获取缓存中key对应的value,如果缓存没命中,返回null。
  • V get(K key) throws ExecutionException 获取key对应的value,若缓存中没有,则调用LocalCache的load方法,从数据源中加载,并缓存。
  • void put(K key, V value) 如果缓存有值,覆盖,否则,新增
  • void putAll(Map m);

    循环调用单个的方法
  • void invalidate(Object key); 删除缓存
  • void invalidateAll(); 清楚所有的缓存,相当远map的clear操作。
  • long size(); 获取缓存中元素的大概个数。

    为什么是大概呢?元素失效之时,并不会实时的更新size,所以这里的size可能会包含失效元素。
  • CacheStats stats(); 缓存的状态数据,包括(未)命中个数,加载成功/失败个数,总共加载时间,删除个数等。
  • asMap()方法获得缓存数据的ConcurrentMap快照
  • cleanUp()清空缓存
  • refresh(Key) 刷新缓存,即重新取缓存数据,更新缓存
  • ImmutableMap getAllPresent(Iterable keys) 一次获得多个键的缓存值



核心类

  • CacheBuilder:类,缓存构建器。构建缓存的入口,指定缓存配置参数并初始化本地缓存。

    CacheBuilder在build方法中,会把前面设置的参数,全部传递给LocalCache,它自己实际不参与任何计算。这种初始化参数的方法值得借鉴,代码简洁易读。
  • CacheLoader:抽象类。用于从数据源加载数据,定义load、reload、loadAll等操作。
  • Cache:接口,定义get、put、invalidate等操作,这里只有缓存增删改的操作,没有数据加载的操作。
  • AbstractCache:抽象类,实现Cache接口。其中批量操作都是循环执行单次行为,而单次行为都没有具体定义。
  • LoadingCache:接口,继承自Cache。定义get、getUnchecked、getAll等操作,这些操作都会从数据源load数据。
  • AbstractLoadingCache:抽象类,继承自AbstractCache,实现LoadingCache接口。
  • LocalCache:类。整个guava cache的核心类,包含了guava cache的数据结构以及基本的缓存的操作方法。
  • LocalManualCache:LocalCache内部静态类,实现Cache接口。

    其内部的增删改缓存操作全部调用成员变量 localCache(LocalCache类型)的相应方法。
  • LocalLoadingCache:LocalCache内部静态类,继承自LocalManualCache类,实现LoadingCache接口。

    其所有操作也是调用成员变量localCache(LocalCache类型)的相应方法。
  • CacheStats:缓存加载/命中统计信息。



个人工具类方法,仅供参考

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;

/**
 * @ClassName: CacheUtil
 * @Description: TODO
 */
public class CacheUtil {

    private static final Logger logger = LoggerFactory.getLogger(CacheUtil.class);
    private static final RedisUtil redisUtil = RedisUtil.getInstance();

    /**
     * load方式缓存
     * 
     * @param key
     * @param value
     */
    public static void putByLoad(String key, Object value) {
        String valStr = JSON.toJSONString(value);
        logger.info("[CacheUtil] putByLoad, key -> " + key + ", value -> " + valStr);
        CacheUtil.getInstanceForLoad().put(key, valStr);
        redisUtil.set(key, valStr);
    }

    /**
     * load方式读取单个bean
     * 
     * @param key
     * @param t
     * @return
     * @throws Exception
     */
    public static <T> T getObjectByLoad(String key, Class<T> t) {
        if (key == null)
            return null;
        String object = null;
        try {
            object = CacheUtil.getInstanceForLoad().get(key);
            logger.info("[CacheUtil] get from cache, key -> " + key + ", value -> " + object);
        } catch (Exception e) {
            logger.error("[CacheUtil] getByLoad error -> " + e);
        }
        if (object == null)
            return null;
        return (T) JSON.parseObject(object, t);
    }

    /**
     * load方式读取list
     * 
     * @param key
     * @param t
     * @return
     */
    public static <T> List<T> getListByLoad(String key, Class<T> t) {
        if (key == null)
            return null;
        String object = null;
        try {
            object = CacheUtil.getInstanceForLoad().get(key);
        } catch (Exception e) {
            logger.error("[CacheUtil] getByLoad error -> " + e);
        }
        if (object == null)
            return null;
        return JSON.parseArray(object, t);
    }

    /**
     * load方式读取map
     * 
     * @param key
     * @param t
     * @return
     */
    public static <T> Map<String, T> getMapByLoad(String key, Class<T> t) {
        if (key == null)
            return null;
        String object = null;
        try {
            object = CacheUtil.getInstanceForLoad().get(key);
            logger.info("[CacheUtil] get from cache, key -> " + key + ", value -> " + object);
        } catch (Exception e) {
            logger.error("[CacheUtil] getByLoad error -> " + e);
        }
        if (object == null)
            return null;
        Map<String, T> map = JSONObject.parseObject(object, new TypeReference<Map<String, T>>() {
        });
        for (String k : map.keySet()) {
            T o = JSON.parseObject(map.get(k).toString(), t);
            map.put(k, o);
        }
        return map;
    }

    /**
     * map方式缓存
     * 
     * @param key
     * @param value
     */
    public static void putByMap(String key, Object value) {
        CacheUtil.getInstanceForMap().put(key, JSON.toJSONString(value));
    }

    /**
     * map方式读取
     * 
     * @param key
     * @param t
     * @return
     */
    public static <T> T getByMap(String key, Class<T> t) {
        Object object = CacheUtil.getInstanceForMap().getIfPresent(key);
        if (object == null)
            return null;
        return (T) JSON.parseObject((String) object, t);
    }

    public static LoadingCache<String, String> getInstanceForLoad() {
        return SingleTonForLoad.cahceBuilder;
    }

    private static class SingleTonForLoad {
        private static LoadingCache<String, String> cahceBuilder = CacheBuilder.newBuilder()
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        redisUtil.expire(key, 0);
                        String re = redisUtil.get(key);
                        logger.info("[CacheUtil] get from redis, key -> " + key + ", value -> " + re);
                        return re;
                    }
                });
    }

    public static Cache<String, Object> getInstanceForMap() {
        return SingleTonForMap.cache;
    }

    private static class SingleTonForMap {
        private static Cache<String, Object> cache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES)
                .build();
    }

    public static void main(String[] args) {
        Map<String, CompetitionBean> map1 = Maps.newHashMap();
        CompetitionBean bean1 = new CompetitionBean();
        bean1.setCategory("足球");
        map1.put("1", bean1);
        CacheUtil.putByLoad("a", map1);
        System.err.println(JSON.toJSONString(CacheUtil.getMapByLoad("a", CompetitionBean.class)));

        Map<String, CompetitionBean> map2 = Maps.newHashMap();
        CompetitionBean bean2 = new CompetitionBean();
        bean2.setCategory("篮球");
        map2.put("2", bean2);
        CacheUtil.putByLoad("a", map2);
        System.err.println(JSON.toJSONString(CacheUtil.getMapByLoad("a", CompetitionBean.class)));
    }
}



参考




https://www.cnblogs.com/shoren/p/guava_cache.html



版权声明:本文为u012187218原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。