Caffeine Cache基础入门

devtools/2024/10/19 6:25:02/

Caffeine Cache基础入门

Caffeine Cache 以其高性能和可扩展性赢得「 本地缓存之王 」的称号,它是一个 Java 缓存库。

Spring Boot 1.x 版本中的默认本地缓存是 Guava Cache。但在 Spring5 (SpringBoot 2.x)后,Spring 官方放弃了 Guava Cache 作为缓存机制,而是使用性能更优秀的 Caffeine 作为默认缓存组件。

Caffeine 官方测试报告:https://github.com/ben-manes/caffeine/wiki/Benchmarks-zh-CN

缓存介绍

image.png

Caffeine特点

1、自动将数据加载到缓存中,同时也可以采用异步的方式加载。

2、内存淘汰策略:基于频次、基于最近访问、最大容量。

3、根据上一次的缓存访问\上一次的数据写入决定缓存的过期的设置。

4、当一条缓存数据过期了,自动清理,清理的时候也是异步线程来做。

5、考虑JVM的内存管理机制,加入弱引用、软引用。

6、缓存数据被清理后,会收到相关的通知信息

7、缓存数据的写入可以传播到外部的存储。

8、统计功能:被访问次数,命中,清理的个数,加载个数

Caffeine Cache入门

官网地址:https://github.com/ben-manes/caffeine

Maven引入

   <!-- Spring boot Cache--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><!--for caffeine cache--><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.7.0</version></dependency>

Cache是一个核心的接口,里面定义了很多方法,我们要使用缓存一般是使用Cache的的子类,根据官方的方法,我们通过caffeine这个类来获得实现Cache的类。

基本使用-简单

Cache是一个核心的接口,里面定义了很多方法,我们要使用缓存一般是使用Cache的的子类,根据官方的方法,我们通过caffeine这个类来获得实现Cache的类。

 public static void Cache() throws Exception{Cache<String, String> cache = Caffeine.newBuilder()//构建一个新的Caffeine实例.maximumSize(100)//设置缓存中保存的最大数量.expireAfterAccess(3L, TimeUnit.SECONDS)//如无访问则3秒后失效.build();//构建Cache接口实例cache.put("mca","www.mashibing.com");//设置缓存项cache.put("baidu","www.baidu.com");//设置缓存项cache.put("spring","www.spring.io");//设置缓存项log.info("获取缓存[getIfPresent]:mca={}",cache.getIfPresent("mca"));//获取数据TimeUnit.SECONDS.sleep(5);//休眠5秒log.info("获取缓存[getIfPresent]:mca={}",cache.getIfPresent("mca"));//获取数据}

最普通的一种缓存,无需指定加载方式,需要手动调用 put()进行加载。需要注意的是,put()方法对于已存在的 key 将进行覆盖。如果这个值不存在,调用 getIfPresent()方法,则会立即返回 null,不会被阻塞。

上面显示效果如下:

image.png

Caffeine 配置说明:

  • initialCapacity=[integer]: 初始的缓存空间大小
  • maximumSize=[long]: 缓存的最大条数,如果达到最大个数,默认会丢弃最早添加的
  • maximumWeight=[long]: 缓存的最大权重
  • expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期
  • expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期
  • refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存
  • weakKeys: 打开 key 的弱引用
  • weakValues:打开 value 的弱引用
  • softValues:打开 value 的软引用
  • recordStats:开发统计功能

基本使用-过期数据的同步加载1

有些时候当缓存数据失效的时候,我们可能希望拿到缓存的时候不返回一个null,可以进行一些数的处理,自定义的进行一些操作,这就要用到Cache接口的get方法,这个get方法里面可以实现一个函数式接口,让我们对数据进行自定义的处理:

 public static void CacheExpire() throws Exception{Cache<String, String> cache = Caffeine.newBuilder()//构建一个新的Caffeine实例.maximumSize(100)//设置缓存中保存的最大数量.expireAfterAccess(3L, TimeUnit.SECONDS)//如无访问则3秒后失效.build();//构建Cache接口实例cache.put("mca","www.mashibing.com");//设置缓存项cache.put("baidu","www.baidu.com");//设置缓存项cache.put("spring","www.spring.io");//设置缓存项TimeUnit.SECONDS.sleep(5);//休眠5秒log.info("获取缓存[getIfPresent]:baidu={}",cache.getIfPresent("baidu"));//获取数据log.info("获取缓存[get]获取缓存:baidu={}",cache.get("baidu",(key)->{log.info("进入[失效处理]函数");try {TimeUnit.SECONDS.sleep(3);//休眠3秒} catch (InterruptedException e) {throw new RuntimeException(e);}log.info("[失效处理]:mca={}",cache.getIfPresent("mca"));//失效处理return key.toUpperCase();}));}

显示效果:

image.png

如果数据已经过期,然后调用了get方面里面的函数式接口之后,会自动的给缓存重新赋值,赋的值就是return返回的值。同时看打印的时间,就知道这里的重新赋值是同步的,会阻塞的。

基本使用-过期数据的同步加载2

Caffeine还提供有一个较为特殊的 Cacheloader 接口,这个接口的触发机制有些不太一样,它所采用的依然是同步的加载处理。

处理流程是:

1)首先在builder()的时候写上一个函数式接口(编写重新加载数据的流程)

2)获取数据的时候,通过getAll( )方法触发builder中的函数式接口流程,进行重新加载数据。

