一:缓存失效问题
1.缓存穿透——查一个不存在的数据
概念:缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数 据库也无此记录,我们没有将这次查询的 null 写入缓存,这将导致这个不存在的数据每次 请求都要到存储层去查询,失去了缓存的意义。
风险:在流量大时,可能 DB 就挂掉了,要是有人利用不存在的 key 频繁攻击我们的应用,数据库瞬时压力增大,最终导致崩溃,这就是漏洞。
解决:把查询出来的null结果同时加入缓存,并且对查询出来的空结果加入短暂的过期时间。三分钟以后就会清除掉空结果,重新查询数据库,为了防止从缓存查出来的数据一直为空。
2.缓存雪崩——存储的数据在同一时间大面积失效
概念:缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB,DB 瞬时压力过重雪崩。
解决:原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的 重复率就会降低,就很难引发集体失效的事件。
3.缓存击穿——热点key在高并发的时候过期
概念:对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问, 是一种非常“热点”的数据。这个时候,需要考虑一个问题:如果这个 key 在大量请求同时进来前正好失效,那么所 有对这个 key 的数据查询都落到 db,我们称为缓存击穿。
解决:加锁,大量并发只让一个去查,其他人等待,查到结果以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去数据库查询。
二:加锁解决缓存击穿
1.单体锁——服务只部署在一台服务器上
拿到锁的第一时间,先看看缓存里面有没有,有可能上一个人已经放好了。如果没有再去查询数据库
/**
* 查出所有分类,按照要求返回,从数据库查询并且封装数据
* @return
*/
public Map<String, List<Catalog2Vo>> getCatalogJsonFromDb() {
synchronized (this){
String catalogJson = redisTemplate.opsForValue().get("catalogJson");
if(!StringUtils.isEmpty(catalogJson)){
TypeReference<Map<String, List<Catalog2Vo>>> typeReference = new TypeReference<Map<String, List<Catalog2Vo>>>() {};
Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson,typeReference);
return result;
}
//从数据库获取三级分类方法,此刻不做演示
}
}
- 只要是同一把锁就能锁住这个锁的所有线程
- synchronized (this):springboot所有的组件在容器中都是单例的,只要所有请求用同一个this就能锁住
- this代表当前对象,有多少台服务器就会有多少个this对象
- synchronized 和lock都是本地锁,只能所住当前进程
分析:分布式情况下每个服务器this对象不一样,有多少服务器就会有多少人请求访问进来,所以此时我们每个this对象代表不同的锁,所以此时我们想要锁住所有,必须要使用分布式锁。
2.单体锁时序问题
流程:
1)当第一个线程查询缓存没有数据时,就会查询数据库,然后将查询到的结果放进缓存。
2)此时第二个缓存进来,第一次查询的结果还没有放进缓存,因为放进缓存的时间挺久的,就会导致第二次线程仍然会查询数据库,就没有锁住。
3)所以我们要在放进缓存以后,再去让第三个线程进来。
代码实现:
/**
* 查出所有分类,按照要求返回,从数据库查询并且封装数据
* @return
*/
public Map<String, List<Catalog2Vo>> getCatalogJsonFromDb() {
synchronized (this){
String catalogJson = redisTemplate.opsForValue().get("catalogJson");
if(!StringUtils.isEmpty(catalogJson)){
TypeReference<Map<String, List<Catalog2Vo>>> typeReference = new TypeReference<Map<String, List<Catalog2Vo>>>() {};
Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson,typeReference);
return result;
}
//从数据库获取三级分类方法,此刻不做演示
Object entity = fromDb();
String s = JSON.toJSONString(entity);
redisTemplate.opsForValue().set("catalogJson",s,1, TimeUnit.DAYS);
return entity;
}
}
3.本地锁在分布式情况下会出现的问题
模拟多个商品服务:在本地我们将商品服务多复制几份,每个商品服务端口不一样,用来进行测试。
结果:每个服务都会查询一次数据库,此时我们需要使用分布式锁。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/84106.html