欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot+Redis布隆過濾器防惡意流量擊穿緩存

 更新時間:2022年03月17日 15:32:25   作者:TGITCIC  
本文主要介紹了SpringBoot+Redis布隆過濾器防惡意流量擊穿緩存,文中根據(jù)實例編碼詳細(xì)介紹的十分詳盡,具有一定的參考價值,感興趣的小伙伴們可以參考一下

本文主要介紹了SpringBoot+Redis布隆過濾器防惡意流量擊穿緩存,具體如下:

什么是惡意流量穿透

假設(shè)我們的Redis里存有一組用戶的注冊email,以email作為Key存在,同時它對應(yīng)著DB里的User表的部分字段。

一般來說,一個合理的請求過來我們會先在Redis里判斷這個用戶是否是會員,因為從緩存里讀數(shù)據(jù)返回快。如果這個會員在緩存中不存在那么我們會去DB中查詢一下。

現(xiàn)在試想,有千萬個不同IP的請求(不要以為沒有,我們就在2018年和2019年碰到了,因為攻擊的成本很低)帶著Redis里根本不存在的key來訪問你的網(wǎng)站,這時我們來設(shè)想一下:

  • 請求到達(dá)Web服務(wù)器;
  • 請求派發(fā)到應(yīng)用層->微服務(wù)層;
  • 請求去Redis撈數(shù)據(jù),Redis內(nèi)不存在這個Key;
  • 于是請求到達(dá)DB層,在DB建立connection后進(jìn)行一次查詢

千萬乃至上億的DB連接請求,先不說Redis是否撐的住DB也會被瞬間打爆。這就是“Redis穿透”又被稱為“緩存擊穿”,它會打爆你的緩存或者是連DB一起打爆進(jìn)而引起一系列的“雪崩效應(yīng)”。

怎么防

那就是使用布隆過濾器,可以把所有的user表里的關(guān)鍵查詢字段放于Redis的bloom過濾器內(nèi)。有人會說,這不瘋了,我有4000萬會員?so what!

你把4000會員放在Redis里是比較夸張,有些網(wǎng)站有8000萬、1億會員呢?因此我沒讓你直接放在Redis里,而是放在布隆過濾器內(nèi)!

布隆過濾器內(nèi)不是直接把key,value這樣放進(jìn)去的,它存放的內(nèi)容是這么一個樣的:

BloomFilter是一種空間效率的概率型數(shù)據(jù)結(jié)構(gòu),由Burton Howard Bloom 1970年提出的。通常用來判斷一個元素是否在集合中。具有極高的空間效率,但是會帶來假陽性(False positive)的錯誤。

False positive&&False negatives
由于BloomFiter犧牲了一定的準(zhǔn)確率換取空間效率。所以帶來了False positive的問題。

False positive
BloomFilter在判斷一個元素在集合中的時候,會出現(xiàn)一定的錯誤率,這個錯誤率稱為False positive的。通常縮寫為fpp。

False negatives
BloomFilter判斷一個元素不在集合中的時候的錯誤率。 BloomFilter判斷該元素不在集合中,則該元素一定不再集合中。故False negatives概率為0。

BloomFilter使用長度為m bit的字節(jié)數(shù)組,使用k個hash函數(shù),增加一個元素: 通過k次hash將元素映射到字節(jié)數(shù)組中k個位置中,并設(shè)置對應(yīng)位置的字節(jié)為1。
查詢元素是否存在: 將元素k次hash得到k個位置,如果對應(yīng)k個位置的bit是1則認(rèn)為存在,反之則認(rèn)為不存在。

由于它里面存的都是bit,因此這個數(shù)據(jù)量會很小很小,小到什么樣的程度呢?在寫本博客時我插了100萬條email信息進(jìn)入Redis的bloom filter也只占用了3Mb不到。

Bloom Filter會有幾比較關(guān)鍵的值,根據(jù)這個值你是大致可以算出放多少條數(shù)據(jù)然后它的誤傷率在多少時會占用多少系統(tǒng)資源的。這個算法有一個網(wǎng)址:https://krisives.github.io/bloom-calculator/,我們放入100萬條數(shù)據(jù),假設(shè)誤傷率在0.001%,看,它自動得出Redis需要申請的系統(tǒng)內(nèi)存資源是多少?

那么怎么解決這個誤傷率呢?很簡單的,當(dāng)有誤傷時業(yè)務(wù)或者是運(yùn)營會來報誤傷率,這時你只要添加一個小白名單就是了,相對于100萬條數(shù)據(jù)來說,1000個白名單不是問題。并且bloom filter的返回速度超塊,80-100毫秒內(nèi)即返回調(diào)用端該Key存在或者是不存了。

布隆過濾器的另一個用武場景

假設(shè)我用python爬蟲爬了4億條url,需要去重?

看,布隆過濾器就是用于這個場景的。

下面就開始我們的Redis BloomFilter之旅。

給Redis安裝Bloom Filter

Redis從4.0才開始支持bloom filter,因此本例中我們使用的是Redis5.4。

Redis的bloom filter下載地址在這:https://github.com/RedisLabsModules/redisbloom.git

git clone https://github.com/RedisLabsModules/redisbloom.git
cd redisbloom
make # 編譯

讓Redis啟動時可以加載bloom filter有兩種方式:

手工加載式:

redis-server --loadmodule ./redisbloom/rebloom.so

每次啟動自加載:

編輯Redis的redis.conf文件,加入:

loadmodule /soft/redisbloom/redisbloom.so

Like this:

在Redis里使用Bloom Filter

基本指令:

bf.reserve {key} {error_rate} {size}

127.0.0.1:6379> bf.reserve userid 0.01 100000
OK

上面這條命令就是:創(chuàng)建一個空的布隆過濾器,并設(shè)置一個期望的錯誤率和初始大小。{error_rate}過濾器的錯誤率在0-1之間,如果要設(shè)置0.1%,則應(yīng)該是0.001。該數(shù)值越接近0,內(nèi)存消耗越大,對cpu利用率越高

bf.add {key} {item}

127.0.0.1:6379> bf.add userid '181920'
(integer) 1

上面這條命令就是:往過濾器中添加元素。如果key不存在,過濾器會自動創(chuàng)建。

bf.exists {key} {item}

127.0.0.1:6379> bf.exists userid '101310299'
(integer) 1

上面這條命令就是:判斷指定key的value是否在bloomfilter里存在。存在:返回1,不存在:返回0。

結(jié)合SpringBoot使用

網(wǎng)上很多寫的都是要么是直接使用jedis來操作的,或者是java里execute一個外部進(jìn)程來調(diào)用Redis的bloom filter指令的。很多都是調(diào)不通或者h(yuǎn)elloworld一個級別的,是根本無法上生產(chǎn)級別應(yīng)用的。

筆者給出的代碼保障讀者完全可用!

筆者不是數(shù)學(xué)家,因此就借用了google的guava包來實現(xiàn)了核心算法,核心代碼如下:

BloomFilterHelper.java

package org.sky.platform.util;
 
import com.google.common.base.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 expectedInsertions, double fpp) {
		Preconditions.checkArgument(funnel != null, "funnel不能為空");
		this.funnel = funnel;
		bitSize = optimalNumOfBits(expectedInsertions, fpp);
		numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
	}
 
	int[] murmurHashOffset(T value) {
		int[] offset = 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;
			}
			offset[i - 1] = nextHash % bitSize;
		}
 
		return offset;
	}
 
	/**
	 * 計算bit數(shù)組的長度
	 */
	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方法執(zhí)行次數(shù)
	 */
	private int optimalNumOfHashFunctions(long n, long m) {
		return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
	}
}

下面放出全工程解說,我已經(jīng)將源碼上傳到了我的git上了,確保讀者可用,源碼地址在這:https://github.com/mkyuangithub/mkyuangithub.git

搭建spring boot工程

項目Redis配置

我們在redis-practice工程里建立一個application.properties文件,內(nèi)容如下:

spring.redis.database=0  
spring.redis.host=192.168.56.101
spring.redis.port=6379
spring.redis.password=111111
spring.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環(huán)境的配置。

我們此處依舊使用的是在前一篇springboot+nacos+dubbo實現(xiàn)異常統(tǒng)一管理中的xxx-project->sky-common->nacos-parent的依賴結(jié)構(gòu)。

