前言
咱們以前講了Redis的緩存雪崩、穿透、擊穿。在文章裏咱們說了解決緩存穿透的辦法之一,就是布隆過濾器
,可是上次並無講如何使用布隆過濾器。java
做爲暖男的老哥,給大家補上,請叫我IT老暖男
。web
什麼是布隆過濾器
布隆過濾器(Bloom Filter),是1970年,由一個叫布隆的小夥子提出的,距今已經五十年了,和老哥同樣老。redis
它其實是一個很長的二進制向量和一系列隨機映射函數,二進制你們應該都清楚,存儲的數據不是0就是1,默認是0。算法
主要用於判斷一個元素是否在一個集合中,0表明不存在
某個數據,1表明存在
某個數據。spring
懂了嗎?做爲暖男
的老哥在給大家畫張圖來幫助理解:數組
布隆過濾器用途
-
解決Redis緩存穿透(今天重點講解)緩存
-
在爬蟲時,對爬蟲網址進行過濾,已經存在布隆中的網址,不在爬取。微信
-
垃圾郵件過濾,對每個發送郵件的地址進行判斷是否在布隆的黑名單中,若是在就判斷爲垃圾郵件。數據結構
以上只是簡單的用途舉例,你們能夠觸類旁通,靈活運用在工做中。編輯器
布隆過濾器原理
存入過程
布隆過濾器上面說了,就是一個二進制數據的集合。當一個數據加入這個集合時,經歷以下洗禮(這裏有缺點,下面會講):
-
經過K個哈希函數計算該數據,返回K個計算出的hash值
-
這些K個hash值映射到對應的K個二進制的數組下標
-
將K個下標對應的二進制數據改爲1。
例如,第一個哈希函數返回x,第二個第三個哈希函數返回y與z,那麼:X、Y、Z對應的二進制改爲1。
如圖所示:
查詢過程
布隆過濾器主要做用就是查詢一個數據,在不在這個二進制的集合中,查詢過程以下:
-
經過K個哈希函數計算該數據,對應計算出的K個hash值
-
經過hash值找到對應的二進制的數組下標
-
判斷:若是存在一處位置的二進制數據是0,那麼該數據不存在。若是都是1,該數據存在集合中。(這裏有缺點,下面會講)
刪除過程
通常不能刪除布隆過濾器裏的數據,這是一個缺點之一,咱們下面會分析。
布隆過濾器的優缺點
優勢
-
因爲存儲的是二進制數據,因此佔用的空間很小
-
它的插入和查詢速度是很是快的,時間複雜度是O(K),能夠聯想一下HashMap的過程
-
保密性很好,由於自己不存儲任何原始數據,只有二進制數據
缺點
這就要回到咱們上面所說的那些缺點了。
添加數據是經過計算數據的hash值,那麼頗有可能存在這種狀況:兩個不一樣的數據計算獲得相同的hash值。
例如圖中的「你好
」和「hello
」,假如最終算出hash值相同,那麼他們會將同一個下標的二進制數據改成1。
這個時候,你就不知道下標爲2的二進制,究竟是表明「你好
」仍是「hello
」。
由此得出以下缺點:
1、存在誤判
假如上面的圖沒有存"hello
",只存了"你好
",那麼用"hello
"來查詢的時候,會判斷"hello
"存在集合中。
由於「你好
」和「hello
」的hash值是相同的,經過相同的hash值,找到的二進制數據也是同樣的,都是1。
2、刪除困難
到這裏我不說你們應該也明白爲何吧,做爲大家的暖男老哥
,仍是講一下吧。
仍是用上面的舉例,由於「你好
」和「hello
」的hash值相同,對應的數組下標也是同樣的。
這時候老哥想去刪除「你好
」,將下標爲2裏的二進制數據,由1改爲了0。
那麼咱們是否是連「hello
」都一塊兒刪了呀。(0表明有這個數據,1表明沒有這個數據)
到這裏是否是對布隆過濾器已經明白了,都說了我是暖男。
實現布隆過濾器
有不少種實現方式,其中一種就是Guava
提供的實現方式。
1、引入Guava pom配置
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
2、代碼實現
這裏咱們順便測一下它的誤判率。
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
public class BloomFilterCase {
/**
* 預計要插入多少數據
*/
private static int size = 1000000;
/**
* 指望的誤判率
*/
private static double fpp = 0.01;
/**
* 布隆過濾器
*/
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);
public static void main(String[] args) {
// 插入10萬樣本數據
for (int i = 0; i < size; i++) {
bloomFilter.put(i);
}
// 用另外十萬測試數據,測試誤判率
int count = 0;
for (int i = size; i < size + 100000; i++) {
if (bloomFilter.mightContain(i)) {
count++;
System.out.println(i + "誤判了");
}
}
System.out.println("總共的誤判數:" + count);
}
}
運行結果:
10萬數據裏有947個誤判,約等於0.01%,也就是咱們代碼裏設置的誤判率:fpp = 0.01。
深刻分析代碼
核心BloomFilter.create
方法
@VisibleForTesting
static <T> BloomFilter<T> create(
Funnel<? super T> funnel, long expectedInsertions, double fpp, Strategy strategy) {
。。。。
}
這裏有四個參數:
-
funnel
:數據類型(通常是調用Funnels工具類中的) -
expectedInsertions
:指望插入的值的個數 -
fpp
:誤判率(默認值爲0.03) -
strategy
:哈希算法
咱們重點講一下fpp
參數
fpp誤判率
情景一:fpp = 0.01
-
誤判個數:947
-
佔內存大小:9585058位數
情景二:fpp = 0.03
(默認參數)
-
誤判個數:3033
-
佔內存大小:7298440位數
情景總結
-
誤判率能夠經過
fpp
參數進行調節 -
fpp越小,須要的內存空間就越大:0.01須要900多萬位數,0.03須要700多萬位數。
-
fpp越小,集合添加數據時,就須要更多的hash函數運算更多的hash值,去存儲到對應的數組下標裏。(忘了去看上面的布隆過濾存入數據的過程)
上面的numBits
,表示存一百萬個int類型數字,須要的位數爲7298440,700多萬位。理論上存一百萬個數,一個int是4字節32位,須要481000000=3200萬位。若是使用HashMap去存,按HashMap50%的存儲效率,須要6400萬位。能夠看出BloomFilter的存儲空間很小,只有HashMap的1/10左右
上面的numHashFunctions
表示須要幾個hash函數運算,去映射不一樣的下標存這些數字是否存在(0 or 1)。
解決Redis緩存雪崩
上面使用Guava實現的布隆過濾器是把數據放在了本地內存中。分佈式的場景中就不合適了,沒法共享內存。
咱們還能夠用Redis來實現布隆過濾器,這裏使用Redis封裝好的客戶端工具Redisson。
其底層是使用數據結構bitMap,你們就把它理解成上面說的二進制結構,因爲篇幅緣由,bitmap不在這篇文章裏講,咱們以後寫一篇文章介紹。
代碼實現
pom配置:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.4</version>
</dependency>
java代碼:
public class RedissonBloomFilter {
public static void main(String[] args) {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
config.useSingleServer().setPassword("1234");
//構造Redisson
RedissonClient redisson = Redisson.create(config);
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("phoneList");
//初始化布隆過濾器:預計元素爲100000000L,偏差率爲3%
bloomFilter.tryInit(100000000L,0.03);
//將號碼10086插入到布隆過濾器中
bloomFilter.add("10086");
//判斷下面號碼是否在布隆過濾器中
//輸出false
System.out.println(bloomFilter.contains("123456"));
//輸出true
System.out.println(bloomFilter.contains("10086"));
}
}
因爲Guava那個版本,咱們已經很詳細的講了布隆過濾器的那些參數,這裏就不重複贅述了。
給個[在看],是對IT老哥最大的支持
本文分享自微信公衆號 - IT老哥(dys_family)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。