前言

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

造成原因

  • 业务自身代码或数据出现问题
  • 恶意攻击、爬虫造成大量空的命中,会对数据库造成很大压力

博客架构

案例分析

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

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

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

for num in range(1,1000000):
//爬死你,开100个线程

解决方案

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

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

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>25.1-jre</version>
</dependency>

编写布隆过滤器:

/**
* 布隆缓存过滤器
*/
@Component
public class BloomCacheFilter { public static BloomFilter<Integer> bloomFilter = null; @Autowired
private DynamicQuery dynamicQuery;
/**
* 初始化
*/
@PostConstruct
public void init(){
String nativeSql = "SELECT id FROM blog";
List<Object> list = dynamicQuery.query(nativeSql,new Object[]{});
bloomFilter = BloomFilter.create(Funnels.integerFunnel(), list.size());
list.forEach(blog ->bloomFilter.put(Integer.parseInt(blog.toString())));
}
/**
* 判断key是否存在
* @param key
* @return
*/
public static boolean mightContain(long key){
return bloomFilter.mightContain((int)key);
}
}

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

/**
* 博文
*/
@RequestMapping("{id}.shtml")
public String page(@PathVariable("id") Long id, ModelMap model) {
if(BloomCacheFilter.mightContain(id)){
Blog blog = blogService.getById(id);
model.addAttribute("blog",blog);
return "article";
}else{
return "error";
}
}

效率

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

 public static void main(String[] args) {
int capacity = 1000000;
int key = 6666;
BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), capacity);
for (int i = 0; i < capacity; i++) {
bloomFilter.put(i);
}
/**返回计算机最精确的时间,单位纳妙 */
long start = System.nanoTime();
if (bloomFilter.mightContain(key)) {
System.out.println("成功过滤到" + key);
}
long end = System.nanoTime();
System.out.println("布隆过滤器消耗时间:" + (end - start));
}

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

错判率

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

 public static void main(String[] args) {
int capacity = 1000000;
BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), capacity);
for (int i = 0; i < capacity; i++) {
bloomFilter.put(i);
}
int sum = 0;
for (int i = capacity + 20000; i < capacity + 30000; i++) {
if (bloomFilter.mightContain(i)) {
sum ++;
}
}
//0.03
DecimalFormat df=new DecimalFormat("0.00");//设置保留位数
System.out.println("错判率为:" + df.format((float)sum/10000));
}

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

public static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions) {
return create(funnel, expectedInsertions, 0.03D);
}

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

BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), capacity,0.01);

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

@NacosValue(value = "${bloomCache:false}", autoRefreshed = true)
private boolean bloomCache;
//省略部分代码
if(bloomCache||BloomCacheFilter.mightContain(id)){
Blog blog = blogService.getById(id);
model.addAttribute("blog",blog);
return "article";
}else{
return "error";
}

小结

缓存穿透大多数情况下都是恶意攻击导致的空命中率。虽然十万博客还没有被百度收录,每天也就寥寥的几十个IP,但是梦想还是有的,万一实现了呢?所以,还是要做好准备的!

源码

https://gitee.com/52itstyle/spring-boot-blog

