SpringBoot整合Redis:缓存击穿--互斥锁解决

news2024/12/27 5:35:55

🎉🎉欢迎光临,终于等到你啦🎉🎉

🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀

🌟持续更新的专栏Redis实战与进阶

本专栏讲解Redis从原理到实践

这是苏泽的个人主页可以看到我其他的内容哦👇👇

努力的苏泽icon-default.png?t=N7T8http://suzee.blog.csdn.net


最近超级无敌忙  就断更好久了  实在是抽不出时间来 没办法  这篇文章也只是整理我以前学习的资料  目前还有一整套企业级的Redis处理方案没写哈 敬请期待朋友们

下面是正文


目录

首先我们要明白什么是缓存击穿

分析有什么办法能解决

业务解析

​编辑

代码实现:

然后我们将缓存穿透的函数给封装起来

原函数

我们将这部分逻辑 封装到 queryWithPassThrough中

我们再写一个函数queryWithMutex来用互斥锁解决缓存穿透的问题

于是原本的查询函数的结构就变成了这样

这样做的优缺点

优点:

缺点:

下一篇我们来讲解如何使用另一个方案解决这个问题


首先我们要明白什么是缓存击穿

Redis缓存击穿是指在高并发的情况下,当某个热点数据的缓存过期或不存在时,大量的请求同时涌入数据库或后端服务,导致数据库或后端服务负载过高,甚至崩溃的情况。

分析有什么办法能解决

当涉及到并发访问共享资源时,互斥锁和逻辑过期是两种常用的技术手段。

  1. 互斥锁(Mutex):
    互斥锁是一种并发控制机制,用于在多个线程或进程之间保证共享资源的互斥访问。它通过在关键代码段前后设置锁来确保同一时间只有一个线程或进程可以执行关键代码段。当某个线程或进程获取到互斥锁时,其他线程或进程需要等待锁的释放才能继续执行。

业务解析

代码实现:

先写两个函数 一个加锁 一个释放锁

private boolean tryLock(String key){
    //自定义互斥锁  将申请锁的结果返回
    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10L, TimeUnit.SECONDS);
    return BooleanUtil.isTrue(flag);

}
//释放锁
private void unLock(String key){
    stringRedisTemplate.delete(key);

}

然后我们将缓存穿透的函数给封装起来

原函数

public Result queryById(Long id) {
    //1.从Redis查询id  这里使用的数据结构可以是String也可以是hash  若是查询不到就为空了 CACHE_SHOP_KEY就是"cache:shop:"
    String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
    //2.判断是否存在 在字符串意义上是否为空
    if (StrUtil.isNotBlank(shopJson)) {
        //3.存在直接返回
        Shop shop = JSONUtil.toBean(shopJson, Shop.class);
        return  Result.ok(shop);
    }
    //3.判断是否命中写入redis中的null
    if(shopJson != null){
        return Result.fail("店铺信息不能为空!");
    }
    //4.不存在 查询数据库
    Shop shop = getById(id);
    //5.数据库中不存在 返回报错
    if (shop == null){
        //空值写入redis
        stringRedisTemplate.opsForValue().set("cache:shop:" + id, null,CACHE_NULL_TTL, TimeUnit.MINUTES);
        return Result.fail("店铺不存在!");
    }
    //6.数据库中存在  写入Redis  并返回
    stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);

    return Result.ok(shop);
}

我们将这部分逻辑 封装到 queryWithPassThrough中

//封装缓存穿透函数
private Shop queryWithPassThrough(Long id){
    //1.从Redis查询id  这里使用的数据结构可以是String也可以是hash  若是查询不到就为空了 CACHE_SHOP_KEY就是"cache:shop:"
    String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
    //2.判断是否 在字符串意义上是否为空
    if (StrUtil.isNotBlank(shopJson)) {
        //3.存在直接返回
        Shop shop = JSONUtil.toBean(shopJson, Shop.class);
        return  shop;
    }
    //3.判断是否命中写入redis中的null
    if(shopJson != null){
        return null;
    }
    Shop shop = getById(id);
    //5.数据库中不存在 返回报错
    if (shop == null){
        //空值null写入redis
        stringRedisTemplate.opsForValue().set("cache:shop:" + id, null,CACHE_NULL_TTL, TimeUnit.MINUTES);
        return null;
    }
    //6.数据库中存在  写入Redis  并返回
    stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);

    return shop;
}

我们再写一个函数queryWithMutex来用互斥锁解决缓存穿透的问题

于是原本的查询函数的结构就变成了这样

public Result queryById(Long id) {
        //缓存穿透
//        Shop shop = queryWithPassThrough(id);
        //互斥锁解决缓存穿透
        Shop shop = queryWithMutex(id);
        if (shop == null) {
            return Result.fail("店铺不存在");
        }
        //返回
        return Result.ok(shop);
    }
//解决缓存穿透的问题
private Shop queryWithMutex(Long id) throws InterruptedException{
    String lockKey = "lockKey" + id;
    while (true) {
        //1.从Redis查询id  这里使用的数据结构可以是String也可以是hash  若是查询不到就为空了 CACHE_SHOP_KEY就是"cache:shop:"
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        //2.判断是否 在字符串意义上是否为空
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return  shop;
        }
        //3.判断是否命中写入redis中的null
        if(shopJson != null){
            return null;
        }
        //4重建缓存
        //4.1申请互斥锁

        boolean flag = tryLock(lockKey);
        //4.2判断是否成功
        if (!flag){
            try {
            Thread.sleep(100);
        } 
            catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        else//成功了就跳出循环  这里不敢用递归 怕栈溢出了
            break;
    }
    //4.4 成功 查询数据库
    Shop shop = getById(id);
    //5.数据库中不存在 返回报错
    if (shop == null){
        //空值null写入redis
        stringRedisTemplate.opsForValue().set("cache:shop:" + id, null,CACHE_NULL_TTL, TimeUnit.MINUTES);
        return null;
    }
    //6.数据库中存在  写入Redis  并返回
    stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);
    //7.释放互斥锁
    unLock(lockKey);
    return shop;
}

这样做的优缺点

互斥锁作为一种并发控制机制,在解决缓存击穿问题时具有以下优点和缺点:

优点:

  1. 确保数据一致性:互斥锁可以确保同一时间只有一个线程或进程可以访问共享资源,避免了并发访问导致的数据不一致性问题。

  2. 避免竞态条件:互斥锁可以防止多个线程或进程同时执行关键代码段,避免了竞态条件的发生。竞态条件是指多个线程或进程对共享资源的访问顺序不确定,导致结果的不可预测性。

  3. 简单易用:互斥锁的使用相对简单,可以通过加锁和解锁操作来控制对共享资源的访问。

缺点:

  1. 性能开销:互斥锁在多线程环境下会引入一定的性能开销。当多个线程竞争同一个锁时,其他线程需要等待锁的释放,这会导致一些线程的阻塞和等待,降低系统的并发性能。

  2. 可能引发死锁:如果在使用互斥锁时处理不当,可能会发生死锁的情况。死锁是指多个线程或进程相互等待对方持有的资源,导致所有线程都无法继续执行。

  3. 容易导致线程饥饿:当某个线程持有互斥锁并长时间不释放时,其他线程可能会一直等待锁的释放,导致线程饥饿现象出现。

下一篇我们来讲解如何使用另一个方案解决这个问题

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

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

相关文章

【旅游】泉州攻略v1.0.0

一、泉州古城 泉州市距离深圳大约520公里,从深圳北站出发,高铁大约3小时30分。 到达泉州西站后,往东南方向大约8公里,就可以到达主要的旅游景点泉州古城。 古城很适合使用一天玩耍,核心路线如下: 一路的景…

#Linux系统编程(关于解决Source Insight自动补全的问题)

(一)发行版:Ubuntu16.04.7 (二)记录: (1)参考博文 Source Insight 4.0 添加函数库的头文件实现函数的自动补齐 - 简书 (jianshu.com)https://www.jianshu.com/p/96595eefb988 &am…

关于内存函数的介绍

1.memcpy 2.memmove 3.memset 4.memcmp 其中,重点讲解memcpy 以及memmove。 1.C 库函数: void *memcpy(void *str1, const void *str2, size_t n) 函数作用:在 str2 复制 n 个字节到 str1。 其中,str1用于指向存储复制内容…

前端学习<二>CSS基础——04-CSS选择器:伪类

伪类(伪类选择器) 伪类:同一个标签,根据其不同的种状态,有不同的样式。这就叫做“伪类”。伪类用冒号来表示。 比如div是属于box类,这一点很明确,就是属于box类。但是a属于什么类?…

onnxruntime 中的 Gather 算子

上一篇文章中介绍了 Division by Invariant Integers using Multiplication 的原理,很多框架均才用该算法优化除法运算。onnxruntime 是已知实现中最为简洁的,因此本文结合 onnxruntime 的 Gather 实现进行介绍。 Gather 算子是一个索引类算子&#xff0…

unity学习(72)——编译游戏发生错误4——GAME_STATE状态

1.经过一天的冷静,我感觉问题出在mapHandler的update中。 如果还没有初始化对象,就开始读取对象的内容,一定会有异常的。 2.之前已有GameState结构体,我一直没当回事,这次用到了 3.从user切换到map场景的过程中会触发如…

号码采集协议讲解

仅供学习研究交流使用 需要的进去拿源码或者成品

【区块链】C语言编程实现三叉Merkle树

