Redis缓存穿透解决方案
1、什么是缓存穿透
查询的key在redis中不存在,对应的ID在数据库也不存在,此时被非法用户进行攻击,大量的请求会直接冲入数据库上造成宕机,从而影响整个系统,这种现象称之为:缓存穿透
@GetMapping("/findCategory/{cid}")
public List<Category> findCategory(@PathVariable("cid") Integer cid) {
// 判断分类id是否传入,如果没有传入那么直接返回
KAssert.isEmpty(cid, 401, "分类不存在");
List<Category> categoryList = new ArrayList<>();
// 先去缓存中去获取分类信息
String categories = (String) redisTemplate.opsForValue().get("subCid:" + cid);
if (StringUtils.isEmpty(categories)) {
log.info("数据库查询");
categoryList = categoryService.findCategroies(cid);
if (!CollectionUtils.isEmpty(categoryList)) {
redisTemplate.opsForValue().set("subCid:" + cid, JsonUtil.obj2String(categoryList));
}
} else {
categoryList = JsonUtil.string2Obj(categories, List.class, Category.class);
}
return categoryList;
}
以上的代码是在开发中非常常见的代码。
但是如果有一些黑客或者肉机,故意输入一些不存在的ID,或者大量的用户输错了地址,那么缓存永远都是null。永远都命中到数据库。如果这个时候出现的请求并发很大,可能就会把数据库冲垮。
比如:cid==666 在数据表中不存在
String categories = (String) redisTemplate.opsForValue().get("subCid:" + cid);
上面的缓存代码为空,就会进入到数据db去查询,如果黑客或者肉机,大量错误输入,请求非常大的话就造成缓存穿透。
2、解决方案
2.1 将不存在的key,存一个默认值:null ,并且给予过期时间。
@GetMapping("/findCategory/{cid}")
public List<Category> findCategory(@PathVariable("cid") Integer cid) {
// 判断分类id是否传入,如果没有传入那么直接返回
KAssert.isEmpty(cid, 401, "分类不存在");
List<Category> categoryList = new ArrayList<>();
// 先去缓存中去获取分类信息
String categories = (String) redisTemplate.opsForValue().get("subCid:" + cid);
if (StringUtils.isEmpty(categories)) {
log.info("数据库查询");
// 如果不存在就从数据库查询
categoryList = categoryService.findCategroies(cid);
// 判断如果集合有数据,那么就会放入缓存。就是这个判断,造成了缓存穿透。
if (CollectionUtils.isEmpty(categoryList)) {
// 将不存在的key,存一个默认值:null,并且给予过期时间
redisTemplate.opsForValue().set("subCid:" + cid, JsonUtil.obj2String(categoryList), 5 * 60, TimeUnit.SECONDS);
} else {
// 放入到redis缓存中
redisTemplate.opsForValue().set("subCid:" + cid, JsonUtil.obj2String(categoryList));
}
} else {
// 是否存在,如果存在直接从缓存返回
categoryList = JsonUtil.string2Obj(categories, List.class, Category.class);
}
return categoryList;
}
2.2 布隆过滤器
@GetMapping("/findCategory/{cid}")
public List<Category> findCategory(@PathVariable("cid") Integer cid) {
// 判断分类id是否传入,如果没有传入那么直接返回
KAssert.isEmpty(cid, 401, "分类不存在");
// // 进行布隆过滤 bf.madd redis:bloom:category “1” "2" "3" "4" "5"
// // 进行布隆过滤 bf.mexists redis:bloom:category “1”
boolean[] booleans = reBloomClient.existsMulti("redis:bloom:category", cid + "");
// // 如果cid没有在布隆过滤器中,说明你数据不存在,直接返回
if (!booleans[0]) {
throw new ValidationException(401, "分类不存在");
}
// BloomFilter的认识
// 用了Redis缓存就真的不会进入数据库了吗?
List<Category> categoryList = new ArrayList<>();
// 先去缓存中去获取分类信息
String categories = (String) redisTemplate.opsForValue().get("subCid:" + cid);
if (StringUtils.isEmpty(categories)) {
log.info("数据库查询");
categoryList = categoryService.findCategroies(cid);
if (!CollectionUtils.isEmpty(categoryList)) {
redisTemplate.opsForValue().set("subCid:" + cid, JsonUtil.obj2String(categoryList));
}
} else {
categoryList = JsonUtil.string2Obj(categories, List.class, Category.class);
}
return categoryList;
}