详解布隆过滤器的原理、使用场景和注意事项
在進(jìn)入正文之前,之前看到的有句話我覺得說得很好:
Data structures are nothing different. They are like the bookshelves of your application where you can organize your data. Different data structures will give you different facility and benefits. To properly use the power and accessibility of the data structures you need to know the trade-offs of using one.
大意是不同的數(shù)據(jù)結(jié)構(gòu)有不同的適用場景和優(yōu)缺點(diǎn),你需要仔細(xì)權(quán)衡自己的需求之后妥善適用它們,布隆過濾器就是踐行這句話的代表。
什么是布隆過濾器
本質(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ù)組,長這樣:
如果我們要映射一個(gè)值到布隆過濾器中,我們需要使用多個(gè)不同的哈希函數(shù)生成多個(gè)哈希值,并對每個(gè)生成的哈希值指向的 bit 位置 1,例如針對值 “baidu” 和三個(gè)不同的哈希函數(shù)分別生成了哈希值 1、4、7,則上圖轉(zhuǎn)變?yōu)?#xff1a;
Ok,我們現(xiàn)在再存一個(gè)值 “tencent”,如果哈希函數(shù)返回 3、4、8 的話,圖繼續(xù)變?yōu)?#xff1a;
值得注意的是,4 這個(gè) bit 位由于兩個(gè)值的哈希函數(shù)都返回了這個(gè) bit 位,因此它被覆蓋了?,F(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)率會變高。
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 上。
參考資料
- probabilistic data structures:bloom filter
- bloom filters
- 本文遵守創(chuàng)作共享 CC BY-NC-SA 3.0協(xié)議
總結(jié)
以上是生活随笔為你收集整理的详解布隆过滤器的原理、使用场景和注意事项的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 二叉树遍历的递归、非递归方法(前序、中序
- 下一篇: 将外部知识整合到群体智能中,以获得更具体