Blog

Keep up to date with the latest news

谷粒商城分布式高级(六)—— 缓存-缓存使用(整合redis & 缓存穿透/雪崩/击穿 & 本地锁)

(1) 修改 com.atguigu.gulimall.product.service.impl.CategoryServiceImpl 的 getCatalogJson 方法

//TODO 产生堆外内存溢出:OutOfDirectMemoryError//(1)springboot2.0以后默认使用lettuce作为操作redis客户端。它使用netty进行网络通信//(2)lettuce的bug导致netty堆外内存溢出 -Xmx300m;netty如果没有指定堆外内存,默认使用-Xmx300m// 可以通过-Dio.netty.maxDirectMemory进行设置// 解决方案:不能使用-Dio.netty.maxDirectMemory只去调大堆外内存。//(1)升级lettuce客户端//(2)切换使用jedis@Overridepublic Map> getCatalogJson() { //给缓存中放json字符串,拿出的json字符串,还要逆转为能用的对象类型:【序列化与反序列化】 /** * 1、空结果缓存:解决缓存穿透 * 2、设置过期时间(加随机值):解决缓存雪崩 * 3、枷锁:解决缓存击穿 */ //1、加入缓存逻辑,缓存中存的数据是json字符串 //JSO跨语言、跨平台兼容 String catalogJson = redisTemplate.opsForValue().get("catalogJson"); if(StringUtils.isEmpty(catalogJson)){ //2、缓存中没有,查询数据库 Map> catalogJsonFromDb = getCatalogJsonFromDb(); System.out.println("缓存不命中....将要查询数据库...."); return catalogJsonFromDb; } System.out.println("缓存命中....直接返回....."); //转为我们指定的对象 Map> result = JSON.parseObject(catalogJson, new TypeReference>>(){}); return result;}/** * 从数据库查询并封装分类数据 * @return */public Map> getCatalogJsonFromDb() { //只要是同一把锁,就能锁住,需要这个锁的所有线程 //1、synchronized (this):SprinBoot所有的组件在容器中都是单例的 //TODO 本地锁:synchronized,JUC(lock),在分布式情况下,想要锁住所有,必须使用分布式锁 synchronized (this){ //得到锁以后,我们应该再去缓存中确定一次,如果没有才需要继续查询 String catalogJson = redisTemplate.opsForValue().get("catalogJson"); if(!StringUtils.isEmpty(catalogJson)){ //缓存不为null直接返回 Map> result = JSON.parseObject(catalogJson, new TypeReference>>(){}); return result; } System.out.println("查询了数据库....."); /** * 1、将数据库的多次查询变为一次 */ List selectList = baseMapper.selectList(null); //1、查询所有一级分类 List level1Catagorys = getParent_cid(selectList, 0L); //2、封装数据 Map> parent_cid = level1Catagorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> { // 1、每一个的一级分类,查到这个以及分类的二级分类 List categoryEntities = getParent_cid(selectList, v.getCatId()); //2、封装上面的结果 List catelog2Vos = null; if (categoryEntities != null) { catelog2Vos = categoryEntities.stream().map(l2 -> { Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName()); //1、找当前二级分类的三级分类封装成vo List level3Catalog = getParent_cid(selectList, l2.getCatId()); if(level3Catalog!=null){ List collect = level3Catalog.stream().map(l3 -> { //2、封装成指定格式 Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName()); return category3Vo; }).collect(Collectors.toList()); catelog2Vo.setCatalog3List(collect); } return catelog2Vo; }).collect(Collectors.toList()); } return catelog2Vos; })); //3、查到的数据再放入缓存,将对象转为json放到缓存中 String s = JSON.toJSONString(parent_cid); redisTemplate.opsForValue().set("catalogJson", s, 1, TimeUnit.DAYS); return parent_cid; }}private List getParent_cid( List selectList, Long parent_cid) { List collect = selectList.stream().filter(item -> item.getParentCid() == parent_cid).collect(Collectors.toList()); return collect;}

锁时序问题:之前的逻辑是查缓存没有,然后取竞争锁查数据库,这样就造成多 次查数据库。 解决方法:竞争到锁后,再次确认缓存中没有,再去查数据库。(2)jemeter 测试 100的并发 查看是否锁住,是否只查询了一次数据库 测试结果:

确认只查了一次,确实锁住了