从SpringBoot构建十万博文聊聊缓存穿透的更多相关文章

  1. 从SpringBoot构建十万博文聊聊限流特技

    前言 在开发十万博客系统的的过程中,前面主要分享了爬虫.缓存穿透以及文章阅读量计数等等.爬虫的目的就是解决十万+问题:缓存穿透是为了保护后端数据库查询服务:计数服务解决了接近真实阅读数以及数据库服务的 ...

  2. 从SpringBoot构建十万博文聊聊Tomcat集群监控

    前言 在十万博文终极架构中,我们使用了Tomcat集群,但这并不能保证系统不会出问题,为了保证系统的稳定运行,我们还需要对 Tomcat 进行有效的运维监控手段,不至于问题出现或者许久一段时间才知道. ...

  3. 从SpringBoot构建十万博文聊聊高并发文章浏览量设计

    前言 在经历了,缓存.限流.布隆穿透等等一系列加强功能,十万博客基本算是成型,网站上线以后也加入了百度统计来见证十万+ 的整个过程. 但是百度统计并不能对每篇博文进行详细的浏览量统计,如果做一些热点博 ...

  4. SpringBoot开发案例之打造十万博文Web篇

    前言 通过 Python 爬取十万博文之后,最重要的是要让互联网用户访问到,那么如何做呢? 选型 从后台框架.前端模板.数据库连接池.缓存.代理服务.限流等组件多个维度选型. 后台框架 SpringB ...

  5. SpringBoot微服务电商项目开发实战 --- Redis缓存雪崩、缓存穿透、缓存击穿防范

    最近已经推出了好几篇SpringBoot+Dubbo+Redis+Kafka实现电商的文章,今天再次回到分布式微服务项目中来,在开始写今天的系列五文章之前,我先回顾下前面的内容. 系列(一):主要说了 ...

  6. springboot中redis的缓存穿透问题

    什么是缓存穿透问题?? 我们使用redis是为了减少数据库的压力,让尽量多的请求去承压能力比较大的redis,而不是数据库.但是高并发条件下,可能会在redis还没有缓存的时候,大量的请求同时进入,导 ...

  7. 从.Net到Java学习第七篇——SpringBoot Redis 缓存穿透

    从.Net到Java学习系列目录 场景描述:我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回.这个时候如果我们查询的某一个数 ...

  8. Redis基础用法、高级特性与性能调优以及缓存穿透等分析

     一.Redis介绍 Redis是一个开源的,基于内存的结构化数据存储媒介,可以作为数据库.缓存服务或消息服务使用.Redis支持多种数据结构,包括字符串.哈希表.链表.集合.有序集合.位图.Hype ...

  9. redis的缓存穿透、击穿、雪崩以及实用解决方案

    今天来聊聊redis的缓存穿透.击穿.雪崩以及解决方案,其中解决方案包括类似于布隆过滤器这种网上一搜一大片但是实际生产部署有一定复杂度的,也有基于spring注解通过一行代码就能解决的,其中各有优劣, ...

随机推荐

  1. web应用分页

    1. 场景描述 目前大部分的应用程序中都会用到分页功能,以便减少前端浏览器及后台服务器的压力,以及其他方面的考虑. (1)分页从概念上可分为逻辑分页和物理分页,逻辑分页主要是通过应用程序(前端或者后端 ...

  2. ASP.NET Core系列(二):创建第一个.Net Core 项目

    前面讲过 .NET Core简介及开发环境安装,本章会讲一讲ASP.NET Core 2.0的项目结构,查看完整的ASP.NET Core系列文章:https://www.cnblogs.com/zh ...

  3. golang开发:类库篇(四)配置文件解析器goconfig的使用

    为什么要使用goconfig解析配置文件 目前各语言框架对配置文件书写基本都差不多,基本都是首先配置一些基础变量,基本变量里面有环境的配置,然后通过环境变量去获取该环境下的变量.例如,生产环境跟测试环 ...

  4. SpringBoot工程热部署

    SpringBoot工程热部署 1.在pom文件中添加热部署依赖 <!-- 热部署配置 --> <dependency> <groupId>org.springfr ...

  5. vs2010编译zapline-zapline.systemoptimization 注释工程中的//#define abs(value) (value >= 0 ? value : -(value))即可

    vs2010编译zapline-zapline.systemoptimization-8428e72c88e0.zip出错 1>d:\program files (x86)\microsoft ...

  6. vbox 设置时间不与主机同步

    C:\Users\2345-lp0395>"D:\Program Files\Oracle\VirtualBox\VBoxManage.exe" setexadata win ...

  7. 异步编程之Async,Await和ConfigureAwait的关系

    在.NET Framework 4.5中,async / await关键字已添加到该版本中,简化多线程操作,以使异步编程更易于使用.为了最大化利用资源而不挂起UI,你应该尽可能地尝试使用异步编程.虽然 ...

  8. cron 表达式的格式 了解

    cron 表达式的格式 Quartz cron 表达式的格式十分类似于 UNIX cron 格式,但还是有少许明显的区别.区别之一就是 Quartz 的格式向下支持到秒级别的计划,而 UNIX cro ...

  9. Kotlin之var和val区别

    var 和 val 是Kotlin的两个声明变量的关键字, var声明的变量是一个可变的变量,而val声明的变量是一个只读的变量(类似于java中的final变量)

  10. activeMQ_helloworld(一)

    一.activeMQ下载,直接在Linux上wget http://mirror.bit.edu.cn/apache//activemq/5.14.5/apache-activemq-5.14.5-b ...