Guava Cache详解

Posted ding-dang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Guava Cache详解相关的知识,希望对你有一定的参考价值。

适用性

  缓存在很多场景下都是相当有用的。例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时候,就应当考虑使用缓存

  Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存

  通常来说,Guava Cache适用于:

    • 你愿意消耗一些内存空间来提升速度。
    • 你预料到某些键会被查询一次以上。
    • 缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Redis这类工具)

  如果你的场景符合上述的每一条,Guava Cache就适合你。

  如果你不需要Cache中的特性,使用ConcurrentHashMap有更好的内存效率——但Cache的大多数特性都很难基于旧有的ConcurrentMap复制,甚至根本不可能做到

代码详解

  1 /**
  2  * @author LiuHuan
  3  * @date 2020-06-17 15:52
  4  * @desc Guava Cache学习
  5  */
  6 public class GuavaCacheTest {
  7 
  8     public static void main(String[] args) throws ExecutionException {
  9         GuavaCacheTest test = new GuavaCacheTest();
 10         Cache<String, String> cache = test.getGuavaCache();
 11         // 放入/覆盖一个缓存
 12         cache.put("key", "value");
 13         // 获取一个缓存,如果该缓存不存在则返回一个null值
 14         cache.getIfPresent("");
 15         // 获取缓存,当缓存不存在时,则通Callable进行加载并返回。该操作是原子
 16         cache.get("key", () -> loadingValue("key"));
 17         // 回收key为k1的缓存
 18         cache.invalidate("key");
 19         // 使用Map的put方法进行覆盖刷新
 20         cache.asMap().put("key", "value");
 21         // 使用ConcurrentMap的replace方法进行覆盖刷新
 22         cache.asMap().replace("key", "value1");
 23         // 使用Map的putAll方法进行批量覆盖刷新
 24         Map<String,String> needRefresh = ImmutableMap.of("key1","value1", "key2", "value2");
 25         cache.asMap().putAll(needRefresh);
 26         // 批量回收key为key1、key2的缓存
 27         List<String> needInvalidateKeys = Arrays.asList("key1", "key2");
 28         cache.invalidateAll(needInvalidateKeys);
 29         // 回收所有缓存
 30         cache.invalidateAll();
 31 
 32         // 用来开启Guava Cache的统计功能。统计打开后,Cache.stats()方法会返回CacheStats对象
 33         CacheStats stats = cache.stats();
 34         // 缓存命中率
 35         double hitRate = stats.hitRate();
 36         // 加载新值的平均时间,单位为纳秒
 37         double averageLoadPenalty = stats.averageLoadPenalty();
 38         // 缓存项被回收的总数,不包括显式清除
 39         long evictionCount = stats.evictionCount();
 40 
 41         LoadingCache<String, String> loadingCache = test.getGuavaLoadingCache();
 42         // loadingCache 在进行刷新时无需显式的传入value
 43         loadingCache.refresh("key");
 44     }
 45 
 46     /**
 47      * 获取GuavaCache实例
 48      * @return
 49      */
 50     public Cache<String, String> getGuavaCache(){
 51         // 异步触发监听器
 52         RemovalListener<Object, Object> removalListener = RemovalListeners.asynchronous(removal -> {
 53             // 如果被显示移除这里为true
 54             boolean wasEvicted = removal.wasEvicted();
 55             // 移除的原因
 56             RemovalCause cause = removal.getCause();
 57         }, Executors.newSingleThreadExecutor());
 58         
 59         // 通过CacheBuilder构建一个缓存实例
 60         Cache<String, String> cache = CacheBuilder.newBuilder()
 61             // 由于Guava的缓存使用了分离锁的机制,扩容的代价非常昂贵,所以合理的初识容量能够减少扩容次数
 62             .initialCapacity(100)
 63             // 设置缓存的最大条数
 64             .maximumSize(100)
 65             // maximumWeight逻辑上用来表示一种“权重”,这里与maximumSize冲突,设置一个即可
 66             // 这里我们将key和value所占的字节数,作为weight,当cache中所有的“weight”总和达到maximumWeight时,将会触发“剔除策略”
 67             .maximumWeight(1024 * 1024)
 68             .weigher((Weigher<String, String>)(key, value) -> key.getBytes().length + value.getBytes().length)
 69             // 使用弱引用存储键。当键没有其它(强或软)引用时,该缓存可能会被回收
 70             .weakKeys()
 71             // 使用弱引用存储值。当值没有其它(强或软)引用时,该缓存可能会被回收
 72             .weakValues()
 73             // 使用软引用存储值。当内存不足并且该值其它强引用引用时,该缓存就会被回收
 74             // 通过软/弱引用的回收方式,相当于将缓存回收任务交给了GC,使得缓存的命中率变得十分的不稳定,在非必要的情况下,还是推荐基于数量和容量的回收
 75             .softValues()
 76             // 设置缓存在写入一分钟后失效
 77             .expireAfterWrite(1, TimeUnit.MINUTES)
 78             // 设置缓存最后一次访问10分钟之后失效,与session类似。与expireAfterWrite冲突,设置一个即可
 79             .expireAfterAccess(Duration.ofMinutes(10))
 80             // 开启缓存统计
 81             .recordStats()
 82             // 设置并发级别为CPU核心数
 83             .concurrencyLevel(Runtime.getRuntime().availableProcessors())
 84             // 移除监听器,这里是移除缓存时同步调用,会拖慢正常的请求
 85             .removalListener(removal -> {
 86                 // 如果被显示移除这里为true
 87                 boolean wasEvicted = removal.wasEvicted();
 88                 // 移除的原因
 89                 RemovalCause cause = removal.getCause();
 90             })
 91             // 异步触发监听器
 92             .removalListener(removalListener)
 93             .build();
 94         return cache;
 95     }
 96 
 97     /**
 98      * 获取GuavaLoadingCache实例,会有默认的缓存加载策略
 99      * @return
100      */
101     public LoadingCache<String, String> getGuavaLoadingCache(){
102         // 通过CacheBuilder构建一个缓存实例
103         LoadingCache<String, String> cache = CacheBuilder.newBuilder()
104             // 设置缓存在写入10分钟后,通过CacheLoader的load方法进行刷新
105             .refreshAfterWrite(Duration.ofMinutes(10))
106             .build(new CacheLoader<String, String>() {
107                 @Override
108                 public String load(String key) throws Exception {
109                     // 缓存加载策略
110                     return loadingValue(key);
111                 }
112 
113             });
114         return cache;
115     }
116 
117     /**
118      * 缓存加载策略
119      * @param key
120      * @return
121      */
122     private static String loadingValue(String key){
123         return null;
124     };
125 
126 }

缓存清理  

  使用CacheBuilder构建的缓存不会"自动"执行清理和回收工作,也不会在某个缓存项过期后马上清理,也没有诸如此类的清理机制。相反,它会在写操作时顺带做少量的维护工作,如果写操作实在太少的话,偶尔在读操作时做这个操作。这样做的原因在于:如果要自动地持续清理缓存,就必须有一个线程,这个线程会和用户操作竞争共享锁。此外,某些环境下线程创建可能受限制,这样CacheBuilder就不可用了。

  如果你的缓存是高吞吐的,那就无需担心缓存的维护和清理等工作。如果你的缓存只会偶尔有写操作,而你又不想清理工作阻碍了读操作,那么可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp()ScheduledExecutorService可以帮助你很好地实现这样的定时调度

 

以上是关于Guava Cache详解的主要内容,如果未能解决你的问题,请参考以下文章

guava collection/cache初探

Yii2片段缓存详解

使用google guava做内存缓存

Guava Cache 缓存工具使用

Guava Cache 缓存工具使用

guava cache 为啥删除元素时移动元素