zoukankan      html  css  js  c++  java
  • Guava的两种本地缓存策略

    Guava的两种缓存策略


    缓存在很多场景下都需要使用,如果电商网站的商品类别的查询,订单查询,用户基本信息的查询等等,针对这种读多写少的业务,都可以考虑使用到缓存。在一般的缓存系统中,除了分布式缓存,还会有多级缓存,在提升一定性能的前提下,可以在一定程度上避免缓存击穿或缓存雪崩,也能降低分布式缓存的负载。

    GuavaCache的优点

    1)很好的封装了get、put操作,能够集成数据源。一般我们在业务中操作缓存都会操作缓存和数据源两部分。例如:put数据时,先插入DB再删除原来的缓存,get数据时,先查缓存,命中则返回,没有命中时需要查询DB,再把查询结果放入缓存中。Guava封装了这么多步骤,只需要调用一次get/put方法即可

    2)它是线程安全的缓存,与ConcurrentMap相似,但前者增加了更多的元素失效策略,后者只能显示的移除元素

    3)GuavaCache提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。定时回收有两种:按照写入时间,最早写入的最先回收;按照访问时间,最早访问的最早回收

    4)它可以监控加载/命中情况

    Cache类型本地缓存

    package com.mine.localcache.guava;
    
    import com.google.common.cache.Cache;
    import com.google.common.cache.CacheBuilder;
    
    import java.util.Map;
    import java.util.concurrent.Callable;
    import java.util.concurrent.TimeUnit;
    
    /**
     * ******************************
     * author:      柯贤铭
     * createTime:   2019/7/30 14:21
     * description:  Guava 本地缓存 -> Cache类型
     *               用于SpringBoot项目中,启用单例模式 项目启动时进行初始化
     * pay attention -> A. 注意不要重复实例化, 最好交由IOC管理
     *                  B. 注意如果是写操作则获取缓存值后拷贝一份副本,然后传递该副本,进行修改操作
     *                  C. 支持自定义call回调
     * version:      V1.0
     * ******************************
     */
    public class CacheUtil {
    
        /***
         * 构造方法 - 进行初始化
         * @param maxSize      最大容量
         * @param invalidTime  刷新时间 | 基于分钟级别
         */
        public CacheUtil(long maxSize, long invalidTime) {
            init(maxSize, invalidTime);
        }
    
        /***
         * 初始化
         */
        private void init (long maxSize, long invalidTime) {
    
            // 缓存
            cache = CacheBuilder.newBuilder()
                    // 设置缓存在写入invalidTime分钟后失效
                    .expireAfterWrite(invalidTime, TimeUnit.MINUTES)
                    // 设置缓存个数
                    .maximumSize(maxSize)
                    .concurrencyLevel(Runtime.getRuntime().availableProcessors())
                    .recordStats()
                    .build();
        }
    
    
        /***
         * Guava Cache类型缓存
         */
        private Cache cache;
    
        /**
         * 对外暴露的方法 -> 从缓存中取value,没取到会返回null
         *
         */
        public Object getValue (String key) {
            return cache.getIfPresent(key);
        }
    
        /**
         * 对外暴露的方法 -> 从缓存中取value,没取到会执行call
         *
         */
        public Object getValue (String key, Callable callable) throws Exception {
            return cache.get(key, callable);
        }
    
        /**
         * 对外暴露的方法 -> put
         *
         */
        public void putValue (String key, Object value) {
            cache.put(key, value);
        }
    
        /**
         * 对外暴露的方法 -> putMap
         *
         */
        public void putMap (String key, Map map) {
            cache.putAll(map);
        }
    
        /**
         * 对外暴露的方法 -> 判断是否存在key
         *
         */
        public boolean constainsKey (String key) {
            return cache.asMap().containsKey(key);
        }
    }
    

    Loading类型缓存

    package com.mine.localcache.guava;
    
    import com.google.common.cache.CacheBuilder;
    import com.google.common.cache.CacheLoader;
    import com.google.common.cache.LoadingCache;
    import com.google.common.util.concurrent.ListenableFuture;
    import com.google.common.util.concurrent.ListeningExecutorService;
    import com.google.common.util.concurrent.MoreExecutors;
    
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * ******************************
     * author:      柯贤铭
     * createTime:   2019/7/30 14:21
     * description:  Guava 本地缓存 -> LoadingCache类型
     *               用于SpringBoot项目中,启用单例模式 项目启动时进行初始化
     *               博文参考: https://www.cnblogs.com/csonezp/p/10011031.html
     * pay attention -> A. 注意不要重复实例化, 最好交由IOC管理
     *                  B. 注意重写与之匹配的数据源获取方法 - getFromDB
     *                  C. 注意如果是写操作则获取缓存值后拷贝一份副本,然后传递该副本,进行修改操作
     *                  D. 注意绝对不要返回null值作为value, 会引发InvalidCacheLoadException异常
     *                     对于该情况可以自定义处理方式, 主动将其捕获
     *                  E. 此类型缓存提倡自动加载缓存数据, 因此尽量避免手动put
     *                     如果需要更灵活的方案可以使用Cache类型
     *                  F. 灵活设置参数, 启用自动失效策略或者自动刷新策略
     * version:      V1.0
     * ******************************
     */
    public class LoadingCacheUtil {
    
        /***
         * 构造方法 - 进行初始化
         * @param maxSize      最大容量
         * @param refreshTime  刷新时间 | 基于分钟级别
         */
        public LoadingCacheUtil(long maxSize, long refreshTime) {
            init(maxSize, refreshTime);
        }
    
        /***
         * 初始化
         */
        private void init (long maxSize, long refreshTime) {
            // 刷新线程池 -> 如果数据都没了则启用后台线程进行刷新,让用户无感知 -> 核心线程数 1, 最大线程数 2
            backgroundRefreshPools = MoreExecutors.listeningDecorator(new ThreadPoolExecutor(1, 2, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()));
    
            // 缓存
            cache = CacheBuilder.newBuilder()
                    // 缓存刷新时间
                    .refreshAfterWrite(refreshTime, TimeUnit.MINUTES)
                    // 设置缓存在写入invalidTime分钟后失效
                    //.expireAfterWrite(refreshTime, TimeUnit.MINUTES)
                    // 设置缓存个数
                    .maximumSize(maxSize)
                    .concurrencyLevel(Runtime.getRuntime().availableProcessors())
                    .recordStats()
                    .build(new CacheLoader<String, Object>() {
                        // 当本地缓存命没有中时,调用load方法获取结果并将结果缓存
                        @Override
                        public Object load(String appKey) {
                            return getFromDB(appKey);
                        }
    
                        // 刷新时,开启一个新线程异步刷新,老请求直接返回旧值,防止耗时过长
                        @Override
                        public ListenableFuture<Object> reload(String key, Object oldValue) {
                            return backgroundRefreshPools.submit(() -> getFromDB(key));
                        }
    
                        // 数据库进行查询
                        private Object getFromDB (String key) {
                            // return entryMapper.selectByName(name)
                            return null;
                        }
                    });
        }
    
        /**
         * 后台处理线程池
         */
        private ListeningExecutorService backgroundRefreshPools;
    
        /***
         * Guava LoadingCache类型缓存
         */
        private LoadingCache cache;
    
        /**
         * 对外暴露的方法 -> 从缓存中取value,没取到会自动重载缓存,如果载入为null则触发异常
         *
         */
        public Object getValue (String key) throws ExecutionException {
            return cache.get(key);
        }
    
        /**
         * 对外暴露的方法 -> 判断是否存在key
         *
         */
        public boolean constainsKey (String key) {
            return cache.asMap().containsKey(key);
        }
    }
    

    总结

    • 1.本地缓存其实很多种数据结构都支持,比如线程安全的ConcurrentHashMap,用该结构配合TimerTask定时清除key,也可以实现,但是一是自己写的代码肯定没有谷歌工具厉害,另外一点,缓存更重要的特性不是可存可取,而是可以自动的去识别哪些key更活跃,哪些key不活跃,删除掉,
      因此基于LRU算法,Google提供的Guava就可以很好的满足这一点

    • 2.Cache类型缓存更像ConcurrentHashMap,有点随便存随便取的意思,同时支持定时回收,也支持get不到缓存内容时走call回调接口去数据,总的来说非常方便

    • 3.LoadingCache类型缓存相比而言用的更加规范一些,它提供的思想是有一套完整的DB方案,提供定时刷新缓存,提供默认load方法,reload方法,相比于Cache,它要求更加严格,比如缓存内容不可返回null等等,也不建议手动put数据,而是专门通过DB的途径去刷新数据,因此真正的生产环境用的会更多一些

    QQ:806857264

    GitHub:https://github.com/kkzhilu

    如有什么问题,望指正,互相交流

  • 相关阅读:
    POJ 3660 Cow Contest (floyd求联通关系)
    POJ 3660 Cow Contest (最短路dijkstra)
    POJ 1860 Currency Exchange (bellman-ford判负环)
    POJ 3268 Silver Cow Party (最短路dijkstra)
    POJ 1679 The Unique MST (最小生成树)
    POJ 3026 Borg Maze (最小生成树)
    HDU 4891 The Great Pan (模拟)
    HDU 4950 Monster (水题)
    URAL 2040 Palindromes and Super Abilities 2 (回文自动机)
    URAL 2037 Richness of binary words (回文子串,找规律)
  • 原文地址:https://www.cnblogs.com/kkzhilu/p/12859497.html
Copyright © 2011-2022 走看看