Guava---最全缓存cache讲解

目录

    • 创建LoadingCache
    • 初始化大小,最大个数
    • 三种时间设置
    • 基于引用的回收--强(strong)、软(soft)、弱(weak)
    • 监听器
    • 缓存命中统计
    • 完整参数配置
    • 后续

创建LoadingCache

LoadingCache cache = CacheBuilder.newBuilder()
                .build(new CacheLoader() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return key.hashCode();
                    }
                });

初始化大小,最大个数

个数设置

LoadingCache cache = CacheBuilder.newBuilder()
                .initialCapacity(10)//初始化个数
                .maximumSize(55)//设置最大个数
                .build(new CacheLoader() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return key.hashCode();
                    }
                });

注:初始化如何太小,会导致扩容,比较浪费时间,太大浪费内存.
最大数太大,可能导致内存溢出.

重量设置

LoadingCache cache = CacheBuilder.newBuilder()
                .maximumWeight(1000) //设置重量,配合weigher使用
                .weigher(new Weigher() {
                    @Override
                    public int weigh(Object key, Object value) {
                        return 100;
                    }
                })
                .build(new CacheLoader() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return key.hashCode();
                    }
                });

weigher相当一杆秤,称每个元数多重,maximumWeight相当总重量.一般用的较少.

三种时间设置

过期时间

LoadingCache cache = CacheBuilder.newBuilder()
                .expireAfterAccess(10, TimeUnit.SECONDS) //多长时间未读写后过期
                .expireAfterWrite(10, TimeUnit.SECONDS)  //多长时间未写后过期
                .build(new CacheLoader() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return key.hashCode();
                    }
                });

注:元数过期,guava并不会自动回收,它会在写操作时顺带做少量的维护工作,或者偶尔在读操作时做——如果写操作实在太少的话。 这样做的原因在于:如果要自动地持续清理缓存,就必须有一个线程,这个线程会和用户操作竞争共享锁。此外,某些环境下线程创建可能受限制,这样CacheBuilder就不可用了。
相反,我们把选择权交到你手里。如果你的缓存是高吞吐的,那就无需担心缓存的维护和清理等工作。如果你的 缓存只会偶尔有写操作,而你又不想清理工作阻碍了读操作,那么可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp()。ScheduledExecutorService可以帮助你很好地实现这样的定时调度。

刷新时间

public static void main(String[] args) throws ExecutionException, InterruptedException {
        // guava线程池,用来产生ListenableFuture
        ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
        LoadingCache cache = CacheBuilder.newBuilder()
                //指定时间内没有被创建/覆盖,则指定时间过后,再次访问时,会去刷新该缓存,在新值没有到来之前,始终返回旧值
                .refreshAfterWrite(2, TimeUnit.SECONDS)
                .build(new CacheLoader() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return System.currentTimeMillis();
                    }
						
				//重写reload,使其异步刷新数据
                    @Override
                    public ListenableFuture reload(Object key, Object oldValue) throws Exception {
                        System.out.println("......后台线程池异步刷新:" + key);
                        return service.submit(new Callable() { //模拟一个需要耗时2s的数据库查询任务
                            @Override
                            public Object 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() + key;
                            }
                        });

                    }
                });
    }
 
  

分析:

  • 使用expireAfterAccess或expireAfterWrite时,当缓存过期后,恰好有N个客户端发起请求,需要读取值。使用Guava Cache可以保证只让一个线程去加载数据(比如从数据库中),而其他线程则等待这个线程的返回结果.这样就能避免大量用户请求穿透缓存,但同时也降低了吞吐量.
  • refreshAfterWrite: 当缓存数据过期的时候,真正去加载数据的线程会阻塞一段时间,其余线程立马返回过期的值,然这种处理方式更符合实际的使用场景。
  • 真正加载数据的那个线程一定会阻塞,我们希望这个加载过程是异步的。这样就可以让所有线程立马返回旧值,在后台刷新缓存数据。refreshAfterWrite默认的刷新是同步的,会在调用者的线程中执行。我们可以改造成异步的,实现CacheLoader.reload()。上面的代码就将其实现.

基于引用的回收–强(strong)、软(soft)、弱(weak)

LoadingCache cache = CacheBuilder.newBuilder()
                .softValues()
                .weakKeys()
                .weakValues()
                .build(new CacheLoader() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return System.currentTimeMillis();
                    }
                });

强(strong)、软(soft)、弱(weak)请参考: Guava—缓存之Reference

监听器

LoadingCache cache = CacheBuilder.newBuilder()
                .removalListener(new RemovalListener() {
                    @Override
                    public void onRemoval(RemovalNotification notification) {
                        /**
                         *RemovalCause 枚举
                         * 标明是什么情况下 被移除的
                         */
                        RemovalCause cause = notification.getCause();
                        if (notification.wasEvicted()) { //是否被移除(排除主动删除,和替换)
                            System.out.println(notification.getKey());
                            System.out.println(notification.getValue());
                        }
                    }
                })
                .build(new CacheLoader() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return System.currentTimeMillis();
                    }
                });

缓存命中统计

 LoadingCache cache = CacheBuilder.newBuilder()
                .recordStats()//统计
                .build(new CacheLoader() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return System.currentTimeMillis();
                    }
                });


        CacheStats stats = cache.stats(); //不可变对象
        stats.hitCount(); //命中次数
        stats.hitRate();  //命中概率
        stats.missCount();
        stats.missRate();

完整参数配置

ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
    LoadingCache cache = CacheBuilder.newBuilder()
            .initialCapacity(10)//初始化个数
            .maximumSize(55)//设置最大个数
            .maximumWeight(1000) //设置重量,配合weigher使用
            .weigher(new Weigher() { //weigher相当一杆秤,称每个元数多重
                @Override
                public int weigh(String key, Object value) {
                    return 100;
                }
            })
            .expireAfterAccess(10, TimeUnit.SECONDS) //多长时间未读写后过期
            .expireAfterWrite(10, TimeUnit.SECONDS)  //多长时间未写后过期
            //指定时间内没有被创建/覆盖,则指定时间过后,再次访问时,会去刷新该缓存,在新值没有到来之前,始终返回旧值
            .refreshAfterWrite(2, TimeUnit.SECONDS)
            .concurrencyLevel(1) //写的并发数
            .softValues() //软引用
            .weakKeys() //弱引用
            .weakValues() //弱引用
            .recordStats() //统计的
            .removalListener(new RemovalListener() {
                @Override
                public void onRemoval(RemovalNotification notification) {
                    /**
                     *RemovalCause 枚举
                     * 标明是什么情况下 被移除的
                     */
                    RemovalCause cause = notification.getCause();
                    if (notification.wasEvicted()) { //是否被移除(排除主动删除,和替换)
                        System.out.println(notification.getKey() + notification.getValue());
                    }
                }
            })
            .build(new CacheLoader() { //若没有元素,则创建并且放入缓存
                @Override
                public Object load(Object key) throws Exception {
                    return System.currentTimeMillis();
                }

                @Override
                public ListenableFuture reload(Object key, Object oldValue) throws Exception {
                    System.out.println("......后台线程池异步刷新:" + key);
                    return service.submit(new Callable() { //模拟一个需要耗时2s的数据库查询任务
                        @Override
                        public Object 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() + key;
                        }
                    });
                }
            });
 
  

上述算是完整的LoadingCache, 按照实际业务自行配置参数

后续

Spring5放弃掉Guava Cache作为缓存机制,而改用Caffeine作为新的本地Cache的组件。
这个组件目前还没接触到,据说效率很高,待后续…

你可能感兴趣的:(Guava)