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