LoadingCache的使用 guava cache详细介绍 (缓存)

文章目录

    • 优点
    • 缺点
    • 3. 常用方法
    • 4.核心类
    • load()和loadAll()的作用:
    • 1. 生成一个LoadingCache对象
      • 项目中用到的
    • refreshAfterWrite

参考文章,包含创建的不同的方式
缓存的key的刷新机制
刷新机制,参考文章2

优点

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

缺点

超时机制不是精确的。

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次,其他则命中。

  • 不能持久化本地缓存

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) 一次获得多个键的缓存值

4.核心类

  • 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;

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)));
    }
}

load()和loadAll()的作用:

参考博文,点击这里
在每次从cache中get(K)时,如果不存在会自动调用load方法原子的将值计算出来并加到缓存中。(调用load方法是同步的)

getAll(Iterable)方法用来执行批量查询注意:getAll()方法也是guava cache中的方法。默认情况下,对每个不在缓存中的键,getAll方法会单独调用CacheLoader.load来加载缓存项。如果批量的加载比多个单独加载更高效,可以重载CacheLoader.loadAll来利用这一点提示getAll(Iterable)的性能。看一个例子:

1. 生成一个LoadingCache对象

参考文章,点击这里

 
  LoadingCache userCache = CacheBuilder.newBuilder()
                .maximumSize(10000))//设置缓存上线
                .expireAfterAccess(10, TimeUnit.MINUTES)//设置时间对象没有被读/写访问则对象从内存中删除
                .expireAfterWrite(10, TimeUnit.MINUTES)//设置时间对象没有被写访问则对象从内存中删除
                //移除监听器,缓存项被移除时会触发
                .removalListener(new RemovalListener<String, UserProfile>() {
                    @Override
                    public void onRemoval(RemovalNotification<String, UserProfile> notification) {
                       //逻辑
                        }
                    }
                })
                .recordStats()
                //CacheLoader类 实现自动加载
                .build(new CacheLoader<String, Object>() {
                    @Override
                    public Object load(String key) {
                       //从SQL或者NoSql 获取对象
                    }
                });

项目中用到的

    Map<MetaCacheKey, CacheValue<T>> cacheRes = cache.getAll(keys);
            for (Map.Entry<MetaCacheKey, CacheValue<T>> cacheMapEntry: cacheRes.entrySet()) {
                CacheValue<T> cacheValue = cacheMapEntry.getValue();
                if (cacheValue.getV() != null) {
                    res.put(cacheMapEntry.getKey().getOriginHashId(), cacheValue);
                    checkExpire(cacheMapEntry.getKey(), cacheValue);
                } else {
                    cache.invalidate(cacheMapEntry.getKey());
                }
            }

refreshAfterWrite

refreshAfterWrite的使用参考文章
参考文章2

主要参考文章,点击这里

package net.aty.guava;
 
import com.google.common.base.Stopwatch;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
 
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
 
 
public class Main {
 
    // 模拟一个需要耗时2s的数据库查询任务
    private static Callable<String> callable = new Callable<String>() {
        @Override
        public String call() throws Exception {
            System.out.println("begin to mock query db...");
            Thread.sleep(2000);
            System.out.println("success to mock query db...");
            return UUID.randomUUID().toString();
        }
    };
 
 
    // 1s后刷新缓存
    private static LoadingCache<String, String> cache = CacheBuilder.newBuilder().refreshAfterWrite(1, TimeUnit.SECONDS)
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    return callable.call();
                }
            });
 
    private static CountDownLatch latch = new CountDownLatch(1);
 
 
    public static void main(String[] args) throws Exception {
 
        // 手动添加一条缓存数据,睡眠1.5s让其过期
        cache.put("name", "aty");
        Thread.sleep(1500);
 
        for (int i = 0; i < 8; i++) {
            startThread(i);
        }
 
        // 让线程运行
        latch.countDown();
 
    }
 
    private static void startThread(int id) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + "...begin");
                    latch.await();
                    Stopwatch watch = Stopwatch.createStarted();
                    System.out.println(Thread.currentThread().getName() + "...value..." + cache.get("name"));
                    watch.stop();
                    System.out.println(Thread.currentThread().getName() + "...finish,cost time=" + watch.elapsed(TimeUnit.SECONDS));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
 
        t.setName("Thread-" + id);
        t.start();
    }
 
 
}

你可能感兴趣的:(开发中常见的一些方法,java)