在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.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
 
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
	/**
	 * 選擇redis作為默認(rèn)緩存工具
	 * 
	 * @param redisTemplate
	 * @return
	 */
	@Bean
	public CacheManager cacheManager(RedisTemplate redisTemplate) {
		RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
		return rcm;
	}
 
	/**
	 * retemplate相關(guān)配置
	 * 
	 * @param factory
	 * @return
	 */
	@Bean
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
 
		RedisTemplate<String, Object> template = new RedisTemplate<>();
		// 配置連接工廠
		template.setConnectionFactory(factory);
 
		// 使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值(默認(rèn)使用JDK的序列化方式)
		Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
 
		ObjectMapper om = new ObjectMapper();
		// 指定要序列化的域,field,get和set,以及修飾符范圍,ANY是都有包括private和public
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		// 指定序列化輸入的類型,類必須是非final修飾的,final修飾的類,比如String,Integer等會跑出異常
		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
		jacksonSeial.setObjectMapper(om);
 
		// 值采用json序列化
		template.setValueSerializer(jacksonSeial);
		// 使用StringRedisSerializer來序列化和反序列化redis的key值
		template.setKeySerializer(new StringRedisSerializer());
 
		// 設(shè)置hash key 和value序列化模式
		template.setHashKeySerializer(new StringRedisSerializer());
		template.setHashValueSerializer(jacksonSeial);
		template.afterPropertiesSet();
 
		return template;
	}
 
	/**
	 * 對hash類型的數(shù)據(jù)操作
	 *
	 * @param redisTemplate
	 * @return
	 */
	@Bean
	public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
		return redisTemplate.opsForHash();
	}
 
	/**
	 * 對redis字符串類型數(shù)據(jù)操作
	 *
	 * @param redisTemplate
	 * @return
	 */
	@Bean
	public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
		return redisTemplate.opsForValue();
	}
 
	/**
	 * 對鏈表類型的數(shù)據(jù)操作
	 *
	 * @param redisTemplate
	 * @return
	 */
	@Bean
	public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
		return redisTemplate.opsForList();
	}
 
	/**
	 * 對無序集合類型的數(shù)據(jù)操作
	 *
	 * @param redisTemplate
	 * @return
	 */
	@Bean
	public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
		return redisTemplate.opsForSet();
	}
 
	/**
	 * 對有序集合類型的數(shù)據(jù)操作
	 *
	 * @param redisTemplate
	 * @return
	 */
	@Bean
	public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
		return redisTemplate.opsForZSet();
	}
}

這個配置除實現(xiàn)了springboot自動發(fā)現(xiàn)redis在application.properties中的配置外我們還添加了不少redis基本的數(shù)據(jù)結(jié)構(gòu)的操作的封裝。

我們?yōu)榇诉€要再封裝一套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.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.base.Preconditions;
import org.springframework.data.redis.core.RedisTemplate;
 
@Component
public class RedisUtil {
	@Autowired
	private RedisTemplate<String, String> redisTemplate;
 
	/**
	 * 默認(rèn)過期時長,單位:秒
	 */
	public static final long DEFAULT_EXPIRE = 60 * 60 * 24;
 
	/**
	 * 不設(shè)置過期時長
	 */
	public static final long NOT_EXPIRE = -1;
 
	public boolean existsKey(String key) {
		return redisTemplate.hasKey(key);
	}
 
	/**
	 * 重名名key,如果newKey已經(jīng)存在,則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.renameIfAbsent(oldKey, newKey);
	}
 
	/**
	 * 刪除key
	 *
	 * @param key
	 */
	public void deleteKey(String key) {
		redisTemplate.delete(key);
	}
 
	/**
	 * 刪除多個key
	 *
	 * @param keys
	 */
	public void deleteKey(String... keys) {
		Set<String> kSet = Stream.of(keys).map(k -> k).collect(Collectors.toSet());
		redisTemplate.delete(kSet);
	}
 
	/**
	 * 刪除Key的集合
	 *
	 * @param keys
	 */
	public void deleteKey(Collection<String> keys) {
		Set<String> kSet = keys.stream().map(k -> k).collect(Collectors.toSet());
		redisTemplate.delete(kSet);
	}
 
	/**
	 * 設(shè)置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設(shè)置為永久有效
	 *
	 * @param key
	 */
	public void persistKey(String key) {
		redisTemplate.persist(key);
	}
 
