算法(3)---布隆過濾器原理
開發一個電商項目,因為數據量一直在增加(已達億級),所以需要重構之前開發好的秒殺功能,為了更好的支持高並發,在驗證用戶是否重復購買的環節,就考慮用布隆過濾器。
也順便更加深入的去了解下布隆過濾器的原理,感覺還是蠻有意思的,這一連串的公式不靜下心來思考,很容易被繞暈。
一、概述
1、什么是布隆過濾器
本質上布隆過濾器是一種數據結構,比較巧妙的概率型數據結構
,特點是高效地插入和查詢
。根據查詢結果可以用來告訴你 某樣東西一定不存在或者可能存在
這句話是該算法的核心。
相比於傳統的 List、Set、Map 等數據結構,它更高效、占用空間更少,但是缺點是其返回的結果是概率性的,而不是確切的,同時布隆過濾器還有一個缺陷就是
數據只能插入不能刪除
。
2、數據如何存入布隆過濾器
布隆過濾器是由一個很長的bit數組和一系列哈希函數組成的
。
數組的每個元素都只占1bit空間,並且每個元素只能為0或1。
布隆過濾器還擁有k個哈希函數,當一個元素加入布隆過濾器時,會使用k個哈希函數對其進行k次計算,得到k個哈希值,並且根據得到的哈希值,在維數組中把對應下標的值置位1。
判斷某個數是否在布隆過濾器中,就對該元素進行k次哈希計算,得到的值在位數組中判斷每個元素是否都為1,如果每個元素都為1,就說明這個值在布隆過濾器中。
3、布隆過濾器為什么會有誤判
當插入的元素越來越多時,當一個不在布隆過濾器中的元素,經過同樣規則的哈希計算之后,得到的值在位數組中查詢,有可能這些位置因為其他的元素先被置1了。
所以布隆過濾器存在誤判的情況,但是如果布隆過濾器判斷某個元素不在布隆過濾器中,那么這個值就一定不在。
如果對布隆過濾器的概念還不是很理解的話,推薦一篇博客,圖文並茂好理解很多。詳解布隆過濾器的原理、使用場景和注意事項
4、使用場景
- 網頁爬蟲對URL的去重,避免爬去相同的URL地址。
- 垃圾郵件過濾,從數十億個垃圾郵件列表中判斷某郵箱是否是殺垃圾郵箱。
- 解決數據庫緩存擊穿,黑客攻擊服務器時,會構建大量不存在於緩存中的key向服務器發起請求,在數據量足夠大的時候,頻繁的數據庫查詢會導致掛機。
- 秒殺系統,查看用戶是否重復購買。
二、實際應用場景
背景
現在有個100億個黑名單網頁數據,每個網頁的URL占用64字節。現在想要實現一種網頁過濾系統,可以根據網頁的URL判斷該網站是否在黑名單上,請設計該系統。
需求
可以允許有0.01%以下的判斷失誤率,並且使用的總空間不要超過200G。
這里一共有4個常量:
100億條黑名單數據
,每條數據占64個字節
,萬分之一的失誤率
,總空間不要超過200G
。
如果不考慮不攏過濾器,那么這里存儲100億條數據就需要 100億 * 64字節 = 596G 顯然超過300G
解題
在滿足有 100億條數據 並且允許 萬分之一的失誤率 的布隆過濾器需要多大的bit數組呢?
- 設bit數組大小為m,樣本數量為n,失誤率為p。
- 由題可知 n = 100億,p = 0.01%
布隆過濾器的大小m公式

求得 m = 19.19n,向上取整為 20n。所以2000億bit,約為186G。
算完m,我們順便來算下m,n已知,這時滿足最小誤差的k是幾個。
哈希函數的個數k公式

求得 k = 14,即需要14個哈希函數。
通過通過 m = 20n, k = 14我們再來算下真實的失誤率。
布隆過濾器真實失誤率p公式

求得 p = 0.006%,即布隆過濾器的真實失誤率為0.006%。
通過布隆過濾器公式也可以看出:
單個數據的大小不影響布隆過濾器大小,因為樣本會通過哈希函數得到輸出值
。
就好比上面的 每個網頁的URL占用64字節 這個數據大小 跟布隆過濾器大小沒啥關系。
這三個公式就是有關布隆過濾器已經推倒出的公式,下面我們來推下這個公式是如何推導出來的。
三、公式推導
講公式,應該先知道幾個關鍵的常量。
誤判率p
、布隆過濾器長度m
、元素個數n
、哈希函數個數k
我們再來一步一步由簡單到難推導公式。
1、誤差率公式推導
前提條件
:就是假設每個元素哈希得到的值分布到m數組上的每一個數組節點的概率是相等的。
1) 假設布隆過濾器長度為m,元素個數n為1,哈希函數個數k也為1。那么在插入時某一數組節點沒有被置為1的概率。

這個應該很好理解。
2)如果上面其它不變,而哈希函數個數變成k個,那么在插入時某一數組節點沒有被置為1的概率。

好理解!
3)如果元素個數變成n個,而哈希函數個數變成k個,那么在插入時某一數組節點沒有被置為1的概率。

4)從上面推導出的是: 當布隆過濾器長度為m,元素個數變成n個,哈希函數個數變成k個的時候,某一節點被置為1的概率為

到這里應該也好理解,第三步是該位置從未被置為1,那么1去減去它就是至少有一次被置為1,那么只要存在一次被置1,那么該位置的bit標示就是1,因為布隆過濾器是不能刪除的。
5)這個還需要考慮到,一個元素通過hash會生成多個k,放入m數組中,所以需要這k個值都為1才會認為該該元素已經存在。所以是這樣的。

上面這個公式推導在轉換下就成了

思考
為什么上面這個公式的值就是最終的誤差率?
因為當一個布隆過濾器中不存在的元素進來的是的時候,首先通過hash算法產生k個哈希值,分布在m數組上都為1的的概率不就是上面推導出的這個公式嗎,那不就是誤差嗎?
因為明明是不存在的值,卻有這個概率表明已經存在。
思考
給定的m和n,思考k值為多少誤差會最小。
為什么k值的大小不合理會影響誤差呢?
我們來思考下,一個元素最終生成k個hash值,那么會在數組m上的k個位置標記為1。
假設k為1,那么每次進來只在m上的某一個位置標記為1,這樣的話如果一個新元素進來剛好hash值也在這里,而不用其它位置來判斷是否為1,這個誤差就會比較大。
假設k為m,那么第一個元素進來,在m上所有位置上都表為1了 ,以后只要進來一個元素就會標記為已存在。這個誤差也太大了。
上面只是舉了兩個極端的例子,但也說明k值太大、太小都不好,它的最優值一定跟m、n存在某種關系。
至於完整公式的推導,我這里就不在寫了,后面會貼一個人家怎么推導的博客。
它們之間的關系只要記住下面這個公式就可以了。

這篇博客就到這里了,后面有整理通過谷歌的guava工具 和 redis 實現布隆過濾器的示例。通過Lua腳本批量插入數據到Redis布隆過濾器
參考
只要自己變優秀了,其他的事情才會跟着好起來(上將9)