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.…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
