• springboot:整合redis解决缓存击穿,缓存雪崩,缓存穿透


    springboot:整合redis解决缓存击穿,缓存雪崩,缓存穿透

    一、缓存穿透

    一个在缓存和数据库都不存在的数据,而用户不断发起请求,借此攻击数据库,造成数据库压力过大。比如请求 id < 0 的数据

    解决方案:

    1. 接口校验、限流
    2. 布隆过滤器
    3. 缓存空值,设置过期时间短些
     @GetMapping("/penetrate")
        public String cachePenetrate(Integer id) {
            String cacheKey = "penetrate:" + id;
            long cacheTime = 30L;
    
            //缓存查询
            String cacheValue = (String) redisTemplate.opsForValue().get(cacheKey);
            if (cacheValue == null) {
                //缓存没有,查询数据库
                Product product = productService.getById(id);
                if (product == null) {
                    //数据库没有,设置空值或默认值
                    cacheValue = "";
                } else {
                    cacheValue = product.getName();
                }
                redisTemplate.opsForValue().set(cacheKey, cacheValue, cacheTime, TimeUnit.SECONDS);
            }
    
            return cacheValue;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    二、缓存击穿

    一个key数据库存在,原来缓存有但现在过期了,发送请求发现过期了直接查询数据库并回设缓存,此时若发生高并发可能会瞬间把数据库压垮

    解决方案:

    1. 设置key永不过期
    2. 队列排队
    3. 互斥锁
    @GetMapping("/puncture")
        public String cachePuncture(Integer id) {
            String cacheKey = "puncture:" + id;
            long cacheTime = 30L;
    
            //缓存查询
            String cacheValue = (String) redisTemplate.opsForValue().get(cacheKey);
            if (cacheValue == null) {
                //缓存没有,使用互斥锁查询数据库更新缓存,其余阻塞排队
                synchronized (cacheKey) {
                    //此时可能有缓存数据了
                    cacheValue = (String) redisTemplate.opsForValue().get(cacheKey);
                    if (cacheValue == null) {
                        //缓存还是没有,查询数据库
                        Product product = productService.getById(id);
                        cacheValue = product.getName();
                        //回设缓存
                        redisTemplate.opsForValue().set(cacheKey, cacheValue, cacheTime * 10, TimeUnit.SECONDS);
                    }
                }
            }
    
            return cacheValue;
        }
    
        public String cachePuncture2(Integer id) throws InterruptedException {
            String cacheKey = "puncture:" + id;
            String blockKey = "block:" + id;
            long cacheTime = 30L;
    
            //缓存查询
            String cacheValue = (String) redisTemplate.opsForValue().get(cacheKey);
            if (cacheValue == null) {
                //setIfAbsent == SETNX :只有key不存在的时候才能设置成功,利用它可以实现锁的效果
                if (redisTemplate.opsForValue().setIfAbsent(blockKey, "1", cacheTime, TimeUnit.SECONDS)) {
                    //查询数据库
                    Product product = productService.getById(id);
                    cacheValue = product.getName();
                    //回设缓存
                    redisTemplate.opsForValue().set(cacheKey, cacheValue, cacheTime * 10, TimeUnit.SECONDS);
                    redisTemplate.delete(blockKey);
                } else {
                    //阻塞一会,再重试获取数据
                    Thread.sleep(50);
                    return cachePuncture2(id);
                }
            }
    
            return cacheValue;
        }
    
    • 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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    三、缓存雪崩

    当缓存服务器重启或者大量缓存集中在某一时间段失效,这样在失效的时候,也会给数据库带来很大压力。跟缓存击穿不一样,雪崩是大量key集体过期

    解决方案:

    1. 热点数据不过期
    2. 在原有的失效时间基础上增加一个随机值,减少集体失效
    3. 加锁排队
    @GetMapping("/avalanche")
        public String cacheAvalanche(Integer id) {
            String cacheKey = "avalanche:" + id;
            long cacheTime = 30L;
    
            //缓存查询
            String cacheValue = (String) redisTemplate.opsForValue().get(cacheKey);
            if (cacheValue == null) {
                //缓存没有,使用互斥锁查询数据库更新缓存,其余阻塞排队
                synchronized (cacheKey) {
                    //此时可能有缓存数据了
                    cacheValue = (String) redisTemplate.opsForValue().get(cacheKey);
                    if (cacheValue == null) {
                        //缓存还是没有,查询数据库
                        Product product = productService.getById(id);
                        cacheValue = product.getName();
                        //回设缓存
                        redisTemplate.opsForValue().set(cacheKey, cacheValue, cacheTime * 10, TimeUnit.SECONDS);
                    }
                }
            }
    
            return cacheValue;
        }
    
        @GetMapping("/avalanche2")
        public String cacheAvalanche2(Integer id) {
            String cacheKey = "avalanche:" + id;
            String signKey = "avalanche:sign" + id;
            long cacheTime = 60L;
    
            //缓存查询
            String cacheValue = (String) redisTemplate.opsForValue().get(cacheKey);
            //缓存标记
            String signValue = (String) redisTemplate.opsForValue().get(signKey);
            if (signValue == null) {
                //缓存标记过期
                //设置成功的去查询数据库并更新缓存,其余的返回旧的缓存值(缓存值的时间是缓存标记的2倍)
                if (redisTemplate.opsForValue().setIfAbsent(signKey, "1", cacheTime, TimeUnit.SECONDS)) {
                    //查询数据库
                    Product product = productService.getById(id);
                    cacheValue = product.getName();
                    redisTemplate.opsForValue().set(cacheKey, cacheValue, cacheTime * 2, TimeUnit.SECONDS);
                }
            }
    
            return cacheValue;
        }
    
    • 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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    这里第一种方法,采用加锁排队有可能还要解决分布式锁的问题,线程还会被阻塞,用户体验很差

    第二种方法:

    缓存标记:记录缓存数据是否过期,如果过期就去更新实际key的缓存;
    缓存数据:它的过期时间比缓存标记的时间延长1倍。这样,当缓存标记过期后,实际缓存还能把旧数据返回给调用端,直到新的key值更新完成后,才会返回新缓存

  • 相关阅读:
    SpringBoot_JsonFormat日期序列化
    SpringBoot 源码分析(三) 监听器分析以及属性文件加载分析
    华为云云耀云服务器L实例评测 | MacOS系统-宝塔建站
    Oracle11g或19c imp/impdp导入记录
    JS-事件委托-阻止事件冒泡和阻止默认行为-滚动事件-加载事件-元素尺寸位置
    69.x的平方根
    Vue3+TS+Vite+NaiveUI搭建一个项目骨架
    关于swiper插件在vue2的使用
    rar格式转换zip格式,如何做?
    华为存储培训
  • 原文地址:https://blog.csdn.net/weixin_43296313/article/details/125447527