【Redis】缓存穿透、缓存击穿、缓存雪崩的原因及解决方案

news2024/11/24 17:26:20

文章目录

  • 一、缓存穿透
    • 1.1 产生原因
    • 1.2 解决方法
      • 接口校验
      • 对空值进行缓存
      • 使用布隆过滤器
      • 实时监控
  • 二、缓存雪崩
    • 2.2 解决方法
      • 将失效时间分散开
      • 给业务添加多级缓存
      • 构建缓存高可用集群
      • 使用锁或者队列的方式
      • 设置缓存标记
  • 三、缓存击穿
    • 3.2 解决方法
      • 使用互斥锁
      • ”提前“使用互斥锁 / 逻辑过期
      • 提前对热点数据进行设置
      • 监控数据,适时调整
    • 3.3 实现
      • 1 互斥锁
      • 测试
      • 2 逻辑过期

一、缓存穿透

1.1 产生原因

客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会访问数据库。导致DB的压力瞬间变大而卡死或者宕机。

  • 大量的高并发的请求打在redis上
  • 这些请求发现redis上并没有需要请求的资源,redis命中率降低
  • 因此这些大量的高并发请求转向DB请求对应的资源
  • DB压力瞬间增大,直接将DB打垮,进而引发一系列“灾害”

缓存穿透发生的场景一般有两类:

  • 原来数据是存在的,但由于某些原因(误删除、主动清理等)在缓存和数据库层面被删除了,但前端或前置的应用程序依旧保有这些数据;
  • 恶意攻击行为,利用不存在的Key或者恶意尝试导致产生大量不存在的业务数据请求。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jACPoikM-1688634946898)(【Redis】缓存穿透、缓存击穿、缓存雪崩的原因及解决方案/image-20230705214531919.png)]

1.2 解决方法

接口校验

类似于用户权限的拦截,对于id = -3872这些无效访问就直接拦截,不允许这些请求到达Redis、DB上。

对空值进行缓存

比如,虽然数据库中没有id = 1022的用户的数据,但是在redis中对他进行缓存(key=1022, value=null),这样当请求到达redis的时候就会直接返回一个null的值给客户端,避免了大量无法访问的数据直接打在DB上。

但需要注意:

  • key设置的过期时间不能太长,防止占用太多redis资源,设置一个合适的TTL,比如两三分钟。
  • 当遇到黑客暴力请求很多不存在的数据时,就需要写入大量的null值到Redis中,可能导致Redis内存占用不足的情况。

使用布隆过滤器

简单的说就是:通过将一个key的hash值分布到一个大的bit数组上面,判断一个key是否存在时只需判断该的hash对应的bit位是否都是1,如果全是1则表示存在,否则不存在。性能很高但可能存在误判:

如果他告诉你不存在,则一定不存在;如果他告诉你存在,则可能不存在。

使用BitMap作为布隆过滤器,将目前所有可以访问到的资源通过简单的映射关系放入到布隆过滤器中(哈希计算),当一个请求来临的时候先进行布隆过滤器的判断,如果有那么才进行放行,否则就直接拦截。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XmeWjaTb-1688634946899)(【Redis】缓存穿透、缓存击穿、缓存雪崩的原因及解决方案/image-20230705220925615.png)]

实时监控

对redis进行实时监控,当发现redis中的命中率下降的时候进行原因的排查,配合运维人员对访问对象和访问数据进行分析查询,从而进行黑名单的设置限制服务(拒绝黑客攻击)。

二、缓存雪崩

当redis中的大量key集体过期,可以理解为Redis中的大部分数据都清空 / 失效了,这时候如果有大量并发的请求来到,Redis就无法进行有效的响应(命中率急剧下降),也会导致DB先生的绝望。

缓存雪崩的场景通常有两个:

  • 大量热点key同时过期
  • 缓存服务故障或宕机

2.2 解决方法

将失效时间分散开

常用且易于实现通过使用自动生成随机数使得key的过期时间TTL是随机的,防止集体过期。

给业务添加多级缓存

使用nginx缓存 + redis缓存 + 其他缓存,不同层使用不同的缓存,可靠性更强。

构建缓存高可用集群

主要针对缓存服务故障的情景,使用Redis集群来提高服务的可用性。

使用锁或者队列的方式

如果查不到就加上排它锁,其他请求只能进行等待,但这种方式可能影响并发量。

设置缓存标记

热点数据可以不考虑失效,后台异步更新缓存,适用于不严格要求缓存一致性的情景。

三、缓存击穿

Redis中的某个热点key过期,但是此时有大量的用户访问该过期key。

