ps:想只读有效信息,见红字
当在高并发,高性能,降低数据库压力的情况下,首先会选择redis作为缓存机制,当有大量请求需要查询数据库时,为了降低数据库的压力,并提高请求查询性能(redis基于内存,读取速度快),会将数据库的信息缓存到redis中,这样就形成了很好的分层结构,请求可以直接查询redis中缓存的信息,然后返回,就不需要经过数据库,减小了数据库的压力,同时,可以迅速查询到信息,岂不美哉。
先从缓存取数据,娶不到从数据库取,取到了就返回并添加缓存或更新缓存,取不到就返回空。
==正常请求操作==
第4步,当从数据库拿到数据时,一并添加到redis缓存,下次若是相同请求,直接访问redis并返回,大大提升了性能和可用性,持久性。
但有利就有弊,如果请求的数据是数据库中没有的,同样redis中也不会出现此数据缓存,这样当某短时间大量无效请求(数据库无对应数据)访问时,由于redis中没有此数据缓存,请求就给到了数据库,那么压力就来到了数据库这边,可想而知,其中会浪费掉多少性能,若是有心怀**之人故意为之,后果可想而知。所以,这就需要一个很好的解决方案,当大量无效请求(数据库并没有此数据)来访问时,就会导致缓存穿透。
缓存和数据库都娶不到数据,若大量无效请求发起,数据库负载压力过大
==非正常操作(在大量无益请求的场景下)==
第4步变为直接返回null,那么请求一多,全都访问数据库,好嘛,要是网站被攻击了,那数据库就白想活着~
redis设置key-null,TTL设置短时间
那么就又要考虑了,当一个请求访问时,我们两层都没有相应数据,数据库操作后接着对redis设一个空的键值,那么当我过了段时间又增加了一条数据,正好就是这个请求需要的,当第一步走到redis时直接就死掉了,那么可太不划算了。
所以在缓存设空时尽量TTL一个短时间,防住短时间大量请求即可,谁也不会闲的干这事。
那么我们接下来模拟一下具体场景 下面是操作流程代码块
@Override
public Result queryById(Long id) {
//从redis查询商铺缓存
String key = "cache:shop"+id;
//根据key拿到数据json块
String shopJson = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(shopJson)) {
//存在,转化为对应实体类直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
//判断命中的是否为空值,下下面代码存入的value为""空
if (shopJson!=null) {//等效于shopJson==""
//返回一个错误信息
return Result.fail("店铺不存在");
}
//不存在根据id查数据库
Shop shop = getById(id);
if (shop == null) {
//数据库中不存在,将null写入redis,并设置TTL
stringRedisTemplate.opsForValue()
.set(key,"",2, TimeUnit.MINUTES);
return Result.fail("店铺不存在");
}
//存在,存入redis
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),60, TimeUnit.MINUTES);
return Result.ok(shop);
}
一条请求,这里是根据id来查询,此时设置id为0,数据库并不存在这条数据。观察到,返回了不存在json信息
而且redis是没有缓存的,所以执行了相应数据库的sql操作,数据库并没有id为0的数据,所以下一步就是增加redis空缓存
此时,redis增加了一个key表示店铺id为0的空缓存,右上角TTL还剩·114秒(设置的为2分钟)
这样就能在短时间在缓存层面拦截大量无意义请求。
利用hash算法计算数据库数据得到byte数组,当请求来访问先经过布隆过滤器,根据此数组0和1判断数据是否存在,存在就放行到redis否则拒绝。