缓存:加本地锁解决缓存击穿问题
作者:互联网
如果是单体应用,可以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