缓存-缓存使用2

1.缓存击穿、穿透、雪崩

1.缓存穿透

指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此纪录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

风险:

利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃

解决:

null结果缓存,并加入短暂过期时间

2.缓存雪崩

缓存雪崩:缓存雪崩是指在我们设置缓存是Key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决:原有的失效时间上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

3.缓存击穿

缓存击穿:

  • 对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常"热点"地数据。
  • 如过这个热点 key 在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。

解决:

加锁

大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,悬停,先查缓存,就会有数据,不用去db

2.解决问题

先解决缓存穿透和雪崩

java 复制代码
  private static final String CATALOG_JSON="CATALOG_JSON";
    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {

        /**
         * 空结果缓存:解决缓存穿透
         * 设置过期时间(加随机值) 缓存雪崩
         * 加锁 解决缓存击穿
         */
        Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
        if(result!=null){
            return (Map<String, List<Catelog2Vo>>) result;
        }

        Map<String, List<Catelog2Vo>> map = getCatalogJsonFromDB();
        if (map==null){
            /**
             * 解决缓存穿透
             */
            map=new HashMap<>();
        }
        redisTemplate.opsForValue().set(CATALOG_JSON,map, Duration.ofDays(1));
        return map;
    }

解决缓存击穿

1.使用本地锁解决

springboot容器对象默认是单例模式,所以可以synchronized锁住同一个对象,在使用双重检测模式,可以并发执行

java 复制代码
 public synchronized Map<String, List<Catelog2Vo>> getCatalogJsonFromDB() {

        Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
        if (result != null) {
            return (Map<String, List<Catelog2Vo>>) result;
        }


        //1.查出所有1级分类
        List<CategoryEntity> selectList = baseMapper.selectList(null);
        /**
         * 将数据库的多次查询变成一次
         */

        //2. 封装数据
        List<CategoryEntity> level1Category = selectList.stream().filter(s -> s.getParentCid().equals(0L)).collect(Collectors.toList());
        Map<String, List<Catelog2Vo>> map = level1Category.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //1.每一个的一级分类,查到1级分类的所有二级分类
            List<CategoryEntity> categoryEntities = selectList.stream().filter(s -> s.getParentCid().equals(v.getCatId())).collect(Collectors.toList());

            List<Catelog2Vo> catelog2VoList = categoryEntities.stream().map(c -> {
                Catelog2Vo catelog2Vo = new Catelog2Vo();
                catelog2Vo.setId(c.getCatId().toString());
                catelog2Vo.setName(c.getName());
                catelog2Vo.setCatalog1Id(v.getCatId().toString());

                List<CategoryEntity> categoryEntities1 = selectList.stream().filter(s -> s.getParentCid().equals(c.getCatId())).collect(Collectors.toList());
                List<Catelog2Vo.Catelog3Vo> collect = categoryEntities1.stream().map(c3 -> {
                    Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo();
                    catelog3Vo.setId(c3.getCatId().toString());
                    catelog3Vo.setName(c3.getName());
                    catelog3Vo.setCatalog2Id(c.getCatId().toString());
                    return catelog3Vo;
                }).collect(Collectors.toList());

                catelog2Vo.setCatalog3List(collect);

                return catelog2Vo;
            }).collect(Collectors.toList());


            return catelog2VoList;
        }));
        return map;
    }
java 复制代码
 public Map<String, List<Catelog2Vo>> getCatalogJson() {

        /**
         * 空结果缓存:解决缓存穿透
         * 设置过期时间(加随机值) 缓存雪崩
         * 加锁 解决缓存击穿
         */
        Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
        if (result != null) {
            return (Map<String, List<Catelog2Vo>>) result;
        }

        Map<String, List<Catelog2Vo>> map = getCatalogJsonFromDB();
        if (map == null) {
            /**
             * 解决缓存穿透
             */
            map = new HashMap<>();
        }
        redisTemplate.opsForValue().set(CATALOG_JSON, map, Duration.ofDays(1));
        return map;
    }

以上代码逻辑还是会有问题,并发的时候会导致一号线程查完数据库,还没放入缓存就释放锁了,导致二号线程查询缓存没有数据,又去查了一次数据库,没有保证只有一个线程去查数据库

