1、缓存失效的三大问题
正常情况下数据缓存原理
1.1、缓存穿透
- 解释 是指某些恶意攻击者会使用不存的数据,请求查询接口,就会出现缓存不存在此数据,然后去查询数据库,数据库中也不存在此数据,此数据也不会放入到缓存中,这样的情况下攻击者会发送大量请求,就会造成所有查询数据的请求都会直接跳过缓存,查询数据库,导致数据库压力过大。
- 特点
- 查询的数据不存在
- 原理如图所示:
1.2、缓存雪崩
- 解释 是指大量的缓存数据的有效期相同,在某一时刻有大量的请求访问数据时,这时缓存数据全部失效,大量的请求就会查询数据库,导致数据库压力过大。
- 特点:
- 大量的请求查询不同的数据
- 不同的数据缓存失效
- 原理如图所示:
1.3、缓存击穿
- 解释 是指某个缓存数据恰好失效时,突然大量的请求查询此数据,就会出现缓存中查不到此数据,大量的请求就会查询数据库。
- 特点:
- 大量的请求查询同个的数据
- 一个数据缓存失效
- 原理如图所示:
2、解决方案
以下使用此方法作为实例讲解,这时一个查询分类详情的方法
public CategoryEntity getInfoCache(Long catId) {
ValueOperations<String, String> strOpt = redisTemplate.opsForValue();
String key = "category:" + catId;
String value = strOpt.get(key);
if (StrUtil.isNotEmpty(value)){
return JSONObject.parseObject(value, CategoryEntity.class);
}
CategoryEntity categoryEntity = baseMapper.selectById(catId);
if (Objects.isNull(categoryEntity)){
return null;
}
strOpt.set(key, JSONObject.toJSONString(categoryEntity));
return categoryEntity;
}
2.1、缓存穿透的解决方案
2.1.1 将不存在的数据也放入缓存
public CategoryEntity getInfoCache(Long catId) {
ValueOperations<String, String> strOpt = redisTemplate.opsForValue();
String key = "category:" + catId;
String value = strOpt.get(key);
if (StrUtil.isNotEmpty(value)){
return JSONObject.parseObject(value, CategoryEntity.class);
}
CategoryEntity categoryEntity = baseMapper.selectById(catId);
// 将不存在的分类也缓存
strOpt.set(key, JSONObject.toJSONString(categoryEntity));
return categoryEntity;
}
2.1.2使用布隆过滤器
2.2缓存雪崩解决方案
public CategoryEntity getInfoCache(Long catId) {
ValueOperations<String, String> strOpt = redisTemplate.opsForValue();
String key = "category:" + catId;
String value = strOpt.get(key);
if (StrUtil.isNotEmpty(value)){
return JSONObject.parseObject(value, CategoryEntity.class);
}
CategoryEntity categoryEntity = baseMapper.selectById(catId);
// 设置随机的失效时间
strOpt.set(key, JSONObject.toJSONString(categoryEntity), RandomUtil.randomInt(), TimeUnit.MINUTES);
return categoryEntity;
}
2.3缓存击穿解决方案
public CategoryEntity getInfoCache(Long catId) {
ValueOperations<String, String> strOpt = redisTemplate.opsForValue();
String key = "category:" + catId;
String value = strOpt.get(key);
if (StrUtil.isNotEmpty(value)){
return JSONObject.parseObject(value, CategoryEntity.class);
}
// 使用同步锁,保证只有一个线程访问数据库,防止缓存击穿
synchronized (this) {
// 拿到锁之后,再次判断缓存中是否有数据,如果有直接返回,避免其他线程重复查询数据库
if (StrUtil.isNotEmpty(strOpt.get(key))){
return JSONObject.parseObject(value, CategoryEntity.class);
}
CategoryEntity categoryEntity = baseMapper.selectById(catId);
// 设置随机的失效时间
strOpt.set(key, JSONObject.toJSONString(categoryEntity), RandomUtil.randomInt(), TimeUnit.MINUTES);
return categoryEntity;
}
}