当前位置: 首页 > article >正文

redis 缓存穿透

一、场景介绍

        我们平时做数据缓存的时候,一般套路是在项目启动时,将热点数据加载到缓存中,请求接口时,如果缓存中存在,优先从缓存中获取返回,如果缓存中不存在,才会从数据库中查询并设置到缓存中,通过这种方式来提升系统的相应性能、减少数据库访问压力。

        缓存穿透的意思就是,一些恶意请求故意去获取缓存和数据库都不存在的数据,导致每次都会跳过缓存直击数据库,当这种恶意请求数量巨大时,首先会导致其他正常请求无法得到响应,第二是有可导致数据库崩溃导致系统不可用,危害巨大。

二、不合法数据存入redis

        将恶意请求的key存入redis中,等下次同样的请求再次请求时,就可以直接从redis中获取并返回,减少数据库访问压力。

        这是一种好办法,不过既然都是恶意请求了,肯定不可能只发一个缓存和数据库都不存在的key,一定是大量不重复的请求数据,这就是个问题了,redis空间是有限的,一直往redis中存储这种恶意请求的数据,那么redis迟早也会被占满,到时候redis就算不挂(redis内存被占满时,可以配置合适的淘汰策略防止redis无法对外服务),里面也就剩些垃圾数据了,到时候数据库还是得完蛋。所以这种方案不可靠。

三、布隆过滤器

3.1、布隆过滤器介绍

        布隆过滤器就是在redis前在挡一层,请求先经过布隆过滤器,如果布隆过滤器中存在,再走查缓存或者数据库的逻辑,否则就直接返回。

3.2、布隆过滤器设计理念

        布隆顾虑器本质上是由一个int数组和多个hash方法组成,具体的工作流程是这样的:初始化状态下,数组中的元素都为0。数据需要存到布隆过滤器时,先经过N个hash方法计算出N个下标,将这些下标的数组元素设置为1。数据读取时也是一样,通过N个hash方法计算出下标,查看所有命中的下标元素是否都是1,如果是,说明数据是合法的,运行进行后续的逻辑,否则直接返回。

        布隆顾虑器有缺点吗?没有完美的技术,缺点那必然是有的,比如说key1设置的数组下标是1、2、3;key2设置数组下标是1、3、5;key3设置的数组下标是2、6、7;此时如果正好有一个恶意的key4,他经过hash后,计算出的下标是2、5、6。这三个下标中的元素已经被设置成1了,所以虽然key4虽然不存在,但是也能通过布隆过滤器的校验,这个问题学名叫做误判率,使用布隆过滤器,这个误判率是一定存在的,默认情况下,这个误判率是百分之五,一般项目都是可以接受的,如果要减少误判率,可以通过增大数组的方式来做。

3.3、布隆过滤器使用

        3.3.1、我们先用代码简单模拟一下缓存穿透的样子,我们先获取缓存中存在的数据,如下图所示,在缓存中存在时,控制台不会打印查询sql,说明没走数据库,直接从缓存中获取到了数据。

@Test
    public void requestSearchCodeRuleTest(){
        String data = findData(1L);
        System.out.println(data);
    }

    /**
     * 功能描述: 通过id查询
     * @Author:huhy
     * @Date: 2025/3/21 21:49
     */
    private String findData(Long id){
        String key = "dataConsistent:" + id;
        Object dataObj = redisTemplate.opsForValue().get(key);
        //如果缓存中不存在,则从数据库中查询
        if(Objects.isNull(dataObj)){
            return searchCodeRuleAndSetRedis(id);
        }else {
            //如果缓存中存在,直接返回
            return dataObj.toString();
        }
    }

    /**
     * 功能描述: 查询并设置到缓存
     * @Author:huhy
     * @Date: 2025/3/21 20:56
     */
    private String searchCodeRuleAndSetRedis(Long id){
        //通过id查询编码规则
        TSCodeRule tsCodeRule = codeRuleService.selectTSCodeRuleById(id);
        if(Objects.isNull(tsCodeRule)){
            hrow new RuntimeException("id【"+id+"】 不存在!");
        }
        String roleJson = JSON.toJSONString(tsCodeRule);
        //将测试数据保存到redis中
        String key = "dataConsistent:"+tsCodeRule.getId();
        redisTemplate.opsForValue().set(key,roleJson);
        return roleJson;
    }

          

        此时我们开始搞点事情,请求一个缓存不存在的数据看看控制台的效果。为了效果更明显一点,我在代码中故意抛出了异常,其实实际开发时不需要这么写,这个需要注意。

        如下图所示,故意请求一个id不存在的,日志打印了sql,说明数据库被访问了。

