【實戰問題】-- 快取穿透之布隆過濾器(1)

第十六封發表於2021-03-27

前面我們提到,在防止快取穿透的情況(快取穿透是指,快取和資料庫都沒有的資料,被大量請求,比如訂單號不可能為-1,但是使用者請求了大量訂單號為-1的資料,由於資料不存在,快取就也不會存在該資料,所有的請求都會直接穿透到資料庫。),我們可以考慮使用布隆過濾器,來過濾掉絕對不存於集合中的元素。

布隆過濾器是什麼呢?

布隆過濾器(Bloom Filter)是由布隆(Burton Howard Bloom)在1970年提出的,它實際上是由一個很長的二進位制向量和一系列隨機hash對映函式組成(說白了,就是用二進位制陣列儲存資料的特徵)。可以使用它來判斷一個元素是否存在於集合中,它的優點在於查詢效率高,空間小,缺點是存在一定的誤差,以及我們想要剔除元素的時候,可能會相互影響。

也就是當一個元素被加入集合的時候,通過多個hash函式,將元素對映到位陣列中的k個點,置為1。

為什麼需要布隆過濾器?

一般情況下,我們想要判斷是否存在某個元素,一開始考慮肯定是使用陣列,但是使用陣列的情況,查詢的時候效率比較慢,要判斷一個元素不存在於陣列中,需要每次遍歷完所有的元素。刪除完一個元素後,還得把後面的其他元素往前面移動。

其實可以考慮使用hash表,如果有hash表來儲存,將是以下的結構:

但是這種結構,雖然滿足了大部分的需求,可能存在兩點缺陷:

  • 只有一個hash函式,其實兩個元素hash到一塊,也就是產生hash衝突的可能性,還是比較高的。雖然可以用拉鍊法(後面跟著一個連結串列)的方式解決,但是操作時間複雜度可能有所升高。
  • 儲存的時候,我們需要把元素引用給儲存進去,要是上億的資料,我們要將上億的資料儲存到一個hash表裡面,不太建議這樣操作。

對於上面存在的缺陷,其實我們可以考慮,用多個hash函式來減少衝突(注意:衝突時不可以避免的,只能減少),用位來儲存每一個hash值。這樣既可以減少hash衝突,還可以減少儲存空間。

假設有三個hash函式,那麼不同的元素,都會使用三個hash函式,hash到三個位置上。

假設後面又來了一個張三,那麼在hash的時候,同樣會hash到以下位置,所有位都是1,我們就可以說張三已經存在在裡面了。

那麼有沒有可能出現誤判的情況呢?這是有可能的,比如現在只有張三,李四,王五,蔡八,hash對映值如下:

後面來了陳六,但是不湊巧的是,它hash的三個函式hash出來的位,剛剛好就是被別的元素hash之後,改成1了,判斷它已經存在了,但是實際上,陳六之前是不存在的。

上面的情況,就是誤判,布隆過濾器都會不可避免的出現誤判。但是它有一個好處是,布隆過濾器,判斷存在的元素,可能不存在,但是判斷不存在的元素,一定不存在。,因為判斷不存在說明至少有一位hash出來是對不上的。

也是由於會出現多個元素可能hash到一起,但有一個資料被踢出了集合,我們想把它對映的位,置為0,相當於刪除該資料。這個時候,就會影響到其他的元素,可能會把別的元素對映的位,置為了0。這也就是為什麼布隆過濾器不能刪除的原因。

具體步驟

新增元素:

    1. 使用多個hash函式對元素item進行hash運算,得到多個hash值。
    1. 每一個hash值對bit位陣列取模,得到位陣列中的位置索引index。
    1. 如果index的位置不為1,那麼就將該位置為1。

判斷元素是否存在:

    1. 使用多個hash函式對元素item進行hash運算,得到多個hash值。
    1. 每一個hash值對bit位陣列取模,得到位陣列中的位置索引index。
    1. 如果index所處的位置都為1,說明元素可能已經存在了。

誤判率推導

慶幸的是,布隆過濾器的誤判率是可以預測的,由上面的分析,也可以得知,其實是與位陣列的大小,以及hash函式的個數等,這些都是息息相關的。

假設位陣列的大小是m,我們一共有k個hash函式,那麼每一個hash函式,進行hash的時候,只能hash到m位中的一個位置,所以沒有被hash到的概率是:
$$1-\frac{1}{m}$$

k個hash函式都hash之後,該位還是沒有被hash到1的概率是:
$$(1-\frac{1}{m})^k$$

如果我們插入了n個元素,也就是hash了n*k次,該位還是沒有被hash成1的概率是:
$$(1-\frac{1}{m})^{kn}$$

那該位為1的概率就是:
$$1-(1-\frac{1}{m})^{kn}$$

如果需要檢測某一個元素是不是在集合中,也就是該元素對應的k個hash元素hash出來的值,都需要設定為1。也就是該元素不存在,但是該元素對應的所有位都被hash成為1的概率是:
$${(1-(1-\frac{1}{m}){kn})}{k}\approx {(1-e{-kn/m})}k $$

可以大致看出,隨著位陣列大小m和hash函式個數的增加,其實概率會下降,隨著插入的元素n的增加,概率會有所上升。

最後也可以通過自己期待的誤判率P和期待新增的個數n,來大致計算出布隆過濾器的位陣列的長度:
$$m=-(\frac{nInP}{(In2)^2})$$

上面就是誤判率的大致計算方式,同時也提示我們,可以根據自己業務的資料量以及誤判率,來調整我們的陣列的大小。

布隆過濾器的作用

除了我們前面說的過濾爬蟲惡意請求,還可以對一些URL進行去重,過濾海量資料裡面的重複資料,過濾資料庫裡面不存在的id等等。

但是,即使有布隆過濾器,我們也不可能完全避免,或者徹底解決快取穿透這個問題。只是相當於做了優化,將準確率提高。

很多的key-value資料庫也會使用布隆過濾器來加快查詢效率,因為全部挨個判斷一遍,這個效率太低了。

【刷題筆記】
Github倉庫地址:https://github.com/Damaer/codeSolution
筆記地址:https://damaer.github.io/codeSolution/

【作者簡介】
秦懷,公眾號【秦懷雜貨店】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。個人寫作方向:Java原始碼解析,JDBC,Mybatis,Spring,redis,分散式,劍指Offer,LeetCode等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花裡胡哨,大多寫系列文章,不能保證我寫的都完全正確,但是我保證所寫的均經過實踐或者查詢資料。遺漏或者錯誤之處,還望指正。

2020年我寫了什麼?

開源刷題筆記

平日時間寶貴,只能使用晚上以及週末時間學習寫作,關注我,我們一起成長吧~

相關文章