算法:详解布隆过滤器的原理、使用场景和注意事项@知乎.Young Chen
算法:詳解布隆過濾器的原理、使用場景和注意事項(xiàng)@知乎.Young Chen
什么是布隆過濾器
本質(zhì)上布隆過濾器是一種數(shù)據(jù)結(jié)構(gòu),比較巧妙的概率型數(shù)據(jù)結(jié)構(gòu)(probabilistic data structure),特點(diǎn)是高效地插入和查詢,可以用來告訴你 “某樣?xùn)|西一定不存在或者可能存在”。
相比于傳統(tǒng)的 List、Set、Map 等數(shù)據(jù)結(jié)構(gòu),它更高效、占用空間更少,但是缺點(diǎn)是其返回的結(jié)果是概率性的,而不是確切的。
實(shí)現(xiàn)原理
HashMap 的問題
講述布隆過濾器的原理之前,我們先思考一下,通常你判斷某個(gè)元素是否存在用的是什么?應(yīng)該蠻多人回答 HashMap 吧,確實(shí)可以將值映射到 HashMap 的 Key,然后可以在 O(1) 的時(shí)間復(fù)雜度內(nèi)返回結(jié)果,效率奇高。但是 HashMap 的實(shí)現(xiàn)也有缺點(diǎn),例如存儲容量占比高,考慮到負(fù)載因子的存在,通常空間是不能被用滿的,而一旦你的值很多例如上億的時(shí)候,那 HashMap 占據(jù)的內(nèi)存大小就變得很可觀了。
還比如說你的數(shù)據(jù)集存儲在遠(yuǎn)程服務(wù)器上,本地服務(wù)接受輸入,而數(shù)據(jù)集非常大不可能一次性讀進(jìn)內(nèi)存構(gòu)建 HashMap 的時(shí)候,也會存在問題。
布隆過濾器數(shù)據(jù)結(jié)構(gòu)
布隆過濾器是一個(gè) bit 向量或者說 bit 數(shù)組,長這樣:
?
image
如果我們要映射一個(gè)值到布隆過濾器中,我們需要使用多個(gè)不同的哈希函數(shù)生成多個(gè)哈希值,并對每個(gè)生成的哈希值指向的 bit 位置 1,例如針對值 “baidu” 和三個(gè)不同的哈希函數(shù)分別生成了哈希值 1、4、7,則上圖轉(zhuǎn)變?yōu)?#xff1a;
?
image
Ok,我們現(xiàn)在再存一個(gè)值 “tencent”,如果哈希函數(shù)返回 3、4、8 的話,圖繼續(xù)變?yōu)?#xff1a;
?
image
值得注意的是,4 這個(gè) bit 位由于兩個(gè)值的哈希函數(shù)都返回了這個(gè) bit 位,因此它被覆蓋了。現(xiàn)在我們?nèi)绻氩樵?“dianping” 這個(gè)值是否存在,哈希函數(shù)返回了 1、5、8三個(gè)值,結(jié)果我們發(fā)現(xiàn) 5 這個(gè) bit 位上的值為 0,說明沒有任何一個(gè)值映射到這個(gè) bit 位上,因此我們可以很確定地說 “dianping” 這個(gè)值不存在。而當(dāng)我們需要查詢 “baidu” 這個(gè)值是否存在的話,那么哈希函數(shù)必然會返回 1、4、7,然后我們檢查發(fā)現(xiàn)這三個(gè) bit 位上的值均為 1,那么我們可以說 “baidu” 存在了么?答案是不可以,只能是 “baidu” 這個(gè)值可能存在。
這是為什么呢?答案跟簡單,因?yàn)殡S著增加的值越來越多,被置為 1 的 bit 位也會越來越多,這樣某個(gè)值 “taobao” 即使沒有被存儲過,但是萬一哈希函數(shù)返回的三個(gè) bit 位都被其他值置位了 1 ,那么程序還是會判斷 “taobao” 這個(gè)值存在。
支持刪除么
目前我們知道布隆過濾器可以支持 add 和 isExist 操作,那么 delete 操作可以么,答案是不可以,例如上圖中的 bit 位 4 被兩個(gè)值共同覆蓋的話,一旦你刪除其中一個(gè)值例如 “tencent” 而將其置位 0,那么下次判斷另一個(gè)值例如 “baidu” 是否存在的話,會直接返回 false,而實(shí)際上你并沒有刪除它。
如何解決這個(gè)問題,答案是計(jì)數(shù)刪除。但是計(jì)數(shù)刪除需要存儲一個(gè)數(shù)值,而不是原先的 bit 位,會增大占用的內(nèi)存大小。這樣的話,增加一個(gè)值就是將對應(yīng)索引槽上存儲的值加一,刪除則是減一,判斷是否存在則是看值是否大于0。
如何選擇哈希函數(shù)個(gè)數(shù)和布隆過濾器長度
很顯然,過小的布隆過濾器很快所有的 bit 位均為 1,那么查詢?nèi)魏沃刀紩祷亍翱赡艽嬖凇?#xff0c;起不到過濾的目的了。布隆過濾器的長度會直接影響誤報(bào)率,布隆過濾器越長其誤報(bào)率越小。
另外,哈希函數(shù)的個(gè)數(shù)也需要權(quán)衡,個(gè)數(shù)越多則布隆過濾器 bit 位置位 1 的速度越快,且布隆過濾器的效率越低;但是如果太少的話,那我們的誤報(bào)率會變高。
?
image
k 為哈希函數(shù)個(gè)數(shù),m 為布隆過濾器長度,n 為插入的元素個(gè)數(shù),p 為誤報(bào)率。
至于如何推導(dǎo)這個(gè)公式,我在知乎發(fā)布的文章有涉及,感興趣可以看看,不感興趣的話記住上面這個(gè)公式就行了。
最佳實(shí)踐
常見的適用常見有,利用布隆過濾器減少磁盤 IO 或者網(wǎng)絡(luò)請求,因?yàn)橐坏┮粋€(gè)值必定不存在的話,我們可以不用進(jìn)行后續(xù)昂貴的查詢請求。
另外,既然你使用布隆過濾器來加速查找和判斷是否存在,那么性能很低的哈希函數(shù)不是個(gè)好選擇,推薦 MurmurHash、Fnv 這些。
大Value拆分
Redis 因其支持 setbit 和 getbit 操作,且純內(nèi)存性能高等特點(diǎn),因此天然就可以作為布隆過濾器來使用。但是布隆過濾器的不當(dāng)使用極易產(chǎn)生大 Value,增加 Redis 阻塞風(fēng)險(xiǎn),因此生成環(huán)境中建議對體積龐大的布隆過濾器進(jìn)行拆分。
拆分的形式方法多種多樣,但是本質(zhì)是不要將 Hash(Key) 之后的請求分散在多個(gè)節(jié)點(diǎn)的多個(gè)小 bitmap 上,而是應(yīng)該拆分成多個(gè)小 bitmap 之后,對一個(gè) Key 的所有哈希函數(shù)都落在這一個(gè)小 bitmap 上。
作者知乎主頁:https://zhuanlan.zhihu.com/p/43263751
?
總結(jié)
以上是生活随笔為你收集整理的算法:详解布隆过滤器的原理、使用场景和注意事项@知乎.Young Chen的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。