BloomFilter應用與D-Lelft BloomFilter實現

此篇文章是開發過程中對BloomFilter應用場景的一些介紹,另外項目中實現了D-Left BloomFilter,相關實現時一些注意的地方,簡單介紹下!

首先看一些應用場景:

1.海量的黑白名單。

2.爬蟲抓取時重複的URL處理。

3.數據key是否存在檢測

4.(一些面試題幾十億不重複整數中判斷其中一個整數是否存在的問題,BitMap/BloomFilter能很好的解決)

。。。。

對於以上提出的三種場景,如果數據量相對較少,可以將數據存儲在數據庫,或者加載內存中判斷對應的黑白名單是否存在判斷即可。

可是當數據量達到很大時,比如1億條的黑白名單,假如每個名單佔用空間爲10byte,對於查詢中將遇到兩種問題,如果數據都在數據庫中且併發量很大,每次查詢將給DB帶來的壓力不可想象的。 另外如果所有名單加載到緩存中,將佔據的內存空間是:1G,如果數據量更大,所佔空間更多。

對於以上問題,如果通過BloomFilter,通過犧牲一定的準確率,使用1G/80 和內存空間,就能解決此問題。

一.什麼是BloomFilter?

BloomFilter是一種高效的隨機數據結構,被用於檢測一個元素是否是一個集合中的一個元素,這種檢測只會對在集合內的數據錯判,而不會對不是集合內的數據進行錯判,這樣每個檢測請求返回有“在集合內(可能錯誤)”和“不在集合內(絕對不在集合內)”兩種情況,即如果它判斷元素不在集合裏,此元素一定不是集合中的元素,如果判斷元素在集合裏,有可能存在一定的錯誤率,可見 Bloom filter 是犧牲了正確率換取時間和空間。(因此需要注意使用時應用場景)

二.BloomFilter實現:

Bloom filter 採用的是哈希函數(hash fucnction)的方法,將一個元素映射到一個 m 長度的陣列上的一個點,當這個點是 1 時,那麼這個元素在集合內,反之則不在集合內。這個方法的缺點就是當檢測的元素很多的時候可能有衝突,解決方法就是使用 k 個哈希 函數對應 k 個點,如果所有點都是 1 的話,那麼元素在集合內,如果有 0 的話,元素則不再集合內。(實現BloomFilter時,這個hash的k數可以設定,一般是4或者8)

BloomFilter測試發現,要想保持錯誤率低,最好讓位數組有一半還空着。

三.BloomFilter應用的地方

BloomFilter在開源項目中有很多地方都有使用,其中Hadoop源碼中實現了使用OpenBitSet實現了BloomFilter,另外還有CountingBloomFilter的實現。 另外Hbase源碼中也有BloomFilter的實現,其中有BloomFilter的各種變種,都是跟據自己特定場景需要,進行相關實現。

四.BloomFilter的缺點:

標準的BloomFilter只支持插入和查找兩種操作,如果表達的集合是靜態集合的時候,在初始化集合大小,確定hash k的個數,控制錯誤率的基礎上,BloomFilter可以很好的滿足需求。但是對於一些動態集合,BloomFilter不滿足需求了,其不支持刪除操作,現引入Counting BloomFilter,能解決相關問題。

五.什麼是Counting BloomFilter

Counting Bloom Filter,它將標準Bloom Filter位數組的每一位擴展爲一個小的計數器(Counter),在插入元素時給對應的k(k爲哈希函數個數)個Counter的值分別加1,刪除元素時給對應的k個Counter的值分別減1。Counting Bloom Filter通過多佔用幾倍的存儲空間的代價,給Bloom Filter增加了刪除操作.如圖示:


此方法實現是給每位增加一個4位的計數器,處理碰撞問題,即相當於原BloomFilter5倍的空間,可以動態維護BloomFilter, 當計數超過4位時,則溢出不處理,hash函數選擇合理,數據集構造時合理,一般不會出現溢出。

Counting BloomFilter有刪除操作後,其應用價值變得更廣。

但是Counting BloomFilter也存在很明顯的缺點:由於每個bucket負載不均衡,很多空間都被浪費掉了。因此引入了D Left BloomFilter.

六.D-Left Bloom Filter介紹

D-Left Bloom Filter也是BloomFilter的一種變種,它在比Counting BloomFilter使用更少的空間下,帶來了刪除操作,同時還能帶來更少的錯誤率。

在引入D-Left Bloom Filter之間首先介紹下d-Left hash:有d個哈希表以及d個哈希函數,每一次插入時都先計算d個哈希值,然後通過計算出的地址h[key]來判斷在這個位置上的負載是多少,把鍵值插入到負載小的表中,當負載一樣時,則插入到Left表中。