可以看成缓存雪崩的一个特殊子集。

比如xxx塌房哩、xxx商品活动,这时候大量用户都在访问该热点事件,但是可能优于某种原因,redis的这个热点key过期了,那么这时候大量高并发对于该key的请求就得不到redis的响应,那么就会将请求直接打在DB服务器上,导致整个DB瘫痪。

3.2 解决方法

使用互斥锁

只有一个请求可以获取到互斥锁,然后到DB中将数据查询并返回到Redis,之后所有请求就可以从Redis中得到响应。【缺点:所有线程的请求需要一同等待】

”提前“使用互斥锁 / 逻辑过期

在value内部设置一个比缓存(Redis)过期时间短的过期时间标识,当异步线程发现该值快过期时,马上延长内置的这个时间,并重新从数据库加载数据,设置到缓存中去。【缺点:不保证一致性,实现相较互斥锁更复杂】

提前对热点数据进行设置

类似于新闻、某博等软件都需要对热点数据进行预先设置在Redis中,或者适当延长Redis中的Key过期时间。

监控数据,适时调整

监控哪些数据是热门数据,实时的调整key的过期时长。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3p28T6My-1688634946899)(【Redis】缓存穿透、缓存击穿、缓存雪崩的原因及解决方案/image-20230705223722624.png)]

3.3 实现

1 互斥锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vebCChYc-1688634946900)(【Redis】缓存穿透、缓存击穿、缓存雪崩的原因及解决方案/image-20230705224115044.png)]

使用setnx作为Redis中的锁。

  • Redis中查询缓存
    • 存在且不为空值,直接返回
    • 为空值(比如“”、0等特殊值),返回失败结果
    • 不存在,获取锁
  • 获取锁失败,等待重试
  • 获取成功,查找MySQL
    • 不存在,Redis存入空值
    • 存在,写入Redis
  • 释放锁,返回结果
    /**
     * 根据id查找商户,先到redis中找,再到MySQL中找
     * @param id
     * @return
     */
    @Override
    public Result queryShopById(Long id) {

        // 用String形式存储JSON
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);

        // 如果查询结果不为null,直接返回
        if (StrUtil.isNotBlank(shopJson)) {
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }

        // 否则Redis中查询结果为空,判断是否为“”
        if (shopJson != null) {
            return Result.fail("店铺不存在,请确认id是否正确");
        }

        // 尝试获取锁,
        // 如果没有得到锁,Sleep一段时间
        if (!tryLock(LOCK_SHOP_KEY + id)) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 从开始重试
            return queryShopById(id);
        }

        // 获得了锁,从MySQl中查找
        Shop shop = this.getById(id);
        // 模拟重建的延时
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 不在MySQL中
        if (shop == null) {
            // 将空值写入Redis
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            // 释放锁
            unLock(LOCK_SHOP_KEY + id);
            return Result.fail("店铺不存在,请确认id是否正确");
        }
        else {
            // 在MySQL中,存入redis
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
            // 释放锁
            unLock(LOCK_SHOP_KEY + id);
            return Result.ok(shop);
        }
    }

    public boolean tryLock(String key) {
        // 尝试获取锁,set成功返回true,否则返回false
        Boolean getLock = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        // 避免getLock为null,使用工具类
        return BooleanUtil.isTrue(getLock);
    }

    public void unLock(String key) {
        stringRedisTemplate.delete(key);
    }


测试

F:\Jmeter\bin\ApacheJMeter.jar

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uo5t40vQ-1688634946900)(【Redis】缓存穿透、缓存击穿、缓存雪崩的原因及解决方案/image-20230706153638543.png)]

对应地,MySQL只执行了1次SQL:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6nAJ3Gg8-1688634946900)(【Redis】缓存穿透、缓存击穿、缓存雪崩的原因及解决方案/image-20230706153739245.png)]

