ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

缓存:加本地锁解决缓存击穿问题

2022-01-26 23:32:46  阅读:180  来源: 互联网

标签:Map 缓存 return ops 击穿 JSON json 本地


 

 

 

 

 

 

 如果是单体应用,可以synchronized 来操作:

 @Override
    public Map<String, List<Catelog2Vo>> getCatelogJson() {
        //加入缓存逻辑
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String json = ops.get("CatalogJSON");
        if(StringUtils.isEmpty(json)){
            //缓存没有,从数据库中查询
            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
            //将查出的对象转为JSON放在数据库  :存json的好处,json是跨语言跨平台兼容的
            ops.set("CatalogJSON", JSON.toJSONString(catalogJsonFromDb));
            return catalogJsonFromDb;
        }

        //视频中是这样转然后返回的
//        Map<String, List<Catelog2Vo>> object
//                = JSON.parseObject(json, new TypeReference<Map<String, List<Catelog2Vo>>>() {});

        return (Map<String, List<Catelog2Vo>>) JSON.parse(json);
    }



    //    @Cacheable(value = "category", key = "#root.methodName")

    //从数据库查询并封装数据
    public synchronized Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {
        //加synchronize锁,如果缓存不为空,就直接返回
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String json = ops.get("CatalogJSON");
        if(!StringUtils.isEmpty(json)){
            return  (Map<String, List<Catelog2Vo>>) JSON.parse(json);
        }

        List<CategoryEntity> entityList = baseMapper.selectList(null);
        // 查询所有一级分类
        List<CategoryEntity> level1 = getCategoryEntities(entityList, 0L);

        Map<String, List<Catelog2Vo>> parent_cid
                = level1.stream()
                .collect(Collectors.toMap(
                        k -> k.getCatId().toString(),
                        v -> {
                            // 拿到每一个一级分类 然后查询他们的二级分类
                            List<CategoryEntity> entities = getCategoryEntities(entityList, v.getCatId());

                            List<Catelog2Vo> catelog2Vos = null;
                            if (entities != null) {
                                catelog2Vos = entities.stream().map(l2 -> {
                                    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), l2.getName(), l2.getCatId().toString(), null);
                                    // 找当前二级分类的三级分类
                                    List<CategoryEntity> level3 = getCategoryEntities(entityList, l2.getCatId());
                                    // 三级分类有数据的情况下
                                    if (level3 != null) {
                                        List<Catalog3Vo> catalog3Vos
                                                = level3.stream()
                                                .map(l3 -> new Catalog3Vo(l3.getCatId().toString(), l3.getName(), l2.getCatId().toString()))
                                                .collect(Collectors.toList());
                                        catelog2Vo.setCatalog3List(catalog3Vos);
                                    }
                                    return catelog2Vo;
                                }).collect(Collectors.toList());
                            }
                            return catelog2Vos;
                        }));
        return parent_cid;
    }

如果一百万个线程过来,都要这个json数据,同时的话,如果redis中没有,就都同时差数据库,这样肯定不行。

所以加锁

加锁的方式,synchronized 写到方法上,或者synchronized 同步代码块,里面可以写this, 因为springboot的实例默认都是单例的。

然后加好锁了之后,下一个线程在拿到锁,也不能直接差数据库,还是得在判断redis中有没有,有就直接返回。

====================

这个过程有点类似于,单例模式的那个,双重锁检查。

单例模式的双重判断懒汉式:

public class Single {
    //构造器私有化
    private Single(){

    }
    //静态变量
    private static volatile Single single = null;
    
    public static Single getSingle() {
        if(single==null){
            synchronized (Single.class){
                if(single==null){
                    single = new Single();
                }
            }
        }
        return single;
    }
}

 

============================

这样加锁如果在单体应用下合适,但是分布式情况下就不行了。

 

 

 

这样的话,有几台机器,就有几个锁。

本地锁(sync.. lock等juc下的)快一点,但是在分布式情况下是锁不住所有的服务,在这种场景下,其实本地锁是可行的,这里只要减轻数据库的压力,不要求别的。

上面的代码本地锁还是存在多个线程查询的问题,因为进入下面的第二个判断,其他线程还没有来得及写入到redis中去,所以其他线程仍然没查到,就走db路线了。

 

 

所以应该改进成:

 

 

@Override
    public Map<String, List<Catelog2Vo>> getCatelogJson() {
        //加入缓存逻辑
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String json = ops.get("CatalogJSON");
        if(StringUtils.isEmpty(json)){
            //缓存没有,从数据库中查询
            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
            return catalogJsonFromDb;
        }

        //视频中是这样转然后返回的
//        Map<String, List<Catelog2Vo>> object
//                = JSON.parseObject(json, new TypeReference<Map<String, List<Catelog2Vo>>>() {});

        return (Map<String, List<Catelog2Vo>>) JSON.parse(json);
    }



    //    @Cacheable(value = "category", key = "#root.methodName")

    //从数据库查询并封装数据
    public synchronized Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {
        //加synchronize锁,如果缓存不为空,就直接返回
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String json = ops.get("CatalogJSON");
        if(!StringUtils.isEmpty(json)){
            return  (Map<String, List<Catelog2Vo>>) JSON.parse(json);
        }

        。。。。。查DB

        //在这里把结果放入缓存
        //将查出的对象转为JSON放在数据库  :存json的好处,json是跨语言跨平台兼容的
        ops.set("CatalogJSON", JSON.toJSONString(parent_cid));
        return parent_cid;
    }

 

标签:Map,缓存,return,ops,击穿,JSON,json,本地
来源: https://www.cnblogs.com/wyw123456/p/15848468.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有