	/**
	 * 根據(jù)給定的布隆過濾器添加值
	 */
	public <T> void addByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
		Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能為空");
		int[] offset = bloomFilterHelper.murmurHashOffset(value);
		for (int i : offset) {
			redisTemplate.opsForValue().setBit(key, i, true);
		}
	}
 
	/**
	 * 根據(jù)給定的布隆過濾器判斷值是否存在
	 */
	public <T> boolean includeByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
		Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能為空");
		int[] offset = bloomFilterHelper.murmurHashOffset(value);
		for (int i : offset) {
			if (!redisTemplate.opsForValue().getBit(key, i)) {
				return false;
			}
		}
 
		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文件夾,源碼如上已經(jīng)貼了,因此此處就不再作重復(fù)。

最后我們在sky-common里放置一個UserVO用于演示

UserVO.java

package org.sky.vo;
 
import java.io.Serializable;
 
public class UserVO implements Serializable {
 
	private String name;
	private String address;
	private Integer age;
	private String email = "";
 
	public String getEmail() {
		return email;
	}
 
	public void setEmail(String email) {
		this.email = email;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public String getAddress() {
		return address;
	}
 
	public void setAddress(String address) {
		this.address = address;
	}
 
	public Integer getAge() {
		return age;
	}
 
	public void setAge(Integer age) {
		this.age = age;
	}
 
}

下面給出我們所有g(shù)itrepo里依賴的nacos-parent的pom.xml文件內(nèi)容,此次我們增加了對于“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.RELEASE</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-service.version>0.0.1-SNAPSHOT</nacos-service.version>
		<spring.data.redis>1.8.14-RELEASE</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>
				<groupId>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/maven-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</artifactId>
			<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(basePackages = { "org.sky" })
@EnableAutoConfiguration
public class Application {
 
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

然后我們制作了一個controller名為UserController,該controller里有兩個方法:

  • public ResponseEntity<String> addUser(@RequestBody String params),該方法用于接受來自外部的api post然后把一條email地址塞入redis的bloomfilter中;
  • public ResponseEntity<String> findEmailInBloom(@RequestBody String params),該方法用于接受來自外部的api post然后去redis的bloomfilter中驗證是否外部輸入的user信息中的email地址在上百萬的email記錄中存在;

以此來完成驗證塞入redis的bloom filter中上百萬條記錄占用了多少內(nèi)存以及使用bloom filter查詢一條記錄有多快。

UserController.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.UserVO;
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.ResponseEntity;
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.base.Charsets;
import com.google.common.hash.Funnel;
 
@RestController
@RequestMapping("user")
public class UserController extends BaseController {
 
	@Resource
	private RedisTemplate redisTemplate;
 
	@Resource
	private RedisUtil redisUtil;
 
	@PostMapping(value = "/addEmailToBloom", produces = "application/json")
	public ResponseEntity<String> addUser(@RequestBody String params) {
		ResponseEntity<String> response = null;
		String returnResultStr;
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
		Map<String, Object> result = new HashMap<>();
		try {
			JSONObject requestJsonObj = JSON.parseObject(params);
			UserVO inputUser = getUserFromJson(requestJsonObj);
			BloomFilterHelper<String> myBloomFilterHelper = new BloomFilterHelper<>((Funnel<String>) (from,
					into) -> into.putString(from, Charsets.UTF_8).putString(from, Charsets.UTF_8), 1500000, 0.00001);
			redisUtil.addByBloomFilter(myBloomFilterHelper, "email_existed_bloom", inputUser.getEmail());
			result.put("code", HttpStatus.OK.value());
			result.put("message", "add into bloomFilter successfully");
			result.put("email", inputUser.getEmail());
			returnResultStr = JSON.toJSONString(result);
			logger.info("returnResultStr======>" + returnResultStr);
			response = new ResponseEntity<>(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);
			response = new ResponseEntity<>(returnResultStr, headers, HttpStatus.INTERNAL_SERVER_ERROR);
		}
		return response;
	}
 