2 逻辑过期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WsYMPuEj-1688634946900)(【Redis】缓存穿透、缓存击穿、缓存雪崩的原因及解决方案/image-20230706160509947.png)]

    /**
     * 线程池
     */
    private static final ThreadFactory NAMED_THREAD_FACTORY = new ThreadFactoryBuilder().build();

    private static final ExecutorService POOL = new ThreadPoolExecutor(5, 200,
            0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), NAMED_THREAD_FACTORY,
            new ThreadPoolExecutor.AbortPolicy());


    public Result queryWithExpire(Long id) {
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);

        // 如果查询结果为null,直接失败
        if (StrUtil.isBlank(shopJson)) {
            return Result.fail("您查询的数据不存在,请检查您的输入");
        }

        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        Shop shop = JSONUtil.toBean((JSONObject)redisData.getData(), Shop.class);
        // 判断缓存是否过期
        LocalDateTime time = redisData.getExpireTime();
        // 未过期,直接返回信息
        if (time.isAfter(LocalDateTime.now())) {
            return Result.ok(shop);
        }
        // 过期,获取互斥锁失败,返回过期信息
        if (!tryLock(LOCK_SHOP_KEY + id)) {
            unLock(LOCK_SHOP_KEY + id);
            return Result.ok(shop);
        }
        // 过期,获取互斥锁成功,开启新线程,重建数据库
       POOL.submit(() -> {
           this.saveShop2Redis(id, 20L);
           unLock(LOCK_SHOP_KEY + id);
       });
       // 返回过期信息
       return Result.ok(shop);
    }

	/**
     * 在MySQL中查找id的shop,写入Redis并更新虚拟过期时间
     * @param id
     * @param expireSeconds
     */
    private void saveShop2Redis(Long id, Long expireSeconds) {
        // 获取
        Shop shop = this.getById(id);

        // 封装
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));

        // 写入Redis
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
    }

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

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

相关文章

QT开发技巧之QTableWidget设置表头颜色字体

1.默认的表头和内容背景字体一样不好区别&#xff0c;可以通过qss设置修改表头样式 2.修改后效果如下 qss代码&#xff1a; /*表格头背景色*/ QHeaderView::section { background: rgb(128, 255, 255); font-family: "宋体"; font-weight:bold; font-size:16px; }

网站弱口令爆破小脚本

介绍 weakpass_exploit&#xff0c;网站弱口令爆破小脚本 优点&#xff1a; 绕过图形验证码 绕过前端数据加密 不足&#xff1a; ddddocr识别不够精确 单线程 注: 本项目所有文件仅供学习和研究使用,请勿使用项目中的技术源码用于非法用途,任何人造成的任何负面影响,与…

什么是linux内存节点?为什么要有内存节点?

什么是内存节点 在Linux中&#xff0c;内存节点指的是NUMA&#xff08;Non-Uniform Memory Access&#xff09;架构中的逻辑内存节点&#xff0c;也被称为内存域&#xff08;Memory Domain&#xff09;。NUMA是一种多处理器体系结构&#xff0c;其中每个处理器都有自己的本地内…

FP32、FP16 和 INT8

文章目录 FP32、FP16 和 INT81. FP322. FP163. INT8 FP32、FP16 和 INT8 当涉及到深度学习和计算任务时&#xff0c;FP32、FP16、INT8 和 INT4 是常用的数据类型&#xff0c;用于表示不同的数值精度和存储需求。 1. FP32 单精度浮点数&#xff1a;提供了较高的精度和动态范围…

彻底解决IJ IDEA 代码运行时中文乱码 | 完美解决方案

前言&#xff1a; 在我们刚接触到IDEA时&#xff0c;想美滋滋的敲一个“hello world”来问候这个世界&#xff0c;但难免会遇到这种问题 乱码&#xff01;乱码&#xff01;乱码&#xff01; 内心的崩溃就在一瞬间&#xff0c;下面我就来分享几个实用的解决方法 1.调整系统语…

Plant Physiology:DAP-seq技术在毛白杨PtoWRKY68等位基因变异调控干旱胁迫响应机制研究中的应用

干旱胁迫限制了树木的生长&#xff0c;并影响其地域性分布。为了应对干旱胁迫&#xff0c;植物进化出了一系列的生理生化反应机制&#xff0c;以保护植物细胞免受损害。因此&#xff0c;研究干旱胁迫下树木生理和光合作用变化的分子机制&#xff0c;将有助于培育耐旱性树木新品…

excel 表格多行自动合并

在现实生活中常常遇到这样的一些需求 使用的是三方的插件来实现的 参考&#xff1a; 慧办公-官网 (hbg666.com) (支持 Office 及 WPS)下载地址&#xff1a; https://www.hbg666.com/ 使用方法系统都有教导

单片机第一季:零基础4——数码管

1,第七章&#xff1a;静态数码管和动态数码管 工作原理&#xff1a; (1)亮灭原理&#xff08;其实就是内部的照明LED&#xff09;&#xff1b; (2)显示数字&#xff08;甚至文字&#xff09;原理&#xff1a;利用内部的LED的亮和灭让外部的组成数字的笔画显示或者不显示&#…

IDEA 错误:找不到或无法加载主类Main 完美解决方法

