Redis缓存穿透、击穿、雪崩问题原理和解决方案

news2024/11/28 5:43:06

目录

    • 一、Redis缓存穿透
      • 1.1、缓存穿透原理
      • 1.2、缓存穿透代码演示
      • 1.3、缓存穿透解决方案
        • 解决方案一(数据库中查询不到数据也将key进行缓存)
        • 解决方案二(使用布隆过滤器)
    • 二、Redis缓存击穿(缓存失效)
    • 三、Redis缓存雪崩
      • 3.1、缓存雪崩原理
      • 3.2、缓存雪崩解决方法

一、Redis缓存穿透

1.1、缓存穿透原理

    缓存穿透是指查询一个根本不存在的数据,比如一个商品表其中有商品ID:P001、P002、P003,在调用查询商品详情时传入商品ID:P004,P004在缓存中一定是不存在的会直接越过缓存层执行查询数据库逻辑,并且商品表中也是不存在的,查询不到数据则不会进行数据缓存。

  • 造成缓存穿透的基本原因有两个:
    • 自身业务代码或者数据出现问题
    • 恶意攻击、 爬虫等造成大量空命中

在这里插入图片描述

1.2、缓存穿透代码演示

    在下面示例代码中,当调用获取商品详情方法传入商品ID为P001-3时能获取到商品详情数据,并且会将数据库中查询到的商品详情插入Redis中,下一次查询相同商品ID会直接读取Redis中的数据而不会再去读取数据库,如果传入商品ID不为P001-3时,比如说P004,数据库中没有这个商品,数据库中查询不到则不会将P004缓存到Redis中,那么每次查询P004都会执行查询数据库逻辑,这就是缓存穿透问题,流量没有被缓存处理还是会打到数据库。

@Service
public class ProductDetailsService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    // 商品详情key前缀
    private final String PRODUCT_DETAILS_KEY_PREFIX = "PRODUCT_DETAILS_KEY:";
    /**
     * 获取商品详情V1
     * @param productId 商品ID
     */
    public Object getProductDetailsV1(String productId) {
        if (StringUtils.isEmpty(productId)) {
            throw new RuntimeException("商品ID不能为空");
        }
        // 缓存key
        String productDetailsKey = PRODUCT_DETAILS_KEY_PREFIX + productId;
        // 判断缓存是否存在,如果存在直接返回
        boolean exist = redisTemplate.hasKey(productDetailsKey);
        if (exist) {
            Object value = redisTemplate.opsForValue().get(productDetailsKey);
            System.out.println("获取到缓存数据 value=" + value);
            return value;
        }
        // 如果缓存中没有获取到商品详情则查询数据库
        Object productDetails = getDBProductDetails(productId);
        // 如果查询数据库获取到了商品详情则将商品详情插入缓存
        if (productDetails != null) {
            redisTemplate.opsForValue().set(productDetailsKey, productDetails);
        }
        return productDetails;
    }

    /** 模拟查询数据库 */
    private Object getDBProductDetails(String productId) {
        switch (productId) {
            case "P001":
                return "商品P001-Java编程思想";
            case "P002":
                return "商品P002-Java并发编程的艺术";
            case "P003":
                return "商品P003-DDD领域驱动设计";
        }
        return null;
    }
}

1.3、缓存穿透解决方案

解决方案一(数据库中查询不到数据也将key进行缓存)

    在上面V1方法的基础上将数据库中查询不到数据返回的null值也进行缓存,但是一定要设置一个短暂的过期时间,这样第二次查询就会被缓存拦截流量不会打到数据库,避免了部分场景的缓存穿透问题,但是无法很好的避免恶意攻击,恶意攻击时可以每次生成的请求商品详情ID都不同,如果将这些商品详情ID都存储到缓存中就算设置了过期时间,Redis压力也会非常大,如果不考虑恶意攻击问题其实这个方案基本能解决缓存穿透问题。

    public Object getProductDetailsV2(String productId) {
        if (StringUtils.isEmpty(productId)) {
            throw new RuntimeException("商品ID不能为空");
        }
        // 缓存key
        String productDetailsKey = PRODUCT_DETAILS_KEY_PREFIX + productId;
        // 判断缓存是否存在,如果存在直接返回
        boolean exist = redisTemplate.hasKey(productDetailsKey);
        if (exist) {
            Object value = redisTemplate.opsForValue().get(productDetailsKey);
            System.out.println("获取到缓存数据 value=" + value);
            return value;
        }
        // 如果缓存中没有获取到商品详情则查询数据库
        Object productDetails = getDBProductDetails(productId);

        // 如果查询数据库获取到了商品详情则将商品详情插入缓存
        if (productDetails != null) {
            redisTemplate.opsForValue().set(productDetailsKey, productDetails);
        }else {
            // 如果数据库查询不到数据也进行缓存,并且设置一个过期时间
            redisTemplate.opsForValue().set(productDetailsKey, null,1, TimeUnit.MINUTES);
        }
        return productDetails;
    }
