你知道的越多,你不知道的也越多html
點贊再看,養成習慣java
你在開發或者面試過程當中,有沒有遇到過海量數據須要查重,緩存穿透怎麼避免等等這樣的問題呢?下面這個東西超屌,好好了解下,面試過關斬將,凸顯你的不同。node
布隆過濾器(英語:Bloom Filter)是1970年由一個叫布隆的小夥子提出的。它其實是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器能夠用於檢索一個元素是否在一個集合中。它的優勢是空間效率和查詢時間都遠遠超過通常的算法,缺點是有必定的誤識別率和刪除困難。面試
布隆過濾器的原理是,當一個元素被加入集合時,經過K個散列函數將這個元素映射成一個位數組中的K個點,把它們置爲1。檢索時,咱們只要看看這些點是否是都是1就(大約)知道集合中有沒有它了:若是這些點有任何一個0,則被檢元素必定不在;若是都是1,則被檢元素極可能在。這就是布隆過濾器的基本思想。redis
Bloom Filter跟單哈希函數Bit-Map不一樣之處在於:Bloom Filter使用了k個哈希函數,每一個字符串跟k個bit對應。從而下降了衝突的機率。算法
每次查詢都會直接打到DB
數據庫
簡而言之,言而簡之就是咱們先把咱們數據庫的數據都加載到咱們的過濾器中,好比數據庫的id如今有:一、二、3api
那就用id:1 爲例子他在上圖中通過三次hash以後,把三次本來值0的地方改成1數組
下次數據進來查詢的時候若是id的值是1,那麼我就把1拿去三次hash 發現三次hash的值,跟上面的三個位置徹底同樣,那就能證實過濾器中有1的緩存
反之若是不同就說明不存在了
那應用的場景在哪裏呢?通常咱們都會用來防止緩存擊穿
簡單來講就是你數據庫的id都是1開始而後自增的,那我知道你接口是經過id查詢的,我就拿負數去查詢,這個時候,會發現緩存裏面沒這個數據,我又去數據庫查也沒有,一個請求這樣,100個,1000個,10000個呢?你的DB基本上就扛不住了,若是在緩存裏面加上這個,是否是就不存在了,你判斷沒這個數據就不去查了,直接return一個數據爲空不就行了嘛。
這玩意這麼好使那有啥缺點麼?有的,咱們接着往下看
bloom filter之因此能作到在時間和空間上的效率比較高,是由於犧牲了判斷的準確率、刪除的便利性
存在誤判,可能要查到的元素並無在容器中,可是hash以後獲得的k個位置上值都是1。若是bloom filter中存儲的是黑名單,那麼能夠經過創建一個白名單來存儲可能會誤判的元素。
刪除困難。一個放入容器的元素映射到bit數組的k個位置上是1,刪除的時候不能簡單的直接置爲0,可能會影響其餘元素的判斷。能夠採用Counting Bloom Filter
布隆過濾器有許多實現與優化,Guava中就提供了一種Bloom Filter的實現。
在使用bloom filter時,繞不過的兩點是預估數據量n以及指望的誤判率fpp,
在實現bloom filter時,繞不過的兩點就是hash函數的選取以及bit數組的大小。
對於一個肯定的場景,咱們預估要存的數據量爲n,指望的誤判率爲fpp,而後須要計算咱們須要的Bit數組的大小m,以及hash函數的個數k,並選擇hash函數
根據預估數據量n以及誤判率fpp,bit數組大小的m的計算方式:
由預估數據量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>
複製代碼
測試分兩步:
一、往過濾器中放一百萬個數,而後去驗證這一百萬個數是否能經過過濾器
二、另外找一萬個數,去檢驗漏網之魚的數量
/** * 測試布隆過濾器(可用於redis緩存穿透) * * @author 敖丙 */
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 的大小就能解決一樣的問題。
布隆過濾器主要是在回答道緩存穿透的時候引出來的,文章裏面仍是寫的比較複雜了,不少都是網上我看到就複製下來了,你們只要知道他的原理,還有就是知道他的場景能在面試中回答出他的做用就行了。
經過個人連接購買,一年最低僅需86塊(新用戶專享,若是不是新用戶的能夠用家裏人的帳號購買),80一年,200三年我以爲跟白嫖差很少,我直接用老媽帳號買了三年的,準備搭建一個小項目玩玩。你們能夠買來玩玩,想看服務器文章的朋友留言給我,能夠的話我會寫一期怎麼利用服務器的文章。
好了 以上就是這篇文章的所有內容了,你們能夠看一下我更新的Java面試吊打系列和Java技術棧相關的文章。若是你有什麼想知道的,也能夠點留言
給我,我一有時間就會寫出來,咱們共同進步。
很是感謝您能看到這裏,若是這個文章寫得還不錯的話 求點贊
求關注
求分享
求留言
各位的支持和承認,就是我創做的最大動力,OK各位咱們下期見!
敖丙 | 文
後面會持續更新《吊打面試官》系列能夠關注個人公衆號 JavaFamily 第一時間閱讀,也會有朋友一線大廠的內推機會不按期推出(字節跳動,阿里,網易,PDD,滴滴,蘑菇街等),就業上有什麼問題也能夠直接微信滴滴我,我也是個新人,不過不影響咱們一塊兒進步,做爲渣男,我給不了你工做,還給不了你溫暖嘛?