基于SpringBoot实现高性能缓存组件
1. 简介
为了体现我们的实力,首先我们要有造轮子的能力。这意味着我们不仅要熟练掌握现有的技术栈和框架,还要具备深厚的技术功底。通过自主设计和实现关键组件,如高性能缓存系统,我们能够深入理解技术背后的原理,掌握核心技术的精髓,从而在面对复杂问题时能够提出独到见解和解决方案。
本篇文章将带大家实现一个简化版但功能类似Spring Cache的缓存组件。虽然不会像Spring Cache那样全面和复杂,但我们将通过动手实践,掌握缓存机制的核心原理。
2. 实战案例
2.1 环境准备
既然是基于redis实现,那么我们通过引入以下依赖来简化环境配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId></dependency>
通过data-redis,我们需要做的就是配置redis服务器信息即可。
spring:redis:timeout: 10000connectTimeout: 20000host: 127.0.0.1password: xxxooolettuce:pool:maxActive: 8maxIdle: 100minIdle: 10maxWait: -1
通过以上的配置,在项目中我们就可以直接通过SpringBoot自动配置的StringRedisTemplate来实现各种操作。
2.2 自定义注解
这里我们仿照spring-cache定义如下两个注解(@Cacheable 触发缓存, @CacheEvict 删除缓存)。
**@Cacheable:**设置缓存或读取缓存;如果缓存中存在直接返回,如果不存在则将方法返回值设置到缓存中。
**@CacheEvict:**将当前指定key从缓存中删除。
触发缓存:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {// 缓存Key;String key() default "" ;// 缓存名称(类似分组)String name() default "" ;
}
删除缓存
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheEvict {// 缓存KeyString key() default "" ;// 缓存名称(类似分组)String name() default "" ;
}
以上2个注解基本相同,分别完成读写,清除缓存操作。
注:为了灵活,我们需要让上面注解中的key支持SpEL表达式。
2.3 定义切面
该切面用来处理带有上面注解的类或方法,如:方法上有**@Cacheable**注解,那么先从缓存中读取内容,如果缓存中不存在则将当前方法返回值写入缓存中。
@Component
@Aspect
public class CacheAspect {private static final Logger logger = LoggerFactory.getLogger(CacheAspect.class) ;private final StringRedisTemplate stringRedisTemplate ;public CacheAspect(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate ;}private DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer() ;@Pointcut("@annotation(com.pack.redis.cache.Cacheable)")private void cacheable() {}@Pointcut("@annotation(com.pack.redis.cache.CacheEvict)")private void cacheevict() {}@Around("cacheable() || cacheevict()") public Object proceed(ProceedingJoinPoint pjp) throws Throwable {Object ret = null ;Method method = ((MethodSignature) pjp.getSignature()).getMethod() ;Object[] args = pjp.getArgs() ;// SPEL 表达式解析SpelExpressionParser parser = new SpelExpressionParser() ;StandardEvaluationContext context = new StandardEvaluationContext() ;// 获取参数名称String[] parameterNames = discoverer.getParameterNames(method) ;for (int i = 0, len = parameterNames.length; i < len; i++) {context.setVariable(parameterNames[i], args[i]) ;}// 类上的注解不做处理了;只处理方法上Cacheable cacheable = method.getAnnotation(Cacheable.class) ;if (cacheable != null) {// 假设都配置了name和keyString name = cacheable.name() ;String expression = cacheable.key() ;Object value = parser.parseExpression(expression).getValue(context) ;String cacheKey = name + ":" + value;boolean hasKey = this.stringRedisTemplate.hasKey(cacheKey) ;if (!hasKey) {ret = pjp.proceed() ;this.stringRedisTemplate.opsForValue().set(cacheKey, new ObjectMapper().writeValueAsString(ret)) ;logger.info("写缓存【{}】,数据:{}", cacheKey, ret) ;return ret ; }logger.info("从缓存【{}】获取", cacheKey);String result = this.stringRedisTemplate.opsForValue().get(cacheKey) ;return new ObjectMapper().readValue(result, method.getReturnType()) ;}CacheEvict cacheevict = method.getAnnotation(CacheEvict.class) ;if (cacheevict != null) {String name = cacheevict.name() ;String expression = cacheevict.key() ;Object value = parser.parseExpression(expression).getValue(context) ;String cacheKey = name + ":" + value ;this.stringRedisTemplate.delete(cacheKey) ;}return pjp.proceed() ;}}
上面的环绕通知通过或(||)表达式的方式拦截2个注解,实现比较的简单主要是结合了SpEL表达式。上面的代码我们假设是你key进行了配置,如果没有配置,我们可以通过如下方式去生成key
private String getKey(Class<?> targetType, Method method) {StringBuilder builder = new StringBuilder();builder.append(targetType.getSimpleName());builder.append('#').append(method.getName()).append('(');Class<?>[] types = method.getParameterTypes() ;for (Class<?> clazz : types) {builder.append(clazz.getSimpleName()).append(",") ;}if (method.getParameterTypes().length > 0) {builder.deleteCharAt(builder.length() - 1);}return (builder.append(')').toString()).replaceAll("[^a-zA-Z0-9]", "") ;
}
如果你没有配置key,可以通过当前执行的类及方法生成唯一的key。
2.4 测试
这里我是通过JPA实现CRUD操作
@Service
public class UserService {private final UserRepository userRepository ;public UserService(UserRepository userRepository) {this.userRepository = userRepository ;}@Cacheable(name = "user", key = "#id")public User queryById(Long id) {return this.userRepository.findById(id).orElse(null) ;}@CacheEvict(name = "user", key = "#user.id")public void clearUserById(User user) {this.userRepository.deleteById(user.getId()) ;}
}
这里的clearUserById方法是有意将方法参数设置为User对象,就是为了方便演示SpEL表达式的使用。
测试接口
@GetMapping("/{id}")
public User getUser(@PathVariable("id") Long id) {return this.userService.queryById(id) ;
}@DeleteMapping("/{id}")
public void removeUser(@PathVariable("id") Long id) {User user = new User() ;user.setId(id) ;this.userService.clearUserById(user) ;
}
到此,一个简单的缓存组件就实现了。这里待完善及优化的还是非常多的,比如更新缓存,并发量大时缓存那里是不是应该加锁,不然肯定会有很多的线程都执行查询再存入缓存;我们这里是不是还可以借助本地缓存做多级缓存的优化呢?可以参考下面这篇文章。
[[Redis结合Caffeine实现二级缓存:提高应用程序性能]]
相关文章:
基于SpringBoot实现高性能缓存组件
1. 简介 为了体现我们的实力,首先我们要有造轮子的能力。这意味着我们不仅要熟练掌握现有的技术栈和框架,还要具备深厚的技术功底。通过自主设计和实现关键组件,如高性能缓存系统,我们能够深入理解技术背后的原理,掌握…...
【深度学习基础模型】递归神经网络 (Recurrent Neural Networks, RNN) 详细理解并附实现代码。
【深度学习基础模型】递归神经网络 (Recurrent Neural Networks, RNN) 【深度学习基础模型】递归神经网络 (Recurrent Neural Networks, RNN) 文章目录 【深度学习基础模型】递归神经网络 (Recurrent Neural Networks, RNN)1.算法原理介绍:递归神经网络 (Recurrent…...
python全栈学习记录(十九) hashlib、shutil和tarfile、configparser
hashlib、shutil和tarfile、configparser 文章目录 hashlib、shutil和tarfile、configparser一、hashlib二、shutil和tarfile1.shutil2.tarfile 三、configparser 一、hashlib hash是一种算法,该算法接受传入的内容,经过运算得到一串hash值。如果把hash…...
RL进阶(一):变分推断、生成模型、SAC
参考资料: 视频课程《CS285: Deep Reinforcement Learning, Decision Making, and Control》第18讲、第19讲,Sergey Levine,UCerkeley课件PDF下载:https://rail.eecs.berkeley.edu/deeprlcourse/主要内容:变分推断、生成模型、以及Soft Actor-Critic。变分推断在model-bas…...
WPF 绑定 DataGrid 里面 Button点击事件 TextBlock 双击事件
TextBlock双击事件 <DataGridTemplateColumn Width"*" Header"内标"><DataGridTemplateColumn.CellTemplate><DataTemplate><Grid><TextBlockBackground"Transparent"Tag"{Binding InternalId}"Text"…...
828华为云征文|华为云Flexus云服务器X实例Windows系统部署一键短视频生成AI工具moneyprinter
在追求创新与效率并重的今天,我们公司迎难而上,决定自主搭建一款短视频生成AI工具——MoneyPrinter,旨在为市场带来前所未有的创意风暴。面对服务器选择的难题,我们经过深思熟虑与多方比较,最终将信任票投给了华为云Fl…...
非标精密五金加工的技术要求
非标精密五金加工在现代制造业中占据着重要地位,其对于产品的精度、质量和性能有着较高的要求。以下是时利和整理的其具体的技术要求: 一、高精度的加工设备 非标精密五金加工需要先进的加工设备来保证加工精度。例如,高精度的数控机床是必不…...
新手小白怎么通过云服务器跑pytorch?
新手小白怎么通过云服务器跑pytorch?安装PyTorch的步骤可以根据不同的操作系统和需求有所差异,通过云服务器运行PyTorch的过程主要包括选择GPU云服务器平台、配置服务器环境、部署和运行PyTorch模型、优化性能等步骤。具体步骤如下: 第一步&a…...
Spring 全家桶使用教程
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
Spark SQL性能优化高频面试题及答案
目录 高频面试题及答案1. 如何通过分区(Partitioning)优化Spark SQL查询性能?2. 什么是数据倾斜(Data Skew)?如何优化?3. 如何使用广播(Broadcast)优化Join操作ÿ…...
云原生链路观测平台 openobserve + fluent-bit,日志收集
grpc-opentracing https://github.com/grpc-ecosystem/grpc-opentracing openobserve fluent-bit 为啥会选择这个组合 一个 rust 写的一个是c写的,性能和内存方面不用担心,比java 那套好太多了 openobserve 文档 :https://openobserve.ai/…...
Android 车载应用开发指南 - CarService 详解(下)
车载应用正在改变人们的出行体验。从导航到娱乐、从安全到信息服务,车载应用的开发已成为汽车智能化发展的重要组成部分。而对于开发者来说,如何将自己的应用程序无缝集成到车载系统中,利用汽车的硬件和服务能力,是一个极具挑战性…...
【Linux网络 —— 网络基础概念】
Linux网络 —— 网络基础概念 计算机网络背景网络发展 初始协议协议分层协议分层的好处 OSI七层模型TCP/IP五层(或四层)模型 再识协议为什么要有TCP/IP协议?什么是TCP/IP协议?TCP/IP协议与操作系统的关系所以究竟什么是协议? 网络传输基本流程…...
el-form动态标题和输入值,并且最后一个输入框不校验
需求:给了固定的label,叫xx单位,要输入单位的信息,但是属性名称都一样的,UI画图也是表单的形式,所以改为动态添加的形式,实现方式也很简单,循环就完事了,连着表单校验也动…...
一,初始 MyBatis-Plus
一,初始 MyBatis-Plus 文章目录 一,初始 MyBatis-Plus1. MyBatis-Plus 的概述2. 入门配置第一个 MyBatis-Plus 案例3. 补充说明:3.1 通用 Mapper 接口介绍3.1.1 Mapper 接口的 “增删改查”3.1.1.1 查询所有记录3.1.1.2 插入一条数据3.1.1.3 …...
安卓13删除下拉栏中的关机按钮版本2 android13删除下拉栏关机按钮
总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改5.编译6.彩蛋1.前言 顶部导航栏下拉可以看到,底部这里有个设置按钮,点击可以进入设备的设置页面,这里我们将更改为删除,不同用户通过这个地方进入设置。我们之前写过一个文章也是一样的删除…...
快递物流单号识别API接口代码
官网:快递鸟 API参数 一、接口描述/说明 (1)该接口仅对运单号做出识别,识别可能属于的一家或多家快递公司。 (2)接口并不返回物流轨迹,用户可结合即时查询接口和订阅查询接口完成轨迹查询、订…...
AI时代的程序员:如何保持和提升核心竞争力
1.引言 随着AIGC(如 ChatGPT、Midjourney、Claude 等)大语言模型的快速崛起,AI辅助编程工具逐渐成为程序员工作的重要组成部分。这一转变不仅改变了工作方式,更深刻影响了程序员的职业角色和技术路径。有人担心,AI将取…...
Oracle 数据库常用命令与操作指南
Oracle 数据库是企业级系统中常用的数据库管理系统,掌握基础的命令可以让你在日常管理中更加高效。本指南将介绍几条常用的 Oracle 数据库命令,涵盖用户权限管理、修改用户密码、删除用户、以及其他日常操作。 目录 授权用户操作权限使用最高权限登录 O…...
spring boot项目对接人大金仓
先确认一下依赖 第一 是否引入了mybatis-plus多数据源,如果引入了请将版本保持在3.5.0以上 <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>${dynam…...
学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...