今天在运行项目的时候 Rebuild Prodject 后突然出了这样一个错误&#xff1a;IDEA 错误 找不到或无法加载主类,相信只要是用过IDEA的朋友都遇到过它吧&#xff0c;把我自己搞的焦头烂额&#xff01;&#xff01;csdn翻遍了没解决 1&#xff0c;未能成功编译&#xff1b; 尝试&a…

pycharm的一些常用设置

pycharm的一些常用设置 1、最新安装pycharm ,怎么设置解释器如图&#xff1a; 2、可通过鼠标放大缩小配置&#xff1a; 进入setting>Editor>File and Code Templates&#xff0c;点击python script&#xff0c;进行设置&#xff1a; """Author : A Tim…

【C语言初阶】带你轻松掌握指针基础知识完结篇——野指针,指针运算,指针和数组,二级指针

君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello&#xff0c;这里是君兮_&#xff0c;今天继续给大家更新0基础入门C语言的内容&#xff0c;我们这次主要更新的依然是初阶指针的基础知识 废话不多说咱们直接开始吧&#xff01;&#xff01; 指针基础 一. 野指针1.…

运输层:TCP报文段的首部格式

1.运输层&#xff1a;TCP报文段的首部格式 笔记来源&#xff1a; 湖科大教书匠&#xff1a;TCP报文段的首部格式 声明&#xff1a;该学习笔记来自湖科大教书匠&#xff0c;笔记仅做学习参考 为了实现可靠传输&#xff0c;TCP采用了面向字节流的方式。 但TCP在发送数据时&#…

CSS:选择器的各种类型和用法(含有例子和部分动态效果)

目录 css 基本语法一、基本选择器元素选择器 E{}特殊的 * 所有元素选择器属性选址器 E[attr]{}# id选择器. class选择器也可以结合着用selector1 selector2 包含选择器> 子选择器~兄弟选择器选择器组合 二、伪元素选择器第一个字加样式&#xff1a;第一行加样式&#xff1a;…

基于深度学习的高精度猴子检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度猴子检测识别系统可用于日常生活中或野外来检测与定位猴子目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的猴子目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5目标检测模型…

改进 Elastic Stack 中的信息检索:对段落检索进行基准测试

作者&#xff1a;Grgoire Corbire, Quentin Herreros, Thomas Veasey 在之前的博客文章中&#xff0c;我们讨论了信息检索的常见方法&#xff0c;并介绍了模型和训练阶段的概念。 在这里&#xff0c;我们将检查基准解决方案&#xff0c;以公平的方式比较各种方法。 请注意&…

基于高精度三维机器视觉的螺丝锁付系统应用

Part.1 行业背景 随着社会经济的发展、科技的进步及工业水平的提高&#xff0c;智能工业发展模式在全球范围内备受关注。螺丝锁付作为最常用的装配连接方式&#xff0c;在加工制造的各个环节得到了广泛应用&#xff0c;如&#xff1a;玩具、电子产品、塑胶、汽车零部件、通讯设…

freemark生成pdf

freemark生成pdf 字体库 simsun.ttc 解决中文问题 /*** 生成pdf* param params* param templPath* param ftlName* param htmlPath* param pdfPath* param fontPath* return*/public String processPdf(Map<String, Object> params, String templPath, String ftlName,…

CodeMirror 对 XML 文档熟悉及元素控制自定义

CodeMirror 是一个网络代码编辑器组件。它可以在网站中用于实现支持多种编辑功能的文本输入字段&#xff0c;并具有丰富的编程接口以允许进一步扩展。 本文为 xml 格式的代码提示约束格式规范的自定义示例内容。 先看效果&#xff0c;如下&#xff1a; 官方 Demo 的完整代码如…

【MATLAB第52期】#源码分享 | 基于MATLAB的高斯过程GPR超参数(sigma)自动优化算法 时间序列预测模型 五折交叉验证

【MATLAB第52期】#源码分享 | 基于MATLAB的高斯过程GPR超参数&#xff08;sigma&#xff09;自动优化算法 时间序列预测模型 五折交叉验证 后台私信回复“52期”即可免费获取数据及代码。 一、效果展示 二、优化思路 1.数据 一列时间序列数据 &#xff0c;滑动窗口尺寸为15。…

《前端开发 实践之 Webstorm 学习》

目录 Webstorm 简介官方下载地址安装记录-教程下载其他版本方法 是否推送数据统计许可证激活插件功能版本控制查看代码责任人插件(annotate) Webstorm 简介 作为 jetbrains 公司旗下一款 JavaScript 开发工具&#xff0c;Web前端开发神器之一 个人博客地址&#xff1a; 官方下载…