Caffeine一级缓存介绍和应用

2023-05-16

Caffeine介绍

redis和caffeine的区别

相同点就不用说,广义上都是缓存的方式。咱们就说说不同。

  • redis是将数据存储到内存里;caffeine是将数据存储在本地应用里
  • caffeine和redis相比,没有了网络IO上的消耗

那么在高并发场景中,一般我们都是结合使用,形成一二级缓存。caffeine作为一级缓存,redis作为二级缓存。
使用流程大致如下:去一级缓存中查找数据(caffeine-本地应用内)如果没有的话,去二级缓存中查找数据(redis-内存)再没有,再去数据库中查找数据(数据库-磁盘)。

caffeine项目地址:ben-manes/caffeine: A high performance caching library for Java (github.com)

caffeine的应用

Caffeine 相当于一个缓存工厂,可以创建出多个缓存实例 Cache。这些缓存实例都继承了 Caffeine 的参数配置,Caffeine 是如何配置的,这些缓存实例就具有什么样的特性和功能。
Caffeine 是目前性能最好的本地缓存,因此,在考虑使用本地缓存时,直接选择 Caffeine 即可。

将caffeine作为一级缓存使用

1.配置相关

  • maven引包,可自行根据流行版本更改版本号:
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.9.3</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.5.7</version>
</dependency>
  • 缓存配置类:
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.cache.support.AbstractCacheManager;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author Aunero
 * @description 缓存配置
 */
@Configuration
public class CacheConfig {

    /**
     * 配置缓存管理器
     *
     * @return 缓存管理器
     */
    @Bean
    public AbstractCacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        //把各个cache注册到cacheManager中,CaffeineCache实现了org.springframework.cache.Cache接口
        List<CaffeineCache> caches = new ArrayList<>();
        Arrays.asList(CacheInstance.values()).forEach(cacheInstance -> {
            CaffeineCache caffeineCache = new CaffeineCache(cacheInstance.name(), Caffeine.newBuilder()
                    .recordStats()
                    .expireAfterWrite(cacheInstance.getTtl(), TimeUnit.SECONDS)
                    .build());
            caches.add(caffeineCache);
        });
        cacheManager.setCaches(caches);
        return cacheManager;

    }

    @Bean
    public SimpleKeyGenerator simpleKeyGenerator() {
        return new SimpleKeyGenerator();
    }



}
  • 缓存代理类,用来获取缓存和刷新:
import cn.hutool.core.util.ReflectUtil;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.AbstractCacheManager;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author Aunero
 * @description 缓存代理类
 */
@Component
public class CacheCreator {
    @Autowired
    private AbstractCacheManager cacheManager;

    /**
     * 获取缓存,如果获取不到创建一个
     *
     * @param cacheInstance
     * @param values
     * @return
     */
    public Cache getCache(CacheInstance cacheInstance, List<String> values) {

        String cacheNameSuffix = String.join("&", values);
        String cacheName = cacheInstance.name() + "&" + cacheNameSuffix;
        Cache cache = cacheManager.getCache(cacheName);
        if (null == cache) {
            synchronized (cacheName.intern()) {
                cache = new CaffeineCache(cacheName, Caffeine.newBuilder()
                        .recordStats()
                        .expireAfterWrite(cacheInstance.getTtl(), TimeUnit.SECONDS)
                        .build());
                Map<String, Cache> caches = (ConcurrentHashMap<String, Cache>) ReflectUtil.getFieldValue(cacheManager, "cacheMap");
                caches.put(cacheName, cache);
            }
        }
        return cache;
    }


}

  • 我们还需要一个对业务数据进行区分的缓存枚举类,这些缓存配置将在缓存管理器初始化时加载:
import cn.hutool.core.util.RandomUtil;

/**
 * @author Aunero
 * @description 缓存实例
 */
public enum CacheInstance {
    //枚举自行定义
    STUDENT_INFO,				//学生信息
    CLASS_INFO(600, 1024),		//班级信息, 可自定义过期时间和最大数量
    ;

    private int ttl = RandomUtil.randomInt(300, 360);        //默认过期时间  5分钟~6分钟
    private int maxSize = 1024;    //最大數量

    CacheInstance() {
    }


    CacheInstance(int ttl) {
        this.ttl = ttl;
    }

