布隆過濾器

引言

我們知道檢查一個元素是否在某一個集合中,使用HashSet是比較好的選擇,因爲在不發生Hash碰撞的情況下它的時間複雜度爲常數級別,但是在數據量比較大的情況下,使用HashSet將會佔用大量的內存空間。舉個例子,長城防火牆有100億個需要屏蔽的網址,來自計算機的每一次請求都要經過防火牆的過濾判斷請求URL是否在黑名單中,如果我們使用HashSet來實現過濾的話,我們假設每個URL的大小爲64B,那麼100億個就至少需要大約640GB的內存空間,這顯然是不符合實際情況的。另一種解決方案是我們可以將URL存入關係型數據庫,每次計算機發起請求我們對數據庫進行exits查詢,然而這種方案適用於併發量比較小的情況,若併發量較大,那麼我們就需要對數據庫進行集羣。

以上倆種方案都有一定的侷限性,爲了解決大數據量檢查問題,布隆過濾器就粉墨登場了。

原理

先來簡單瞭解一下布隆過濾器,我們可以把它看做一個會產生誤判並且佔用空間極少的HashSet。它的結構是一個Bit數組(數組中每個位置只佔用一個bit,每個bit位有0和1兩種狀態)和一系列Hash函數的集合,我們將輸入域通過上述一系列Hash函數進行Hash運算得到n個key值,將這n個值對數組的長度進行取餘,然後將bit數組中對應的位置bit位設爲1。在數組足夠大,hash碰撞足夠小的情況下,每個輸入域都會在數組中不同的位置將其bit位置爲1,我們把集合中所有的元素都按照這個方式來一遍的話一個布隆過濾器就生成好了。

那麼如何判斷一個元素是否在布隆過濾器中呢,原理和生成布隆過濾器的過程差不多,我們將要判斷的值通過布隆過濾器的n個Hash函數計算出n個值,對數組長度取餘得到bit數組中n個位置,接下來判斷這n個位置的bit位是否都爲1,若都爲1,則說明該元素在集合中,若有一個爲0,則該元素肯定不在集合中。

誤判

瞭解了布隆過濾器的生成過程,相信大家已經看出來了,這樣會產生一定的誤判,假如輸入對象不再集合中,而由於元素過多並且bit數組過小導致數組中的大部分位置bit位都爲1,那麼有可能會誤判該元素在集合中。但是如果輸入對象本就在集合中,那麼數組中的bit位肯定都爲1,布隆過濾器是一定不會產生誤判的,這就是所謂的“寧可錯殺三千,絕不放過一個”。

對於已經發現的誤判元素,我們可以把他放入HashSet中,在經過布隆過濾器之前先行判斷一下,這部分元素較少不會佔用多少內存,也在一定程度解決了布隆過濾器的誤判問題。

空間佔用估計

計算布隆過濾器的空間佔用有一個現成的公式,m=-\frac{n*\ln p}{(\ln x)^2} ,其中n爲元素的個數,p爲允許的誤差率大小。布隆過濾器中Hash函數個數的公式爲k=(\ln 2)*\frac{m}{n}  ,m爲個公式的結果,n爲元素的個數。

如果覺得公式計算起來太麻煩,也沒有關係,已經有先人幫我們做好工作了,我們只要把參數輸進去,就可以直接看到結果,比如這個網站-- 計算

上面的例子通過公式計算,假如誤判率爲0.01%,如果使用布隆過濾器的話我們的內存佔用空間將會比使用HashSet少佔用大約99.5%,但是得到的效果卻基本相同。

在Redis中的應用

經過上面的介紹,已經對布隆過濾器有了大致的瞭解了,接下來就是他的使用了。Redis的布隆過濾器作爲一個插件,在Redis在4.0中的提供了插件功能後纔可以使用。使用前需要先安裝插件。

這裏主要介紹一下他的幾個命令。

bf.add: 添加元素;

bf.exists: 查詢元素是否存在

bf.madd: 添加多個

bf.mexists: 查詢多個元素是否存在

開發中我們可以使用官方提供的Java客戶端JReBloom,也可以使用Lettuce(前面的文章有介紹)。

其他應用(摘自Redis深度歷險)

在爬蟲系統中,我們需要對 URL 進行去重,已經爬過的網頁就可以不用爬了。但是 URL 太多了,幾千萬幾個億,如果用一個集合裝下這些 URL 地址那是非常浪費空間的。這時候就可以考慮使用布隆過濾器。它可以大幅降低去重存儲消耗,只不過也會使得爬蟲系統錯過少量的頁面。

布隆過濾器在 NoSQL 數據庫領域使用非常廣泛,我們平時用到的 HBase、Cassandra 還有 LevelDB、RocksDB 內部都有布隆過濾器結構,布隆過濾器可以顯著降低數據庫的 IO 請求數量。當用戶來查詢某個 row 時,可以先通過內存中的布隆過濾器過濾掉大量不存在的 row 請求,然後再去磁盤進行查詢。

郵箱系統的垃圾郵件過濾功能也普遍用到了布隆過濾器,因爲用了這個過濾器,所以平時也會遇到某些正常的郵件被放進了垃圾郵件目錄中,這個就是誤判所致,概率很低。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章