	@PostMapping(value = "/checkEmailInBloom", produces = "application/json")
	public ResponseEntity<String> findEmailInBloom(@RequestBody String params) {
		ResponseEntity<String> response = null;
		String returnResultStr;
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
		Map<String, Object> result = new HashMap<>();
		try {
			JSONObject requestJsonObj = JSON.parseObject(params);
			UserVO inputUser = getUserFromJson(requestJsonObj);
			BloomFilterHelper<String> myBloomFilterHelper = new BloomFilterHelper<>((Funnel<String>) (from,
					into) -> into.putString(from, Charsets.UTF_8).putString(from, Charsets.UTF_8), 1500000, 0.00001);
			boolean answer = redisUtil.includeByBloomFilter(myBloomFilterHelper, "email_existed_bloom",
					inputUser.getEmail());
			logger.info("answer=====" + answer);
			result.put("code", HttpStatus.OK.value());
			result.put("email", inputUser.getEmail());
			result.put("exist", answer);
			returnResultStr = JSON.toJSONString(result);
			logger.info("returnResultStr======>" + returnResultStr);
			response = new ResponseEntity<>(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);
			response = new ResponseEntity<>(returnResultStr, headers, HttpStatus.INTERNAL_SERVER_ERROR);
		}
		return response;
	}
 
	private UserVO getUserFromJson(JSONObject requestObj) {
		String userName = requestObj.getString("username");
		String userAddress = requestObj.getString("address");
		String userEmail = requestObj.getString("email");
		int userAge = requestObj.getInteger("age");
		UserVO u = new UserVO();
		u.setName(userName);
		u.setAge(userAge);
		u.setEmail(userEmail);
		u.setAddress(userAddress);
		return u;
 
	}
}

注意UserController中的BloomFilterHelper的用法,我在Redis的bloomfilter里申明了可以用于存放150萬數(shù)據(jù)的空間。如果存和的數(shù)據(jù)大于了你預(yù)先申請的空間怎么辦?那么它會增加“誤傷率”。

下面我們把這個項目運(yùn)行起來看看效果吧。

運(yùn)行redis-practice工程

運(yùn)行起來后

我們可以使用postman先來做個小實驗

我們使用"、addEmailToBloom"往redis bloom filter里插入了一個“yumi@yahoo.com”的email。

接下來我們會使用“/checkEmailInBloom”來驗證這個email地址是否存在

我們使用redisclient連接上我們的redis查看,這個值確實也是插入進(jìn)了bloom filter了。

使用壓測工具喂120萬條數(shù)據(jù)進(jìn)入Redis Bloomfilter看實際效果

接下來,我們用jmeter對著“/addEmailToBloom”喂上個120萬左右數(shù)據(jù)進(jìn)去,然后我們再來看bloom filter在120萬email按照布隆算 法喂進(jìn)去后我們的系統(tǒng)是如何表現(xiàn)的。

我這邊使用的是apache-jmeter5.0,為了偷懶,我用了apache-jmeter里的_RandomString函數(shù)來動態(tài)創(chuàng)造16位字符長度的email。這邊用戶名、地址信息都是恒定,就是email是每次不一樣,都是一串16位的隨機(jī)字符+“@163.com”。

jmeter中BeanShell產(chǎn)生16位字符隨機(jī)組成email的函數(shù)

useremail="${__RandomString(16,abcdefghijklmnop,myemail)}"+"@163.com";
vars.put("random_email",useremail);

jmeter測試計劃設(shè)置成了75個線程,連續(xù)運(yùn)行30分鐘(實踐上筆者運(yùn)行了3個30分鐘,因為是demo環(huán)境,30分鐘每次插大概40萬條數(shù)據(jù)進(jìn)去吧)

jmeter post請求

然后我們使用jmeter命令行來運(yùn)行這個測試計劃:

jmeter -n -t add_randomemail_to_bloom.jmx -l add_email_to_bloom\report\03-result.csv -j add_email_to_bloom\logs\03-log.log -e -o add_email_to_bloom\html_report_3

它代表:

