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();}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/873142.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

降低芯片流片风险的几种方法

[TOC] #说明 该文章仅科普下各方法是如何降低流片失败的可能性的。 #1. UVM方法学 1. uvm方法学的主要思想是通过用其它高级语言&#xff08;python、c等&#xff09;编写参考模型&#xff08;REF&#xff09;实现DUT设计相同功能。再使用uvm的一系列验证组件将相同的激励给…

每天一个数据分析题(四百三十六)- 正态分布

X为服从正态分布的随机变量N(2, 9), 如果P(X>c)P(X<c), 则c的值为&#xff08;&#xff09; A. 3 B. 2 C. 9 D. 2/3 数据分析认证考试介绍&#xff1a;点击进入 题目来源于CDA模拟题库 点击此处获取答案 数据分析专项练习题库 内容涵盖Python&#xff0c;SQL&…

用ssh tunnel的方式设置 AWS DocumentDB 公网访问

AWS DocumentDB的设定是只允许VPC内进行访问的&#xff0c;同时官方文档给了步骤&#xff0c;通过ssh tunnel的方式&#xff0c;可以从公网&#xff0c;或者从VPC外的网络&#xff0c;对DocumentDB进行访问。 我阅读了AWS官方文档并测试了这个步骤&#xff0c;如下是详细的步骤…

解决npm install(‘proxy‘ config is set properly. See: ‘npm help config‘)失败问题

摘要 重装电脑系统后&#xff0c;使用npm install初始化项目依赖失败了&#xff0c;错误提示&#xff1a;‘proxy’ config is set properly…&#xff0c;具体的错误提示如下图所示&#xff1a; 解决方案 经过报错信息查询解决办法&#xff0c;最终找到了两个比较好的方案&a…

HTTP协议、Wireshark抓包工具、json解析、天气爬虫

HTTP超文本传输协议 HTTP&#xff08;Hyper Text Transfer Protocol&#xff09;&#xff1a; 全称超文本传输协议&#xff0c;是用于从万维网&#xff08;WWW:World Wide Web &#xff09;服务器传输超文本到本地浏览器的传送协议。 HTTP 协议的重要特点&#xff1a; 一发一收…

Transformer中的自注意力是怎么实现的?

在Transformer模型中&#xff0c;自注意力&#xff08;Self-Attention&#xff09;是核心组件&#xff0c;用于捕捉输入序列中不同位置之间的关系。自注意力机制通过计算每个标记与其他所有标记之间的注意力权重&#xff0c;然后根据这些权重对输入序列进行加权求和&#xff0c…

JVM:MAT内存泄漏检测原理

文章目录 一、介绍 一、介绍 MAT提供了称为支配树&#xff08;Dominator Tree&#xff09;的对象图。支配树展示的是对象实例间的支配关系。在对象引用图中&#xff0c;所有指向对象B的路径都经过对象A&#xff0c;则认为对象A支配对象B。 支配树中对象本身占用的空间称之为…

Spire.PDF for .NET【文档操作】演示:如何在 C# 中切换 PDF 层的可见性

我们已经演示了如何使用 Spire.PDF在 C# 中向 PDF 文件添加多个图层以及在 PDF 中删除图层。我们还可以在 Spire.PDF 的帮助下在创建新页面图层时切换 PDF 图层的可见性。在本节中&#xff0c;我们将演示如何在 C# 中切换新 PDF 文档中图层的可见性。 Spire.PDF for .NET 是一…

【LabVIEW作业篇 - 1】:中途停止for和while循环

文章目录 for循环while循环如何使用帮助 for循环 在程序框图中&#xff0c;创建for循环结构&#xff0c;选择for循环&#xff0c;鼠标右键-条件接线端&#xff0c;即出现像while循环中的小红圆心&#xff0c;其作用与while循环相同。 运行结果如下。&#xff08;若随机数>…

分布式搜索引擎ES-Elasticsearch进阶

1.head与postman基于索引的操作 引入概念&#xff1a; 集群健康&#xff1a; green 所有的主分片和副本分片都正常运行。你的集群是100%可用 yellow 所有的主分片都正常运行&#xff0c;但不是所有的副本分片都正常运行。 red 有主分片没能正常运行。 查询es集群健康状态&…