    CacheInstance(int ttl, int maxSize) {
        this.ttl = ttl;
        this.maxSize = maxSize;
    }

    public int getMaxSize() {
        return maxSize;
    }

    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }

    public int getTtl() {
        return ttl;
    }

    public void setTtl(int ttl) {
        this.ttl = ttl;
    }
}

2.注解实现和工具类

配置完了缓存,我们需要在业务上使用,我们可以通过切面注解的方式来实现缓存,这样可以大大减少业务代码和缓存代码的耦合性。

  • 缓存注解类
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Aunero
 * @description
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {

    CacheInstance cacheName();

    /**
     * cache缓存名拼接后缀的参数
     * 可填方法名或者针对这个方法独一无二的标识
     * @return
     */
    String[] cacheNameSuffix() default {};

    /**
     * 缓存的键, 可以填入需要作为缓存依据的参数名,
     * 不写默认所有参数作为依据
     * @return
     */
    String [] keys() default {};

}

  • 缓存删除注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Aunero
 * @description
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheEvict {

    CacheInstance[] cacheName() ;

    /**
     * cache缓存名拼接后缀的参数,注意区分顺序
     * @return
     */
    String[] cacheNameSuffix() default {};

}

  • 缓存切面实现类
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.CodeSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Aunero
 * @description 缓存切面处理类
 */
@Slf4j
@Aspect
@Component
public class CacheAspectHandler {
    @Autowired
    private CacheCreator cacheCreator;

    //缓存开启状态 也可以配置到配置文件中读取yml
    private Boolean enableCache = Boolean.TRUE;

    /**
     * 获取缓存, 没有则添加缓存在返回
     * @param pjp
     * @param cacheable
     * @return
     * @throws Throwable
     */
    @Around("@annotation(cacheable)")
    public Object cacheResponse(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable {
        Object result;

        if (enableCache) {
            //获取参数
            String[] argNames = ((CodeSignature) pjp.getSignature()).getParameterNames();
            Object[] args = pjp.getArgs();
            //生成参数键值对
            Map<String, Object> argMap = new HashMap<>();
            for (int i = 0; i < argNames.length; i++) {
                argMap.put(argNames[i], args[i]);
            }

            String key;
            if(cacheable.keys().length != 0){
                key = CacheUtil.generateCacheKeyByMapAndSpecifiedKeys(argMap, cacheable.keys());
            }else {
                key = CacheUtil.generateCacheKeyByMap(argMap);
            }

            Cache cache = cacheCreator.getCache(cacheable.cacheName(), Arrays.asList(cacheable.cacheNameSuffix()));
            result = cache.get(key, Object.class);
            if (result != null) {
                log.debug(String.format("命中缓存,实例:%s, 键:%s ", cache.getName(), key));

            } else {
                result = pjp.proceed();
                cache.put(key, result);
                log.debug(String.format("缓存成功,实例:%s, 键:%s ", cache.getName(), key));
            }
        } else {
            //不开启缓存 直接过方法
            result = pjp.proceed();
        }

        return result;

    }

    /**
     * 删除缓存
     * @param pjp
     * @param cacheEvict
     * @return
     * @throws Throwable
     */
    @Around("@annotation(cacheEvict)")
    public Object evictCacheResponse(ProceedingJoinPoint pjp, CacheEvict cacheEvict) throws Throwable {

        CacheInstance[] cacheInstances = cacheEvict.cacheName();
        Arrays.stream(cacheInstances).forEach(cacheInstance -> {
            Cache cache = cacheCreator.getCache(cacheInstance, Arrays.asList(cacheEvict.cacheNameSuffix()));
            if (null != cache) {
                cache.clear();
                log.debug(String.format("清除缓存成功,实例:%s ", cache.getName()));
            }
        });
        return pjp.proceed();

    }

}

  • 使用到的缓存工具类:
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.json.JSONUtil;
import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.SimpleKey;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author Aunero
 * @description
 */
public class CacheUtil {

    public static <T> T getValue(Cache cache, Object key, Class<T> returnClass) {
        if (cache == null || key == null) {
            return null;
        }
        return cache.get(key, returnClass);
    }

    public static String generateCacheKey(Object... keys) {
        new SimpleKey(keys);
        List<Object> objects = Arrays.asList(keys);
        return objects.stream().map(o -> o == null ? "null" : String.valueOf(o)).collect(Collectors.joining("&"));

    }

    public static void cacheValue(Cache cache, Object value, Object... keys) {
        if (null == cache) {
            throw new IllegalArgumentException("内部错误:缓存器为空");
        }
        cache.put(generateCacheKey(keys), value);
    }


    public static String generateCacheKeyByMap(Map<String, Object> argMap) {
        //直接将参数转为json作为缓存key
        return JSONUtil.toJsonStr(argMap);

    }

    public static String generateCacheKeyByMapAndSpecifiedKeys(Map<String, Object> argMap, String... keys) {
        if (ArrayUtil.isEmpty(keys)) {
            throw new IllegalArgumentException("请指定缓存的key");
        }
        //将需要参数作为缓存key
        Map<String, Object> keysMap = new HashMap<>();
        Arrays.stream(keys).forEach(key -> keysMap.put(key, argMap.get(key)));
        return generateCacheKeyByMap(keysMap);
    }

}

3.缓存使用

  • 添加缓存-情况1:将所有参数作为缓存key, 无需配置keys
@Cacheable(cacheName = CacheInstance.STUDENT_INFO,  //枚举类存放的缓存名
           cacheNameSuffix = "selectStudentList")	//缓存前缀, 对这部分缓存的唯一标识, 这里可以使用方法名, 方便查找和删除
public Map selectStudentList(Student conditon, Clazz cls){
    //业务代码
}
  • 添加缓存-情况2:部分参数作为缓存key, 配置keys
@Cacheable(cacheName = CacheInstance.STUDENT_INFO,  //枚举类存放的缓存名
           cacheNameSuffix = "selectStudentList",	//缓存前缀, 对这部分缓存的唯一标识, 这里可以使用方法名, 方便查找和删除
           keys= {"conditon"})						//只需要将参数condition作为缓存key
public Map selectStudentList(Student conditon, Clazz cls){
    //业务代码
}

缓存成功后,会打印缓存成功的日志,重复调用接口会打印命中缓存的日志,这时可以看到实例以及key

14:51:38.016 [http-nio-8097-exec-1] DEBUG c.k.c.c.CacheAspectHandler - [cacheResponse,68] - 缓存成功,实例:STUDENT_INFO&selectStudentList, 键:{"conditon":{"name":"张"}} 
14:51:44.243 [http-nio-8097-exec-2] DEBUG c.k.c.c.CacheAspectHandler - [cacheResponse,63] - 命中缓存,实例:STUDENT_INFO&selectStudentList, 键:{"conditon":{"name":"张"}} 
  • 删除缓存
@CacheEvict(cacheName = CacheInstance.STUDENT_INFO,  //枚举类存放的缓存名
           cacheNameSuffix = "selectStudentList")	//缓存前缀, 清除该标识下的所有缓存
public void delCache(){
    //业务代码
}

清除缓存成功,则会打印清除成功日志

14:54:43.911 [http-nio-8097-exec-5] DEBUG c.k.c.c.CacheAspectHandler - [lambda$evictCacheResponse$0,94] - 清除缓存成功,实例:STUDENT_INFO&selectStudentList

总结

以上只展示了Caffeine缓存的基础应用,基本的缓存需求可以满足,当然也可以在切面中加入redis作为二级缓存使用。

Caffeine缓存具有很好的性能和很强的扩展性,更多扩展用法可以参考Caffeine缓存的官方文档(Population zh CN · ben-manes/caffeine Wiki (github.com)),若代码有错误或不足的地方可以评论回复。

参考文章:

caffeine本地缓存的使用和详解_小曲同学呀的博客-CSDN博客_caffeine本地缓存

原文地址: Caffeine一级缓存介绍和应用 - Aunero’s Blog

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Caffeine一级缓存介绍和应用 的相关文章

  • 关于计算机研究和写作的链接收集

    研究相关的资源 What is Research in Computer Science 计算科学的研究是什么 xff1f 翻译 Basic Research Skills in Computer Science 计算科学的基本研究方法 R
  • 实战笔记之C++里面双冒号和冒号及点号引用符

    标题党了额 xff0c mark一下 xff0c 有时间再整理 以下节选自多人博客 推荐博文一篇http blog csdn net gyymen article details 4962873 箭头 xff08 gt xff09 xff1
  • 在Linux中如何修改root帐户的登录用户名

    vi etc passwd 按i键进入编辑状态 修改第1行第1个root为新的用户名 按esc键退出编辑状态 xff0c 并输入 x保存并退出 vi etc shadow 按i键进入编辑状态 修改第1行第1个root为新的用户名 按esc键
  • Linux elasticsearch 安装超详细教程

    1 下载elasticsearch 7 11 2 linux x86 64 tar gz 之所以下载7 11 2版本是因为我使用了中文分词器 xff0c 对应中文分词器的版本号 2 可以官方下载 xff0c 会比较慢 xff0c 这里提供我
  • 程序员做一辈子?

    首先 xff0c 程序员真有必要干一辈子吗 xff1f 如果你是个搬砖的 xff0c 你会考虑一辈子搬砖吗 xff1f 你肯定会想着过几年挣钱了 xff0c 买个车跑运输 xff0c 或者自己做工头 对程序员来说 xff0c 真心没有必要干
  • ubuntu系统怎么使用ifconfig和开启远程连接

    在ubuntu系统刚安装好之后ifconfig命令和开启远程连接都是不起作用的 xff0c 如果需要使ifconfig起作用需要安装一个小工具 xff0c 如果想开启远程连接的话也需要安装open ssh工具 开启ifconfig xff1
  • LDMIA、LDMIB、LDMDB、LDMDA、STMIA、LDMFD、LDMFA、LDMED、LDMEA等指令详解

    关于多寄存器加载存储指令 1 LDMIA指令 LDMIB指令 LDMDB指令 LDMDA指令 xff08 1 xff09 LDMIA指令 xff0c IA表示每次传送后地址加4 xff08 2 xff09 LDMIB指令 xff0c 每次传
  • FreeRTOS-互斥信号量

    原文地址 xff1a http blog csdn net xukai871105 article details 43456985 0 前言 在嵌入式操作系统中互斥型信号量是任务间资源保护的重要手段 下面结合一个具体例子说明FreeRTO
  • 执行体线程--ETHREAD

    typedef struct ETHREAD KTHREAD Tcb 内嵌了KTHREAD对象作为第一个数据成员 LARGE INTEGER CreateTime 包含了线程创建时间 xff0c 他是在线程创建时被赋值的 union LAR
  • 显卡驱动的作用(本质作用)

    确切资料表明显卡不需要驱动也可以进行显示 xff0c 只需要将显示的内容存到对应的显存地址就可以 xff08 通过cpu直接或者间接的硬连线实现 xff09 也就是说单纯的显示像素的话不需要使用显卡驱动 xff0c 但是单纯的显示像素甚至某

随机推荐

  • 智能指针之make_unique与make_shared

    make unique的实现 std make shared是C 43 43 11的一部分 xff0c 但是std make unique很可惜不是 它是在C 43 43 14里加入标准库的 xff0c 但我们可以自己实现make uniq
  • NVIDIA Jetson TX1 系列开发教程之十二:libcurl、RapidJSON安装

    NVIDIA Jetson TX1 系列开发教程之十二 xff1a libcurl RapidJSON安装 转载请注明作者和出处 xff1a http blog csdn net u011475210嵌入式平台 xff1a NVIDIA J
  • 用户标签体系的搭建方法

    一 标签体系的困境 每一个需求背后都有对应的痛点和问题 xff0c 在讲具体的方法之前 xff0c 笔者想简单阐述一下标签体系搭建和实践过程中企业一般会遇到的问题 困境 xff0c 方便读者理解本文的搭建思路 目的和未来的优化方向 用户标签
  • Windows 安装TVM 及各种报错解决!无GPU版本

    这篇先来一个不用GPU的下次 xff0c 再整一个带GPU的 xff01 系统基本信息 Windows 11 Anaconda 4 12 Python 3 8 13 Visual Studio 2022 这几天在Windows 下安装TVM
  • C++变量前面加下划线的含义

    C 43 43 变量前面加下划线和不加下划线都不会影响对变量的定义 xff0c 只是风格问题 xff0c 更喜欢将成员变量或者私有成员变量的前面加上下划线 以表示该变量是某个类的属性 比如 xff1a int size int getsiz
  • tensorflow中GPU相关设置解决显存不足

    1 显存不足时报错如下 xff1a E tensorflow stream executor cuda cuda dnn cc 359 could not create cudnn handle CUDNN STATUS INTERNAL
  • freertos 学习笔记——第一章、裸机进阶RTOS