解决方案二(使用布隆过滤器)

    对于恶意攻击,向服务器请求大量不存在的数据造成的缓存穿透,还可以用布隆过滤器先做一次过滤,对于不存在的数据布隆过滤器一般都能够过滤掉,不让请求再往后端发送。当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。
    布隆过滤器就是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的 hash 值算得比较均匀。向布隆过滤器中添加 key 时,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。向布隆过滤器询问 key 是否存在时,跟 add 一样,也会把 hash 的几个位置都算出来,看看位数组中这几个位置是否都为 1,只要有一个位为 0,那么说明布隆过滤器中这个key 不存在。如果都是 1,这并不能说明这个key 就一定存在,只是极有可能存在,因为这些位被置为 1 可能是因为其它的 key 存在所致。如果这个位数组比较稀疏,这个概率就会很大,如果这个位数组比较拥挤,这个概率就会降低。这种方法适用于数据命中不高、 数据相对固定、 实时性低(通常是数据集较大)的应用场景,代码维护较为复杂,但是缓存空间占用很少。
在这里插入图片描述

    Redisson提供了布隆过滤器的实现可以直接使用。

    @Autowired
    private RedissonClient redissonClient;
    public Object getProductDetailsV3(String productId) {
        if (StringUtils.isEmpty(productId)) {
            throw new RuntimeException("商品ID不能为空");
        }
        // 判断布隆过滤器中是否有我们的商品详情ID,如果没有则代表系统没有这个商品详情信息
        RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter(PRODUCT_DETAILS_BLOOM_FILTER_KEY_PREFIX);
        if (!bloomFilter.contains(productId)) {
            System.out.println("通过布隆过滤器判断商品详情不存在 productId=" + productId);
            return null;
        }
        // 缓存key
        String productDetailsKey = PRODUCT_DETAILS_KEY_PREFIX + productId;
        // 判断缓存是否存在,如果存在直接返回
        boolean exist = redisTemplate.hasKey(productDetailsKey);
        if (exist) {
            Object value = redisTemplate.opsForValue().get(productDetailsKey);
            System.out.println("获取到缓存数据 value=" + value);
            return value;
        }
        // 如果缓存中没有获取到商品详情则查询数据库
        Object productDetails = getDBProductDetails(productId);
        // 如果查询数据库获取到了商品详情则将商品详情插入缓存
        if (productDetails != null) {
            redisTemplate.opsForValue().set(productDetailsKey, productDetails);
        }
        return productDetails;
    }

    /**
     * 初始化布隆过滤器
     * 使用布隆过滤器需要把所有数据提前放入布隆过滤器,并且在增加数据时也要往布隆过滤器里放
     * 布隆过滤器只能新增数据不能删除数据,如果要删除得重新初始化数据
     */
    private void initProductDetailsBloomFilter() {
        RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter(PRODUCT_DETAILS_BLOOM_FILTER_KEY_PREFIX);
        //初始化布隆过滤器:预计元素为100000000L,误差率为3%,根据这两个参数会计算出底层的bit数组大小
        bloomFilter.tryInit(100000000L, 0.03);
        //将商品详情ID P001-3 插入到布隆过滤器中
        bloomFilter.add("P001");
        bloomFilter.add("P002");
        bloomFilter.add("P003");
    }

二、Redis缓存击穿(缓存失效)

    由于大批量缓存在同一时间失效可能导致大量请求同时穿透缓存直达数据库,可能会造成数据库瞬间压力过大甚至挂掉,对于这种情况我们在批量增加缓存时最好将这一批数据的缓存过期时间设置为一个时间段内的不同时间。

三、Redis缓存雪崩

