缓存穿透
缓存穿透就是查询一个数据库一定不存在的数据,首先根据key去查询缓存,若缓存中不存在或者缓存已经过期就去查询数据库,把查询出来的结果放入缓存,如果为空则不放入缓存。
public Goods getGoods(@PathVariable("id") Integer id) {
//查询缓存
Object obj = redisTemplate.opsForValue().get(String.valueOf(id));
if (Objects.nonNull(obj)) {
return (Goods) obj;
}
//查询数据库
Goods goods = goodsMapper.getGoods(id);
if (Objects.nonNull(goods)) {
redisTemplate.opsForValue().set(String.valueOf(id), goods, 60, TimeUnit.MINUTES);
}
return goods;
}
根据数据库主键自增策略,若传入的参数是-1,那么这个参数一定在数据库中查不到结果,而且每次都不会进行缓存,假如有恶意攻击,就会利用这个漏洞压垮数据库,怎么破?
可以利用缓存空值的方式,如果在数据库中查不到结果,就把null放入缓存中,设置缓存过期时间较小,比如60秒。
public Goods getGoods(@PathVariable("id") Integer id) {
//查询缓存
Object obj = redisTemplate.opsForValue().get(String.valueOf(id));
if (Objects.nonNull(obj)) {
System.out.println("从缓存中取数据");
return (Goods) obj;
}
//查询数据库
Goods goods = goodsMapper.getGoods(id);
System.out.println("从数据库中取数据");
if (Objects.nonNull(goods)) {
redisTemplate.opsForValue().set(String.valueOf(id), goods, 60, TimeUnit.MINUTES);
} else {
redisTemplate.opsForValue().set(String.valueOf(id), null, 60, TimeUnit.SECONDS);
}
return goods;
}
但是以上代码在高并发下依然存在问题,当我用压测工具压测接口的时候结果如图:
多线程下很多数据都是从数据库取,但是我们想要的效果是只有第一次取数据的时候走数据库,其余都走缓存,所以有了如下优化:
public Goods getGoods(@PathVariable("id") Integer id) {
//查询缓存
Goods goods = (Goods) redisTemplate.opsForValue().get(String.valueOf(id));
if(Objects.nonNull(goods)){
System.out.println("查询缓存");
return goods;
}
//加入双重检查锁
synchronized (this) {
goods = (Goods) redisTemplate.opsForValue().get(String.valueOf(id));
if (Objects.isNull(goods)) {
//查询数据库
goods = goodsMapper.getGoods(id);
System.out.println("查询数据库");
if (Objects.isNull(goods)) {
redisTemplate.opsForValue().set(String.valueOf(id), null, 60, TimeUnit.SECONDS);
}
//缓存空值
redisTemplate.opsForValue().set(String.valueOf(id), goods, 60, TimeUnit.MINUTES);
}
}
return goods;
}
结果如图: