当前位置: 首页 > news >正文

Caffenie配合Redis做两级缓存

一、什么是两级缓存

在项目中。一级缓存用Caffeine,二级缓存用Redis,查询数据时首先查本地的Caffeine缓存,没有命中再通过网络去访问Redis缓存,还是没有命中再查数据库。具体流程如下
在这里插入图片描述

二、简单的二级缓存实现-v1

目录结构
在这里插入图片描述

2.1 double-cache模块主要文件

在这里插入图片描述

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>double-cache</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.2</version><relativePath/></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.9.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency></dependencies></project>

2.2 测试模块的主要文件

在这里插入图片描述

OrderServiceImpl

@Slf4j
@Service
@RequiredArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {private final OrderMapper orderMapper;private final Cache cache;private final RedisTemplate redisTemplate;@Overridepublic Order getOrderById(Long id) {String key = CacheConstant.ORDER + id;Order order = (Order) cache.get(key,k -> {//先查询 RedisObject obj = redisTemplate.opsForValue().get(k);if (Objects.nonNull(obj)) {log.info("get data from redis");return obj;}// Redis没有则查询 DBlog.info("get data from database");Order myOrder = orderMapper.selectOne(new LambdaQueryWrapper<Order>().eq(Order::getId, id));redisTemplate.opsForValue().set(k, myOrder, 120, TimeUnit.SECONDS);return myOrder;});return order;}@Overridepublic void updateOrder(Order order) {log.info("update order data");String key = CacheConstant.ORDER + order.getId();orderMapper.updateById(order);//修改 RedisredisTemplate.opsForValue().set(key, order, 120, TimeUnit.SECONDS);// 修改本地缓存cache.put(key, order);}@Overridepublic void deleteOrder(Long id) {log.info("delete order");orderMapper.deleteById(id);String key = CacheConstant.ORDER + id;redisTemplate.delete(key);cache.invalidate(key);}
}

application.yml

server:port: 8090spring:application:name: test-demodatasource:url: jdbc:mysql://localhost:3306/ktl?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driverredis:host: 192.168.200.131port: 6379database: 0timeout: 10000mslettuce:pool:max-active: 8max-wait: -1msmax-idle: 8min-idle: 0password: rootlogging:level:com.cn.dc: debugorg.springframework: warn

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>testcache</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.2</version><relativePath/></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><mybatis-plus.version>3.3.2</mybatis-plus.version></properties><dependencies><dependency><groupId>org.example</groupId><artifactId>double-cache</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.8.1</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version><scope>provided</scope></dependency></dependencies>
</project>

2.3 测试

测试get/{id}接口的时候,会把从db查出来的数据放入到redis和Caffeine中,在有效期内不需要再次从数据库查询

三、二级缓存实现-v2

v1的代码入侵性很强,因此加入了注解@Cacheable@CachePut@CacheEvict

3.1 double-cache模块

在这里插入图片描述

3.2 测试模块

OrderServiceImpl

@Slf4j
@Service
@RequiredArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {private final OrderMapper orderMapper;private final RedisTemplate redisTemplate;@Override@Cacheable(value = "order",key = "#id")
//@Cacheable(cacheNames = "order",key = "#p0")public Order getOrderById(Long id) {String key= CacheConstant.ORDER + id;//先查询 RedisObject obj = redisTemplate.opsForValue().get(key);if (Objects.nonNull(obj)){log.info("get data from redis");return (Order) obj;}// Redis没有则查询 DBlog.info("get data from database");Order myOrder = orderMapper.selectOne(new LambdaQueryWrapper<Order>().eq(Order::getId, id));redisTemplate.opsForValue().set(key,myOrder,120, TimeUnit.SECONDS);return myOrder;}@Override@CachePut(cacheNames = "order",key = "#order.id")public Order updateOrder(Order order) {log.info("update order data");orderMapper.updateById(order);//修改 RedisredisTemplate.opsForValue().set(CacheConstant.ORDER + order.getId(),order, 120, TimeUnit.SECONDS);return order;}@Override@CacheEvict(cacheNames = "order",key = "#id")public void deleteOrder(Long id) {log.info("delete order");orderMapper.deleteById(id);redisTemplate.delete(CacheConstant.ORDER + id);}
}

四、二级缓存实现-v3

模仿spring通过注解管理缓存的方式,我们也可以选择自定义注解,然后在切面中处理缓存,从而将对业务代码的入侵降到最低。

首先定义一个注解,用于添加在需要操作缓存的方法上:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DoubleCache {String cacheName();String key();	//支持springEl表达式long l2TimeOut() default 120;CacheType type() default CacheType.FULL;
}

我们使用cacheName + key作为缓存的真正key(仅存在一个Cache中,不做CacheName隔离),l2TimeOut为可以设置的二级缓存Redis的过期时间,type是一个枚举类型的变量,表示操作缓存的类型,枚举类型定义如下:

public enum CacheType {FULL,   //存取PUT,    //只存DELETE  //删除
}

因为要使key支持springEl表达式,所以需要写一个方法,使用表达式解析器解析参数:

public class ElParser {public static String parse(String elString, TreeMap<String,Object> map){elString=String.format("#{%s}",elString);//创建表达式解析器ExpressionParser parser = new SpelExpressionParser();//通过evaluationContext.setVariable可以在上下文中设定变量。EvaluationContext context = new StandardEvaluationContext();map.entrySet().forEach(entry->context.setVariable(entry.getKey(),entry.getValue()));//解析表达式Expression expression = parser.parseExpression(elString, new TemplateParserContext());//使用Expression.getValue()获取表达式的值,这里传入了Evaluation上下文String value = expression.getValue(context, String.class);return value;}
}

至于Cache相关参数的配置,我们沿用V1版本中的配置即可。准备工作做完了,下面我们定义切面,在切面中操作Cache来读写Caffeine的缓存,操作RedisTemplate读写Redis缓存。

@Slf4j
@Component
@Aspect
@AllArgsConstructor
public class CacheAspect {private final Cache cache;private final RedisTemplate redisTemplate;private final String COLON = ":";@Pointcut("@annotation(org.example.doublecache.annotation.DoubleCache)")public void cacheAspect() {}@Around("cacheAspect()")public Object doAround(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();//        if (!method.isAnnotationPresent(DoubleCache.class))
//            return null;//拼接解析springEl表达式的mapString[] paramNames = signature.getParameterNames();Object[] args = point.getArgs();TreeMap<String, Object> treeMap = new TreeMap<>();for (int i = 0; i < paramNames.length; i++) {treeMap.put(paramNames[i],args[i]);}DoubleCache annotation = method.getAnnotation(DoubleCache.class);String elResult = ElParser.parse(annotation.key(), treeMap);String realKey = annotation.cacheName() + COLON + elResult;//强制更新if (annotation.type()== CacheType.PUT){Object object = point.proceed();redisTemplate.opsForValue().set(realKey, object,annotation.l2TimeOut(), TimeUnit.SECONDS);cache.put(realKey, object);return object;}//删除else if (annotation.type()== CacheType.DELETE){redisTemplate.delete(realKey);cache.invalidate(realKey);return point.proceed();}//读写,查询CaffeineObject caffeineCache = cache.getIfPresent(realKey);if (Objects.nonNull(caffeineCache)) {log.info("get data from caffeine");return caffeineCache;}//查询RedisObject redisCache = redisTemplate.opsForValue().get(realKey);if (Objects.nonNull(redisCache)) {log.info("get data from redis");cache.put(realKey, redisCache);return redisCache;}log.info("get data from database");Object object = point.proceed();if (Objects.nonNull(object)){//写回RedisredisTemplate.opsForValue().set(realKey, object,annotation.l2TimeOut(), TimeUnit.SECONDS);//写入Caffeinecache.put(realKey, object);}return object;}
}

4.1 double-cache模块

在这里插入图片描述

4.2 测试模块

在这里插入图片描述

OrderServiceImpl修改如下

@Slf4j
@Service
@RequiredArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {private final OrderMapper orderMapper;@Override@DoubleCache(cacheName = "order", key = "#id",type = CacheType.FULL)public Order getOrderById(Long id) {Order myOrder = orderMapper.selectOne(new LambdaQueryWrapper<Order>().eq(Order::getId, id));return myOrder;}@Override@DoubleCache(cacheName = "order",key = "#order.id",type = CacheType.PUT)public Order updateOrder(Order order) {orderMapper.updateById(order);return order;}@Override@DoubleCache(cacheName = "order",key = "#id",type = CacheType.DELETE)public void deleteOrder(Long id) {orderMapper.deleteById(id);}@Override@DoubleCache(cacheName = "order",key = "#id")public Order getOrderByIdAndStatus(Long id,Integer status) {Order myOrder = orderMapper.selectOne(new LambdaQueryWrapper<Order>().eq(Order::getId, id).eq(Order::getStatus,status));return myOrder;}

TestApplication上加@EnableCaching

4.3 测试

从数据库10ms+,生产中会走网络通信会更长。
从Caffeine平均4ms

在这里插入图片描述

相关文章:

Caffenie配合Redis做两级缓存

一、什么是两级缓存 在项目中。一级缓存用Caffeine&#xff0c;二级缓存用Redis&#xff0c;查询数据时首先查本地的Caffeine缓存&#xff0c;没有命中再通过网络去访问Redis缓存&#xff0c;还是没有命中再查数据库。具体流程如下 二、简单的二级缓存实现-v1 目录结构 2…...

MATLAB实现PID参数自动整定

目录 1、项目说明 2、文件说明 1、项目说明 本项目旨在通过 MATLAB 语言实现 PID 参数的自动整定&#xff0c;并设计了一个直观易用的 GUI 界面。该系统特别适用于实验室环境下的 PID 参数自整定任务。整定的核心原则在于优化系统性能&#xff0c;使系统的衰减比尽可能接近理…...

UE5学习笔记21-武器的射击功能

一、创建C类 创建武器子弹的类&#xff0c;创建生产武器子弹的类&#xff0c;创建弹壳的类&#xff0c;生产武器子弹的类的父类是武器的类 创建后如图&#xff0c;ProjectileMyWeapon类(产生子弹的类)继承自weapon类&#xff0c;Projectile(子弹的类)&#xff0c;Casing(弹壳声…...

Mamba模型学习笔记

笔记来源&#xff1a;bilibili Transformer 的死穴 Transformer 结构的核心是自注意力机制层&#xff0c;无论是 encoder 还是 decoder&#xff0c;序列数据都先经过位置编码后喂给这个模块。 但是自注意力机制的计算范围仅限于窗口内&#xff0c;而无法直接处理窗口外的元素…...

android kotlin 基础复习 继承 inherit

1、新建文件kt 2、代码&#xff1a; /**用户基类**/ open class Person1(name:String){/**次级构造函数**/constructor(name:String,age:Int):this(name){//初始化println("-------基类次级构造函数---------")println("name:${name},age:${age}")} }/**子…...

读软件设计的要素06概念完整性

1. 概念完整性 1.1. 当概念组合成一个软件时&#xff0c;它们可以同步以便协调行为 1.1.1. 同步可能会消除一个概念的某些行为&#xff0c;但决不会添加与该概念的规范不一致的新行为 1.1.2. 在使用概念设计软件时&#xff0c;即使你没有精确定义同步&#xff0c;至少要说服自…...

Java 每日一刊(第2期):搭建开发环境

文章目录 JVM、JRE、JDKJVM&#xff08;Java Virtual Machine&#xff0c;Java 虚拟机&#xff09;JRE&#xff08;Java Runtime Environment&#xff0c;Java 运行时环境&#xff09;JDK&#xff08;Java Development Kit&#xff0c;Java 开发工具包&#xff09;JVM、JRE、JD…...

探索EasyCVR与AI技术深度融合:视频汇聚平台的新增长点

随着5G、AI、边缘计算、物联网&#xff08;IoT&#xff09;、云计算等技术的快速发展&#xff0c;万物互联已经从概念逐渐转变为现实&#xff0c;AIoT&#xff08;物联网人工智能&#xff09;的新时代正在加速到来。在这一背景下&#xff0c;视频技术作为信息传输和交互的重要手…...

IBM中国研发部调整:全球化与本土化的新平衡

如何看待IBM中国研发部裁员&#xff1f; 近日&#xff0c;IBM中国宣布撤出在华两大研发中心&#xff0c;引发了IT行业对于跨国公司在华研发战略的广泛讨论。这一决定不仅影响了众多IT从业者的职业发展&#xff0c;也让人思考全球化背景下中国IT产业的竞争力和未来发展方向。面对…...

C++入门基础篇

引言 说到编程语言常常听到的就是C语言C Java 。C语言是面向过程的&#xff0c;C是和Java是面向对象的&#xff0c;那么什么是面向对象呢&#xff1f;什么又是面向过程呢&#xff1f;C是什么&#xff1f;封装、继承、多态是什么&#xff1f;且听我絮絮叨叨。 C入门基础 1.命名…...

Qt QListWidget 代码范例,以及Qt 天坑:setStyleSheet失效问题

一、坑之所在 1.写了StyleSheet的QString并进行了设置 this->setStyleSheet(styleSheet_M);2.注释后&#xff0c;将StyleSheet换到UI form里去&#xff0c;然后又手动清理了UI form里的StyleSheet 重新使用代码设置&#xff0c;此时代码设置失效了 二、根本解决 1.手动从…...

Unity AnimationClip详解(1)

【动画片段】 前文我们介绍了骨骼动画&#xff0c;在Unity中骨骼动画的部分静态数据存储在SkinedMeshRender中&#xff0c;而另一部分动态的关键帧数据就是存储在AnimationClip中的。 关键帧数据来自与FBX、OBJ等动画模型文件&#xff0c;可以在动画导入后的Animation选项卡中…...

在这12种场景下会使Spring事务失效--注意防范

在某些业务场景下&#xff0c;如果一个请求中&#xff0c;需要同事写入多张表的数据&#xff0c;但为了保证操作的原子性&#xff08;要么同事插入数据成功&#xff0c;要么同事插入失败&#xff09;&#xff0c;例如&#xff0c;当我们创建用户的时候&#xff0c;往往会给用户…...

SOPC:Nios II Processor -> Vectors

Reset Vector——复位向量 Exception Vector——执行向量 两个向量地址都存储着程序 1.Reset Vector 当FPGA进行复位时&#xff0c;FPGA就重新开始执行程序&#xff0c;这时就需要从EPCS中读取程序。由于FPGA的程序存放在EPC…...

golang学习笔记11——Go 语言的并发与同步实现详解

推荐学习文档 基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总golang学习笔记01——基本数据类型golang学习笔记02——gin框架及基本原理golang学习笔记03——gin框架的核心数据结构golang学习笔记04——如何真正写好Golang代码&…...

关于汽车加油是加200还是加满的思考

车子快开了1万公里了&#xff0c;每个月保险会送一个200-15的加油优惠券&#xff0c;所以之前一直是每次加200的习惯&#xff0c; 最近突然觉得经常去加油很麻烦&#xff0c;而且很浪费时间&#xff0c; 需要重新评估一下&#xff0c; 一次加200 or 一次加300 or 一次加满&am…...

C# 去掉字符串最后一个字符的5种方法

C# 去掉字符串最后一个字符的 5 种方法 &#xff08;1&#xff09;Substring string original "Hello!"; string result original.Substring(0, original.Length - 1); Console.WriteLine(result); // 输出: Hello &#xff08;2&#xff09;Remove string o…...

[Redis] Redis中的String类型

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…...

Transiting from CUDA to HIP(三)

一、Workarounds 1. memcpyToSymbol 在 HIP (Heterogeneous-compute Interface for Portability) 中&#xff0c;hipMemcpyToSymbol 函数用于将数据从主机内存复制到设备上的全局内存或常量内存中&#xff0c;这样可以在设备端的内核中访问这些数据。这个功能特别有用&#x…...

基于SpringBoot+Vue+MySQL的垃圾分类回收管理系统

系统展示 用户界面 管理员界面 系统背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上不可跨域的鸿沟&#xff0c;信息的传播速度…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、切换节点 3&#xff09;、切换到 apparmor 的目录 4&#xff09;、执行 apparmor 策略模块 5&#xff09;、修改 pod 文件 6&#xff09;、…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

Element Plus 表单(el-form)中关于正整数输入的校验规则

目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入&#xff08;联动&#xff09;2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...