缓存击穿 本地锁的解决

产生缓存击穿的原因

发生缓存击穿的原因是在高并发场景下 大量请求访问一个已失效数据 频繁访问数据库 导致数据库宕机

解决方案

可以引入本地锁
因为在springboot 容器中的bean都是单例 所以只要锁住当前对象即可
在进行缓存查询时 若发现在缓存中数据不存在 去数据库中查询 在数据库查询时添加一把互斥锁 只有拿到锁才能进行查询
在进行数据库查询前 先判断缓存中是否有数据 如果有数据就不用在进行查询了 否则在进行查询时只是将线程阻塞掉了 当释放锁后 还会再次查询数据库
查询后的结果保存到缓存中 然后释放锁 当下一个请求进行访问缓存时, 此时缓存中已有数据 就不会出现缓存击穿的问题

本地锁的业务实现

        /*
        * 加锁业务流程:
        * 1. 判断redis是否有缓存数据
        * 2. 若有缓存数据 直接反序列化对象并进行返回
        *    若缓存中无数据 则进行数据库查询 并将缓存结果保存至redis中
        * */
    public Map<String, List<Catalog2VO>> getCatalogJsonResult() throws JsonProcessingException {
        /*
        * 加锁流程:
        * 1. 判断redis是否有缓存数据
        * 2. 若有缓存数据 直接反序列化对象并进行返回
        *    若缓存中无数据 则进行数据库查询 并将缓存结果保存至redis中
        * */

        // 因为在springboot 容器中的bean都是单例 所以只要锁住当前对象即可
        synchronized (this) {
            String categoryCache = redisTemplate.opsForValue().get(REDIS_CACHE_CATEGORY);

            if (StringUtils.isEmpty(categoryCache)) {
                System.out.println("数据库查询");

                List<PmsCategory> categories = categoryMapper.selectList(null);
                // 查询一级分类
                List<PmsCategory> categoryByFirstCategory = getParentCid(categories, 0L);
                // 数据库查询业务逻辑
                Map<String, List<Catalog2VO>> categoryMap = categoryByFirstCategory.stream().collect(Collectors.toMap((item) -> item.getCatId().toString(), (item) -> {
                    List<PmsCategory> category2List = getParentCid(categories, item.getCatId());

                    // 根据一级分类id 查询二级分类
                    return category2List.stream().map((category2) -> {
                        Catalog2VO catalog2VO = new Catalog2VO();
                        catalog2VO.setCatalog1Id(item.getCatId().toString());
                        catalog2VO.setId(category2.getCatId().toString());
                        catalog2VO.setName(category2.getName());

                        List<PmsCategory> category3List = getParentCid(categories, category2.getCatId());

                        // 根据二级分类id 查询三级分类
                        List<Catalog2VO.Catalog3> catalog3s = category3List.stream().map((category3) -> {
                            Catalog2VO.Catalog3 catalog3 = new Catalog2VO.Catalog3();
                            catalog3.setCatalog2Id(category3.getParentCid().toString());
                            catalog3.setId(category3.getCatId().toString());
                            catalog3.setName(category3.getName());
                            return catalog3;
                        }).collect(Collectors.toList());
                        catalog2VO.setCatalog3List(catalog3s);

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

                // 将缓存存入redis中
                redisTemplate.opsForValue().set(REDIS_CACHE_CATEGORY, objectMapper.writeValueAsString(categoryMap));

                return categoryMap;
            } else {
                // 若缓存存在 则直接反序列对象并进行返回
                return objectMapper.readValue(categoryCache, new TypeReference<Map<String, List<Catalog2VO>>>() {});
            }
        }
    }

本地锁的不足

本地锁只能锁住当前进程 在分布式场景下 每个服务有多个实例 而每个实例又是不同的进程 导致锁不住所有服务 会读取多次数据库 这时候就需要分布式锁了

posted @   RainbowMagic  阅读(78)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示