3.3.2、真正振奋人心的时候到了,我们使用布隆过滤器来解决这个问题,还记得前两期我们使用过的分布式锁框架redisson吗?redisson为我们提供了布隆过滤器的API,我们可以直接使用,简直太方便了(让我还掏着了,嘿嘿嘿)。

@Test
    public void requestSearchCodeRuleTest(){
        String data = findData(100L);
        System.out.println(data);
    }

    /**
     * 功能描述: 通过id查询
     * @Author:huhy
     * @Date: 2025/3/21 21:49
     */
    private String findData(Long id){
    //创建布隆顾虑器
        RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("bloom-filter");
        //设置误判率
        bloomFilter.tryInit(100967256, 0.01);
        //初始化数据
        bloomFilter.add("dataConsistent:" + 1L);
        bloomFilter.add("dataConsistent:" + 2L);
        String key = "dataConsistent:" + id;
        //先判断布隆过滤器中是否存在
        boolean bloomContains = bloomFilter.contains(key);
        if(!bloomContains){
            System.out.println("id ["+id+"] 查无数据!");
            return null;
        }
        Object dataObj = redisTemplate.opsForValue().get(key);
        //如果缓存中不存在,则从数据库中查询
        if(Objects.isNull(dataObj)){
            return searchCodeRuleAndSetRedis(id);
        }else {
            //如果缓存中存在,直接返回
            return dataObj.toString();
        }
    }

    /**
     * 功能描述: 查询并设置到缓存
     * @Author:huhy
     * @Date: 2025/3/21 20:56
     */
    private String searchCodeRuleAndSetRedis(Long id){
        //通过id查询编码规则
        TSCodeRule tsCodeRule = codeRuleService.selectTSCodeRuleById(id);
        String roleJson = JSON.toJSONString(tsCodeRule);
        //将测试数据保存到redis中
        String key = "dataConsistent:"+tsCodeRule.getId();
        redisTemplate.opsForValue().set(key,roleJson);
        return roleJson;
    }

四、结语

        本次简单介绍了缓存穿透的问题,这只是缓存三部曲第一个,后续我会继续和大家分享缓存击穿和雪崩问题,希望可以和大家一起学习进步,谢谢大家。


http://www.kler.cn/a/610379.html

相关文章:

  • Spring Boot网站性能优化全解析
  • 浏览器渲染原理与优化详解
  • 【redis】哨兵节点作用演示和重选主节点详细流程
  • 简单方法胜过大语言模型?!单细胞扰动敲除方法的实验
  • Rust从入门到精通之入门篇:5.控制流
  • AOA与TOA混合定位,MATLAB例程,自适应基站数量,三维空间下的运动轨迹,滤波使用EKF
  • Git 是什么
  • AI日报 - 2025年3月27日
  • 【解锁 Oracle OCP:数据库专家的进阶之路】
  • Jetpack LiveData 使用与原理解析
  • 关于服务器只能访问localhost:8111地址,局域网不能访问的问题
  • SQL语句---特殊查询
  • 蓝桥杯算法实战分享
  • 基于腾讯云高性能HAI-CPU实现企业财报分析
  • css写法汇总
  • 【android】补充
  • 使用 fetch 实现流式传输:核心原理与实践
  • “立正挨打”之后,黄仁勋正式公布英伟达的量子计算应对战略
  • 版本控制工具
  • 力扣32.最长有效括号(栈)