public static void LoadingCache() throws Exception{LoadingCache<String, String> cache = Caffeine.newBuilder().maximumSize(100)//设置缓存中保存的最大数量.expireAfterAccess(3L, TimeUnit.SECONDS)//如无访问则3秒后失效.build(new CacheLoader<String, String>() {@Overridepublic  String load( String key) throws Exception {log.info("正在重新加载数据...");TimeUnit.SECONDS.sleep(1);return key.toUpperCase();}});cache.put("mca","www.mashibing.com");//设置缓存项cache.put("baidu","www.baidu.com");//设置缓存项cache.put("spring","www.spring.io");//设置缓存项TimeUnit.SECONDS.sleep(5);//创建key的列表,通过cache.getAll()拿到所有key对应的值ArrayList<String> keys = new ArrayList<>();keys.add("mca");keys.add("baidu");keys.add("spring");//拿到keys对应缓存的值Map<String, String> map = cache.getAll(keys);for (Map.Entry<String, String> entry : map.entrySet()) {log.info("缓存的键:{}、缓存值:{}",entry.getKey(),entry.getValue());//获取数据}log.info("LoadingCache 方法结束");}

运行效果:

image.png

与之前的 get()的同步加载操作不同的是,这里使用了专属的功能接口完成了数据的加载,从实现的结构上来说的更加的标准化,符合于 Caffeine 自己的设计要求。

第一种方式是针对于临时的一种使用方法,第二种更加的统一,同时有模板效应

基本使用-过期数据的异步加载

假如你在拿去缓存数据的时候,如果有3个值都过期了,你使用的同步的方式得依次加载,这样阻塞等待的时间较长,所以这里可以使用异步的方式,就能同时进行加载。

 public static void AsyncLoadingCache () throws Exception{AsyncLoadingCache<String, String> cache = Caffeine.newBuilder().maximumSize(100)//设置缓存中保存的最大数量.expireAfterAccess(3L, TimeUnit.SECONDS).buildAsync(new CacheLoader<String, String>() {@Overridepublic  String load( String key) throws Exception {log.info("正在重新加载数据...");TimeUnit.SECONDS.sleep(1);return key.toUpperCase();}});//使用了异步的缓存之后,缓存的值都是被CompletableFuture给包裹起来的//所以在追加缓存和得到缓存的时候要通过操作CompletableFuture来进行cache.put("mca",CompletableFuture.completedFuture("www.mashibing.com"));//设置缓存项cache.put("baidu",CompletableFuture.completedFuture("www.baidu.com"));//设置缓存项cache.put("spring",CompletableFuture.completedFuture("www.spring.io"));//设置缓存项TimeUnit.SECONDS.sleep(5);//创建key的列表,通过cache.getAll()拿到所有key对应的值ArrayList<String> keys = new ArrayList<>();keys.add("mca");keys.add("baidu");keys.add("spring");//拿到keys对应缓存的值Map<String, String> map = cache.getAll(keys).get();for (Map.Entry<String, String> entry : map.entrySet()) {log.info("缓存的键:{}、缓存值:{}",entry.getKey(),entry.getValue());//获取数据}log.info("AsyncLoadingCache 方法结束");}

显示效果:

image.png

AsyncLoadingCache的父接口是AsyncCache,而AsycnCache和Cache接口是同级的。

image.png

缓存淘汰机制

缓存之中的数据内容不可能一直被保留,因为只要时间一到,缓存就应该将数据进行驱逐,但是除了时间之外还需要考虑到个问题,缓存数据满了之后呢?是不是也应该进行一些无用数据的驱逐处理呢?

Caffeine提供三类驱逐策略:基于大小(size-based),基于时间(time-based)和基于引用(reference-based)

基于大小

最大容量 和 最大权重 只能二选一作为缓存空间的限制

最大容量

最大容量,如果缓存中的数据量超过这个数值,Caffeine 会有一个异步线程来专门负责清除缓存,按照指定的清除策略来清除掉多余的缓存。

public static void ExpireMaxType() throws Exception{//Caffeine 会有一个异步线程来专门负责清除缓存Cache<String, String> cache = Caffeine.newBuilder()//将最大数量设置为一.maximumSize(1).expireAfterAccess(3L, TimeUnit.SECONDS).build();cache.put("name","张三");cache.put("age","18");System.out.println(cache.getIfPresent("name"));TimeUnit.MILLISECONDS.sleep(100);System.out.println(cache.getIfPresent("name"));System.out.println(cache.getIfPresent("age"));}

image.png

可以看到,"name"的数据已经被清除了

最大权重

最大权重,存入缓存的每个元素都要有一个权重值,当缓存中所有元素的权重值超过最大权重时,就会触发异步清除。

weigher 方法设置权重规则。

 public static void ExpireWeigherType() throws Exception{Cache<String, String> cache = Caffeine.newBuilder().maximumWeight(100).weigher(((key, value) -> {System.out.println("权重处理,key="+key+" value="+value);//这里直接返回一个固定的权重,真实开发会有一些业务的运算if(key.equals("age")){return 30;}return 50;})).expireAfterAccess(3L, TimeUnit.SECONDS).build();cache.put("name","张三");cache.put("age","18");cache.put("sex","男");TimeUnit.MILLISECONDS.sleep(100);System.out.println(cache.getIfPresent("name"));System.out.println(cache.getIfPresent("age"));System.out.println(cache.getIfPresent("sex"));}

image.png

运行结果:第一个数据被清除了,因为第三个进来权重大于100,导致被清理。

最后一次读

public static void ExpireAfterAccess() throws Exception{Cache<String, String> cache = Caffeine.newBuilder().maximumSize(100).expireAfterAccess(1L,TimeUnit.SECONDS).build();cache.put("name","张三");for (int i = 0; i < 10; i++) {System.out.println("第"+i+"次读:"+cache.getIfPresent("name"));TimeUnit.SECONDS.sleep(2);}}

最后一次写

 //时间驱逐策略--最后一次写public static void ExpireAfterWrite() throws InterruptedException {Cache<String, String> cache = Caffeine.newBuilder().maximumSize(100).expireAfterWrite(1L,TimeUnit.SECONDS).build();cache.put("name","张三");for (int i = 0; i < 10; i++) {System.out.println("第"+i+"次读:"+cache.getIfPresent("name"));TimeUnit.SECONDS.sleep(1);}}

自定义失效策略

public static void MyExpire() throws InterruptedException {Cache<String, String> cache = Caffeine.newBuilder().maximumSize(100).expireAfter(new MyExpire()).build();cache.put("name", "张三");for (int i = 0; i < 10; i++) {System.out.println("第" + i + "次读:" + cache.getIfPresent("name"));TimeUnit.SECONDS.sleep(1);}}
package cn.db.caffeine;import com.github.benmanes.caffeine.cache.Expiry;import java.util.concurrent.TimeUnit;class MyExpire implements Expiry<String,String> {//创建后(多久失效)@Overridepublic long expireAfterCreate( String key,  String value, long currentTime) {//创建后System.out.println("创建后,失效计算 -- "+key+": "+value);//将两秒转换为纳秒,并返回;代表创建后两秒失效return TimeUnit.NANOSECONDS.convert(2,TimeUnit.SECONDS);}//更行后(多久失效)@Overridepublic long expireAfterUpdate( String key,  String value, long currentTime,  long currentDuration) {//更新后System.out.println("更新后,失效计算 -- "+key+": "+value);return TimeUnit.NANOSECONDS.convert(5,TimeUnit.SECONDS);}//读取后(多久失效)@Overridepublic long expireAfterRead( String key,  String value, long currentTime,  long currentDuration) {//读取后System.out.println("读取后,失效计算 -- "+key+": "+value);return TimeUnit.NANOSECONDS.convert(100,TimeUnit.SECONDS);}
}

image.png

基于引用驱逐策略

软引用
 //基于引用驱逐策略--软引用:-Xms20m -Xmx20mpublic static void ExpireSoft() throws InterruptedException {Cache<String, Object> cache = Caffeine.newBuilder().maximumSize(100).softValues().build();cache.put("name",new SoftReference<>("张三"));System.out.println("第1次读:"+cache.getIfPresent("name"));List<byte[]> list = new LinkedList<>();try {for(int i=0;i<100;i++) {list.add(new byte[1024*1024*1]); //1M的对象}} catch (Throwable e) {//抛出了OOM异常时TimeUnit.SECONDS.sleep(1);System.out.println("OOM时读:"+cache.getIfPresent("name"));System.out.println("Exception*************"+e.toString());}}

image.png

弱引用
//基于引用驱逐策略--弱引用public static void ExpireWeak() throws InterruptedException {Cache<String, Object> cache = Caffeine.newBuilder().maximumSize(100).weakValues().build();cache.put("name",new WeakReference<>("张三"));System.out.println("第1次读:"+cache.getIfPresent("name"));System.gc();//进行一次GC垃圾回收System.out.println("GC后读:"+cache.getIfPresent("name"));}

image.png

状态收集器

Caffeine 开发组件有一个最为重要的特点是自带有数据的统计功能,例如:你的缓存查询了多少次,有多少次是查询准确(指定数据的 KEY 存在并且可以返回最终的数据),查询有多少次是失败的。默认情况下是没有开启此数据统计信息,如果要想获取到统计信息,则通过在build之前,添加 recordStats()来开启数据统计功能

public static void CacheStats () throws Exception{Cache<String, String> cache = Caffeine.newBuilder().maximumSize(2).recordStats() //开启统计功能.expireAfterAccess(200L,TimeUnit.SECONDS).build();cache.put("name","张三");cache.put("sex","男");cache.put("age","18");//设置的key有些是不存在的,通过这些不存在的进行非命中操作String[] keys = new String[]{"name","age","sex","phone","school"};for (int i = 0; i < 1000; i++) {cache.getIfPresent(keys[new Random().nextInt(keys.length)]);}CacheStats stats = cache.stats();System.out.println("用户请求查询总次数:"+stats.requestCount());System.out.println("命中个数:"+stats.hitCount());System.out.println("命中率:"+stats.hitRate());System.out.println("未命中次数:"+stats.missCount());System.out.println("未命中率:"+stats.missRate());System.out.println("加载次数:"+stats.loadCount());System.out.println("总共加载时间:"+stats.totalLoadTime());System.out.println("平均加载时间(单位-纳秒):"+stats.averageLoadPenalty ());System.out.println("加载失败率:"+stats.loadFailureRate()); //加载失败率,= 总共加载失败次数 / 总共加载次数System.out.println("加载失败次数:"+stats.loadFailureCount());System.out.println("加载成功次数:"+stats.loadSuccessCount());System.out.println("被淘汰出缓存的数据总个数:"+stats.evictionCount());System.out.println("被淘汰出缓存的那些数据的总权重:"+stats.evictionWeight());}

caffeine支持自定义状态收集

package cn.db.caffeine;import com.github.benmanes.caffeine.cache.stats.CacheStats;
import com.github.benmanes.caffeine.cache.stats.StatsCounter;public class MyStatsCounter implements StatsCounter {@Overridepublic void recordHits( int count) {System.out.println("命中之后执行的操作");}@Overridepublic void recordMisses( int count) {}@Overridepublic void recordLoadSuccess( long loadTime) {}@Overridepublic void recordLoadFailure( long loadTime) {}@Overridepublic void recordEviction() {}@Overridepublic CacheStats snapshot() {return null;}
}

清除、更新异步监听

缓存中的数据发送更新,或者被清除时,就会触发监听器,在监听器里可以自定义一些处理手段。可以查看哪个数据被清除,清除的原因等。这个触发和监听的过程是异步的。

 Cache<String, String> cache = Caffeine.newBuilder().maximumSize(2).removalListener(((key, value, cause) -> System.out.println("键:"+key+" 值:"+value+" 清除原因:"+cause))).expireAfterAccess(1, TimeUnit.SECONDS).build();cache.put("name","张三");cache.put("sex","男");cache.put("age","18");TimeUnit.SECONDS.sleep(2);cache.put("name2","张三");cache.put("age2","18");cache.invalidate("age2");TimeUnit.SECONDS.sleep(10);

image.png


http://www.ppmy.cn/devtools/122985.html

相关文章

Golang | Leetcode Golang题解之第451题根据字符出现频率排序

题目&#xff1a; 题解&#xff1a; func frequencySort(s string) string {cnt : map[byte]int{}maxFreq : 0for i : range s {cnt[s[i]]maxFreq max(maxFreq, cnt[s[i]])}buckets : make([][]byte, maxFreq1)for ch, c : range cnt {buckets[c] append(buckets[c], ch)}an…

无人机视角垃圾检测数据集,26700余张无人机图像,超过4万标注信息,共3.6GB数据量,可用于环卫快速检查,垃圾快速定位等应用。

无人机视角垃圾检测&#xff0c;26700余张无人机图像&#xff0c;超过4万标注信息&#xff0c;共3.6GB数据量&#xff0c;可用于环卫快速检查&#xff0c;垃圾快速定位等应用。 名称 无人机视角垃圾检测数据集 规模 图像数量&#xff1a;26700余张标注信息&#xff1a;超过4…

HT338 2x50W D类立体声音频功放

1 特性 ● 输出功率(BTL) 2x40W (VDD18V,RL4Ω, THDN10%) 2x50W(VDD24V.RL6Ω,THDN10%) 2x40W(VDD24V,RL8Ω, THDN10%) ● 输出功率(PBTL模式) 80W(VDD24V,RL4Ω,THDN10%) 100W(VDD24VRL3Ω,THDN10%) ● 单电源系统&#xff0c;4.5V-26V宽电压输入范围 ● 超过90%效率&#xf…

上海交通大学《2022年+2023年816自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《上海交通大学816自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2022年真题 2023年真题 Part1&#xff1a;2022年2023年完整版真题 2022年真题 2023年…

全网都在找的Python生成器竟然在这里!简单几步,让你的代码更简洁、更高效!

博客主页&#xff1a;长风清留扬-CSDN博客系列专栏&#xff1a;Python基础专栏每天更新大数据相关方面的技术&#xff0c;分享自己的实战工作经验和学习总结&#xff0c;尽量帮助大家解决更多问题和学习更多新知识&#xff0c;欢迎评论区分享自己的看法感谢大家点赞&#x1f44…

插槽slot在vue中的使用

介绍 在 Vue.js 中&#xff0c;插槽&#xff08;slot&#xff09;是一种用于实现组件内容分发的功能。通过插槽&#xff0c;可以让父组件在使用子组件时自定义子组件内部的内容。插槽提供了一种灵活的方式来组合和复用组件。 项目中有很多地方需要调用一个组件&#xff0c;比…

国庆练习(Day24)

作业一 数组练习 选择题 1.1、若有定义语句&#xff1a;int a[3][6]; &#xff0c;按在内存中的存放顺序&#xff0c;a 数组的第10个元素是 a[0][4] B) a[1][3] C)a[0][3] D)a[1][4] 解析&#xff1a; 从 a[0][0] 开始…

vue3+PPTXjs 在线ppt预览

- 使用PPTXjs做ppt预览&#xff0c;有完整的代码包&#xff0c;基于jquery - vue3使用iframe引入用于预览ppt的网页&#xff0c;通过url参数传递需要预览的ppt链接 - 通过网页选择文件上传也可以通过下面的函数把文件转换成链接&#xff0c;实现在文件上传到服务器前就可以预…