写点什么

干掉 GuavaCache:Caffeine 才是本地缓存的王

  • 2021 年 11 月 11 日
  • 本文字数:2407 字

    阅读完需:约 8 分钟

  1. 自动把数据加载到本地缓存中,并且可以配置异步;

  2. 基于数量剔除策略;

  3. 基于失效时间剔除策略,这个时间是从最后一次访问或者写入算起;

  4. 异步刷新;

  5. Key 会被包装成 Weak 引用;

  6. Value 会被包装成 Weak 或者 Soft 引用,从而能被 GC 掉,而不至于内存泄漏;

  7. 数据剔除提醒;

  8. 写入广播机制;

  9. 缓存访问可以统计;


使用


=====================================================================


Caffeine 使用还是非常简单的,如果你用过 GuavaCache,那就更简单了,因为 Caffeine 的 API 设计大量借鉴了 GuavaCache。首先,引入 Maven 依赖:


<dependency>


<groupId>com.github.ben-manes.caffeine</groupId>


<artifactId>caffeine</artifactId>


<version>2.8.4</version>


</dependency>


然后构造 Cache 使用即可:


Cache<String, String


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


cache = Caffeine.newBuilder()


// 数量上限


.maximumSize(1024)


// 过期机制


.expireAfterWrite(5, TimeUnit.MINUTES)


// 弱引用 key


.weakKeys()


// 弱引用 value


.weakValues()


// 剔除监听


.removalListener((RemovalListener<String, String>) (key, value, cause) ->


System.out.println("key:" + key + ", value:" + value + ", 删除原因:" + cause.toString()))


.build();


// 将数据放入本地缓存中


cache.put("username", "afei");


cache.put("password", "123456");


// 从本地缓存中取出数据


System.out.println(cache.getIfPresent("username"));


System.out.println(cache.getIfPresent("password"));


System.out.println(cache.get("blog", key -> {


// 本地缓存没有的话,从数据库或者 Redis 中获取


return getValue(key);


}));


当然,使用本地缓存时,我们也可以使用异步加载机制:


AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()


// 数量上限


.maximumSize(2)


// 失效时间


.expireAfterWrite(5, TimeUnit.MINUTES)


.refreshAfterWrite(1, TimeUnit.MINUTES)


// 异步加载机制


.buildAsync(new CacheLoader<String, String>() {


@Nullable


@Override


public String load(@NonNull String key) throws Exception {


return getValue(key);


}


});


System.out.println(cache.get("username").get());


System.out.println(cache.get("password").get(10, TimeUnit.MINUTES));


System.out.println(cache.get("username").get(10, TimeUnit.MINUTES));


System.out.println(cache.get("blog").get());


接下来,我们对一些重要特性进行更加深入的分析。

过期机制

本地缓存的过期机制是非常重要的,因为本地缓存中的数据并不像业务数据那样需要保证不丢失。本地缓存的数据一般都会要求保证命中率的前提下,尽可能的占用更少的内存,并可在极端情况下,可以被 GC 掉。


Caffeine 的过期机制都是在构造 Cache 的时候申明,主要有如下几种:


  1. expireAfterWrite:表示自从最后一次写入后多久就会过期;

  2. expireAfterAccess:表示自从最后一次访问(写入或者读取)后多久就会过期;

  3. expireAfter:自定义过期策略;

刷新机制

在构造 Cache 时通过 refreshAfterWrite 方法指定刷新周期,例如 refreshAfterWrite(10, TimeUnit.SECONDS)表示 10 秒钟刷新一次:


.build(new CacheLoader<String, String>() {


@Override


public String load(String k) {


// 这里我们就可以从数据库或者其他地方查询最新的数据


return getValue(k);


}


});


需要注意的是,Caffeine 的刷新机制是**「被动」的。举个例子,假如我们申明了 10 秒刷新一次。我们在时间 T 访问并获取到值 v1,在 T+5 秒的时候,数据库中这个值已经更新为 v2。但是在 T+12 秒,即已经过了 10 秒我们通过 Caffeine 从本地缓存中获取到的「还是 v1」**,并不是 v2。在这个获取过程中,Caffeine 发现时间已经过了 10 秒,然后会将 v2 加载到本地缓存中,下一次获取时才能拿到 v2。即它的实现原理是在 get 方法中,调用 afterRead 的时候,调用 refreshIfNeeded 方法判断是否需要刷新数据。这就意味着,如果不读取本地缓存中的数据的话,无论刷新时间间隔是多少,本地缓存中的数据永远是旧的数据!

剔除机制

在构造 Cache 时可以通过 removalListener 方法申明剔除监听器,从而可以跟踪本地缓存中被剔除的数据历史信息。根据 RemovalCause.java 枚举值可知,剔除策略有如下 5 种:


  • 「EXPLICIT」:调用方法(例如:cache.invalidate(key)、cache.invalidateAll)显示剔除数据;

  • 「REPLACED」:不是真正被剔除,而是用户调用一些方法(例如:put(),putAll()等)改了之前的值;

  • 「COLLECTED」:表示缓存中的 Key 或者 Value 被垃圾回收掉了;

  • 「EXPIRED」: expireAfterWrite/expireAfterAccess 约定时间内没有任何访问导致被剔除;

  • 「SIZE」:超过 maximumSize 限制的元素个数被剔除的原因;


GuavaCache 和 Caffeine 差异


========================================================================================


  1. 剔除算法方面,GuavaCache 采用的是**「LRU」算法,而 Caffeine 采用的是「Window TinyLFU」**算法,这是两者之间最大,也是根本的区别。

  2. 立即失效方面,Guava 会把立即失效 (例如:expireAfterAccess(0) and expireAfterWrite(0)) 转成设置最大 Size 为 0。这就会导致剔除提醒的原因是 SIZE 而不是 EXPIRED。Caffiene 能正确识别这种剔除原因。

  3. 取代提醒方面,Guava 只要数据被替换,不管什么原因,都会触发剔除监听器。而 Caffiene 在取代值和先前值的引用完全一样时不会触发监听器。

  4. 异步化方方面,Caffiene 的很多工作都是交给线程池去做的(默认:ForkJoinPool.commonPool()),例如:剔除监听器,刷新机制,维护工作等。


内存占用对比


=========================================================================


Caffeine 可以根据使用情况延迟初始化,或者动态调整它内部数据结构。这样能减少对内存的占用。如下图所示,使用了 gradle memoryOverhead 对内存占用进行了压测。结果可能会受到 JVM 的指针压缩、对象 Padding 等影响:



LRU P.K. W-TinyLFU

评论

发布
暂无评论
干掉GuavaCache:Caffeine才是本地缓存的王