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作为主流关系型数据库,其日期时间类型的…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制
目录 节点的功能承载层(GATT/Adv)局限性: 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能,如 Configuration …...
论文阅读:Matting by Generation
今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...
