缓存击穿
大量并发请求同时访问一个在redis中不存在的数据时,就会绕过redis去直接访问底层数据库,对数据库造成极大的访问压力
解决方案
使用双重检测锁
缓存穿透
当请求访问redis中的某个资源时,若返回的结果为null,则会绕过redis去访问底层数据库,若底层数据库中没有相关数据,则返回结果仍然是null,且底层数据库会将null写入redis中,但由于redis中的数据也是null,redis就失去了缓存的作用,后续并发请求仍然会去访问底层数据库,给数据库造成了访问压力
解决方案
若底层数据库中也没有对应数据,则不要返回null,可以返回一个非正常数值或是一个空集合并写入redis中,且写入redis中的无用数据可以设置一个较短的过期时间。如此一来,后续的请求则会直接从redis中获取数据(只不过获得的数据也是无用的)
缓存雪崩
redis中的数据在同一时刻大量过期,导致请求这些数据的大量并发请求绕过redis去访问底层数据库,对数据库造成访问压力。
解决方案
- 为redis中的数据设置不同的过期时间
- 在流量洪峰到达前提前缓存热点数据,过期时间设置到流量最低的时段
项目中的一个代码片段
public ResultVO listIndexImgs() {
List<IndexImg> indexImgs = null;
try {
//尝试从redis中查询轮播图信息,可能查不到
String imgsStr = stringRedisTemplate.boundValueOps("indexImgs").get();
if (imgsStr != null) {
//从redis中查询到轮播图信息
//代码含义:将json字符串转换为ArrayList集合,
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, IndexImg.class);
//readValue 方法可以将json字符串转换成指定的对象
indexImgs = objectMapper.readValue(imgsStr, javaType);
} else {
//双重检测锁
synchronized (this){
//再次查询redis(防止缓存击穿)
String s = stringRedisTemplate.boundValueOps("indexImgs").get();
if (s==null){
//高并发访问的第一次访问会进入此处
indexImgs = indexImgMapper.listIndexImgs();
if(indexImgs!=null){
stringRedisTemplate.boundValueOps("indexImgs").set(objectMapper.writeValueAsString(indexImgs));
stringRedisTemplate.boundValueOps("indexImgs").expire(1, TimeUnit.DAYS);
}else {
//防止缓存穿透
List<IndexImg> nullImgsList = new ArrayList<>();
stringRedisTemplate.boundValueOps("indexImgs").set(objectMapper.writeValueAsString(nullImgsList));
stringRedisTemplate.boundValueOps("indexImgs").expire(10, TimeUnit.SECONDS);
}
}else {
//高并发对同一资源的后续访问会进入这,即后续会直接从redis中查找数据
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, IndexImg.class);
indexImgs = objectMapper.readValue(s, javaType);
}
}
}
} catch (JsonProcessingException e) {
e.printStackTrace();
}
if (indexImgs!=null){
return new ResultVO(ResStatus.OK,"success",indexImgs);
}else {
return new ResultVO(ResStatus.NO,"fail",null);
}
}