3.1、缓存雪崩原理

    缓存雪崩指的是缓存层支撑不住或宕掉后, 流量会全部打向后端存储层。由于缓存层承载着大量请求, 有效地保护了存储层, 但是如果缓存层由于某些原因不能提供服务(比如超大并发过来,缓存层支撑不住,或者由于缓存设计不好,类似大量请求访问bigkey,导致缓存能支撑的并发急剧下降), 于是大量请求都会打到存储层, 存储层的调用量会暴增, 造成存储层也会级联宕机的情况。

3.2、缓存雪崩解决方法

  • 1、保证缓存层服务高可用,比如使用Redis Sentinel或Redis Cluster。
  • 2、做好高并发测试,确保现有架构能抗住线上并发。
  • 3、使用Sentinel或Hystrix服务保护组件对业务接口做限流熔断降级处理。

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

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

相关文章

KV STUDIO对plc的读取与电焊机的配料设置

今天又开始了明天的工作总结&#xff0c;希望对于看小编博客的粉丝有所帮助&#xff0c;前程似锦&#xff01;&#xff01;&#xff01; KV STUDIO对plc的读取 一&#xff0c;先将电脑与设备相连接&#xff0c;有许多种发生&#xff0c;小编这使用的是以太网方式 二&#xff0…

倒计时丨距离RestCloud新品发布仅有6天!

6天倒计时&#xff0c;RestCloud零代码集成自动化平台重磅发布 ⏰11月9日14:00&#xff0c;期待您的参与&#xff01; 点击报名&#xff1a;http://c.nxw.so/dfaJ9

【LeetCode刷题-链表】--146.LRU缓存

146.LRU缓存 方法一&#xff1a;哈希表双向链表 使用一个哈希表和一个双向链表维护所有在缓存中的键值对 双向链表按照被使用的顺序存储了这些键值对&#xff0c;靠近头部的键值对是最近使用的&#xff0c;而靠近尾部的键值对是最久使用的哈希表即为普通的哈希映射&#xff0…

Java基础之类型(内涵面试题)

目录 一、自动类型转换&#xff1a; 二、强制类型转换&#xff1a; 1.强制类型转换可能造成数据丢失&#xff08;溢出&#xff09;。 2.浮点型强转成整型&#xff0c;直接丢掉小数部分&#xff0c;保留整数部分返回。 三、自增、自减&#xff08;、--&#xff09;有关面试题…

JavaScript设计模式之发布-订阅模式

发布者和订阅者完全解耦&#xff08;通过消息队列进行通信&#xff09; 适用场景&#xff1a;功能模块间进行通信&#xff0c;如Vue的事件总线。 ES6实现方式&#xff1a; class eventManager {constructor() {this.eventList {};}on(eventName, callback) {if (this.eventL…

【凡人修仙传】定档曝光,最新更新时间有所调整,期待值暴涨

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析国漫资讯。 深度爆料&#xff0c;备受瞩目的动漫作品《凡人修仙传》终于在新年之际宣布定档了&#xff01;这个消息让广大动漫爱好者们激动不已。在某知名视频网站上&#xff0c;这部作品的官方发布了一个名为“新年番定…

在 Python 中创建奇数列表

我们将在本文中介绍在 Python 中创建奇数列表的不同方法。 Python 中的奇数 定义奇数有两种方法&#xff0c;第一种是整数不能被 2 整除时的情况。另一种是整数除以 2 时余数为 1 的情况。 例如&#xff0c;1、5、9、11、45等都是奇数。 从列表中获取奇数的方法有很多&#x…

Attention is all you need 论文阅读

论文链接 Attention is all you need 0. Abstract 主要序列转导模型基于复杂的循环或卷积神经网络&#xff0c;包括编码器和解码器。性能最好的模型还通过注意力机制连接编码器和解码器提出Transformer&#xff0c;它 完全基于注意力机制&#xff0c;完全不需要递归和卷积对两…

为什么我们要重视学历提升?因为“一纸文凭”就是我们重要的通行证!

在当下社会内卷越来越紧的时代&#xff0c;“一纸文凭”就是我们最重要的通行证&#xff0c;可能学历背景不够好&#xff0c;在职场上就难以获得发展&#xff0c;未来规划也无法成功。 学历提升的重要性 1.就业 现在的就业市场越来越看重一个人的学历出身&#xff0c;单位招…

【小白的Spring源码手册】 Bean的扫描、装配和注册,面试学习可用

