本文主要介绍了springboot+redis布隆过滤器防恶意流量击穿缓存,具体如下:
假设我们的redis里存有一组用户的注册email,以email作为key存在,同时它对应着db里的ur表的部分字段。
一般来说,一个合理的请求过来我们会先在redis里判断这个用户是否是会员,因为从缓存里读数据返回快。如果这个会员在缓存中不存在那么我们会去db中查询一下。
现在试想,有千万个不同ip的请求(不要以为没有,我们就在2018年和2019年碰到了,因为攻击的成本很低)带着redis里根本不存在的key来访问你的网站,这时我们来设想一下:
请求到达web服务器;请求派发到应用层->微服务层;请求去redis捞数据,redis内不存在这个key;于是请求到达db层,在db建立connection后进行一次查询千万乃至上亿的db连接请求,先不说redis是否撑的住db也会被瞬间打爆。这就是“redis穿透”又被称为“缓存击穿”,它会打爆你的缓存或者是连db一起打爆进而引起一系列的“雪崩效应”。
那就是使用布隆过滤器,可以把所有的ur表里的关键查询字段放于redis的bloom过滤器内。有人会说,这不疯了,我有4000万会员?so what!
你把4000会员放在redis里是比较夸张,有些网站有8000万、1亿会员呢?因此我没让你直接放在redis里,而是放在布隆过滤器内!
布隆过滤器内不是直接把key,value这样放进去的,它存放的内容是这么一个样的:
bloomfilter是一种空间效率的概率型数据结构,由burton howard bloom 1970年提出的。通常用来判断一个元素是否在集合中。具有极高的空间效率,但是会带来假阳性(fal positive)的错误。
fal positive&&fal negatives
由于bloomfiter牺牲了一定的准确率换取空间效率。所以带来了fal positive的问题。
fal positive
bloomfilter在判断一个元素在集合中的时候,会出现一定的错误率,这个错误率称为fal positive的。通常缩写为fpp。
fal negatives
bloomfilter判断一个元素不在集合中的时候的错误率。 bloomfilter判断该元素不在集合中,则该元素一定不再集合中。故fal negatives概率为0。
bloomfilter使用长度为m bit的字节数组,使用k个hash函数,增加一个元素: 通过k次hash将元素映射到字节数组中k个位置中,并设置对应位置的字节为1。
查询元素是否存在: 将元素k次hash得到k个位置,如果对应k个位置的bit是1则认为存在,反之则认为不存在。
由于它里面存的都是bit,因此这个数据量会很小很小,小到什么样的程度呢?在写本博客时我插了100万条email信息进入redis的bloom filter也只占用了3mb不到。
bloom filter会有几比较关键的值,根据这个值你是大致可以算出放多少条数据然后它的误伤率在多少时会占用多少系统资源的。这个算法有一个网址:https://krisives.github.io/bloom-calculator/,我们放入100万条数据,假设误伤率在0.001%,看,它自动得出redis需要申请的系统内存资源是多少?
那么怎么解决这个误伤率呢?很简单的,当有误伤时业务或者是运营会来报误伤率,这时你只要添加一个小白名单就是了,相对于100万条数据来说,1000个白名单不是问题。并且bloom filter的返回速度超块,80-100毫秒内即返回调用端该key存在或者是不存了。
假设我用python爬虫爬了4亿条url,需要去重?
看,布隆过滤器就是用于这个场景的。
下面就开始我们的redis bloomfilter之旅。
redis从4.0才开始支持bloom filter,因此本例中我们使用的是redis5.4。
redis的bloom filter下载地址在这:https://github.com/redislabsmodules/redisbloom.git
git clone https://github.com/redislabsmodules/redisbloom.gitcd redisbloommake # 编译
让redis启动时可以加载bloom filter有两种方式:
手工加载式:
redis-rver --loadmodule ./redisbloom/rebloom.so
每次启动自加载:
编辑redis的redis.conf文件,加入:
loadmodule /soft/redisbloom/redisbloom.so
like this:
基本指令:
bf.rerve {key} {error_rate} {size}
127.0.0.1:6379> bf.rerve urid 0.01 100000ok
上面这条命令就是:创建一个空的布隆过滤器,并设置一个期望的错误率和初始大小。{error_rate}过滤器的错误率在0-1之间,如果要设置0.1%,则应该是0.001。该数值越接近0,内存消耗越大,对cpu利用率越高。
bf.add {key} {item}
127.0.0.1:6379> bf.add urid '181920'(integer) 1
上面这条命令就是:往过滤器中添加元素。如果key不存在,过滤器会自动创建。
bf.exists {key} {item}
127.0.0.1:6379> bf.exists urid '101310299'(integer) 1
上面这条命令就是:判断指定key的value是否在bloomfilter里存在。存在:返回1,不存在:返回0。
网上很多写的都是要么是直接使用jedis来操作的,或者是java里execute一个外部进程来调用redis的bloom filter指令的。很多都是调不通或者helloworld一个级别的,是根本无法上生产级别应用的。
笔者给出的代码保障读者完全可用!
笔者不是数学家,因此就借用了google的guava包来实现了核心算法,核心代码如下:
bloomfilterhelper.java
package org.sky.platform.util; import com.google.common.ba.preconditions;import com.google.common.hash.funnel;import com.google.common.hash.hashing; public class bloomfilterhelper<t> {private int numhashfunctions; private int bitsize; private funnel<t> funnel; public bloomfilterhelper(funnel<t> funnel, int expectedinrtions, double fpp) {preconditions.checkargument(funnel != null, "funnel不能为空");this.funnel = funnel;bitsize = optimalnumofbits(expectedinrtions, fpp);numhashfunctions = optimalnumofhashfunctions(expectedinrtions, bitsize);} int[] murmurhashofft(t value) {int[] offt = new int[numhashfunctions]; long hash64 = hashing.murmur3_128().hashobject(value, funnel).aslong();int hash1 = (int) hash64;int hash2 = (int) (hash64 >>> 32);for (int i = 1; i <= numhashfunctions; i++) {int nexthash = hash1 + i * hash2;if (nexthash < 0) {nexthash = ~nexthash;}offt[i - 1] = nexthash % bitsize;} return offt;} /** * 计算bit数组的长度 */private int optimalnumofbits(long n, double p) {if (p == 0) {p = double.min_value;}return (int) (-n * math.log(p) / (math.log(2) * math.log(2)));} /** * 计算hash方法执行次数 */private int optimalnumofhashfunctions(long n, long m) {return math.max(1, (int) math.round((double) m / n * math.log(2)));}}
下面放出全工程解说,我已经将源码上传到了我的git上了,确保读者可用,源码地址在这:https://github.com/mkyuangithub/mkyuangithub.git
项目redis配置
我们在redis-practice工程里建立一个application.properties文件,内容如下:
spring.redis.databa=0 spring.redis.host=192.168.56.101spring.redis.port=6379spring.redis.password=111111spring.redis.pool.max-active=10 spring.redis.pool.max-wait=-1 spring.redis.pool.max-idle=10 spring.redis.pool.min-idle=0 spring.redis.timeout=1000
以上这个是demo环境的配置。
我们此处依旧使用的是在前一篇中的xxx-project->sky-common->nacos-parent的依赖结构。
在redis-practice工程的org.sky.config包中放入redis的springboot配置
redisconfig.java
package org.sky.config; import com.fasterxml.jackson.annotation.jsonautodetect;import com.fasterxml.jackson.annotation.propertyaccessor;import com.fasterxml.jackson.databind.objectmapper;import org.springframework.cache.cachemanager;import org.springframework.cache.annotation.cachingconfigurersupport;import org.springframework.cache.annotation.enablecaching;import org.springframework.context.annotation.bean;import org.springframework.context.annotation.configuration;import org.springframework.data.redis.cache.rediscachemanager;import org.springframework.data.redis.connection.redisconnectionfactory;import org.springframework.data.redis.core.*;import org.springframework.data.redis.rializer.jackson2jsonredisrializer;import org.springframework.data.redis.rializer.stringredisrializer; @configuration@enablecachingpublic class redisconfig extends cachingconfigurersupport {/** * 选择redis作为默认缓存工具 * * @param redistemplate * @return */@beanpublic cachemanager cachemanager(redistemplate redistemplate) {rediscachemanager rcm = new rediscachemanager(redistemplate);return rcm;} /** * retemplate相关配置 * * @param factory * @return */@beanpublic redistemplate<string, object> redistemplate(redisconnectionfactory factory) { redistemplate<string, object> template = new redistemplate<>();// 配置连接工厂template.tconnectionfactory(factory); // 使用jackson2jsonredisrializer来序列化和反序列化redis的value值(默认使用jdk的序列化方式)jackson2jsonredisrializer jacksonial = new jackson2jsonredisrializer(object.class); objectmapper om = new objectmapper();// 指定要序列化的域,field,get和t,以及修饰符范围,any是都有包括private和publicom.tvisibility(propertyaccessor.all, jsonautodetect.visibility.any);// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如string,integer等会跑出异常om.enabledefaulttyping(ob教育大省jectmapper.defaulttyping.non_final);jacksonial.tobjectmapper(om); // 值采用json序列化template.tvaluerializer(jacksonial);// 使用stringredisrializer来序列化和反序列化redis的key值template.tkeyrializer(new stringredisrializer()); // 设置hash key 和value序列化模式template.thashkeyrializer(new stringredisrializer());template.thashvaluerializer(jacksonial);template.afterpropertiest(); return template;} /** * 对hash类型的数据操作 * * @param redistemplate * @return */@beanpublic hashoperations<string, string, object> hashoperations(redistemplate<string, object> redistemplate) {return redistemplate.opsforhash();} /** * 对redis字符串类型数据操作 * * @param redistemplate * @return */@beanpublic valueoperations<string, object> valueoperations(redistemplate<string, object> redistemplate) {return redistemplate.opsforvalue();} /** * 对链表类型的数据操作 * * @param redistemplate * @return */@beanpublic listoperations<string, object> listoperations(redistemplate<string, object> redistemplate) {return redistemplate.opsforlist();} /** * 对无序集合类型的数据操作 * * @param redistemplate * @return */@beanpublic toperations<string, object> toperations(redistemplate<string, object> redistemplate) {return redistemplate.opsfort();} /** * 对有序集合类型的数据操作 * * @param redistemplate * @return */@beanpublic ztoperations<string, object> ztoperations(redistemplate<string, object> redistemplate) {return redistemplate.opsforzt();}}
这个配置除实现了springboot自动发现redis在application.properties中的配置外我们还添加了不少redis基本的数据结构的操作的封装。
我们为此还要再封装一套redis util小组件,它们位于sky-common工程中
redisutil.java
package org.sky.platform.util; import org.springframework.beans.factory.annotation.autowired;import org.springframework.data.redis.core.redistemplate;import org.springframework.stereotype.component; import java.util.collection;import java.util.date;import java.util.t;import java.util.concurrent.timeunit;import java.util.stream.collectors;import java.util.stream.st西南交大分数线ream;import com.google.common.ba.preconditions;import org.springframework.data.redis.core.redistemplate; @componentpublic class redisutil {@autowiredprivate redistemplate<string, string> redistemplate; /** * 默认过期时长,单位:秒 */public static final long default_expire = 60 * 60 * 24; /** * 不设置过期时长 */public static final long not_expire = -1; public boolean existskey(string key) {return redistemplate.haskey(key);} /** * 重名名key,如果newkey已经存在,则newkey的原值被覆盖 * * @param oldkey * @param newkey */public void renamekey(string oldkey, string newkey) {redistemplate.rename(oldkey, newkey);} /** * newkey不存在时才重命名 * * @param oldkey * @param newkey * @return 修改成功返回true */public boolean renamekeynotexist(string oldkey, string newkey) {return redistemplate.renameifabnt(oldkey, newkey);} /** * 删除key * * @param key */public void deletekey(string key) {redistemplate.delete(key);} /** * 删除多个key * * @param keys */public void deletekey(string... keys) {t<string> kt = stream.of(keys).map(k -> k).collect(collectors.tot());redistemplate.delete(kt);} /** * 删除key的集合 * * @param keys */public void deletekey(collection<string> keys) {t<string> kt = keys.stream().map(k -> k).collect(collectors.tot());redistemplate.delete(kt);} /** * 设置key的生命周期 * * @param key * @param time * @param timeunit */public void expirekey(string key, long time, timeunit timeunit) {redistemplate.expire(key, time, timeunit);} /** * 指定key在指定的日期过期 * * @param key * @param date */public void expirekeyat(string key, date date) {redistemplate.expireat(key, date);} /** * 查询key的生命周期 * * @param key * @param timeunit * @return */public long getkeyexpire(string key, timeunit timeunit) {return redistemplate.getexpire(key, timeunit);} /** * 将key设置为永久有效 * * @param key */public void persistkey(string key) {redistemplate.persist(key);} /** * 根据给定的布隆过滤器添加值 */public <t> void addbybloomfilter(bloomfilterhelper<t> bloomfilterhelper, string key, t value) {preconditions.checkargument(bloomfilterhelper != null, "bloomfilterhelper不能为空");int[] offt = bloomfilterhelper.murmurhashofft(value);for (int i : offt) {redistemplate.opsforvalue().tbit(key, i, true);}} /** * 根据给定的布隆过滤器判断值是否存在 */public <t> boolean includebybloomfilter(bloomfilterhelper<t> bloomfilterhelper, string key, t value) {preconditions.checkargument(bloomfilterhelper != null, "bloomfilterhelper不能为空");int[] offt = bloomfilterhelper.murmurhashofft(value);for (int i : offt) {if (!redistemplate.opsforvalue().getbit(key, i)) {return fal;}} return true;}}
rediskeyutil.java
package org.sky.platform.util; public class rediskeyutil {/** * redis的key 形式为: 表名:主键名:主键值:列名 * * @param tablename 表名 * @param majorkey 主键名 * @param majorkeyvalue 主键值 * @param column 列名 * @return */public static string getkeywithcolumn(string tablename, string majorkey, string majorkeyvalue, string column) {stringbuffer buffer = new stringbuffer();buffer.append(tablename).append(":");buffer.append(majorkey).append(":");buffer.append(majorkeyvalue).append(":");buffer.append(column);return buffer.tostring();} /** * redis的key 形式为: 表名:主键名:主键值 * * @param tablename 表名 * @param majorkey 主键名 * @param majorkeyvalue 主键值 * @return */public static string getkey(string tablename, string majorkey, string majorkeyvalue) {stringbuffer buffer = new stringbuffer();buffer.append(tablename).append(":");buffer.append(majorkey).append(":");buffer.append(majorkeyvalue).append(":");return buffer.tostring();}}
然后就是制作 redis里如何使用bloomfilter的bloomfilterhelper.java了,它也位于sky-common文件夹,源码如上已经贴了,因此此处就不再作重复。
最后我们在sky-common里放置一个urvo用于演示
urvo.java
package org.sky.vo; import java.io.rializable; public class urvo implements rializable { private string name;private string address;private integer age;private string email = ""; public string getemail() {return email;} public void temail(string email) {this.email = email;} public string getname() {return name;} public void tname(string name) {this.name = name;} public string getaddress() {return address;} public void taddress(string address) {this.address = address;} public integer getage() {return age;} public void tage(integer age) {this.age = age;} }
下面给出我们所有gitrepo里依赖的nacos-parent的pom.xml文件内容,此次我们增加了对于“spring-boot-starter-data-redis”,它跟着我们的全局springboot版本走:
parent工程的pom.xml
<project xmlns="http://maven.apache.org/pom/4.0.0"xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"xsi:schemalocation="http://maven.apache.org/pom/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelversion>4.0.0</modelversion><groupid>org.sky.demo</groupid><artifactid>nacos-parent</artifactid><version>0.0.1-snapshot</version><packaging>pom</packaging><description>demo project for spring boot dubbo nacos</description><modules></modules> <properties><java.version>1.8</java.version><spring-boot.version>1.5.15.relea</spring-boot.version><dubbo.version>2.7.3</dubbo.version><curator-framework.version>4.0.1</curator-framework.version><curator-recipes.version>2.8.0</curator-recipes.version><druid.version>1.1.20</druid.version><guava.version>27.0.1-jre</guava.version><fastjson.version>1.2.59</fastjson.version><dubbo-registry-nacos.version>2.7.3</dubbo-registry-nacos.version><nacos-client.version>1.1.4</nacos-client.version><mysql-connector-java.version>5.1.46</mysql-connector-java.version><disruptor.version>3.4.2</disruptor.version><aspectj.version>1.8.13</aspectj.version><nacos-rvice.version>0.0.1-snapshot</nacos-rvice.version><spring.data.redis>1.8.14-relea</spring.data.redis><skycommon.version>0.0.1-snapshot</skycommon.version><maven.compiler.source>${java.version}</maven.compiler.source><maven.compiler.target>${java.version}</maven.compiler.target><compiler.plugin.version>3.8.1</compiler.plugin.version><war.plugin.version>3.2.3</war.plugin.version><jar.plugin.version>3.1.2</jar.plugin.version><project.build.sourceencoding>utf-8</project.build.sourceencoding><project.reporting.outputencoding>utf-8</project.reporting.outputencoding></properties><dependencymanagement><dependencies><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-web</artifactid><version>${spring-boot.version}</version></dependency><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-dependencies</artifactid><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><dependency><g如何去颈纹roupid>org.apache.dubbo</groupid><artifactid>dubbo-spring-boot-starter</artifactid><version>${dubbo.version}</version><exclusions><exclusion><groupid>org.slf4j</groupid><artifactid>slf4j-log4j12</artifactid></exclusion></exclusions></dependency><dependency><groupid>org.apache.dubbo</groupid><artifactid>dubbo</artifactid><version>${dubbo.version}</version></dependency><dependency><groupid>org.apache.curator</groupid><artifactid>curator-framework</artifactid><version>${curator-framework.version}</version></dependency> <dependency><groupid>org.apache.curator</groupid><artifactid>curator-recipes</artifactid><version>${curator-recipes.version}</version></dependency><dependency><groupid>mysql</groupid><artifactid>mysql-connector-java</artifactid><version>${mysql-connector-java.version}</version></dependency><dependency><groupid>com.alibaba</groupid><artifactid>druid</artifactid><version>${druid.version}</version></dependency><dependency><groupid>com.lmax</groupid><artifactid>disruptor</artifactid><version>${disruptor.version}</version></dependency><dependency><groupid>com.google.guava</groupid><artifactid>guava</artifactid><version>${guava.version}</version></dependency><dependency><groupid>com.alibaba</groupid><artifactid>fastjson</artifactid><version>${fastjson.version}</version></dependency><dependency><groupid>org.apache.dubbo</groupid><artifactid>dubbo-registry-nacos</artifactid><version>${dubbo-registry-nacos.version}</version></dependency><dependency><groupid>com.alibaba.nacos</groupid><artifactid>nacos-client</artifactid><version>${nacos-client.version}</version></dependency><dependency><groupid>org.aspectj</groupid><artifactid>aspectjweaver</artifactid><version>${aspectj.version}</version></dependency><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-data-redis</artifactid><version>${spring-boot.version}</version></dependency></dependencies></dependencymanagement><build><plugins><plugin><groupid>org.apache.maven.plugins</groupid><artifactid>maven-compiler-plugin</artifactid><version>${compiler.plugin.version}</version><configuration><source>${java.version}</source><target>${java.version}</target></configuration></plugin><plugin><groupid>org.apache.maven.plugins</groupid><artifactid>maven-war-plugin</artifactid><version>${war.plugin.version}</version></plugin><plugin><groupid>org.apache.maven.plugins</groupid><artifactid>maven-jar-plugin</artifactid><version>${jar.plugin.version}</version></plugin></plugins></build></project>
sky-common中pom.xml文件
<project xmlns="http://maven.apache.org/pom/4.0.0"xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"xsi:schemalocation="http://maven.apache.org/pom/4.0.0 https://maven.apache.org/xsd/mave会计电算化n-4.0.0.xsd"><modelversion>4.0.0</modelversion><groupid>org.sky.demo</groupid><artifactid>skycommon</artifactid><version>0.0.1-snapshot</version><parent><groupid>org.sky.demo</groupid><artifactid>nacos-parent</artifactid><version>0.0.1-snapshot</version></parent><dependencies> <dependency><groupid>org.apache.curator</groupid><artifactid>curator-framework</artifactid></dependency><dependency><groupid>org.apache.curator</groupid><artifactid>curator-recipes</artifactid></dependency> <dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-test</artifactid><scope>test</scope></dependency><dependency><groupid>org.spockframework</groupid><artifactid>spock-core</artifactid><scope>test</scope></dependency><dependency><groupid>org.spockframework</groupid><artifactid>spock-spring</artifactid></dependency><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-configuration-processor</artifactid><optional>true</optional></dependency> <dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-log4j2</artifactid></dependency><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-web</artifactid><exclusions><exclusion><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-logging</artifactid></exclusion></exclusions></dependency> <dependency><groupid>org.aspectj</groupid><artifactid>aspectjweaver</artifactid></dependency><dependency><groupid>com.lmax</groupid><artifactid>disruptor</artifactid></dependency><dependency><groupid>redis.clients</groupid><artifactid>jedis</artifactid></dependency><dependency><groupid>com.google.guava</groupid><artifactid>guava</artifactid></dependency><dependency><groupid>com.alibaba</groupid><artifactid>fastjson</artifactid></dependency><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-data-redis</artifactid></dependency></dependencies></project>
到此,我们的springboot+redis基本框架、util类、bloomfilter组件搭建完毕,接下来我们重点说我们的demo工程
demo工程:redis-practice说明
pom.xml文件,它依赖于nacos-parent同时还引用了sky-common
<project xmlns="http://maven.apache.org/pom/4.0.0"xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"xsi:schemalocation="http://maven.apache.org/pom/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelversion>4.0.0</modelversion><groupid>org.sky.demo</groupid><artifactid>redis-practice</artifactid><version>0.0.1-snapshot</version><description>demo redis advanced features</description><parent><groupid>org.sky.demo</groupid><artifactid>nacos-parent</artifactid><version>0.0.1-snapshot</version></parent> <dependencies> <dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-jdbc</artifactid><exclusions><exclusion><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-logging</artifactid></exclusion></exclusions></dependency><dependency><groupid>org.apache.dubbo</groupid><artifactid>dubbo</artifactid></dependency><dependency><groupid>org.apache.curator</groupid><artifactid>curator-framework</artifactid></dependency><dependency><groupid>org.apache.curator</groupid><artifactid>curator-recipes</artifactid></dependency><dependency><groupid>mysql</groupid><artifactid>mysql-connector-java</artifactid></dependency><dependency><groupid>com.alibaba</groupid><artifactid>druid</artifactid></dependency><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-test</artifactid><scope>test</scope></dependency><dependency><groupid>org.spockframework</groupid><artifactid>spock-core</artifactid><scope>test</scope></dependency><dependency><groupid>org.spockframework</groupid><artifactid>spock-spring</artifactid></dependency><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-configuration-processor</artifactid><optional>true</optional></dependency><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-data-redis</artifactid></dependency><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-log4j2</artifactid></dependency><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-web</artifactid><exclusions><exclusion><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-logging</artifactid></exclusion></exclusions><exclusion><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-tomcat</artifactid></exclusion></dependency> <dependency><groupid>org.aspectj</groupid><artifactid>aspectjweaver</artifactid></dependency><dependency><groupid>com.lmax</groupid><artifactid>disruptor</artifactid></dependency><dependency><groupid>redis.clients</groupid><artifactid>jedis</artifactid></dependency><dependency><groupid>com.google.guava</groupid><artifactid>guava</artifactid></dependency><dependency><groupid>com.alibaba</groupid><artifactid>fastjson</artifactid></dependency><dependency><groupid>org.sky.demo</groupid><artifactid>skycommon</artifacqq好友克隆tid><version>${skycommon.version}</version></dependency><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-data-redis</artifactid></dependency></dependencies><build><sourcedirectory>src/main/java</sourcedirectory><testsourcedirectory>src/test/java</testsourcedirectory><plugins><plugin><groupid>org.springframework.boot</groupid><artifactid>spring-boot-maven-plugin</artifactid></plugin></plugins><resources><resource><directory>src/main/resources</directory></resource><resource><directory>src/main/webapp</directory><targetpath>meta-inf/resources</targetpath><includes><include>**/**</include></includes></resource><resource><directory>src/main/resources</directory><filtering>true</filtering><includes><include>application.properties</include><include>application-${profileactive}.properties</include></includes></resource></resources></build></project>
用于启动的application.java
package org.sky; import org.springframework.boot.springapplication;import org.springframework.boot.autoconfigure.enableautoconfiguration;import org.springframework.context.annotation.componentscan;import org.springframework.transaction.annotation.enabletransactionmanagement; @enabletransactionmanagement@componentscan(bapackages = { "org.sky" })@enableautoconfigurationpublic class application { public static void main(string[] args) {springapplication.run(application.class, args);}}
然后我们制作了一个controller名为urcontroller,该controller里有两个方法:
public responentity<string> addur(@requestbody string params),该方法用于接受来自外部的api post然后把一条email地址塞入redis的bloomfilter中;public responentity<string> findemailinbloom(@requestbody string params),该方法用于接受来自外部的api post然后去redis的bloomfilter中验证是否外部输入的ur信息中的email地址在上百万的email记录中存在;以此来完成验证塞入redis的bloom filter中上百万条记录占用了多少内存以及使用bloom filter查询一条记录有多快。
urcontroller.java
package org.sky.controller; import java.util.hashmap;import java.util.map;import java.util.concurrent.timeunit; import javax.annotation.resource; import org.sky.platform.util.bloomfilterhelper;import org.sky.platform.util.redisutil;import org.sky.vo.urvo;import org.springframework.data.redis.core.redistemplate;import org.springframework.data.redis.core.valueoperations;import org.springframework.http.httpheaders;import org.springframework.http.httpstatus;import org.springframework.http.mediatype;import org.springframework.http.responentity;import org.springframework.web.bind.annotation.postmapping;import org.springframework.web.bind.annotation.requestbody;import org.springframework.web.bind.annotation.requestmapping;import org.springframework.web.bind.annotation.restcontroller; import com.alibaba.fastjson.json;import com.alibaba.fastjson.jsonobject;import com.google.common.ba.charts;import com.google.common.hash.funnel; @restcontroller@requestmapping("ur")public class urcontroller extends bacontroller { @resourceprivate redistemplate redistemplate; @resourceprivate redisutil redisutil; @postmapping(value = "/addemailtobloom", produces = "application/json")public responentity<string> addur(@requestbody string params) {responentity<string> respon = null;string returnresultstr;httpheaders headers = new httpheaders();headers.tcontenttype(mediatype.application_json_utf8);map<string, object> result = new hashmap<>();try {jsonobject requestjsonobj = json.parobject(params);urvo inputur = geturfromjson(requestjsonobj);bloomfilterhelper<string> mybloomfilterhelper = new bloomfilterhelper<>((funnel<string>) (from,into) -> into.putstring(from, charts.utf_8).putstring(from, charts.utf_8), 1500000, 0.00001);redisutil.addbybloomfilter(mybloomfilterhelper, "email_existed_bloom", inputur.getemail());result.put("code", httpstatus.ok.value());result.put("message", "add into bloomfilter successfully");result.put("email", inputur.getemail());returnresultstr = json.tojsonstring(result);logger.info("returnresultstr======>" + returnresultstr);respon = new responentity<>(returnresultstr, headers, httpstatus.ok);} catch (exception e) {logger.error("add a new product with error: " + e.getmessage(), e);result.put("message", "add a new product with error: " + e.getmessage());returnresultstr = json.tojsonstring(result);respon = new responentity<>(returnresultstr, headers, httpstatus.internal_rver_error);}return respon;} @postmapping(value = "/checkemailinbloom", produces = "application/json")public responentity<string> findemailinbloom(@requestbody string params) {responentity<string> respon = null;string returnresultstr;httpheaders headers = new httpheaders();headers.tcontenttype(mediatype.application_json_utf8);map<string, object> result = new hashmap<>();try {jsonobject requestjsonobj = json.parobject(params);urvo inputur = geturfromjson(requestjsonobj);bloomfilterhelper<string> mybloomfilterhelper = new bloomfilterhelper<>((funnel<string>) (from,into) -> into.putstring(from, charts.utf_8).putstring(from, charts.utf_8), 1500000, 0.00001);boolean answer = redisutil.includebybloomfilter(mybloomfilterhelper, "email_existed_bloom",inputur.getemail());logger.info("answer=====" + answer);result.put("code", httpstatus.ok.value());result.put("email", inputur.getemail());result.put("exist", answer);returnresultstr = json.tojsonstring(result);logger.info("returnresultstr======>" + returnresultstr);respon = new responentity<>(returnresultstr, headers, httpstatus.ok);} catch (exception e) {logger.error("add a new product with error: " + e.getmessage(), e);result.put("message", "add a new product with error: " + e.getmessage());returnresultstr = json.tojsonstring(result);respon = new responentity<>(returnresultstr, headers, httpstatus.internal_rver_error);}return respon;} private urvo geturfromjson(jsonobject requestobj) {string urname = requestobj.getstring("urname");string uraddress = requestobj.getstring("address");string uremail = requestobj.getstring("email");int urage = requestobj.getinteger("age");urvo u = new urvo();u.tname(urname);u.tage(urage);u.temail(uremail);u.taddress(uraddress);return u; }}
注意urcontroller中的bloomfilterhelper的用法,我在redis的bloomfilter里申明了可以用于存放150万数据的空间。如果存和的数据大于了你预先申请的空间怎么办?那么它会增加“误伤率”。
下面我们把这个项目运行起来看看效果吧。
运行redis-practice工程
运行起来后
我们可以使用postman先来做个小实验
我们使用”、addemailtobloom”往redis bloom filter里插入了一个“yumi@yahoo.com”的email。
接下来我们会使用“/checkemailinbloom”来验证这个email地址是否存在
我们使用redisclient连接上我们的redis查看,这个值确实也是插入进了bloom filter了。
接下来,我们用jmeter对着“/addemailtobloom”喂上个120万左右数据进去,然后我们再来看bloom filter在120万email按照布隆算 法喂进去后我们的系统是如何表现的。
我这边使用的是apache-jmeter5.0,为了偷懒,我用了apache-jmeter里的_randomstring函数来动态创造16位字符长度的email。这边用户名、地址信息都是恒定,就是email是每次不一样,都是一串16位的随机字符+“@163.com”。
jmeter中beanshell产生16位字符随机组成email的函数
uremail="${__randomstring(16,abcdefghijklmnop,myemail)}"+"@163.com";vars.put("random_email",uremail);
jmeter测试计划设置成了75个线程,连续运行30分钟(实践上笔者运行了3个30分钟,因为是demo环境,30分钟每次插大概40万条数据进去吧)
jmeter post请求
然后我们使用jmeter命令行来运行这个测试计划:
jmeter -n -t add_randomemail_to_bloom.jmx -l add_email_to_bloom\report-result.csv -j add_email_to_bloom\logs-log.log -e -o add_email_to_bloom\html_report_3
它代表:
-t 指定jmeter执行计划文件所在路径;-l 生成report的目录,这个目录如果不存在则创建 ,必须是一个空目录;-j 生成log的目录,这个目录如果不存在则创建 ,必须是一个空目录;-e 生成html报告,它配合着-o参数一起使用;-o 生成html报告所在的路径,这个目录如果不存在则创建 ,必须是一个空目录;回车后它就开始运行了
一直执行到这个过程全部结束,跳出command命令符为止。
我们查看我们用-e -o生成的jmeter html报告,前面说过了,我一共运行了3次,第一次是10分钟70059条数据 ,第二次是30分钟40多万条数据 ,第三次是45他钟70多万条数据。我共计插入了1,200,790条email。
而这120万数据总计在redis中占用内存不超过8mb,见下面demo环境的zabbix录制的记录
120万条数据插进去后,我们接着从我们的log4j的输出中随便找一条logger.info住的email如:egpoghnfjekjajdo@163.com来看一下,redis bloomfilter找到这条记录的表现如何,76ms,我运行了多次,平均在80ms左右:
通过上面这么一个实例,大家可以看到把email以hash后并以bit的形式存入bloomfilter后,它占用的内存是多么的小,而查询效率又是多么的高。
往往在生产上,我们经常会把上千万或者是上亿的记录”load”进bloomfilter,然后拿它去做“防击穿”或者是去重的动作。
只要bloomfilter中不存在的key直接返回客户端fal,配合着nginx的动态扩充、cdn、waf、接口层的缓存,整个网站抗6位数乃至7位数的并发其实是件非常简单的事。
到此这篇关于springboot+redis布隆过滤器防恶意流量击穿缓存的文章就介绍到这了,更多相关springboot防恶意流量击穿缓存内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!
本文发布于:2023-04-06 04:29:12,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/d960bce0534270735074756f28864e23.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:SpringBoot+Redis布隆过滤器防恶意流量击穿缓存.doc
本文 PDF 下载地址:SpringBoot+Redis布隆过滤器防恶意流量击穿缓存.pdf
留言与评论(共有 0 条评论) |