  • -t 指定jmeter執(zhí)行計劃文件所在路徑;
  • -l 生成report的目錄,這個目錄如果不存在則創(chuàng)建 ,必須是一個空目錄;
  • -j 生成log的目錄,這個目錄如果不存在則創(chuàng)建 ,必須是一個空目錄;
  • -e 生成html報告,它配合著-o參數(shù)一起使用;
  • -o 生成html報告所在的路徑,這個目錄如果不存在則創(chuàng)建 ,必須是一個空目錄;

回車后它就開始運(yùn)行了

一直執(zhí)行到這個過程全部結(jié)束,跳出command命令符為止。

我們查看我們用-e -o生成的jmeter html報告,前面說過了,我一共運(yùn)行了3次,第一次是10分鐘70059條數(shù)據(jù) ,第二次是30分鐘40多萬條數(shù)據(jù) ,第三次是45他鐘70多萬條數(shù)據(jù)。我共計插入了1,200,790條email。

而這120萬數(shù)據(jù)總計在redis中占用內(nèi)存不超過8mb,見下面demo環(huán)境的zabbix錄制的記錄

120萬條數(shù)據(jù)插進(jìn)去后,我們接著從我們的log4j的輸出中隨便找一條logger.info住的email如:egpoghnfjekjajdo@163.com來看一下,redis bloomfilter找到這條記錄的表現(xiàn)如何,76ms,我運(yùn)行了多次,平均在80ms左右:

通過上面這么一個實例,大家可以看到把email以hash后并以bit的形式存入bloomfilter后,它占用的內(nèi)存是多么的小,而查詢效率又是多么的高。

往往在生產(chǎn)上,我們經(jīng)常會把上千萬或者是上億的記錄"load"進(jìn)bloomfilter,然后拿它去做“防擊穿”或者是去重的動作。

只要bloomfilter中不存在的key直接返回客戶端false,配合著nginx的動態(tài)擴(kuò)充、cdn、waf、接口層的緩存,整個網(wǎng)站抗6位數(shù)乃至7位數(shù)的并發(fā)其實是件非常簡單的事。

到此這篇關(guān)于SpringBoot+Redis布隆過濾器防惡意流量擊穿緩存的文章就介紹到這了,更多相關(guān)SpringBoot防惡意流量擊穿緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java中線程用法總結(jié)

    Java中線程用法總結(jié)

    這篇文章主要介紹了Java中線程用法,實例總結(jié)了java中線程的常見使用技巧,需要的朋友可以參考下
    2015-06-06
  • Java填充替換數(shù)組元素實例詳解

    Java填充替換數(shù)組元素實例詳解

    這篇文章主要通過兩個實例說明Java填充和替換數(shù)組中元素的方法,需要的朋友可以參考下。
    2017-08-08
  • MyBatis動態(tài)SQL表達(dá)式詳解

    MyBatis動態(tài)SQL表達(dá)式詳解

    動態(tài)SQL可以省略很多拼接SQL的步驟,使用類似于JSTL方式,下面這篇文章主要給大家介紹了關(guān)于Mybatis動態(tài)SQL特性的相關(guān)資料,文字通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-12-12
  • java 8如何自定義收集器(collector)詳解

    java 8如何自定義收集器(collector)詳解

    這篇文章主要給大家介紹了關(guān)于java 8如何自定義收集器(collector)的相關(guān)資料,文中介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。
    2017-06-06
  • java——多線程基礎(chǔ)

    java——多線程基礎(chǔ)

    Java多線程實現(xiàn)方式有兩種,第一種是繼承Thread類,第二種是實現(xiàn)Runnable接口,兩種有很多差異,下面跟著本文一起學(xué)習(xí)吧,希望能給你帶來幫助
    2021-07-07
  • 淺談SpringBoot之開啟數(shù)據(jù)庫遷移的FlyWay使用

    淺談SpringBoot之開啟數(shù)據(jù)庫遷移的FlyWay使用

    這篇文章主要介紹了淺談SpringBoot之開啟數(shù)據(jù)庫遷移的FlyWay使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • Spring中的@PropertySource注解源碼詳解

    Spring中的@PropertySource注解源碼詳解

    這篇文章主要介紹了Spring中的@PropertySource注解源碼詳解,@PropertySource注解用于指定資源文件讀取的位置,它不僅能讀取properties文件,也能讀取xml文件,并且通過yaml解析器,配合自定義PropertySourceFactory實現(xiàn)解析yaml文件,需要的朋友可以參考下
    2023-11-11
  • 詳談Java中的Object、T(泛型)、?區(qū)別

    詳談Java中的Object、T(泛型)、?區(qū)別

    下面小編就為大家?guī)硪黄斦凧ava中的Object、T(泛型)、?區(qū)別。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • Java  隊列實現(xiàn)原理及簡單實現(xiàn)代碼

    Java 隊列實現(xiàn)原理及簡單實現(xiàn)代碼

    這篇文章主要介紹了Java 隊列實現(xiàn)原理及簡單實現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下
    2016-10-10
  • 淺談HttpClient、okhttp和RestTemplate的區(qū)別

    淺談HttpClient、okhttp和RestTemplate的區(qū)別

    這篇文章主要介紹了HttpClient、okhttp和RestTemplate的區(qū)別,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06

最新評論