布隆過濾器BloomFilter

問題引出

1.如何在億級黑名單中快速定位URL地址是否在黑名單中?(每條URL平均64字節)

2.目前有10億數量的自然數,亂序排列,需要對其排序,在32位機器上完成,內存限制爲2G?

什麼是布隆過濾器

本質上布隆過濾器是一種數據結構,比較巧妙的概率型數據結構(probabilistic data structure),特點是高效地插入和查詢,可以用來告訴你 “某樣東西一定不存在或者可能存在”。

相比於傳統的 List、Set、Map 等數據結構,它更高效、佔用空間更少,但是缺點是其返回的結果是概率性的,而不是確切的。

實現方式

1.Google GUAVA

2.Redis bitMap類型數據(其底層仍是字符串,最大限制512M數據,即40億自然數)

實現原理

HashMap 的問題

講述布隆過濾器的原理之前,我們先思考一下,通常你判斷某個元素是否存在用的是什麼?應該蠻多人回答 HashMap 吧,確實可以將值映射到 HashMap 的 Key,然後可以在 O(1) 的時間複雜度內返回結果,效率奇高。但是 HashMap 的實現也有缺點,例如存儲容量佔比高,考慮到負載因子的存在,通常空間是不能被用滿的,而一旦你的值很多例如上億的時候,那 HashMap 佔據的內存大小就變得很可觀了。

還比如說你的數據集存儲在遠程服務器上,本地服務接受輸入,而數據集非常大不可能一次性讀進內存構建 HashMap 的時候,也會存在問題。

布隆過濾器數據結構

布隆過濾器是一個 bit 向量或者說 bit 數組,長這樣:

如果我們要映射一個值到布隆過濾器中,我們需要使用多個不同的哈希函數生成多個哈希值,並對每個生成的哈希值指向的 bit 位置 1,例如針對值 “baidu” 和三個不同的哈希函數分別生成了哈希值 1、4、7,則上圖轉變爲:

 

Ok,我們現在再存一個值 “tencent”,如果哈希函數返回 3、4、8 的話,圖繼續變爲:

 

值得注意的是,4 這個 bit 位由於兩個值的哈希函數都返回了這個 bit 位,因此它被覆蓋了。現在我們如果想查詢 “dianping” 這個值是否存在,哈希函數返回了 1、5、8三個值,結果我們發現 5 這個 bit 位上的值爲 0,說明沒有任何一個值映射到這個 bit 位上,因此我們可以很確定地說 “dianping” 這個值不存在。而當我們需要查詢 “baidu” 這個值是否存在的話,那麼哈希函數必然會返回 1、4、7,然後我們檢查發現這三個 bit 位上的值均爲 1,那麼我們可以說 “baidu” 存在了麼?答案是不可以,只能是 “baidu” 這個值可能存在。

這是爲什麼呢?答案跟簡單,因爲隨着增加的值越來越多,被置爲 1 的 bit 位也會越來越多,這樣某個值 “taobao” 即使沒有被存儲過,但是萬一哈希函數返回的三個 bit 位都被其他值置位了 1 ,那麼程序還是會判斷 “taobao” 這個值存在。

支持刪除麼

目前我們知道布隆過濾器可以支持 add 和 isExist 操作,那麼 delete 操作可以麼,答案是不可以,例如上圖中的 bit 位 4 被兩個值共同覆蓋的話,一旦你刪除其中一個值例如 “tencent” 而將其置位 0,那麼下次判斷另一個值例如 “baidu” 是否存在的話,會直接返回 false,而實際上你並沒有刪除它。

如何解決這個問題,答案是計數刪除。但是計數刪除需要存儲一個數值,而不是原先的 bit 位,會增大佔用的內存大小。這樣的話,增加一個值就是將對應索引槽上存儲的值加一,刪除則是減一,判斷是否存在則是看值是否大於0。

如何選擇哈希函數個數和布隆過濾器長度

很顯然,過小的布隆過濾器很快所有的 bit 位均爲 1,那麼查詢任何值都會返回“可能存在”,起不到過濾的目的了。布隆過濾器的長度會直接影響誤報率,布隆過濾器越長其誤報率越小。

另外,哈希函數的個數也需要權衡,個數越多則布隆過濾器 bit 位置位 1 的速度越快,且布隆過濾器的效率越低;但是如果太少的話,那我們的誤報率會變高。

k 爲哈希函數個數,m 爲布隆過濾器長度,n 爲插入的元素個數,p 爲誤報率。
至於如何推導這個公式,我在知乎發佈的文章有涉及,感興趣可以看看,不感興趣的話記住上面這個公式就行了。

最佳實踐

常見的適用常見有,利用布隆過濾器減少磁盤 IO 或者網絡請求,因爲一旦一個值必定不存在的話,我們可以不用進行後續昂貴的查詢請求。

另外,既然你使用布隆過濾器來加速查找和判斷是否存在,那麼性能很低的哈希函數不是個好選擇,推薦 MurmurHash、Fnv 這些。

大Value拆分

Redis 因其支持 setbit 和 getbit 操作,且純內存性能高等特點,因此天然就可以作爲布隆過濾器來使用。但是布隆過濾器的不當使用極易產生大 Value,增加 Redis 阻塞風險,因此生成環境中建議對體積龐大的布隆過濾器進行拆分。

拆分的形式方法多種多樣,但是本質是不要將 Hash(Key) 之後的請求分散在多個節點的多個小 bitmap 上,而是應該拆分成多個小 bitmap 之後,對一個 Key 的所有哈希函數都落在這一個小 bitmap 上。

發佈了49 篇原創文章 · 獲贊 35 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章