ExoPlayer架构详解与源码分析(15)——Renderer

系列文章目录 ExoPlayer架构详解与源码分析&#xff08;1&#xff09;——前言 ExoPlayer架构详解与源码分析&#xff08;2&#xff09;——Player ExoPlayer架构详解与源码分析&#xff08;3&#xff09;——Timeline ExoPlayer架构详解与源码分析&#xff08;4&#xff09;—…

Pytest+selenium UI自动化测试实战实例

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 今天来说说pytest吧&#xff0c;经过几周的时间学习&#xff0c;有收获也有疑惑&#xff0c;总之…

【Python数据分析】数据分析三剑客:NumPy、SciPy、Matplotlib中常用操作汇总

文章目录 NumPy常见操作汇总SciPy常见操作汇总Matplotlib常见操作汇总官方文档链接NumPy常见操作汇总 在Python的NumPy库中,有许多常用的知识点,这里列出了一些核心功能和常见操作: 类别函数或特性描述基础操作np.array创建数组np.shape获取数组形状np.dtype查看数组数据类…

适用于618/7xx芯片平台 AT开发 远程FOTA升级指南教程

简介 AT版本的远程升级主要是对AT固件版本进行升级&#xff0c;实际方式为通过合宙官方IOT平台升级或者使用自己搭建的服务器进行升级服务。 该文档教程流程适用于 618/716S/718P 芯片平台的Cat.1模块 合宙IOT平台配置 升级日志 —— 如何查看 升级日志 —— 响应码列表 响应…

网络安全相关竞赛比赛

赛事日历&#xff08;包含全国所有网络安全竞赛&#xff09; https://datacon.qianxin.com/competition/competitions https://www.ichunqiu.com/competition/all 全国网络安全竞赛 名称链接全国大学生信息安全竞赛http://www.ciscn.cn/信息安全与对抗技术竞赛&#xff08;In…

PCB(印制电路板)制造涉及的常规设备

印制电路板&#xff08;PCB&#xff09;的制造涉及多种设备和工艺。从设计、制作原型到批量生产&#xff0c;每个阶段都需要不同的专业设备。以下是一些在PCB制造过程中常见的设备&#xff1a; 1. 计算机辅助设计&#xff08;CAD&#xff09;软件&#xff1a; - 用于设计PC…

深度解析 containerd 中的 CNI 插件

CNI&#xff08;Container Network Interface&#xff09;插件是独立的可执行文件&#xff0c;遵循 CNI 规范。Kubernetes 通过 kubelet 调用这些插件来创建和管理容器的网络接口。CNI 插件的主要职责包括网络接口的创建和删除、IP 地址的分配和回收、以及相关网络资源的配置和…

C语言 分割链表

题目来源: 代码部分&#xff0c;参考官方题解的写法: // 思路: 就是把原始链表&#xff0c;拆分为2部分&#xff0c;最后再拼接一下。struct ListNode* partition(struct ListNode* head, int x) {struct ListNode* small malloc(sizeof(struct ListNode));struct ListNode*…

Memcached介绍与使用

引言 本文是笔者对Memcached这个高性能分布式缓存组件的实践案例&#xff0c;Memcached是一种高性能的分布式内存对象缓存系统&#xff0c;用于减轻数据库负载&#xff0c;加速动态Web应用&#xff0c;提高网站访问速度。它通过在内存中缓存数据和对象来减少读取数据库的次数&…

IAR嵌入式开发解决方案已全面支持芯科集成CX3288系列车规RISC-V MCU,共同推动汽车高品质应用的安全开发

中国上海&#xff0c;2024年7月16日 — 全球领先的嵌入式系统开发软件解决方案供应商IAR与芯科集成电路&#xff08;以下简称“芯科集成”&#xff09;联合宣布&#xff0c;最新版本IAR Embedded Workbench for RISC-V 3.30.2功能安全版已全面支持芯科集成CX3288系列车规RISC-V…