目录 前言源码学习Bean配置1. 注解2. xml配置 Bean扫描、装配、注册1. 扫描2. 装配BeanDefinition3. 校验BeanDefinition4. 注册BeanDefinition 总结 前言 如今Spring框架功能众多&#xff0c;每次打开Spring源码&#xff0c;要么就是自顶向下从整个框架来了解Spring整体流程&…

YOLOv5:按每个类别的不同置信度阈值输出预测框

YOLOv5&#xff1a;按每个类别的不同置信度阈值输出预测框 前言前提条件相关介绍YOLOv5&#xff1a;按每个类别的不同置信度阈值输出预测框预测修改detect.py输出结果 验证修改val.py输出结果 参考 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。更…

关于FastJSON序列化Bean时对get方法调用的细节

结论 使用JSON.toJSONString去序列化Bean的时候 FastJSON会把Bean里面的get开头&#xff0c;有返回值且没有参数的方法都调用一遍。 看代码 package org.example.domain;import lombok.Getter; import lombok.Setter;/*** program: parent_pro* description:* author: 渭水* c…

为何袁世凯要把“元宵节”改为“上元节”?

网民把春节除夕日排除在法定假期之外的相关热议&#xff0c;在微博评论区部分已被关闭。官方学者的解释是&#xff1a;“回归传统。” 这就令人难免要回顾历史&#xff0c;并发觉只有在袁世凯称帝之后&#xff0c;才有过取消“元宵节”改为“上元节”的笑话&#xff0c;因为“元…

数模国赛——多波束测线问题模型建立研究分析

第一次参加数模国赛&#xff0c;太菜了~~~~意难平 问题一 画出与测线方向垂直的平面和海底坡面的交线构成一条与水平面夹角为&#x1d400;的斜线的情况下的示意图进行分析&#xff0c;将覆盖宽度分为左覆盖宽度和右覆盖宽度&#xff0c;求出它们与海水深度和&#x1d400;、…

纷享销客荣获最佳制造业数字营销服务商奖

2023年10月26日&#xff0c;第二届中国制造业数智化发展大会在上海盛大召开。本次大会汇聚了制造行业的顶尖企业和专家&#xff0c;共同探讨如何通过数字化转型赋能企业自身成长&#xff0c;实现信息化向数字化的升级转型。 在本次盛会上&#xff0c;纷享销客以其卓越的基本面、…

[激光原理与应用-75]:西门子PLC系列选型

目录 一、西门子PLC PLC系列 二、西门子PLC S7 1200系列 2.1 概述 2.2 12xx系列比较 三、西门子 PLC 1212C系列 四、主要类别比较 4.1 AC/DC/RLY的含义 4.2 AC/DC/RLY与DC/DC/DC 4.3 直流输入与交流输入比较 4.4 继电器输出与DC输出的区别 一、西门子PLC PLC系列 …

人工智能与卫星:颠覆性技术融合开启太空新时代

人工智能与卫星&#xff1a;颠覆性技术融合开启太空新时代 摘要&#xff1a;本文将探讨人工智能与卫星技术的融合&#xff0c;并介绍其应用、发展和挑战。通过深入了解这一领域的前沿动态&#xff0c;我们将展望一个由智能卫星驱动的未来太空时代。 一、引言 近年来&#xf…

【华为OD题库-018】AI面板识别-Java

题目 Al识别到面板上有N(1<N≤100)个指示灯&#xff0c;灯大小一样&#xff0c;任意两个之间无重叠。由于AI识别误差&#xff0c;每次识别到的指示灯位置可能有差异&#xff0c;以4个坐标值描述Al识别的指示灯的大小和位置(左上角x1,y1&#xff0c;右下角x2.y2)。请输出先行…

可视化协作软件有哪些?这10款神器助力团队合作!

可视化协作已经成为一个时下热门词汇&#xff0c;问题是对其并没有一个清晰的定义。有人认为它代表了一个云端环境&#xff0c;具有能够使办公室、混合办公和远程员工一起工作的功能。其他人则认为可视化协作不过是数字化白板而已。 随着这个术语变得更加流行&#xff0c;许多…

番外---9.0 firewall 网络

### 网络配制方式&#xff1a; 00&#xff1a;依据图形界面形式配置&#xff08;nmtui&#xff09;&#xff1b; 01&#xff1a;命令形式配置(nmcli)&#xff1b; 02&#xff1a;使用系统菜单配置&#xff1b; 00&#xff1a;依据图形界面形式配置&#xff08;nmtui&#xff0…