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

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

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

🌟持续更新的专栏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. 容易导致线程饥饿:当某个线程持有互斥锁并长时间不释放时,其他线程可能会一直等待锁的释放,导致线程饥饿现象出现。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python算法100例-4.5 最小公倍数

完整源代码项目地址,关注博主私信源代码后可获取 1.问题描述2.问题分析3.算法设计4.确定程序框架5.完整的程序 1.问题描述 求任意两个正整数的最小公倍数(Least Common Multiple,LCM)。 2.问题分析 如…

开源博客项目Blog .NET Core源码学习(12:App.Application项目结构分析)

开源博客项目Blog的App.Application项目主要定义网站页面使用的数据类,同时定义各类数据的增删改查操作接口和实现类。App.Application项目未安装Nuget包,主要引用App.Core项目的类型。   App.Application项目的顶层文件夹如下图所示,下面逐…

【Java程序设计】【C00352】基于Springboot的疫情隔壁酒店管理系统(有论文)

基于Springboot的疫情隔壁酒店管理系统(有论文) 项目简介项目获取开发环境项目技术运行截图 项目简介 项目获取 🍅文末点击卡片获取源码🍅 开发环境 运行环境:推荐jdk1.8; 开发工具:eclipse以…

大学里女生适合做什么副业?这几个小副业最适合女生

大学里女生应该做什么样的副业合适?对于这个问题没办法给大家确定的答案,毕竟每个人能做什么,适合什么,要根据每个人的性格特点特长来看。 但是女生们不管做什么,尽量都不要选择做廉价劳动力的赚钱方式,因为…

STM32串口收发单字节数据原理及程序实现

线路连接: 显示屏的SCA接在B11,SCL接在B10,串口的RX连接A9,TX连接A10。 程序编写: 在上一个博客中实现了串口的发送代码,这里实现串口的接收代码,在上一个代码的基础上增加程序功能。 Seiral.…

【搞不明白】redis和mysql、mybatisplus有啥关系

一个解决三天的bug,到现在也没有搞明白,如题,到底redis和mysql、mybatisplus能有啥关系。三个不相关的嘛 记录下问题: (框架用的Jeecgboot 3.6.0) – 学习使用 看下面的一段代码,是一个分页查询:注意引用的…

mysql数据库表字段使用desc等关键字报错及解决方法

建议尽量避开关键字!!! 如果实在必须用,那参考下面解决办法: 在要用到关键字的地方加反引号标识:

@EnableWebMvc 导致自定义序列化器失效

目录 前言 一. 自定义序列化器失效 1.1 EnableWebMvc 的作用 1.2 EnableWebMvc 带来了什么后果 1.3 原理分析 1.4 问题解决 二. 总结 前言 在使用Swagger的时候用 到了EnableWebMvc,发现之前为了解决Long类型、日期类型等自定义序列化器失效了 Configurati…

JavaScript、ES6与微信小程序:工具箱、升级与新房子

JavaScript、ES6和微信小程序三者之间有什么联系?我想,作为初学者还是有点蒙。下面作一个简单的分析,供大家参考。 首先,我们可以把JavaScript想象成一个非常强大的工具箱,里面装满了各种各样的工具。这些工具可以帮助我们完成各种任务,比如…

vivado 使用远程主机和计算群集

使用远程主机和计算群集 概述 AMD Vivado™集成设计环境(IDE)支持同时并行合成和实现的执行在多个Linux主机上运行。你可以做到这一点通过配置单个主机或指定要在其上启动作业的命令手动执行现有的计算集群。目前,Linux是Vivado唯一支持远程主…

书生浦语大模型实战营第一课笔记

书生浦语大模型全链路开源体系 课程笔记大模型的发展趋势InternLM2的主要亮点模型到应用的典型流程全链路的开源工具 InternLM2技术报告笔记大型语言模型的发展InternEvoModel Structure训练数据 课程笔记 第一节课主要对大模型进行介绍,特别是书生浦语大模型的发展…

【读书微言】The first summary

系列文章目录 文章目录 系列文章目录前言一、读书微言总结 前言 一、读书微言 强大的内心催生信念我们的生活是否幸福并不是依靠外在的环境,而是依靠我们内在的信念。要想成为自己命运的主宰,我们就必须形成自己的信念。只要我们能坚定自己的内在信念&…

认识线程(Thread)

目录 一、概念 1、 线程是什么 2、为啥要有线程? 3、进程和线程的区别 4、Java 的线程 和 操作系统线程 的关系 二、第⼀个多线程程序 三、创建线程 ⽅法1:继承 Thread 类 ⽅法2:实现 Runnable 接⼝ 对⽐上⾯两种⽅法: 其他变形 四、…

鸿蒙应用开发学习:用Marquee组件做个跑马灯

一、前言 鸿蒙应用的学习持续进行中,这两天阅读官方的API参考文档,发现一个有趣的组件——Marquee,用它做了个跑马灯,做个学习记录。 二、参考资料 官网文档链接如下: https://developer.huawei.com/consumer/cn/d…

【JVM】Java八股文之JVM篇

目录 一、JVM类加载与垃圾回收加载过程加载机制优点图解加载机制 分代回收分代垃圾回收新生代垃圾回收老年代垃圾回收 回收算法 一、JVM类加载与垃圾回收 面试过程中最经典的一题: 请你讲讲在JVM中类的加载过程以及垃圾回收? 加载过程 当Java虚拟机&…

双纤SFP光模块和单纤SFP光模块之间的区别

双纤SFP光模块和单纤SFP光模块是两种不同的光模块类型。对于网络部署而言,了解它们之间的区别至关重要。本文将深入探讨这两种光模块之间的差异,并介绍其特性和适用场景。 双纤与单纤SFP光模块:它们是什么? 双纤SFP光模块是常用…

回文子串 每日温度 接雨水

647. 回文子串 力扣题目链接 如果s【i】和s【j】相同 dp【i1】【j-1】也是回文串的话 (等于true) 那么dp【i】【j】也是回文串 true 定义一个bool二维数组 遍历顺序是从下到上 从左到右 因为dp【i】【j】是通过dp【i1】【j-1】推出来的 i从最后一…

120.龙芯2k1000-qt(19)-做了一个qt测试界面

主要接口和性能测试,主要针对的是龙芯2k1000. 以下是windows下的截图,大概功能就是这样吧,能想到的都想了一遍。 cpu的温度和频率采集不到,就没有放了。

冒泡排序(六大排序)

冒泡排序 冒泡排序的特性总结: 1. 冒泡排序是一种非常容易理解的排序 2. 时间复杂度:O(N^2) 3. 空间复杂度:O(1) 4. 稳定性:稳定 动图分析: 代码实现: Swap(int*p1,int*p2) {int tmp *p1;*p1*p2…

程序员35岁的职业困惑及应对之道

35岁,对许多程序员来说,是一个职业生涯的重要分水岭。在这个年龄,一些人开始感到迷茫和焦虑,担心自己的技能已经落后,难以跟上日新月异的技术变革。而另一些人则充满信心,认为多年来积累的丰富经验和扎实的技术功底,将助力他们在未来的职业道路上取得新的飞跃。 无疑,在AI、自…