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

 

 

 

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

 

 

 如果是单体应用,可以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;
    }

 

上一篇:Python调用纷享销客CRM开放平台API


下一篇:我自己做的基于SheetJs做的例子