正确的做法

java 复制代码
  public synchronized Map<String, List<Catelog2Vo>> getCatalogJsonFromDB() {

        Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
        if (result != null) {
            return (Map<String, List<Catelog2Vo>>) result;
        }


        //1.查出所有1级分类
        List<CategoryEntity> selectList = baseMapper.selectList(null);
        /**
         * 将数据库的多次查询变成一次
         */

        //2. 封装数据
        List<CategoryEntity> level1Category = selectList.stream().filter(s -> s.getParentCid().equals(0L)).collect(Collectors.toList());
        Map<String, List<Catelog2Vo>> map = level1Category.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //1.每一个的一级分类,查到1级分类的所有二级分类
            List<CategoryEntity> categoryEntities = selectList.stream().filter(s -> s.getParentCid().equals(v.getCatId())).collect(Collectors.toList());

            List<Catelog2Vo> catelog2VoList = categoryEntities.stream().map(c -> {
                Catelog2Vo catelog2Vo = new Catelog2Vo();
                catelog2Vo.setId(c.getCatId().toString());
                catelog2Vo.setName(c.getName());
                catelog2Vo.setCatalog1Id(v.getCatId().toString());

                List<CategoryEntity> categoryEntities1 = selectList.stream().filter(s -> s.getParentCid().equals(c.getCatId())).collect(Collectors.toList());
                List<Catelog2Vo.Catelog3Vo> collect = categoryEntities1.stream().map(c3 -> {
                    Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo();
                    catelog3Vo.setId(c3.getCatId().toString());
                    catelog3Vo.setName(c3.getName());
                    catelog3Vo.setCatalog2Id(c.getCatId().toString());
                    return catelog3Vo;
                }).collect(Collectors.toList());

                catelog2Vo.setCatalog3List(collect);

                return catelog2Vo;
            }).collect(Collectors.toList());


            return catelog2VoList;
        }));
        if (map == null) {
            /**
             * 解决缓存穿透
             */
            map = new HashMap<>();
        }
        redisTemplate.opsForValue().set(CATALOG_JSON, map, Duration.ofDays(1));
        return map;
    }

把存入缓存这一操作也放入同步代码块里

java 复制代码
 @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {

        /**
         * 空结果缓存:解决缓存穿透
         * 设置过期时间(加随机值) 缓存雪崩
         * 加锁 解决缓存击穿
         */
        Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
        if (result != null) {
            return (Map<String, List<Catelog2Vo>>) result;
        }

        Map<String, List<Catelog2Vo>> map = getCatalogJsonFromDB();

        return map;
    }

本地锁只能锁住当前进程,所以我们需要分布式锁

3.本地锁在分布式下会有的问题

就是每个锁只能锁住当前进程,也就是每个服务都会查一遍数据库

相关推荐
你的电影很有趣5 小时前
lesson44:Redis 数据库全解析:从数据类型到高级应用
数据库·redis·缓存
kunwen1236 小时前
推理还是训练 || KV缓存和CoT技术
缓存·kv缓存·cot技术
蓝色天空的银码星6 小时前
Spring循环依赖源码调试详解,用两级缓存代替三级缓存
java·spring·缓存
你我约定有三12 小时前
MyBatis--缓存详解
spring boot·缓存·mybatis
天涯海风14 小时前
检索增强生成(RAG) 缓存增强生成(CAG) 生成中检索(RICHES) 知识库增强语言模型(KBLAM)
人工智能·缓存·语言模型
m0_5951998517 小时前
Redis(以Django为例,含具体操作步骤)
数据库·redis·缓存
huisheng_qaq1 天前
【ElasticSearch实用篇-03】QueryDsl高阶用法以及缓存机制
elasticsearch·缓存·nosql·querydsl·score打分机制
卡拉叽里呱啦2 天前
缓存-变更事件捕捉、更新策略、本地缓存和热key问题
分布式·后端·缓存
jakeswang2 天前
应用缓存不止是Redis!——亿级流量系统架构设计系列
redis·分布式·后端·缓存
.Shu.2 天前
Redis zset 渐进式rehash 实现原理、触发条件、执行流程以及数据一致性保障机制【分步源码解析】
数据库·redis·缓存