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的垃圾分类回收管理系统
系统展示 用户界面 管理员界面 系统背景 二十一世纪互联网的出现,改变了几千年以来人们的生活,不仅仅是生活物资的丰富,还有精神层次的丰富。在互联网诞生之前,地域位置往往是人们思想上不可跨域的鸿沟,信息的传播速度…...
如何快速掌握高效屏幕标注:终极免费工具完全指南
如何快速掌握高效屏幕标注:终极免费工具完全指南 【免费下载链接】ppInk Fork from Gink 项目地址: https://gitcode.com/gh_mirrors/pp/ppInk 你是否曾在在线会议中手忙脚乱地试图解释屏幕上的内容?或者作为教师,想要在虚拟课堂上生动…...
别再让你的App‘抢麦’了!Android AudioFocus避坑指南与实战(附8.0+新API详解)
Android音频焦点管理实战:从冲突解决到优雅兼容 音乐播放器突然被通知音打断后无法恢复?语音助手播报时被来电强行中断?这些看似简单的音频冲突背后,是Android音频焦点机制的复杂运作。作为开发者,我们常常低估了正确处…...
工业内窥镜哪家好用?
经常有不同行业的朋友问我,工业内窥镜品牌这么多,到底该怎么选?其实对于大多数企业来说,选择一款适用性广、能满足多种检测场景的设备,才是最划算的。我用了这么多年韦林工业内窥镜,最大的感受就是它几乎能…...
电赛小车结构翻车实录:从STM32F407到剪叉式结构,我们踩过的那些坑
电赛智能车避坑指南:从机械结构到控制系统的实战复盘 第一次参加电子设计竞赛的团队,往往会被智能车项目中隐藏的"坑"绊得措手不及。作为一支从零开始的参赛队伍,我们在机械结构选型、核心器件采购、系统调试等环节踩遍了几乎所有常…...
毫米波混合波束成形技术在VR中的应用与优化
1. 毫米波VR中的混合波束成形技术解析在无线VR应用中,用户对低延迟和高带宽的需求日益增长。传统Wi-Fi标准在密集环境下难以满足这些QoS要求,而毫米波技术凭借其高传输速率和低延迟特性成为理想选择。本文将深入探讨毫米波频段下混合波束成形技术的实现原…...
Keil调试中局部变量修改限制的解决方案
1. 问题现象与背景解析在嵌入式开发过程中,调试环节往往占据整个开发周期的40%以上时间。作为Keil Vision的资深用户,我最近在调试一个基于C166架构的通信协议栈时,遇到了一个看似简单却令人困扰的问题:当我在receive_data函数内部…...
调查研究-142 全球机器人产业深度调研报告【04篇】机器人产业利润池全景:谁最容易赚钱与十大判断指标
TL;DR 场景:关注机器人产业投资、创业、就业方向的投资者、从业者、分析师结论:医疗机器人耗材/服务>高端核心零部件>系统集成>物流RaaS>工业本体>软件AI平台;人形机器人长期空间大但短期商业化仍早产出:三档利润池…...
深入解析SAR ADC:从二分搜索原理到高精度数据采集实战
1. 项目概述:从“猜数字”游戏理解SAR ADC在模拟信号处理的世界里,我们常常需要将现实世界中连续变化的物理量(比如温度、声音、压力)转换成计算机能够理解和处理的数字信号。这个关键的桥梁,就是模数转换器。而在众多…...
GEO生成引擎优化:当品牌竞争从搜索结果页迁移到大模型对话窗口
当生成式AI成为信息的首要分发渠道,你的品牌还只盯着SEO吗?一、用户获取信息的路径,已经变了过去十几年,我们习惯了"搜索关键词 → 浏览结果页 → 点击进入网站"这条线性路径。SEO(搜索引擎优化)…...
【Typescript】13-tsconfig与工程化实践
tsconfig 与工程化实践 很多人学 TypeScript 时,会把注意力几乎全部放在语法上:泛型会不会写、infer 看不看得懂、工具类型会不会用。可真正在工程里决定 TypeScript 上限的,往往不是这些,而是 tsconfig.json。因为它决定了编译器…...