d-left counting bloom filter的構造過程:在沒有應用d-left hashing的情況下,我們使用一個哈希函數,把它的hash value分成兩段,高位作存儲地址,低位作fingerprint。現在要應用d-left hashing,有d個存儲地址需要生成,我們仍然用一個哈希函數,但把它的hash value分成d+1段:高位的d段分別用作d個存儲地址,每個子表對應一個,剩下的低位部分作爲fingerprint在添加一個key時,先對它作一次hash,得到d個存儲位置和一個fingerprint,然後判斷d個位置中的負載情況,並在負載最輕的幾個位置中選擇最左邊的插入。如果選擇的位置已經存儲了相同的fingerprint,就把那個cell的counter加1。在刪除一個key時,同樣地作一次hash,然後在d個存儲位置查找相應的fingerprint,如果找到就將這個cell置空或者將相應的counter減1。

通過以上構造,對於不同元素,相同 fingerprint存在位置選擇重合的問題!

給出的解決方案是:將hashing的整個操作分成兩個階段。第一階段,我們用一個哈希函數H計算要插入元素x的hash value,記做fx;第二階段,爲了獲得d個存儲位置,我們另外引入d個隨機置換(random permutation)。令H(x) = fx = (b, r),其中b是bucket index,表示存儲位置;r是remainder,表示要存儲的fingerprint。然後令d個置換爲:

P1(fx) = (b1, r1), P2(fx) = (b2, r2), … ,Pd(fx) = (bd, rd).其中Pi(fx)對應着x在第i個子表的存儲位置和fingerprint。我們知道置換意味着一一對應,因此不同元素(的hash value)作置換之後的值仍然不同。這樣我們就達到了前面提到的讓不同元素的d個位置選擇完全沒有重合的目標。

引入隨機置換避免了位置重合之後,我們還需要在插入元素之前作一項工作。每次插入一個元素時,先要在它的d個位置選擇中檢查是否已經存有相同的fingerprint,如果有,就把相應cell的counter加1。由於不同元素的存儲位置不會重合,因此只有在碰撞的情況下才會出現兩個相同fingerprint能存入同一組存儲位置的情況。而我們一旦在插入之前作了檢測,再作刪除操作時就永遠不會發現d個位置中有兩個完全相同的fingerprint。

至於選擇什麼樣的置換,論文作者給出的建議是:簡單的線性函數。如果哈希函數的取值範圍爲[2q],隨機置換可以寫成:

Pi(H(x)) = aH(x) mod 2q

其中a是區間[2q]中的隨機奇數。

(對於我們實現中選擇的奇數是定義的一常量,方便定義)

(以上爲理論部分,所有相關理論部分參考http://blog.csdn.net/jiaomeng/article/details/1495500 博客中相關BloomFilter的知識,此作者寫了很多相關BloomFilter的介紹,很詳細,應該是寫論文過程中的一些翻譯記錄,對於相關知識的理解很有幫忙)

七.D-Left Bloom Filter實現

使用java 實現D-Left BloomFilte實現過程中,主要注意的地方有:

1.D-Left Bloom Filter對於子表數的選擇的問題,跟據實現需要選擇,跟據集合數據量,我們的數據量在百萬級別,選用了四張子表,每張子表對應bsize個桶.
2.初始化集合m.
3.每個桶存儲8個cell,每個cell存儲x位手印,每個cell的計數爲y bit,(x位手印數與y位計數器,跟據集合數據量選擇手印的大小,count計數可爲2位或者4位)
4.採用數據結構建議使用byte[][]多維數組實現。bitsets不好控制。
5.對於hash函數的選擇,可任意。(我們項目 中使用md5,128位,足夠使用了).

6.實現過程中通過hash截取,以及相關組裝,置換操作,通過位操作可以很高效的實現。(置換函數可以是:Pi(H(x)) = aH(x) mod 2q,  其中a爲隨機奇數,以及H(x)爲新組裝的高位地址+手印,2q爲h(x)位數對應最大值)

相關測試數據:
經過簡單測試,錯誤率在百萬分之一左右。集合在50萬左右。
在五十萬元素後,增加一個元素的時間在0.2ms到0.3ms左右,查詢一個元素是否存在0.1ms左右,刪除在0.3到0.4ms左右。
對於元素的分配,插入五十萬數據,每個表的分佈如下:
table 0:135203
table 1:128226
table 2:122795
table 3:113776
相對而言,每個表的數據分配還是比較均勻,這樣大大的節省了空間。


項目中使用D-Left BloomFilter主要是對於一個key的查詢,由於外部查詢時,很多不存在的key查詢我們相關數據庫,這樣會對系統DB有很大的負擔,通過緩存查,相應的命中率也很低,爲了解決這些問題,我們把相應的此key的值會在系統初始化時通過D-Left BloomFilter時加載一次,相應的add,remove操作也支持,這樣在能過濾掉大部分不存在系統中的key,而對於判斷存在的key,由於存在部分錯誤率,我們再會從緩存或者數據庫中去判斷是否存在,這樣相應的命中率就會很高,也保證了數據的正確性。

另外同事在做相關爬蟲時也使用到了BloomFilter。對於Hadoop以及Hbase源碼中也有相關模塊,有需要的朋友可能自行查找相關源碼。(ps:也可以一起交流,共同學習)



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