• 黑马点评-05缓存穿透问题及其解决方案,缓存空字符串或使用布隆过滤器


    缓存穿透问题(缓存空)

    缓存穿透的解决方案

    缓存穿透(数据穿透缓存直击数据库): 缓存穿透是指客户端请求访问缓存中和数据库中都不存在的数据,此时缓存永远不会生效并且用户的请求都会打到数据库

    • 数据库能够承载的并发不如Redis这么高,如果大量的请求同时访问这种不存在的数据,这些请求就都会访问到数据库就会造成数据库瘫痪

    缓存穿透的解决方案有哪些

    • 缓存null值

    • 布隆过滤

    • 增强id的复杂度,这样用户就不知道缓存中和数据库中不存在的数据有哪些

    • 做好数据的基础格式校验

    • 加强用户权限校验

    • 做好热点参数的限流

    缓存空对象

    即使访问的数据在数据库中不存在也要把这个数据缓存到redis中去,这样用户下次再访问这个不存在的数据时就能在redis中找到这个数据所以不会进入到缓存

    • 优点: 实现简单且维护方便
    • 缺点: 造成额外的内存消耗(可以设置一个TTL), 可能造成数据库和缓存短期的数据不一致(只有TTL到期时才能更新缓存)

    在这里插入图片描述

    布隆过滤(哈希思想)

    布隆过滤器其实采用的是哈希思想,使用一个庞大的二进制数组通过哈希算法把数据库中的数据对应hash值转换成二级制位保存起来

    • 只有布隆过滤器判断要查询的数据存在时才会放行(如果发生哈希碰撞,布隆认为存在的数据可能不存在),不存在则直接返回(一定不存在)

    • 这个请求会去访问redis,哪怕此时redis中的数据过期了,但是数据库中一定存在这个数据,在数据库中查询出来这个数据后,再将其放入到redis中

    • 优点: 内存占用较少且没有多余key

    • 缺点: 实现复杂且存在误判可能(哈希算法可能存在哈希冲突)

    在这里插入图片描述

    解决商品查询的缓存穿透

    如果查询的数据在数据库中找不到不是返回404,而是把这个数据库中不存在的数据也写入到Redis中并且将value设置为空字符串同时设置一个较短的TTL

    • 再次发起同样的查询请求时,肯定会命中缓存,但是由于value是空字符串会,表示查询的是不存在的数据,直接返回一个错误信息,避免了再次查询数据库的操作

    在这里插入图片描述

    // 设置缓存空字符串的超时时间
    public static final Long CACHE_NULL_TTL = 2L;
    
    • 1
    • 2
    @Override
    public Result queryById(Long id) {
        // 先从Redis中查询对应的店铺缓存信息,这里的常量值是固定店铺的前缀+查询店铺的id
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        // 如果在Redis中查询到了店铺信息,并且店铺的信息不是空字符串则转为Shop类型直接返回,""和null以及"/t/n(换行)"都会判定为空即返回false
        if (StrUtil.isNotBlank(shopJson)) {
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        // 如果缓存的店铺信息是空字符串(shopjson == "")即我们缓存的空数据,返回一个错误信息
        if (shopjson != null) { 
            return Result.fail("店铺不存在!!");
        }
        // 如果没有命中并且店铺信息不是空字符串即shopjson等于null则去数据库中根据查Id查询店铺信息
        Shop shop = getById(id);
        // 在数据库中查询不到店铺,把这个不存在的数据也写入到Redis中并且将value设置为空字符串同时设置一个较短的TTL(如2分钟)
        if (shop == null) {
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            return Result.fail("店铺不存在!!");
        }
        // 查到了则将店铺对象转为json字符串存入redis同时设置TTL
        String jsonStr = JSONUtil.toJsonStr(shop);
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonStr, CACHE_SHOP_TTL, TimeUnit.MINUTES);
        // 最终把查询到的商户信息返回给前端
        return Result.ok(shop);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    单独实现解决缓存穿透的方法queryWithPassThrough,在该方法中如果查到店铺信息返回shop查不到则返回null,最后在queryById中做统一判断返回结果类

    @Override
    public Result queryById(Long id) {
        // 测试缓存穿透
        Shop shop = queryWithPassThrough(id);
        // 如果shop等于null,表示数据库中对应店铺不存在或者缓存的店铺信息是空字符串
        if (shop == null) {
            return Result.fail("店铺不存在!!");
        }
        // shop不等于null,把查询到的商户信息返回给前端
        return Result.ok(shop);
    }
    
    @Override
    public Result queryWithPassThrough(Long id) {
        // 先从Redis中查询对应的店铺缓存信息,这里的常量值是固定的店铺前缀+查询店铺的Id
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        // 如果在Redis中查询到了店铺信息,并且店铺的信息不是空字符串则转为Shop类型直接返回,""和null以及"/t/n(换行)"都会判定为空即返回false
        if (StrUtil.isNotBlank(shopJson)) {
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return shop;
        }
        // 如果缓存的店铺信息是空字符串(shopjson == "")即我们缓存的空数据,返回null
        if (shopjson != null) { 
            return null;
        }
        // 如果没有命中并且店铺信息不是空字符串即shopjson等于null则去数据库中根据查Id查询店铺信息
        Shop shop = getById(id);
        // 在数据库中查询不到店铺,把这个不存在的数据也写入到Redis中并且将value设置为空字符串同时设置一个较短的TTL(如2分钟)
        if (shop == null) {
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }
        // 查到了则将店铺对象转为json字符串存入redis同时设置TTL
        String jsonStr = JSONUtil.toJsonStr(shop);
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonStr, CACHE_SHOP_TTL, TimeUnit.MINUTES);
        // 最终把查询到的商户信息返回给前端
        return shop;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

  • 相关阅读:
    Spire.PDF for Java 8.11.0 Spire.PDF for Java
    C++: 冒泡排序(Bubble Sort)
    Java代码审计——SSH 框架审计技巧
    js匹配查找JSON中属性并返回路径
    熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离
    Fiddler入门:下载、安装、配置、抓包、customize rules
    典型数据结构-栈/队列/链表、哈希查找、二叉树(BT)、线索二叉树、二叉排序树(BST树)、平衡二叉树(AVL树)、红黑树(RB树)
    IDEA这样配置Maven:让你一遍就能学会!
    yum安装mysql5.7
    JavaScript核心Web APIs
  • 原文地址:https://blog.csdn.net/qq_57005976/article/details/133755064