首页 资讯频道 互联频道 智能频道 网络 数据频道 安全频道 服务器频道 存储频道

SpringBoot缓存穿透 并发量过大该如何抗压?

2020-02-05 12:00:46 来源 : 今日头条

在博客系统中,为了提升响应速度,加入了 Redis 缓存,把文章主键 ID 作为 key 值去缓存查询,如果不存在对应的 value,就去数据库中查找 。这个时候,如果请求的并发量很大,就会对后端的数据库服务造成很大的压力。

造成原因

业务自身代码或数据出现问题

恶意攻击、爬虫造成大量空的命中,会对数据库造成很大压力

案例分析

由于文章的地址是这样子的:

https://blog.52itstyle.top/49.html

大家很容易猜出,是不是还有 50、51、52 甚至是十万+?如果是正儿八经的爬虫,可能会读取你的总页数。但是有些不正经的爬虫或者人,还真以为你有十万+博文,然后就写了这么一个脚本。

fornuminrange(1,1000000):

//爬死你,开100个线程

解决方案

设置布隆过滤器,预先将所有文章的主键 ID 哈希到一个足够大的 BitMap 中,每次请求都会经过 BitMap 的拦截,如果 Key 不存在,直接返回异常。这样就避免了对 Redis 缓存以及底层数据库的查询压力。

这里我们使用谷歌开源的第三方工具类来实现:

com.google.guava

guava

25.1-jre

 

编写布隆过滤器:

/**

*布隆缓存过滤器

*/

@Component

publicclassBloomCacheFilter{

publicstaticBloomFilterbloomFilter=null;

@Autowired

privateDynamicQuerydynamicQuery;

/**

*初始化

*/

@PostConstruct

publicvoidinit(){

StringnativeSql="SELECTidFROMblog";

Listlist=dynamicQuery.query(nativeSql,newObject[]{});

bloomFilter=BloomFilter.create(Funnels.integerFunnel(),list.size());

list.forEach(blog->bloomFilter.put(Integer.parseInt(blog.toString())));

}

/**

*判断key是否存在

*@paramkey

*@return

*/

publicstaticbooleanmightContain(longkey){

returnbloomFilter.mightContain((int)key);

}

}

然后,每一次查询之前做一次 Key 值校验:

/**

*博文

*/

@RequestMapping("{id}.shtml")

publicStringpage(@PathVariable("id")Longid,ModelMapmodel){

if(BloomCacheFilter.mightContain(id)){

Blogblog=blogService.getById(id);

model.addAttribute("blog",blog);

return"article";

}else{

return"error";

}

}

效率

那么,在数据量很大的情况下,效率如何呢?我们来做个实验,以 100W 为基数。

publicstaticvoidmain(String[]args){

intcapacity=1000000;

intkey=6666;

BloomFilterbloomFilter=BloomFilter.create(Funnels.integerFunnel(),capacity);

for(inti=0;i

bloomFilter.put(i);

}

/**返回计算机最精确的时间,单位纳妙*/

longstart=System.nanoTime();

if(bloomFilter.mightContain(key)){

System.out.println("成功过滤到"+key);

}

longend=System.nanoTime();

System.out.println("布隆过滤器消耗时间:"+(end-start));

}

布隆过滤器消耗时间:281299,约等于 0.28 毫秒,匹配速度是不是很快?

错判率

万事万物都有所均衡,既然效率如此之高,肯定其它方面定有所牺牲,通过测试我们发现,过滤器有 3% 的错判率,也就是说,本来没有的文章,有可能通过校验被访问到,然后报错!

publicstaticvoidmain(String[]args){

intcapacity=1000000;

BloomFilterbloomFilter=BloomFilter.create(Funnels.integerFunnel(),capacity);

for(inti=0;i

bloomFilter.put(i);

}

intsum=0;

for(inti=capacity+20000;i

if(bloomFilter.mightContain(i)){

sum++;

}

}

//0.03

DecimalFormatdf=newDecimalFormat("0.00");//设置保留位数

System.out.println("错判率为:"+df.format((float)sum/10000));

}

通过源码阅读,发现 3% 的错判率是系统写死的。

publicstaticBloomFiltercreate(Funnelfunnel,longexpectedInsertions){

returncreate(funnel,expectedInsertions,0.03D);

}

当然我们也可以通过传参,降低错判率。测试了一下,查询速度稍微有一丢丢降低,但也只是零点几毫秒级的而已。

BloomFilterbloomFilter=BloomFilter.create(Funnels.integerFunnel(),capacity,0.01);

那么如何做到零错判率呢?答案是不可能的,布隆过滤器,错判率必须大于零。为了保证文章 100% 的访问率,正常情况下,我们可以关闭布隆校验,只有才突发情况下开启。比如,可以通过阿里的动态参数配置 Nacos 实现。

@NacosValue(value="${bloomCache:false}",autoRefreshed=true)

privatebooleanbloomCache;

//省略部分代码

if(bloomCache||BloomCacheFilter.mightContain(id)){

Blogblog=blogService.getById(id);

model.addAttribute("blog",blog);

return"article";

}else{

return"error";

}

相关文章

最近更新