redis缓存击穿和缓存穿透的封装、缓存更新的CacheAside方案、数据预热
redis缓存击穿和缓存穿透的封装
- 一、首先是互斥锁
- 二、封装为工具类
- 三、调用
- 四、数据预热
- 五、缓存更新的CacheAside方案
(来源黑马redis)
一、首先是互斥锁
//拿到锁private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);//相当于setnxreturn BooleanUtil.isTrue(flag);//判断是否成功,因为直接返回可能会导致拆箱}//释放锁private void unlock(String key){stringRedisTemplate.delete(key);}
解释:
tryLock 方法
这个方法尝试获取一个分布式锁,使用 Redis 的 setIfAbsent 方法来实现。
1.方法签名: private boolean tryLock(String key)
key: 锁的键名,用于在 Redis 中标识这个锁。
返回值: 如果成功获取锁,则返回 true;否则返回 false。
2.方法内部逻辑:
使用 stringRedisTemplate.opsForValue().setIfAbsent(key, “1”, 10, TimeUnit.SECONDS) 尝试在 Redis 中设置一个键值对。
key: 锁的键名。
“1”: 锁的值,这里只是一个占位符,表示该键已被锁定。
10, TimeUnit.SECONDS: 设置锁的过期时间为 10 秒。这是为了防止死锁,即某个进程获取了锁但未能正确释放,导致其他进程无法获取锁。
setIfAbsent 方法相当于 Redis 的 SETNX 命令,它会在键不存在时设置键值对,并返回 true;如果键已存在,则不做任何操作并返回 false。
BooleanUtil.isTrue(flag) 用于判断 setIfAbsent 的返回值。这里进行了安全的布尔值判断,避免了自动拆箱可能引发的 NullPointerException。
unlock 方法
这个方法用于释放之前获取的分布式锁。
方法签名: private void unlock(String key)
key: 需要释放的锁的键名。
方法内部逻辑:
使用 stringRedisTemplate.delete(key) 来删除 Redis 中的锁键。这相当于释放了锁,使得其他进程可以尝试获取该锁。
这个方法没有返回值,因为它只是简单地执行删除操作。
注意事项:
在实际的生产环境中,你可能需要处理更多的边界情况和异常,例如网络错误、Redis 服务器故障等。
为了防止误删其他进程的锁,你可能需要在删除前验证锁的值是否与你设置的值相匹配。
在高并发的场景下,你可能需要考虑使用更复杂的锁机制,例如 RedLock 算法,以提高锁的可靠性和安全性。
在某些情况下,你可能需要处理锁续期的问题,特别是当锁的持有时间可能超过你最初设置的过期时间时。这可以通过定时任务或后台线程来实现。
二、封装为工具类
@Slf4j
@Component
public class CaCheClient {private StringRedisTemplate stringRedisTemplate;//注入,操作redis。/*** 构造函数,用于初始化StringRedisTemplate。*/public CaCheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}/*** 向Redis中设置键值对,并指定过期时间。** @param key 键* @param value 值* @param time 过期时间* @param unit 时间单位*/public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value) , time, unit);}/*** 使用逻辑过期方式向Redis中设置键值对。** @param key 键* @param value 值* @param time 过期时间* @param unit 时间单位*/public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {//设置逻辑过期RedisData redisData=new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));//存入redis中stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}/*** 使用互斥锁实现缓存穿透处理的逻辑。** @param keyPreFix 键前缀* @param id 唯一标识符* @param type 返回对象的类型* @param dbFallback 当缓存不存在时,从数据库获取数据的函数(因为有参数有返回值)* @param time 缓存过期时间* @param unit 时间单位* @param <R> 返回对象的类型* @param <ID> 唯一标识符的类型* @return 返回查询到的对象*/public <R,ID> R queryWithPassThrough(String keyPreFix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time,TimeUnit unit){//我们不知道返回什么类型,所以定义泛型<R> R,String key =keyPreFix + id; //定义一个key,包装id和一个字段名//1.从redis中查询商铺缓存String Json = stringRedisTemplate.opsForValue().get(key);//存的是一个对象可以用哈希,也可以用String,这里用hash演示//2.判断是否存在if (StrUtil.isNotBlank(Json)) {//isNotBlank只有里面有字符的时候才是true,null和空或者/n都为空false//3.存在,直接返回//把JSON对象转化为shop对象return JSONUtil.toBean(Json, type);}//判断命中的是否是空值if (Json != null) {//返回一个错误信息return null;}//防止缓存穿透:缓存穿透是指恶意请求或者不存在的数据请求导致大量的查询直接访问数据库,而绕过了缓存层。在这段代码中,如果 Json 不为空(即缓存中存在值),但其实际内容为null,则这可能是一个早前缓存的结果,数据库中确实没有对应数据。在这种情况下,直接返回 null,避免继续查询数据库,从而节省资源。//4.不存在,根据id查询数据库// R r = getById(id);//因为我们这里,需要去查询一个有参有返回值的函数,所以我们在上面定义Function(难点)R r = dbFallback.apply(id);//Function<ID,R> dbFallback,这里传入id,返回R//5.不存在,返回错误if (r == null) {//将空值,写入redisstringRedisTemplate.opsForValue().set(key,"null",2L, TimeUnit.MINUTES);//返回错误信息return null;}//6.存在,把数据写入redis,this.set(key, r, time, unit);//7.然后返回。return r;}/*** 创建一个线程池*/private static final ExecutorService CACHE_BUILDER_EXECUTOR = Executors.newFixedThreadPool(10);//获得十个线程/*** 使用逻辑过期解决缓存击穿问题的查询方法。** @param keyPreFix 键前缀* @param id 唯一标识符* @param type 返回对象的类型* @param dbFallback 当缓存不存在或过期时,从数据库获取数据的函数* @param time 缓存过期时间* @param unit 时间单位* @param <R> 返回对象的类型* @param <ID> 唯一标识符的类型* @return 返回查询到的对象*/public <R,ID> R queryWithLogicalExpire(String keyPreFix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time,TimeUnit unit){String key =keyPreFix + id; //定义一个key,包装id和一个字段名//1.从redis中查询商铺缓存String Json = stringRedisTemplate.opsForValue().get(key);//存的是一个对象可以用哈希,也可以用String,这里用hash演示//2.判断是否存在if (StrUtil.isBlank(Json)) {//isNotBlank只有里面有字符的时候才是true,null和空或者/n都为空false//3.存在,直接返回// 如果缓存中的值为空(包括 null、空字符串或者只包含空白字符),则直接返回 null。// 这是为了避免缓存穿透,即使缓存中有值但实际上没有有效数据时,也不去访问数据库,而是直接返回空结果。//把JSON对象转化为shop对象return null;}//RedisData里面有两个参数: private LocalDateTime expireTime;private Object data;data用来存储数据//4.命中,需要先把json反序列化为对象RedisData redisData = JSONUtil.toBean(Json, RedisData.class);Object data = redisData.getData();R r = JSONUtil.toBean((JSONObject) data,type);LocalDateTime expireTime = redisData.getExpireTime();//5.判断是否过期if (expireTime.isAfter(LocalDateTime.now())) {//意思是,过期时间(expireTime.).是不是在当前(LocalDateTime.now())时间之后(.isAfter)//5.1 未过期,直接返回店铺信息return r;}//5.2 过期了,需要缓存重建//6 缓存重建//6.1获取互斥锁String lockKey = "lock:shop:" + id; //定义一个key,包装id和一个字段名boolean isLock = tryLock(lockKey);//6.2判断是否获取锁成功if (isLock){//6.3 成功,开启独立线程,实现缓存重建CACHE_BUILDER_EXECUTOR.submit(() -> {try {//重建缓存//1.R r1 = dbFallback.apply(id);//apply(id) 方法: dbFallback.apply(id) 调用了函数式接口 dbFallback 的 apply 方法,传入了参数 id,这个方法的作用是根据 id 从数据库中获取数据并返回。//2.写入redisthis.setWithLogicalExpire(key, r1, time, unit);} catch (Exception e) {throw new RuntimeException(e);}finally {//释放锁unlock(lockKey);}});}return r;}//拿到锁private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);//相当于setnxreturn BooleanUtil.isTrue(flag);//判断是否成功,因为直接返回可能会导致拆箱}//释放锁private void unlock(String key){stringRedisTemplate.delete(key);}
}
三、调用
Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Resourceprivate CaCheClient caCheClient;@Overridepublic Result queryById(Long id) {//首先,根据id在redis中查询店铺缓存//缓存穿透,访问不存在的id来测试// Shop shop = caCheClient// .queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);//用互斥锁解决缓存击穿// Shop shop = queryWithMutex(id);//用逻辑过期解决缓存击穿,用jmt快速访问测试Shop shop =caCheClient.queryWithLogicalExpire(CACHE_SHOP_KEY,id,Shop.class,this::getById, 20L, TimeUnit.SECONDS);if (shop == null) {return Result.fail("店铺不存在");}//7.然后返回。return Result.ok(shop);}
四、数据预热
/*** 数据的预热(就是在很多活动开始前,会提前导入数据,方便访问)* @param id* @param expireSeconds*/public void saveShop2Redis(Long id,Long expireSeconds){//我们传入的这两个数据,一个是用来查询的,一个是用来设置过期时间的,都是自己定义的//1.查询店铺数据Shop shop =getById(id);//用过Mp来查询id获得店铺信息//2.封装逻辑过期时间RedisData redisData =new RedisData();//创建一个对象用来接受数据和过期时间,然后一起传进去,我觉得这个和手动设置也没啥区别啊redisData.setData(shop);redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));//3.写入redisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,JSONUtil.toJsonStr(redisData));//通过string类型写入}
五、缓存更新的CacheAside方案
CacheAside:缓存调用者在更新数据库的同时完成对缓存的更新,先操作数据库,后缓存
这里就有面试题了,
(使用事务保证数据库与缓存的操作原子性)
@Override@Transactionalpublic Result update(Shop shop) {Long id = shop.getId();if (id == null) {return Result.fail("店铺id不能为空");}//1.更新数据库updateById(shop);//2.删除缓存stringRedisTemplate.delete(CACHE_SHOP_KEY + id);//3.返回结果return Result.ok();}
相关文章:
redis缓存击穿和缓存穿透的封装、缓存更新的CacheAside方案、数据预热
redis缓存击穿和缓存穿透的封装 一、首先是互斥锁二、封装为工具类三、调用四、数据预热五、缓存更新的CacheAside方案 (来源黑马redis) 一、首先是互斥锁 //拿到锁private boolean tryLock(String key) {Boolean flag stringRedisTemplate.opsForValue…...
ArcGIS Pro SDK (九)几何 5 多边形
ArcGIS Pro SDK (九)几何 5 多边形 文章目录 ArcGIS Pro SDK (九)几何 5 多边形1 构造多边形 - 从映射点的枚举2 构造多边形 - 从包络3 获取多边形的点4 获取多边形的各个部分5 枚举多边形的各个部分6 获取多边形的线段7 构建圆环…...
Docker 镜像使用和安装
1、简介 Docker是一个开源的应用容器引擎;是一个轻量级容器技术; Docker支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像; 运行中的这个镜像…...
JAVA:Filer过滤器+案例:请求IP访问限制和请求返回值修改
JAVA:Filer过滤器 介绍 Java中的Filter也被称为过滤器,它是Servlet技术的一部分,用于在web服务器上拦截请求和响应,以检查或转换其内容。 Filter的urlPatterns可以过滤特定地址http的请求,也可以利用Filter对访问请求…...
FastAPI -- 第三弹(自定义响应、中间件、代理、WebSockets)
路径操作的高级配置 OpenAPI 的 operationId from fastapi import FastAPIapp FastAPI()# 通过 operation_id 参数设置 app.get("/items/", operation_id"some_specific_id_you_define") async def read_items():return [{"item_id": "F…...
网安小贴士(16)网络安全体系
前言 网络安全体系是一个综合性的系统,旨在保护网络系统中的硬件、软件和数据免受未经授权的访问、泄露、破坏或篡改。这个体系涉及多个方面,包括网络安全策略、安全技术和安全管理等。 一、网络安全体系概述 网络安全体系通常包括以下几个关键组成部分…...
UCOSIII 中断管理接口剖析
引言 在实时操作系统中,中断处理是一个非常重要的环节。理解和掌握中断处理流程对提高系统实时性和稳定性至关重要。本文将详细解析uCOS-III内核中的中断管理接口,包括 OSIntEnter() 和 OSIntExit() 函数的流程,并结合流程图对各个步骤进行说…...
windows 11 PC查询连接过的wlan密码
1:管理员打开cmd 2:输入netsh wlan show profiles 3:netsh wlan show profiles Shw2024-5G keyclear 密码关键内容:12345678...
npm install 出现canvas错误
npm install canvas2.8.0 --ignore-scripts只要是:npm ERR! Failed at the XXXX.X.X install script 这种错误 都可以:npm install XXXX.X.X --ignore-scripts进行更改 https://blog.csdn.net/YXWik/article/details/119039561...
Python爬虫入门篇学习记录
免责声明 本文的爬虫知识仅用于合法和合理的数据收集,使用者需遵守相关法律法规及目标网站的爬取规则,尊重数据隐私,合理设置访问频率,不得用于非法目的或侵犯他人权益。因使用网络爬虫产生的任何法律纠纷或损失,由使用…...
怎样对 PostgreSQL 中的慢查询进行分析和优化?
🍅关注博主🎗️ 带你畅游技术世界,不错过每一次成长机会!📚领书:PostgreSQL 入门到精通.pdf 文章目录 怎样对 PostgreSQL 中的慢查询进行分析和优化?一、理解慢查询的危害二、找出慢查询&#x…...
Springboot项目远程部署gitee仓库(docker+Jenkins+maven+git)
创建一个Springboot项目,勾选web将该项目创建git本地仓库,再创建远程仓库推送上去 创建TestController RestController RequestMapping("/test") public class TestController { GetMapping("/hello") public String sayHelloJe…...
Chromium CI/CD 之Jenkins实用指南2024- Windows节点开启SSH服务(七)
1.引言 在现代软件开发和持续集成的过程中,自动化部署和远程管理是不可或缺的关键环节。SSH(Secure Shell)协议以其强大的安全性和灵活性,成为连接和管理远程服务器的首选工具。对于使用Windows虚拟机作为Jenkins从节点的开发者而…...
阿里大数据面试题集锦及参考答案(3万字长文:持续更新)
目录 MapReduce Shuffle为什么要将数据写入环形缓冲区 MapReduce Shuffle为什么容易发生数据倾斜 Hadoop HA当一个Namenode挂掉,会有数据丢失吗 数据倾斜发生的位置 Combiner了解吗? 什么情况下不能用Combiner? Sum、Count、Count(distinct)哪些能用、哪些不能用Comb…...
springboot 配置 spring data redis
1、在pom.xml引入父依赖spring-boot-starter-parent,其中2.7.18是最后一版支持java8的spring <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.18</…...
Numpy基础用法
Numpy基础用法 numpy.all()num.sun() numpy.all() numpy 中的 all() 函数用于测试 NumPy 数组中所有元素是否都满足指定条件。它接受一个 NumPy 数组作为输入,并返回一个布尔值,指示数组中所有元素是否都满足条件。让我们通过具体的代码示例来深入探讨 n…...
设计模式--享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享大量细粒度的对象来减少内存消耗。这个模式的核心思想是把对象的状态分为内在状态和外在状态,其中内在状态是可以共享的,而外在状态是需要独立维护的。 享…...
可视化剪辑,账号矩阵,视频分发,聚合私信一体化营销工具 源----代码开发部署方案
可视化剪辑: 为了实现可视化剪辑功能,可以使用流行的视频编辑软件或者开发自己的视频编辑工具。其中,通过设计用户友好的界面,用户可以简单地拖拽和放大缩小视频片段,剪辑出满足需求的视频。在开发过程中,可…...
CCF-CSP认证考试 202406-2 矩阵重塑(其二) 100分题解
更多 CSP 认证考试题目题解可以前往:CSP-CCF 认证考试真题题解 原题链接: 202406-2 矩阵重塑(其二) 时间限制: 1.0 秒 空间限制: 512 MiB 题目背景 矩阵转置操作是将矩阵的行和列交换的过程。在转置过程…...
初阶数据结构的实现1 顺序表和链表
顺序表和链表 1.线性表1.1顺序表1.1.1静态顺序表(不去实现)1.1.2动态顺序表1.1.2.1 定义程序目标1.1.2.2 设计程序1.1.2.3编写代码1.1.2.3测试和调试代码 1.1.2 顺序表的问题与思考 1.2链表1.2.1链表的概念及结构1.2.1.1 定义程序目标1.2.1.2 设计程序1.…...
破解反爬虫策略 /_guard/auto.js(一) 原理
背景 当用代码或者postman访问一个网站的时候,访问他的任何地址都会返回<script src"/_guard/auto.js"></script>,但是从浏览器中访问显示的页面是正常的,这种就是网站做了反爬虫策略。本文就是带大家来破解这种策略&…...
40.简易频率计(基于等精度测量法)(3)
(1)BCD8421码:十进制数字转换成BCD8421码的方法 补零:你需要显示多少位数字,就在前面补上四倍的位宽。比如你要显示一个十进制8位的数字,就在前面补上8*432个零。判断:判断补零部分显示的十进制…...
关于Centos停更yum无法使用的解决方案
最近在使用Centos7.9系统时候,发现yum仓库无法进行安装软件包了,官方说2024年6月30日进行停更,停更后无法提供对应的软件服务。 我在使用yum安装包的时候发现确实不能使用官方服务了: CentOS停更的影响 CentOS停止更新之后&#…...
插画感言:成都亚恒丰创教育科技有限公司
插画感言:笔触间的灵魂对话 在这个快节奏、高压力的时代,我们时常在寻找那些能够触动心灵、让灵魂得以片刻栖息的角落。而插画,这一融合了艺术与情感的独特形式,便如同一股清泉,缓缓流淌进每个人的心田,以…...
【算法】数组中的第K个最大元素
难度:中等 题目: 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题…...
Perl 语言的特点
Perl 语言入门学习可以涵盖多个方面,包括其特点、基本语法、高级特性以及学习资源和社区支持等。以下是一个详细的入门学习指南: 一、Perl 语言的特点 文本处理能力强:Perl 提供了丰富的字符串处理函数和正则表达式的支持,非常适…...
NLP教程:1 词袋模型和TFIDF模型
文章目录 词袋模型TF-IDF模型词汇表模型 词袋模型 文本特征提取有两个非常重要的模型: 词集模型:单词构成的集合,集合自然每个元素都只有一个,也即词集中的每个单词都只有一个。 词袋模型:在词集的基础上如果一个单词…...
【开源 Mac 工具推荐之 2】洛雪音乐(lx-music-desktop):免费良心的音乐平台
旧版文章:【macOS免费软件推荐】第6期:洛雪音乐 Note:本文在旧版文章的基础上,新更新展示了一些洛雪音乐的新功能,并且描述更为详细。 简介 洛雪音乐(GitHub 名:lx-music-desktop )…...
AMEYA360:思瑞浦推出汽车级理想二极管ORing控制器TPS65R01Q
聚焦高性能模拟芯片和嵌入式处理器的半导体供应商思瑞浦3PEAK(股票代码:688536)发布汽车级理想二极管ORing控制器TPS65R01Q。 TPS65R01Q拥有20mV正向调节功能,降低系统损耗。快速反向关断(Typ:0.39μs),在电池反向和各种汽车电气瞬…...
简约的悬浮动态特效404单页源HTML码
源码介绍 简约的悬浮动态特效404单页源HTML码,页面简约美观,可以做网站错误页或者丢失页面,将下面的代码放到空白的HTML里面,然后上传到服务器里面,设置好重定向即可 效果预览 完整源码 <!DOCTYPE html> <html><head><meta charset="utf-8&q…...
做外贸找工厂货源网站/湖南seo优化服务
1. 查询long_query_time 的值。 mysql> show variables like ‘long_query_time’; 2.通过SQL语句设置慢查 2.1mysql> set global long_query_time5; 2.2 mysql> set long_query_time5;  3.总结 直接修改global 的long_query_time 之后在当前的的窗…...
校园网站建设规划书/台州seo排名公司
链接: https://www.lydsy.com/JudgeOnline/problem.php?id1699 思路: 水题,求区间最大最小值,然后减一下,求区间最大最小值线段树,树状数组,分块,莫队都可以,这里直接用…...
jquery插件 wordpress/杭州新站整站seo
在Java中,如何格式化24小时格式的时间??此示例使用SimpleDateFormat类的sdf.format(date)方法将时间格式化为24小时格式(00:00-24:00)。package com.yiibai;import java.text.SimpleDateFormat;import java.util.*;public class FormatTimeIn…...
骏域网站建设专家/百度云网盘资源
2019独角兽企业重金招聘Python工程师标准>>> ubuntu增加右键命令:在终端中打开 软件中心:搜索nautilus-open-terminal安装 命令行:sudo apt-get install nautilus-open-terminal 重新加载文件管理器 nautilus -q 或注销再登录 转载…...
代充网站怎么做/西安百度竞价外包
这个关于通知栏的bug,虽然挺小的,但是感觉也挺重要的,在这里分享一下。 这个bug是这样的,日历响应后会弹出一个notifycation通知栏,在点击之后该通知并没有立即消失,最后才发现原来应该在一个AlertReceive…...
网络网站建设推广/青岛seo推广公司
几种shell的简介 Bourne Shell (/bin/sh) 伯努利Shell,最原始的shellBourne Again Shell 升级版的Bourne Shell,完全兼容Bourne Shell 但是在其基础上进行了很大的扩展。一般大部分Linux默认的Shell都是此Shell. Shell脚本的运行方式 修改文件的权限位,使脚本文件…...