4

Redis布隆防击穿实战

 2 years ago
source link: https://blog.csdn.net/lifetragedy/article/details/122404968
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Redis布隆防击穿实战_lifetragedy的专栏-CSDN博客

我们圣诞节在生产上碰到了每秒万级并发,经过WAF结合相关日志分析我们发觉我们在小程序上有几个接口被人泛滥了很利害。

而这几个接口我们前端是使用了varnish来做缓存的,理论上因该都是毫秒级返回的。不应该对生产由其首页造成过多的压力呀?

于是我们找了近百个用户的实际请求,进行了“回放”,发觉这几个请求的response time远远高于了我们的varnish对前端返回的速度。

于是我们进一步分析,发现问题出在了这几个请求-都是get方法且问号(?)后面带的参数的value竟然都不是我们站内所该有的商品、渠道、模块。而是随机生成的且每次请求这些参数都不一样。有些value竟然还传进来了老K、S、皮蛋、丁勾、嘻嘻、哈哈。你有看到过sku_id传一个哈哈或者helloworld吗?真是古今中外之没有过!

结合了WAF进行了进一步分析,我们发觉这些带随机产生系统不存在模块id、产品id的IP都只是访问一次性,但是在访问一次时这些请求的ip很多,多达5位数ip同时在某一个点一起来请求一次。这已经很明显了,这是典型的黑产或者是爬虫试图绕开我们前端的varnish、并且进一步绕开了我们的redis、打中了db导致了首页加载时DB压力很大。

黑产、一些数据公司、爬虫,它们本身拥有的IP是大量的。它们根本不需要高频来做网站数据的爬取或者是恶意攻击,它们只要发动6位数甚至7位数的IP每隔几秒来访问一下你的网站,你的网站就扛不住了。

因此从业务逻辑判断,我们说这个商品数据,它有一个product_id的,算你10万个sku不得了了吧,如果来访问时带着的sku_id在系统中都不存在,这种访问你要它干什么呢?

因此我们得到了相应的防击穿解决方案如下

我们在上一篇SpringBoot+Redis布隆过滤器防恶意流量击穿缓存的正确姿势中给出的代码是依赖于redis本身要load布隆过滤器模块的。

而这次我们坚持使用云原生,直接用google的guava的工具类使用的bloom算法然后把它用setBit存入redis中。因为如果单纯的使用guava的话,应用在重启后内存中的bloom filter的内容会被清空,因此我们很好的结合了guava的算法以及使用redis来做存储介质这一手法而不需要像我在上一篇中,要给redis装bloom filter插件。

毕竟,在生产上的redis你给他装插件,是件很夸张的事。同时事出紧急,给到我们的反应只有20分钟时间,因此我们需要马上上一套代码来对这样的恶意请求做拦截,否则小程序应用的首页是扛不住的,因此我们再次使用了这种“聪明”的手法来做实施。

下面给出工程代码。

生产上用的全代码(我的开源版比生产代码要用的新和更强)^_^

application_local.yml

pom.xml

给出几个关键的pom.xml中使用的parent的信息吧

不要抄网上的,大部分都是错的。一定记得这边的spring boot的版本、和guava的版本、redis的版本、redission的版本、jackson的版本是有严格讲究的,log4j相关的内容根据我之前的博客自行替换成2.7.1好了(目前apache log4j2.7.1是比较安全的版本)

然后是自动装配类

BloomFilterHelper

RedisBloomFilter自动装配类

RedisSentinelConfig-Redis核心配置类

在里面我申明了一个BloomFilterHelper<String>返回类型的initBloomFilterHelper方法

代码的使用

布降过滤器记得只可以追加或者删除后重新喂数据。

在生产上我们可以这么干,以两个例子来告诉大家如何操作。

例子一、对于所有的在活动日期开始前的剑客级会员进行防护

设活动开始日期为2022年1月1号元旦,在此之前所有的剑客级会员需要进入防护。

于是我们写一个JOB,一次性把上千万会员一次load进布隆过滤器内。然后在1月1号早上9:00活动开始生效时,只要带着系统内不存在的会员token如:ut这个值进来访问,全部在bloom filter一层就把请求给挡掉了。

例子二、对于所有的类目中的sku_id进行防护。

设小程序或者是前端app的商品类目有16类,每类有1千种sku,差不多有12万的sku。这些sku会伴随着每天会有那么2-3次的上下架操作。那么我们会做一个job,这个job在5分钟内运行一下。从数据库内捞出所有“上架状态”的sku,喂入bloom filter。喂入前先把bloom filter的这个key删了。当然,为了做到更精准,我们会使用mq异步,上下架全完成了后点一下【生效】这个按钮后,紧接着一条MQ通知一个Service先删除原来的bloom filter里的key再喂入全量的sku_id,全过程不地秒级内完成。

下面就以例子1来看业务代码的实现。在此,我们做了一个Service,它在应用一启动时装载所有的用户的token。

UTBloomFilterInit

这个Service会在spring boot启动时模拟加载全量的用户进到布隆过滤器中。

RedisBloomController

测试业务拦截

我们使用布隆过滤器中存在的ut来访问,得到如下结果

 我们使用布隆过滤器中不存在的ut来访问,得到如下结果

这种判断都是在3,000并发下毫秒级别响应

事实上生产我们可以load上亿条数据进入bloom过滤器。而bloom的大小远远比hash或者是md5要小、且几乎不重复。

布隆内数据的大小依赖于这两个值来决定的:

这两个值解读为亿条数据内,出错(遗漏)精度在万分之1.布隆过滤器返回false,100%可以认为不存在。精度越高redis内存储的空间所需越大。它是一次性划分掉的,并不是现在只有1万条因此需要200k下次变成了10万条因此需要12兆。我们看一下亿条数据万分之一精度在redis内占用多少资源吧。

亿条数据不过200兆,这对生产Redis来说太小case了。

布隆过滤器的常用场景的介绍这边也顺便给大家梳理一下


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK