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

目录

    • 一、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服务保护组件对业务接口做限流熔断降级处理。

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

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

相关文章

Notes/Domino中的JVM版本

大家好&#xff0c;才是真的好。 这篇内容主要写给同时关注Domino和Java的技术人员。 很多人都知道&#xff0c;从Notes/Domino R5&#xff08;1999年&#xff09;版本&#xff0c;开始自带一个Java虚拟机&#xff0c;用来支持Java应用运行。但很多人不知道&#xff0c;这个J…

【c++|opencv】二、灰度变换和空间滤波---4.高斯滤波

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 1. 高斯滤波 #include <iostream> #include <opencv2/opencv.hpp> #include"Salt.h"using namespace std; using namespace cv;/…

中国人民大学与加拿大女王大学金融硕士——人生是旷野,不是一条轨道

在这个瞬息万变的时代&#xff0c;我们每个人都像是一颗流星&#xff0c;在宇宙中独自燃烧。我们每个人都有自己的梦想&#xff0c;自己的追求&#xff0c;自己的道路。然而&#xff0c;很多时候&#xff0c;我们却发现自己被现实的轨道所束缚&#xff0c;无法自由地追求自己的…

【一、http】go的http基本请求方法

1、http的基本请求 package mainimport ("bytes""fmt""io""net/http""net/url" )func post(){r, err : http.Post("http://httpbin.org/post", "", nil)if err ! nil {fmt.Println("ss")}de…

电子凭证会计数据标准试点深化后,企业管理的关键点在于什么?

为了加快建设数字中国、发展数字经济发展&#xff0c;并推动经济社会绿色化、低碳化发展&#xff0c;政府部门一直大力推动企业的数字化转型。 企业的经营活动也越来越活跃。企业在经营中产生了大量的票据&#xff0c;由于电子凭证分属不同的部门管理&#xff0c;数据不兼容&am…

竞赛选题 深度学习实现行人重识别 - python opencv yolo Reid

文章目录 0 前言1 课题背景2 效果展示3 行人检测4 行人重识别5 其他工具6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习的行人重识别算法研究与实现 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c…

IDEA远程调试代码

IDEA->RUN->Edit Configurations 端口随便选一个&#xff0c;选择调试模块&#xff0c;然后用IDEA生成的命令调试 java -agentlib:jdwptransportdt_socket,servery,suspendn,address*:8081 -jar backend-1.18.11.jar &

word插入图片不显示

段落格式要设置成非固定的。

约数之和 (普通快速幂求逆元做法)

假设现在有两个自然数 A 和 B&#xff0c;S 是 AB 的所有约数之和。 请你求出 Smod9901 的值是多少。 输入格式 在一行中输入用空格隔开的两个整数 A 和 B 。 输出格式 输出一个整数&#xff0c;代表 Smod9901 的值。 数据范围 0≤A,B≤5107 输入样例&#xff1a; …

【JavaEE初阶】 网络编程基础与Socket套接字

文章目录 &#x1f38b;网络编程基础&#x1f6a9;为什么需要网络编程&#xff1f;&#x1f6a9;什么是网络编程&#xff1f;&#x1f6a9;网络编程中的基本概念&#x1f4cc;发送端和接收端&#x1f4cc;请求和响应&#x1f4cc;客户端和服务端&#x1f4cc;常见的客户端服务端…

httpclient工具类(支持泛型转换)

1、网上搜到的httpclient工具类的问题&#xff1a; 1.1、如下图我们都能够发现这种封装的问题&#xff1a; 代码繁杂、充斥了很多重复性代码返回值单一&#xff0c;无法拿到对应的Java Bean对象及List对象集合实际场景中会对接大量第三方的OPEN API&#xff0c;下述方法的扩展…

二叉树OJ题汇总

本专栏内容为&#xff1a;leetcode刷题专栏&#xff0c;记录了leetcode热门题目以及重难点题目的详细记录 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;Leetcode &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &…

奇偶校验码和循环冗余码

在数据链路层的传输中&#xff0c;1可能变成0&#xff0c;0可能变成1&#xff0c;这是比特差错。 为了应对比特差错&#xff0c;有两种方式&#xff0c;即自动重传请求ARQ&#xff08;Automatic Repeat-reQuest&#xff09;和前向纠错FEC&#xff08;Forward Error Correction&…

c++获取和设置环境变量

这个功能非常常用&#xff0c;但是容易忘记&#xff0c;这里做个记录。 注意&#xff0c;设置的环境变量只在当前进程中生效&#xff0c;所以在电脑中的环境变量设置区域看不到。 std::string env getenv("PATH");env "X:\\envtest";std::string newEnv…

亚马逊、美客多卖家测评:如何建立养号团队实现运营化式测评?

大家好&#xff0c;我是跨境电商测评养号7年从事经验的珑哥。养号环境软件开发&#xff0c;深度解决各跨境平台矩阵养号防关联、砍单、F号问题。关注珑哥解决更多跨境养号测评问题。 测评&#xff0c;相信这个词对于大部分跨境卖家来说&#xff0c;想必都不陌生&#xff0c;因…

LCR 166.珠宝的最高价值 + 动态规划 + 记忆化搜索 + 递推 + 空间优化

LCR 166. 珠宝的最高价值 - 力扣&#xff08;LeetCode&#xff09; 现有一个记作二维矩阵 frame 的珠宝架&#xff0c;其中 frame[i][j] 为该位置珠宝的价值。拿取珠宝的规则为&#xff1a; 只能从架子的左上角开始拿珠宝每次可以移动到右侧或下侧的相邻位置到达珠宝架子的右下…

什么是OTP认证?OTP认证服务器有哪些应用场景?

OTP是一次性密码&#xff0c;即只能使用一次的密码。它基于专门的算法&#xff0c;每隔60秒生成一个不可预测的随机数字组合。这种密码的有效期仅在一次会话或交易过程中&#xff0c;因此不容易受到重放攻击。在计算器系统或其他数字设备上&#xff0c;OTP是一种只能使用一次的…

Spring Boot 3 整合 xxl-job 实现分布式定时任务调度,结合 Docker 容器化部署(图文指南)

目录 前言初始化数据库Docker 部署 xxl-job下载镜像创建容器并运行访问调度中心 SpringBoot 整合 xxl-jobpom.xmlapplication.ymlXxlJobConfig.java执行器注册查看 定时任务测试添加测试任务配置定时任务测试结果 结语附录xxl-job 官方文档xxl-job 源码测试项目源码 前言 xxl-…

【VR开发】【Unity】【VRTK】3-VR项目设置

任何VR避不开的步骤 如何设置VR项目,无论是PC VR还是安卓VR,我在不同的系列教程中都说过了,不过作为任何一个VR开发教程都难以避免的一环,本篇作为VRTK的开发教程还是对VR项目设置交代一下。 准备好你的硬件 头盔必须是6DoF的,推荐Oculus Quest系列,Rift系列,HTC和Pi…