布隆過濾器:高效、大概的判斷數據是否存在


1      什么是布隆過濾器

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

  • 字處理軟件中,需要檢查一個英語單詞是否拼寫正確
  • 在 FBI,一個嫌疑人的名字是否已經在嫌疑名單上
  • 在網絡爬蟲里,一個網址是否被訪問過
  • yahoo, gmail等郵箱垃圾郵件過濾功能

 

 

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

2      使用場景

網頁爬蟲對URL的去重,避免爬取相同的URL地址;

反垃圾郵件,從數十億個垃圾郵件列表中判斷某郵箱是否垃圾郵箱(同理,垃圾短信);

緩存擊穿,將已存在的緩存放到布隆中,當黑客訪問不存在的緩存時迅速返回避免緩存及DB掛掉。

 

 

3      實現原理

3.1      插入數據

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

 

 

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

 

 

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

 

 

值得注意的是,4 這個 bit 位由於兩個值的哈希函數都返回了這個 bit 位,因此它被覆蓋了。

3.2      判斷數據是否存在

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

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

 

4      優點

4.1      占內存少

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

 

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

 

布隆過濾器就不用為每個數都分配空間了,而是直接把所有的數通過算法映射到同一個數組,帶來的問題就是沖突上升,只要概率在可以接受的范圍,用時間換空間,在很多時候是好方案。布隆過濾器需要的空間僅為HashMap的1/8-1/4之間,而且它不會漏掉任何一個在黑名單的可疑對象,問題只是會誤傷一些非黑名單對象。

 

4.2      插入查詢O(1)

見查詢原理

5      缺點

5.1      錯誤率問題

見查詢原理

5.2      刪除問題

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

 

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

 

6      錯誤率估計及如何確定數組長度

顯然,數組越長,錯誤率越低,但占用空間越大,如何做權衡,見下文:

https://blog.csdn.net/tianyaleixiaowu/article/details/74721877

 

7      最佳實踐

7.1      高效的Hash函數

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

7.2      大Value拆分

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

 

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

8      參考

詳解布隆過濾器的原理,使用場景和注意事項

https://zhuanlan.zhihu.com/p/43263751

布隆過濾器(Bloom Filter)的原理和實現

https://www.cnblogs.com/cpselvis/p/6265825.html

使用BloomFilter布隆過濾器解決緩存擊穿、垃圾郵件識別、集合判重

https://blog.csdn.net/tianyaleixiaowu/article/details/74721877

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM