高性能缓存Caffeine的基本使用方式

news2024/11/15 7:54:37

文章目录

  • 介绍
  • 性能比对
  • 使用方式
    • 一、Population(缓存类型)
      • 1.Cache
      • 2.Loading
      • 3.Asynchronous (Manual)
      • 4.Asynchronously Loading
    • 二、Eviction(驱除策略)
      • 1.Size-based(基于容量)
      • 2.Time-based(基于时间)
      • 3.Reference-based(基于引用)
    • 三、Explicit Removals(显示删除)
      • 1.直接删除
      • 2.可监听删除
    • 四、Refresh(刷新机制)
    • 五、Statistics(统计)
  • Caffeine在SpringBoot中的使用

介绍

Caffeine是基于JDK1.8版本的高性能本地缓存库,它是Guava的增强版,与ConcurrentLinkedHashMap相似,支持并发,并且可以在O(1)的时间复杂度内查找、写入元素。

性能比对

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用方式

一、Population(缓存类型)

1.Cache

private static void manual() {
    // 构建caffeine的缓存对象,并指定在写入后的10分钟内有效,且最大允许写入的条目数为10000
    Cache<String, String> cache = Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .maximumSize(10_000)
            .build();
    String key = "hello";
    // 查找某个缓存元素,若找不到则返回null
    String str = cache.getIfPresent(key);
    System.out.println("cache.getIfPresent(key) ---> " + str);
    // 查找某个缓存元素,若找不到则调用函数生成,如无法生成则返回null
    str = cache.get(key, k -> create(key));
    System.out.println("cache.get(key, k -> create(key)) ---> " + str);
    // 添加或者更新一个缓存元素
    cache.put(key, str);
    System.out.println("cache.put(key, str) ---> " + cache.getIfPresent(key));
    // 移除一个缓存元素
    cache.invalidate(key);
    System.out.println("cache.invalidate(key) ---> " + cache.getIfPresent(key));
}


private static String create(Object key) {
    return key + " world";
}

输出结果:

cache.getIfPresent(key) ---> null
cache.get(key, k -> create(key)) ---> hello world
cache.put(key, str) ---> hello world
cache.invalidate(key) ---> null

2.Loading

LoadingCache是附加在CacheLoader之上构建的缓存对象。

可以使用getAll方法执行批量查找,默认情况下,getAll()方法会单独调用CacheLoader.load()方法来加载每个不在缓存中的Key,必要情况下可以重写CacheLoader.loadAll()方法来弥补其缺陷。