    第一章 前言 1 1裸机的软件写法 1 xff0c 轮询系统 void main sys init while 1 task 1 task 2 任务处理在循环中依次执行 2 xff0c 前后台系统 中断被成为前台 xff0c 无限循环被成为
  • freertos学习笔记——第三章、任务控制——3.1 全局变量造成的困扰

    裸机中经常使用全局变量 xff0c 但在RTOS中大量使用全局变量会造成很多问题 在RTOS中也可以使用全局变量但使用时一定要注意有哪些任务会写这个变量 xff0c 哪些任务会读这个变量 尤其切记在使用这个变量的过程中变量的数值发生改变 x
  • MTK项目总结

    一 sensor路径 vendor mediateck propri sensor校准 xff1a 加速度校准 adb shell 34 echo 1 gt sys bus platform drivers gsensor test cal
  • JAVA简述和JDK环境搭建

    一 Java 语言背景介绍 1 什么是 Java 语言 xff1f Java 语言是美国 Sun 公司 xff08 Stanford University Network xff09 在 1995 年推出的计算机语言 2009年 xff0c
  • Java变量、标识符以及类型转换详解

    本篇文章为本人学习笔记 xff0c 如有错误 xff0c 希望指正 一 进制 进制详细概念以及转换 xff1a https blog csdn net diyu122222 article details 80692904 这篇写的很好 x
  • Java运算符的使用和规则

    本篇文章为本人学习笔记 xff0c 如有错误 xff0c 希望指正 Java中运算符大致分为以下几类 xff1a 算数运算符赋值运算符自增自减运算符关系运算符逻辑运算符三元运算符 1 算数运算符 算数运算符包括 xff1a 作用 43 加法
  • Java的内存分配理解

    本篇文章为本人学习笔记 xff0c 如有错误 xff0c 希望指正 Java 程序在运行时 xff0c 需要在内存中分配空间 为了提高运算效率 xff0c 就对空间进行了不同区域的划分每一片区域都有特定的处理数据方式和内存管理方式 区域名称
  • IntelliJ IDEA的Debug教程

    1 什么是Debug模式 它是供程序员使用的程序调试工具 xff0c 它可以用于查看程序的执行流程 xff0c 也可以用于追踪程序执行过程来调试程序 2 Debug模式操作流程 如何加断点 选择要设置断点的代码行 xff0c 在行号的区域后
  • opencv获取相机图像并发布为ROS节点

    仅记录工程中的使用 完整代码请查看 xff1a https github com chx725 cv and ros ROS是最普遍使用的机器人系统之一 xff0c 提供了各种功能包 xff0c 仿真环境 xff0c 模型 xff0c 可视
  • Java中两种方法实现栈和队列(面试)

    学到LinkedList xff0c 上课时老师提了一下代码实现栈和队列 xff0c 面试可能会用上 xff0c 就码了栈和队列两种实现方案 如有问题 xff0c 希望指出 一 栈 1 数组实现栈 span class token comm
  • docker容器迁移教程

    前言 被领导要求部署一个和测试环境一样的演示环境 xff0c 并且数据库也要同步过去 xff0c 服务器上的各种服务都是docker部署的 xff0c 由于之前docker玩的比较少 xff0c 所以还是踩了不少坑的 xff0c 在此记录一
  • Java多sheet模板导出表格

    前言 需求是这样的 xff0c 需要在页面导出列表表格时同时导出每项的详情数据 xff0c 而且详情表格并不是一个常规的二维表格 xff0c 就像图中这样的效果 xff0c 所以要解决的最主要两个问题就是 xff1a 多sheet实现 异形
  • docker容器时间不同步导致在java应用中获取的时间不正确问题

    一 前言 在把Java应用部署到docker容器时 xff0c 发现应用获取到的时间和现实时间相差了8个小时 xff0c 会导致一系列不必要的麻烦 二 解决方案 1 首选需要确保宿主机的时区和时间的准确 现在宿主机上用 date R 看一下
  • Caffeine一级缓存介绍和应用

    Caffeine介绍 redis和caffeine的区别 相同点就不用说 xff0c 广义上都是缓存的方式 咱们就说说不同 redis是将数据存储到内存里 xff1b caffeine是将数据存储在本地应用里caffeine和redis相比