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,二级缓存用Redis,查询数据时首先查本地的Caffeine缓存,没有命中再通过网络去访问Redis缓存,还是没有命中再查数据库。具体流程如下 二、简单的二级缓存实现-v1 目录结构 2…...
MATLAB实现PID参数自动整定
目录 1、项目说明 2、文件说明 1、项目说明 本项目旨在通过 MATLAB 语言实现 PID 参数的自动整定,并设计了一个直观易用的 GUI 界面。该系统特别适用于实验室环境下的 PID 参数自整定任务。整定的核心原则在于优化系统性能,使系统的衰减比尽可能接近理…...
UE5学习笔记21-武器的射击功能
一、创建C类 创建武器子弹的类,创建生产武器子弹的类,创建弹壳的类,生产武器子弹的类的父类是武器的类 创建后如图,ProjectileMyWeapon类(产生子弹的类)继承自weapon类,Projectile(子弹的类),Casing(弹壳声…...
Mamba模型学习笔记
笔记来源:bilibili Transformer 的死穴 Transformer 结构的核心是自注意力机制层,无论是 encoder 还是 decoder,序列数据都先经过位置编码后喂给这个模块。 但是自注意力机制的计算范围仅限于窗口内,而无法直接处理窗口外的元素…...
android kotlin 基础复习 继承 inherit
1、新建文件kt 2、代码: /**用户基类**/ open class Person1(name:String){/**次级构造函数**/constructor(name:String,age:Int):this(name){//初始化println("-------基类次级构造函数---------")println("name:${name},age:${age}")} }/**子…...
读软件设计的要素06概念完整性
1. 概念完整性 1.1. 当概念组合成一个软件时,它们可以同步以便协调行为 1.1.1. 同步可能会消除一个概念的某些行为,但决不会添加与该概念的规范不一致的新行为 1.1.2. 在使用概念设计软件时,即使你没有精确定义同步,至少要说服自…...
Java 每日一刊(第2期):搭建开发环境
文章目录 JVM、JRE、JDKJVM(Java Virtual Machine,Java 虚拟机)JRE(Java Runtime Environment,Java 运行时环境)JDK(Java Development Kit,Java 开发工具包)JVM、JRE、JD…...
探索EasyCVR与AI技术深度融合:视频汇聚平台的新增长点
随着5G、AI、边缘计算、物联网(IoT)、云计算等技术的快速发展,万物互联已经从概念逐渐转变为现实,AIoT(物联网人工智能)的新时代正在加速到来。在这一背景下,视频技术作为信息传输和交互的重要手…...
IBM中国研发部调整:全球化与本土化的新平衡
如何看待IBM中国研发部裁员? 近日,IBM中国宣布撤出在华两大研发中心,引发了IT行业对于跨国公司在华研发战略的广泛讨论。这一决定不仅影响了众多IT从业者的职业发展,也让人思考全球化背景下中国IT产业的竞争力和未来发展方向。面对…...
C++入门基础篇
引言 说到编程语言常常听到的就是C语言C Java 。C语言是面向过程的,C是和Java是面向对象的,那么什么是面向对象呢?什么又是面向过程呢?C是什么?封装、继承、多态是什么?且听我絮絮叨叨。 C入门基础 1.命名…...
Qt QListWidget 代码范例,以及Qt 天坑:setStyleSheet失效问题
一、坑之所在 1.写了StyleSheet的QString并进行了设置 this->setStyleSheet(styleSheet_M);2.注释后,将StyleSheet换到UI form里去,然后又手动清理了UI form里的StyleSheet 重新使用代码设置,此时代码设置失效了 二、根本解决 1.手动从…...
Unity AnimationClip详解(1)
【动画片段】 前文我们介绍了骨骼动画,在Unity中骨骼动画的部分静态数据存储在SkinedMeshRender中,而另一部分动态的关键帧数据就是存储在AnimationClip中的。 关键帧数据来自与FBX、OBJ等动画模型文件,可以在动画导入后的Animation选项卡中…...
在这12种场景下会使Spring事务失效--注意防范
在某些业务场景下,如果一个请求中,需要同事写入多张表的数据,但为了保证操作的原子性(要么同事插入数据成功,要么同事插入失败),例如,当我们创建用户的时候,往往会给用户…...
SOPC:Nios II Processor -> Vectors
Reset Vector——复位向量 Exception Vector——执行向量 两个向量地址都存储着程序 1.Reset Vector 当FPGA进行复位时,FPGA就重新开始执行程序,这时就需要从EPCS中读取程序。由于FPGA的程序存放在EPC…...
golang学习笔记11——Go 语言的并发与同步实现详解
推荐学习文档 基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总golang学习笔记01——基本数据类型golang学习笔记02——gin框架及基本原理golang学习笔记03——gin框架的核心数据结构golang学习笔记04——如何真正写好Golang代码&…...
关于汽车加油是加200还是加满的思考
车子快开了1万公里了,每个月保险会送一个200-15的加油优惠券,所以之前一直是每次加200的习惯, 最近突然觉得经常去加油很麻烦,而且很浪费时间, 需要重新评估一下, 一次加200 or 一次加300 or 一次加满&am…...
C# 去掉字符串最后一个字符的5种方法
C# 去掉字符串最后一个字符的 5 种方法 (1)Substring string original "Hello!"; string result original.Substring(0, original.Length - 1); Console.WriteLine(result); // 输出: Hello (2)Remove string o…...
[Redis] Redis中的String类型
🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…...
Transiting from CUDA to HIP(三)
一、Workarounds 1. memcpyToSymbol 在 HIP (Heterogeneous-compute Interface for Portability) 中,hipMemcpyToSymbol 函数用于将数据从主机内存复制到设备上的全局内存或常量内存中,这样可以在设备端的内核中访问这些数据。这个功能特别有用&#x…...
基于SpringBoot+Vue+MySQL的垃圾分类回收管理系统
系统展示 用户界面 管理员界面 系统背景 二十一世纪互联网的出现,改变了几千年以来人们的生活,不仅仅是生活物资的丰富,还有精神层次的丰富。在互联网诞生之前,地域位置往往是人们思想上不可跨域的鸿沟,信息的传播速度…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