public static void loading() {
    LoadingCache<String, String> cache = Caffeine.newBuilder()
            .maximumSize(10_000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .build(key -> create(key)); // 当调用get或者getAll时,若找不到缓存元素,则会统一调用create(key)生成
    String key = "hello";
    String str = cache.get(key);
    System.out.println("cache.get(key) ---> " + str);
    List<String> keys = Lists.newArrayList("a", "b", "c", "d", "e");
    // 批量查找缓存元素,如果缓存不存在则生成缓存元素
    Map<String, String> maps = cache.getAll(keys);
    System.out.println("cache.getAll(keys) ---> " + maps);
}

private static String create(Object key) {
    return key + " world";
}

输出结果

cache.get(key) ---> hello world
cache.getAll(keys) ---> {a=a world, b=b world, c=c world, d=d world, e=e world}

3.Asynchronous (Manual)

AsyncCache就是Cache的异步实现方式,提供了通过Executor生成缓存元素并返回CompletableFuture的能力。
synchronous()提供了在缓存计算完成前的阻塞能力,AsyncCache默认使用ForkJoinPool.commonPool()线程池,你也可以通过重写Caffeine.executor(executor)来实现自己的线程池。

private static void asynchronous() {
    AsyncCache<String, String> cache = Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .maximumSize(10_000)
            .buildAsync();
    String key = "Hello";
    // 查找某个缓存元素,若找不到则返回null
    CompletableFuture<String> value = cache.getIfPresent(key);
    // 查找某个缓存元素,若不存在则异步调用create方法生成
    value = cache.get(key, k -> create(key));
    // 添加或者更新一个缓存元素
    cache.put(key, value);
    // 移除一个缓存元素
    cache.synchronous().invalidate(key);
}

4.Asynchronously Loading

AsyncLoadingCache就是LoadingCache的异步形式

private static void asynchronouslyLoading() {
    AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
            .maximumSize(10_000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            // 异步构建一个同步的调用方法create(key)
            .buildAsync(key -> create(key));
    // 也可以使用下面的方式来异步构建缓存,并返回一个future
    // .buildAsync((key, executor) -> createAsync(key, executor));
    String key = "Hello";
    // 查找某个缓存元素,若找不到则会异步生成。
    CompletableFuture<String> value = cache.get(key);
    List<String> keys = Lists.newArrayList("a", "b", "c", "d", "e");
    // 批量查找某些缓存元素,若找不到则会异步生成。
    CompletableFuture<Map<String, String>> graphs = cache.getAll(keys);
}

二、Eviction(驱除策略)

1.Size-based(基于容量)

private static void sizeBased() {
    // 基于缓存内元素的个数,尝试回收最近或者未经常使用的元素
    LoadingCache<String, String> values = Caffeine.newBuilder()
            .maximumSize(10_000)
            .build(key -> create(key));
    // 也可以基于缓存元素的权重,进行驱除
    LoadingCache<String, String> graphs = Caffeine.newBuilder()
            .maximumWeight(10_000)
            .weigher((String key, String value) -> value.length())
            .build(key -> create(key));
}

2.Time-based(基于时间)

private static void timeBased() {
    // 自上一次写入或者读取缓存开始,在经过指定时间之后过期。
    LoadingCache<String, String> fixedAccess = Caffeine.newBuilder()
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .build(key -> create(key));
    // 自缓存生成后,经过指定时间或者一次替换值之后过期。
    LoadingCache<String, String> fixedWrite = Caffeine.newBuilder()
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .build(key -> create(key));
    // 自定义缓存过期策略,可以在创建时,写入后、读取时。
    LoadingCache<String, String> varying = Caffeine.newBuilder()
            .expireAfter(new Expiry<String, String>() {
                public long expireAfterCreate(String key, String value, long currentTime) {
                    return currentTime;
                }
                public long expireAfterUpdate(String key, String value,
                                              long currentTime, long currentDuration) {
                    return currentDuration;
                }
                public long expireAfterRead(String key, String value,
                                            long currentTime, long currentDuration) {
                    return currentDuration;
                }
            })
            .build(key -> create(key));
}

3.Reference-based(基于引用)

private static void referenceBased() {
    // 当缓存key和value都不存在强引用关系时,进行驱逐
    LoadingCache<String, String> weak = Caffeine.newBuilder()
            .weakKeys()
            .weakValues()
            .build(key -> create(key));
    // 当发生GC时进行驱逐
    LoadingCache<String, String> soft = Caffeine.newBuilder()
            .softValues()
            .build(key -> create(key));
}

三、Explicit Removals(显示删除)

1.直接删除

// 直接删除
cache.invalidate(key)
// 批量删除
cache.invalidateAll(keys)
// 删除所有
cache.invalidateAll()

2.可监听删除

监听缓存元素被删除或者被驱除事件

private static void removalsListeners() {
    Cache<String, String> cache = Caffeine.newBuilder()
    		// 注意:evictionListener是3.X版本中新特性,2.X版本中没有
            .evictionListener((String key, String value, RemovalCause cause) ->
                    System.out.printf("Key %s was removed (%s)%n", key, cause))
            .removalListener((String key, String value, RemovalCause cause) ->
                    System.out.printf("Key %s was removed (%s)%n", key, cause))
            .build();
    cache.put("Hello", "Caffeine");
    System.out.println(cache.getIfPresent("Hello"));
    cache.invalidate("Hello");
    try {
        // 监听是异步执行的
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

四、Refresh(刷新机制)

refresh只有在LoadingCache或者AsyncLoadingCache时才能使用,与驱逐不同之处,在刷新的时候,如果访问元素仍然可以返回,但返回的是旧值。

static StringBuffer stringBuffer = new StringBuffer(">");
private static void refresh() {
    LoadingCache<String, String> cache = Caffeine.newBuilder()
            .maximumSize(10_000)
            .refreshAfterWrite(1, TimeUnit.SECONDS)
            .build(key -> stringBuffer.append(key).toString());
    for (int i = 0; i < 5; i++) {
    	// 这里需要注意,前两次输出都是:“>*”
    	// 理论上第二次输出应是:“>**”,这是因为refreshAfterWrite刷新实际上指的是在x秒、并且是第二次访问之后才开始刷新
        System.out.println(cache.get("*"));
        try {
            Thread.sleep(1200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果

>*
>*
>**
>***
>****

五、Statistics(统计)

caffeine提供了完善的缓存统计能力,提供了metrics-caffeine类库,可以直接入Prometheus

private static void statistics() {
    Cache<String, String> cache = Caffeine.newBuilder()
            .maximumSize(10_000)
            .recordStats()
            .build();
    cache.put("Hello", "Caffeine");
    for (int i = 0; i < 15; i++) {
        // 命中15次
        cache.getIfPresent("Hello");
    }
    for (int i = 0; i < 5; i++) {
        // 未命中5次
        cache.getIfPresent("a");
    }
    cache.get("b", key -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return key + "B";
    });
    CacheStats stats = cache.stats();
    System.out.println(stats);
    System.out.println("命中率:" + stats.hitRate());
    System.out.println("未命中率:" + stats.missRate());
    System.out.println("加载新值花费的平均时间:" + stats.averageLoadPenalty());
}

输出结果:

CacheStats{hitCount=15, missCount=6, loadSuccessCount=1, loadFailureCount=0, totalLoadTime=1014390300, evictionCount=0, evictionWeight=0}
命中率:0.7142857142857143
未命中率:0.2857142857142857
加载新值花费的平均时间:1.0143903E9
hitRate:命中率
hitCount:命中次数
missRate:未命中率
missCount:未命中次数
loadSuccessCount:成功加载新值的次数
loadFailureCount:失败加载新值的次数
totalLoadTime:总计加载的耗时
averageLoadPenalty:加载新值花费的平均时间
evictionCount:驱逐的条目数
evictionWeight:按权重驱除的次数

Caffeine在SpringBoot中的使用

配置类

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
public class CacheConfig {

    @Bean
    public Cache<String, Object> caffeineCache() {
        return Caffeine.newBuilder()
                .maximumSize(10_000)
                .expireAfterWrite(1500, TimeUnit.MILLISECONDS)
                .removalListener((String key, Object value, RemovalCause cause) ->
                        removalListener(key, value, cause))
                .recordStats()
                .build();
    }

    private void removalListener(String key, Object value, RemovalCause cause) {
        System.out.printf("Key: %s , Value: %s , was removed (%s)%n", key, value, cause);
        System.out.println();
    }


}

测试类

@RunWith(SpringRunner.class)
@SpringBootTest
public class CaffeineCacheTestTests {

    @Resource
    CacheConfig cacheConfig;

    @Test
    public void test() throws InterruptedException {
        Cache<String, Object> caffeineCache = cacheConfig.caffeineCache();

        new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                while(true){
                    Thread.sleep(1000);
                    CacheStats cacheStats = caffeineCache.stats();
                    System.out.println("命中率:" + cacheStats.hitRate());
                    System.out.println("未命中率:" + cacheStats.missRate());
                    System.out.println();
                }
            }
        }).start();

        for (int i = 1; i <= 10; i++) {
            Object cacheKey = caffeineCache.getIfPresent("hello");
            if (Objects.isNull(cacheKey)) {
                caffeineCache.put("hello", "caffeine");
            }
            Thread.sleep(((i % 2) + 1) * 1000);
        }

    }

}

测试结果

命中率:0.0
未命中率:1.0

Key: hello , Value: caffeine , was removed (EXPIRED)

命中率:0.0
未命中率:1.0

命中率:0.0
未命中率:1.0

命中率:0.3333333333333333
未命中率:0.6666666666666666

Key: hello , Value: caffeine , was removed (EXPIRED)

命中率:0.25
未命中率:0.75

命中率:0.4
未命中率:0.6

命中率:0.4
未命中率:0.6

Key: hello , Value: caffeine , was removed (EXPIRED)

命中率:0.3333333333333333
未命中率:0.6666666666666666

命中率:0.42857142857142855
未命中率:0.5714285714285714

命中率:0.42857142857142855
未命中率:0.5714285714285714

Key: hello , Value: caffeine , was removed (EXPIRED)

命中率:0.375
未命中率:0.625

命中率:0.4444444444444444
未命中率:0.5555555555555556

命中率:0.4444444444444444
未命中率:0.5555555555555556

Key: hello , Value: caffeine , was removed (EXPIRED)

命中率:0.4
未命中率:0.6


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/79529.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis Cluster 单机部署

高可用架构-Redis Cluster Redis服务器支持单机、主从复制、Sentinel、集群部署&#xff0c;部署架构也是由简单到复杂&#xff0c;Redis Cluster 集群架构是官方推荐应对大数据量、大并发场景部署方案。Redis的架构变迁如下图所示&#xff0c;其归属就是Redis Cluster Redis …

一款功能强大的课程报名系统 v6.2.0

内容目录一、详细介绍二、效果展示1.部分代码2.效果图展示三、学习资料下载一、详细介绍 课程报名系统可为专为教育培训机构设计的在线选课报名系统&#xff0c;学员可综合考虑课程分类、适合人群、上课地点、上课时间、课程价格等多种选报最合适的班级 课程报名系统发布v6.2…

组件库技术选型和开发环境搭建

点击上方卡片“前端司南”关注我您的关注意义重大原创前端司南本文为稀土掘金技术社区首发签约文章&#xff0c;14天内禁止转载&#xff0c;14天后未获授权禁止转载&#xff0c;侵权必究&#xff01;专栏上篇文章传送门&#xff1a;基于Vite打造业务组件库&#xff08;开篇介绍…

盘点和总结秒杀服务的功能设计及注意事项技术体系

秒杀应该考虑哪些问题 超卖问题 分析秒杀的业务场景&#xff0c;最重要的有一点就是超卖问题&#xff0c;假如备货只有100个&#xff0c;但是最终超卖了200&#xff0c;一般来讲秒杀系统的价格都比较低&#xff0c;如果超卖将严重影响公司的财产利益&#xff0c;因此首当其冲…

0代码20min |微信接入chat-GPT

0代码20min | 微信接入chat-GPT 拥有一个openai账号 这个账号比较麻烦&#xff0c;需要国外手机号认证&#xff0c;这个的话可以直接参考这篇文章ChatGPT注册攻略 - 知乎 (zhihu.com)&#xff0c;这篇文章是用在线接受短信的虚拟号码 - SMS-Activate这个网站来注册账号的。 用…

《操作系统》期末考试试卷2参考答案

《操作系统》期末考试卷&#xff08;第2套&#xff09; 参考答案与评分标准 一、单项选择题&#xff08;共15题&#xff0c;每题2分&#xff0c;共30分&#xff09; 题 号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 答 案 D A C C B A D D A B D C A B D 二、填空题&#xff08;…

ADI Blackfin DSP处理器-BF533的开发详解31:汉字/ASIC II 字库(含源代码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 功能介绍 代码实现了汉字库和 ASIC II 码 字库的功能&#xff0c;代码会在编译时&#xff0c;将字库文件加载到内存中&#xff0c;所以必须使用 …

软件测试基础篇(6)

两个测试模型: 一:软件测试V模型:从上到下是一个开发模型&#xff0c;从下到上是一个测试模型(V模型也是瀑布模型的一个变种) 需求分析--->计划---->设计---->编码----->测试 用户需求 验收测试需求分析与…

java基于springboot的共享单车系统-计算机毕业设计

项目介绍 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Ma…

合并有序数组

目录 I.合并有序数组 Time Limit:1s Memory Limit: 256M Description: Input: Output: Sample input: Sample output: 约束&#xff1a; 思路代码: 1.暴力法 1.2暴力代码: 2.双指针(归并排序思路) 2.2双指针代码 总结: 题目链接: I.合并有序数组 Time Limit:1s Me…

专注推荐系统13年,我收获最大的4个成长

‍‍ 作者 | gongyouliu编辑 | gongyouliu我从2010年开始接触推荐系统&#xff0c;到现在差不多有13年了&#xff0c;这13年间我基本没有间断过对推荐系统的学习和实践&#xff08;今年还看了两本推荐系统相关的书籍和一些论文&#xff09;。回顾一下这十几年的经历&#x…

Java项目:SSM高校教职工差旅报销管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 管理员角色包含以下功能&#xff1a; 管理员登录,修改管理员资料,用户管理,公告管理,报销类型管理,系别信息管理,报销审核管理等功能。 用户角色…

Python webdriver.Chrome()的使用

1.前提 Python与Chrome路径下均安装chromedriver.exe。 2.chromedriver.exe版本选择及下载 下载地址为&#xff1a;http://npm.taobao.org/mirrors/chromedriver/ Chrome版本查看&#xff1a;浏览器右上角三个点->帮助->关于Google Chrome 找到对应的版本&#xff0c;主…

大数据面试之YARN常见题目

大数据面试之YARN常见题目 1 YARN工作机制 1.1 图解 上面有单词少个r&#xff0c;就不改了&#xff0c;大家知道就行。 1.2 文字描述 文字版描述&#xff1a; 1、MapReduce程序提交到Client所在节点&#xff0c;在MR程序的主函数当中有job.waitForCompletion()将任务进行提…

Linux网络-五种IO模型

Linux网络-高级IO零、前言一、什么是IO二、五种IO模型1、阻塞IO2、非阻塞IO3、信号驱动IO4、IO多路转接5、异步IO三、高级IO重要概念1、同步通信 vs 异步通信2、阻塞 vs 非阻塞3、其他高级IO零、前言 本章主要就Linux网络讲解非常重要的一个话题-高级IO 一、什么是IO IO是输入i…

20221211英语学习

今日新词&#xff1a; helplessly adv.无助地&#xff1b;无能为力地 physicist n.物理学家, 物理学研究者 capable adj.有能力的&#xff0c;有才能的&#xff1b;能胜任的&#xff1b;可以…的&#xff1b;容许…的 spokeswoman n.女发言人 production n.产量&#xff1…

卡尔曼滤波应用

卡尔曼滤波器的过程 卡尔曼滤波器的过程分为&#xff1a; 状态方程&#xff1a; xkAxk−1Buk−1ωk−1zkHxkνk这样就得到了状态方程和观测方程的表达式其中xk是状态向量&#xff0c;A是转移矩阵&#xff0c;B是输入转换为状态的矩阵&#xff0c;uk是系统输入&#xff0c;ωk是…

【波段自适应梯度和细节校正:统一遥感融合】

A Unified Pansharpening Model Based on Band-Adaptive Gradient and Detail Correction &#xff08;基于波段自适应梯度和细节校正的统一全色锐化模型&#xff09; 利用全色锐化技术将全色&#xff08;PAN&#xff09;图像与多光谱&#xff08;MS&#xff09;图像融合&…

谈谈Java应用发布时CPU抖动的优化

研究背景 通常情况下应用发布或重启时都存在cpu抖动飙高&#xff0c;甚至打满的现象&#xff0c;这是由于应用启动时&#xff0c;JVM重新进行类加载与对象的初始化&#xff0c;CPU在整个过程中需要进行比平时更多的编译工作。同样&#xff0c;闲鱼的消息系统在重新发布时经常有…

fsQCA+NCA方法的软件操作及注意事项、论文实证分析部分的写作范式

目录前言1 软件操作步骤2 fsQCA方法的详细操作步骤2.1 软件下载2.2 数据的准备2.3 校准点的确定2.4 变量的校准步骤及闪退问题2.5 fsQCA的数据必要性检验&#xff08;开始一次最后一次&#xff09;2.6 频数、一致性水平、PRI一致性水平的确定2.6.1 频数的确定2.6.2 一致性水平、…