Bloom Filter 概念
布隆過濾器(英語:Bloom Filter)是1970年由一個叫布隆的小夥子提出的。它實際上是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器可以用於檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識別率和刪除困難。
Bloom Filter 原理
布隆過濾器的原理是,當一個元素被加入集合時,通過K個散列函數將這個元素映射成一個位數組中的K個點,把它們置爲1。檢索時,我們只要看看這些點是不是都是1就(大約)知道集合中有沒有它了:如果這些點有任何一個0,則被檢元素一定不在;如果都是1,則被檢元素很可能在。這就是布隆過濾器的基本思想。
Bloom Filter跟單哈希函數Bit-Map不同之處在於:Bloom Filter使用了k個哈希函數,每個字符串跟k個bit對應。從而降低了衝突的概率。
緩存穿透
每次查詢都會直接打到DB
簡而言之,言而簡之就是我們先把我們數據庫的數據都加載到我們的過濾器中,比如數據庫的id現在有:1、2、3
那就用id:1 爲例子他在上圖中經過三次hash之後,把三次原本值0的地方改爲1
下次數據進來查詢的時候如果id的值是1,那麼我就把1拿去三次hash 發現三次hash的值,跟上面的三個位置完全一樣,那就能證明過濾器中有1的
反之如果不一樣就說明不存在了
那應用的場景在哪裏呢?一般我們都會用來防止緩存擊穿
簡單來說就是你數據庫的id都是1開始然後自增的,那我知道你接口是通過id查詢的,我就拿負數去查詢,這個時候,會發現緩存裏面沒這個數據,我又去數據庫查也沒有,一個請求這樣,100個,1000個,10000個呢?你的DB基本上就扛不住了,如果在緩存裏面加上這個,是不是就不存在了,你判斷沒這個數據就不去查了,直接return一個數據爲空不就好了嘛。
這玩意這麼好使那有啥缺點麼?有的,我們接着往下看
Bloom Filter的缺點
bloom filter之所以能做到在時間和空間上的效率比較高,是因爲犧牲了判斷的準確率、刪除的便利性
-
存在誤判,可能要查到的元素並沒有在容器中,但是hash之後得到的k個位置上值都是1。如果bloom filter中存儲的是黑名單,那麼可以通過建立一個白名單來存儲可能會誤判的元素。
-
刪除困難。一個放入容器的元素映射到bit數組的k個位置上是1,刪除的時候不能簡單的直接置爲0,可能會影響其他元素的判斷。可以採用Counting Bloom Filter
Bloom Filter 實現
布隆過濾器有許多實現與優化,Guava中就提供了一種Bloom Filter的實現。
在使用bloom filter時,繞不過的兩點是預估數據量n以及期望的誤判率fpp,
在實現bloom filter時,繞不過的兩點就是hash函數的選取以及bit數組的大小。
對於一個確定的場景,我們預估要存的數據量爲n,期望的誤判率爲fpp,然後需要計算我們需要的Bit數組的大小m,以及hash函數的個數k,並選擇hash函數
(1)Bit數組大小選擇
根據預估數據量n以及誤判率fpp,bit數組大小的m的計算方式:
(2)哈希函數選擇
由預估數據量n以及bit數組長度m,可以得到一個hash函數的個數k:
哈希函數的選擇對性能的影響應該是很大的,一個好的哈希函數要能近似等概率的將字符串映射到各個Bit。選擇k個不同的哈希函數比較麻煩,一種簡單的方法是選擇一個哈希函數,然後送入k個不同的參數。
哈希函數個數k、位數組大小m、加入的字符串數量n的關係可以參考Bloom Filters - the math,Bloom_filter-wikipedia
要使用BloomFilter,需要引入guava包:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
測試分兩步:
1、往過濾器中放一百萬個數,然後去驗證這一百萬個數是否能通過過濾器
2、另外找一萬個數,去檢驗漏網之魚的數量
public class TestBloomFilter {
private static int total = 1000000;
private static BloomFilter<Integer> bf = BloomFilter.create(Funnels.integerFunnel(), total);
// private static BloomFilter<Integer> bf = BloomFilter.create(Funnels.integerFunnel(), total, 0.001);
public static void main(String[] args) {
// 初始化1000000條數據到過濾器中
for (int i = 0; i < total; i++) {
bf.put(i);
}
// 匹配已在過濾器中的值,是否有匹配不上的
for (int i = 0; i < total; i++) {
if (!bf.mightContain(i)) {
System.out.println("有壞人逃脫了~~~");
}
}
// 匹配不在過濾器中的10000個值,有多少匹配出來
int count = 0;
for (int i = total; i < total + 10000; i++) {
if (bf.mightContain(i)) {
count++;
}
}
System.out.println("誤傷的數量:" + count);
}
}
運行結果:
運行結果表示,遍歷這一百萬個在過濾器中的數時,都被識別出來了。一萬個不在過濾器中的數,誤傷了320個,錯誤率是0.03左右。
看下BloomFilter的源碼:
public static <T> BloomFilter<T> create(Funnel<? super T> funnel, int expectedInsertions) {
return create(funnel, (long) expectedInsertions);
}
public static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions) {
return create(funnel, expectedInsertions, 0.03); // FYI, for 3%, we always get 5 hash functions
}
public static <T> BloomFilter<T> create(
Funnel<? super T> funnel, long expectedInsertions, double fpp) {
return create(funnel, expectedInsertions, fpp, BloomFilterStrategies.MURMUR128_MITZ_64);
}
static <T> BloomFilter<T> create(
Funnel<? super T> funnel, long expectedInsertions, double fpp, Strategy strategy) {
......
}
BloomFilter一共四個create方法,不過最終都是走向第四個。看一下每個參數的含義:
funnel:數據類型(一般是調用Funnels工具類中的)
expectedInsertions:期望插入的值的個數
fpp 錯誤率(默認值爲0.03)
strategy 哈希算法(我也不懂啥意思)Bloom Filter的應用
在最後一個create方法中,設置一個斷點:
上面的numBits,表示存一百萬個int類型數字,需要的位數爲7298440,700多萬位。理論上存一百萬個數,一個int是4字節32位,需要481000000=3200萬位。如果使用HashMap去存,按HashMap50%的存儲效率,需要6400萬位。可以看出BloomFilter的存儲空間很小,只有HashMap的1/10左右
上面的numHashFunctions,表示需要5個函數去存這些數字
使用第三個create方法,我們設置下錯誤率:
private static BloomFilter<Integer> bf = BloomFilter.create(Funnels.integerFunnel(), total, 0.0003);
再運行看看:
此時誤傷的數量爲4,錯誤率爲0.04%左右。
當錯誤率設爲0.0003時,所需要的位數爲16883499,1600萬位,需要12個函數
和上面對比可以看出,錯誤率越大,所需空間和時間越小,錯誤率越小,所需空間和時間約大
常見的幾個應用場景:
-
cerberus在收集監控數據的時候, 有的系統的監控項量會很大, 需要檢查一個監控項的名字是否已經被記錄到db過了, 如果沒有的話就需要寫入db.
-
爬蟲過濾已抓到的url就不再抓,可用bloom filter過濾
-
垃圾郵件過濾。如果用哈希表,每存儲一億個 email地址,就需要 1.6GB的內存(用哈希表實現的具體辦法是將每一個 email地址對應成一個八字節的信息指紋,然後將這些信息指紋存入哈希表,由於哈希表的存儲效率一般只有 50%,因此一個 email地址需要佔用十六個字節。一億個地址大約要 1.6GB,即十六億字節的內存)。因此存貯幾十億個郵件地址可能需要上百 GB的內存。而Bloom Filter只需要哈希表 1/8到 1/4 的大小就能解決同樣的問題。
總結
布隆過濾器主要是在回答道緩存穿透的時候引出來的,文章裏面還是寫的比較複雜了,很多都是網上我看到就複製下來了,大家只要知道他的原理,還有就是知道他的場景能在面試中回答出他的作用就好了。