zoukankan      html  css  js  c++  java
  • Guava 内存缓存的使用

    一、概述

    guava⽬前有三种刷新本地缓存的机制:

    • expireAfterAccess:当缓存项在指定的时间段内没有被读或写就会被回收。
    • expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收。-- 常用
    • refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。 -- 常用

    二、原理

    expireAfterWrite 为了避免缓存雪崩,guava 会限制只有一个加载操作时进行加锁,其他请求必须阻塞等待这个加载操作完成。而且,在加载完成之后,其他请求的线程会逐一获得锁,去判断是否已被加载完成,每个线程必须轮流地走一个“获得锁,获得值,释放锁”的过程,这样性能会有一些损耗。

    refreshAfterWrite 当缓存项上一次更新操作之后的多久会被刷新。在 refresh 的过程中,guava 会限制只有一个加载操作时进行加锁,而其他查询先返回旧值,这样能有效减少等待和锁争用,所以 refreshAfterWrite 会比 expireAfterWrite 性能好。

    Load 加锁是从从 expire 到 load 到新值为⽌,⽽ refresh->reload 的过程,⼀旦 get 发现需要 refresh,会先判断是否有 loading,再去获得锁,然后释放锁之后再去reload,阻塞的范围只是 insertLoadingValueReference 的⼀个⼩对象的 new 和 set 操作,⼏乎可以忽略不计。

    三、实践

    首先了解⼀个机制,guava 不会⾃动清除清除数据,只有在访问时候再去判断 expire。

    设置合理的 expireAfterWrite 和 refreshAfterWrite 时间来保证缓存不会被瞬间击垮。根据合理的场景设置合理的参数。比如 expireAfterWrite=60 和 refreshAfterWrite=30,保证每隔 30 秒刷新⼀次数据。但是超过 60 秒⼀直没有访问量,突然间访问,还是会触发 load 操作。

    expireAfterWrite 是为了保证在很久没有访问量,⾸次访问不再访问旧值。⽐如已经间隔⼀天,首次访问希望获取最新值,而不是先获取旧值再去刷新新值,这种就可⽤通过设置expireAfterWrite来保证。

    其实如果极端情况下,即新旧值基本不会变更的,直接不采⽤ expireAfterWrite,⽽直接采⽤ refreshAfterWrite 来执⾏ load 也是可以的,性能会更优。

    public class CacheBuilderTest {
    
        private static AtomicInteger atomicInteger = new AtomicInteger();
    
        public static void main(String[] args) throws InterruptedException {
            LoadingCache<String, String> loadingCache = CacheBuilder.newBuilder()
                    .expireAfterWrite(60, TimeUnit.SECONDS)
                    .refreshAfterWrite(30, TimeUnit.SECONDS)
                    .maximumSize(10)
                    .removalListener(new RemovalListener() {
                        @Override
                        public void onRemoval(RemovalNotification notification) {
                            System.out.println("key:" + notification.getKey() + ", remove!");
                        }
                    })
                    .recordStats()
                    .build(new CacheLoader<String, String>() {
    
                        @Override
                        public String load(String key) throws Exception {
                            System.out.println("key:" + key + ", load");
                            return "Hello Guava Cache " + key + "load";
                        }
                        
                        @Override
                        public ListenableFuture<String> reload(String key, String oldValue) throws Exception {
                            ListenableFutureTask<String> listenableFutureTask = ListenableFutureTask.create(() -> {
                                System.out.println("key:" + key + ", reload");
                                return "Hello Guava Cache " + key + "reload" + atomicInteger.incrementAndGet();
                            });
                            CompletableFuture.runAsync(listenableFutureTask);
                            return listenableFutureTask;
                        }
                    });
    
            while (true) {
                String lucky = loadingCache.getUnchecked("lucky");
                System.out.println(lucky);
                CacheStats stats = loadingCache.stats();
                System.out.println("缓存命中率:" + stats.hitCount());
                System.out.println("加载新值的平均时间,单位为毫秒:" + stats.averageLoadPenalty() / 10000);
                System.out.println("缓存项被回收的总数,不包括显式清除:" + stats.evictionCount());
                Thread.sleep(65000);
                System.out.println("---------------------------------------------------------");
            }
        }
    }
    
  • 相关阅读:
    learning scala view collection
    scala
    learning scala dependency injection
    learning scala implicit class
    learning scala type alise
    learning scala PartialFunction
    learning scala Function Recursive Tail Call
    learning scala Function Composition andThen
    System.Threading.Interlocked.CompareChange使用
    System.Threading.Monitor的使用
  • 原文地址:https://www.cnblogs.com/jmcui/p/15131915.html
Copyright © 2011-2022 走看看