目录 1. Merkle树简介2. 构建Merkle树3. 生成SPV路径4. 验证SPV路径5. 三叉Merkle树创建、SPV生成及验证总程序6. 程序运行结果 1. Merkle树简介 如上图所示,Merkle 树的叶子节点为交易序列,对每一笔交易进行 Hash(SHA 256算法) 之…

vivado 在远程主机上启动作业、ISE命令图、实施类别,战略描述和指令映射

在远程主机上启动作业 一旦配置了远程主机,使用它们启动Vivado作业就很容易了。下图显示了启动运行对话框。启动跑步时,选择“在远程上启动跑步”hosts或Launch在群集上运行,然后选择特定的群集。这些作业将使用您的要执行的预配置设置。 作业…

针对COT控制模式下低ESR电容造成次谐波振荡问题的片内斜波补偿方案

COT模式:MOS管固定导通时间控制模式,关断时间由输出反馈电压与内部基准源的相较值决定。 RBCOT控制模式:Ripple-Based COT基于纹波的固定导通时间控制方法,特别的是环路控制部分主要有固定导通时间发生装置及比较器组成。RBCOT控…

DreamPolisher、InternLM2 、AniArtAvatar、PlainMamba、AniPortrait

本文首发于公众号:机器感知 DreamPolisher、InternLM2 、AniArtAvatar、PlainMamba、AniPortrait DreamPolisher: Towards High-Quality Text-to-3D Generation via Geometric Diffusion We present DreamPolisher, a novel Gaussian Splatting based method wit…

PPP实验

一、实验拓扑图 二、实验要求 1、R1和R2使用PPP链路直连,R2和R3把2条PPP链路捆绑为PPP MP直连 2、按照图示配置IP地址 3、R2对R1的PPP进行单向chap验证 4、R2和R3的PPP进行双向chap验证 三、实验步骤 1、PPP MP: (1)R2配置&#x…

C语言从入门到实战----数据在内存中的存储

1. 整数在内存中的存储 在讲解操作符的时候,我们就讲过了下⾯的内容: 整数的2进制表⽰⽅法有三种,即 原码、反码和补码 有符号的整数,三种表⽰⽅法均有符号位和数值位两部分,符号位都是⽤0表⽰“正”,⽤…

dji esdk开发(4)SDK互联互通(与云端进行小数据通信)

Edge SDK 提供接口可以通过上云 API 与和机场建立连接的云端服务器进行小数据交互,即向云端服务器发送自定义小数据与接收来自云端服务器的自定义小数据。 注意: 使用该接口发送和接收数据上下行通道最大带宽不应超过 0.5Mb/S。 1、云端低速通道介绍 使用自定义小数据通道需…

C++类和对象、面向对象编程 (OOP)

文章目录 一、封装1.抽象、封装2.类和对象(0)学习视频(1)类的构成(2)三种访问权限(3)struct和class的区别(4)私有的成员变量、共有的成员函数(5)类内可以直接访问私有成员,不需要经过对象 二、继承三、多态1.概念2.多态的满足条件3.多态的使用条件4.多态原理剖析5.纯…

详细描述红黑树如何左旋、右旋(图文结合)

红黑树 首先要理解二叉查找树 二叉查找树(BST)具备什么特性呢? 左子树上所有结点的值均小于或等于它的根结点的值。 右子树上所有结点的值均大于或等于它的根结点的值。 左、右子树也分别为二叉排序树。 二叉查找树是二分查找的思想&…

vben admin路由跳转拿不到param参数问题

vben admin路由跳转拿不到param参数问题 问题原因: 也就是说,从Vue Router的2022-8-22 这次更新后,我们使用上面的方式在新页面无法获取: vue也给我们提出了解决方案: ​ 1.使用 query 的方式传参 ​ 2.将参数放…

Linux项目自动化构建工具make和makefile

前言 前面我们对yum、vim、gcc/g做了介绍,本期我们再来介绍一个好用的工具,就是make和makefile! 本期内容介绍 什么是make和makefile makefile文件内容的解释 make执行makefile的原理 我们想要的makefile 一、什么是make 和 makefile ? make是一条指令…

DVB-S系统仿真学习

DVB-S系统用于卫星电视信号传输,发送端框图如下所示 扰码 实际数字通信中,载荷数据的码元会出现长连0或长连1的情况,不利于接收端提取时钟信号,同时会使得数据流中含有大量的低频分量,使得QPSK调制器的相位长时间不变…

Python算法100例-4.6 歌星大奖赛

完整源代码项目地址,关注博主私信源代码后可获取 1.问题描述2.问题分析3.算法设计4.确定程序框架5.完整的程序6.问题拓展7.知识点补充 1.问题描述 在歌星大奖赛中,有10个评委为参赛的选手打分,分数为1~100分。选手最…