今天來聊一聊面試八股文:布隆過濾器。
說道布隆過濾器,就免不了說到緩存穿透。
緩存穿透
在高並發下,查詢一個並不存在的值時,緩存不會被命中,導致大量請求直接落到數據庫。
數據庫的響應能力肯定沒有緩存大,出線這樣的情況,一般是黑客攻擊,拖慢了系統的響應速度。
頭腦風暴
朴素的分析思路: 在緩存前加一道屏障:放置存在(可能存在)的查詢鍵,屏蔽不可能存在的查詢鍵,
業內一般使用布隆過濾器來做這個屏障。
布隆過濾器的實現過程
布隆過濾器內部維護了一個全為0的bit數組,幾個hash函數(f1,f2)
假設有輸入集合{N1,N2},哈希函數f1、f2
- 經過計算 f1(N1)=2, f2(N1)=5, 則將數組下表為2,5的位置標記為1:
- 同理計算f2(N1)=3, f2(N2)=4,則將數組下表為3,4的位置標記為1:
- 有第三個數N3,我們判斷N3在不在集合{N1,N2}中, 就進行f1(N3)、f2(N3)的計算
- 如果f1(N3),f2(N3)計算的值均落在上圖紅色區域, 則說明N3可能屬於集合{N1,N2}中的一員
- 如果f1(N3),f2(N3)計算的值有一個落在紅色區域的外面,則說明N3一定不屬於集合{N1,N2}
布隆過濾器的設計原理
(這里是重點,再看不懂,私聊我)
數據庫所有的鍵,經過一次哈希運算,收斂到(A,B)區間,
某個待查詢的鍵K,如果經過同樣的哈希運算,落在(A,B)區間,因為存在哈希碰撞,所以我們說K有可能屬於數據庫中所有的鍵中的一員;
但是如果該K經過哈希運算,沒有落在收斂區間,則證明K一定不屬於原數據庫鍵。
那為什么要使用多個哈希函數?
因為經過一次哈希函數落在收斂區間,只能說該K有可能屬於原數據庫鍵,但是如果經過多個哈希函數,還是落到收斂區間,概率疊加,無形中增大了該K屬於原數據庫鍵的概率。
總體上看: 布隆過濾器是利用了哈希算法的單向收斂性+概率論。
時間復雜度: 要判斷N是否屬於原查詢鍵,只需要經過幾次哈希運算,所以布隆過濾器判斷的過程是很快的,
布隆過濾器的應用
很明顯,布隆過濾器存在誤報, 經過上面的分析:誤報率跟哈希碰撞和有幾個哈希函數有關,
成熟的布隆過濾器,這些你都不需要考慮,只需要指定 ① 哈希結果的存儲區 ②容量 ③誤報率
package | nuget |
---|---|
BloomFilter.NetCore | 以內存存儲哈希結果 |
BloomFilter.Redis.NetCore | 以redis存儲哈希結果 |
BloomFilter.EasyCaching.NetCore |
using BloomFilter;
using System;
using System.Collections.Generic;
namespace BoomFilter
{
class Program
{
static readonly IBloomFilter bf = FilterBuilder.Build(10000000, 0.03);
static void Main(string[] args)
{
int size = 10000000;
for (int i = 0; i < size; i++)
{
bf.Add(i);
}
var list = new List<int>();
// 故意取100個不在布隆過濾器中的值,看下有多少值誤報
for(int i= size+1;i<size+100;i++)
{
if (bf.Contains(i))
{
list.Add(i);
}
}
Console.WriteLine($"誤報的個數為:{list.Count}");
}
}
}
總結
布隆過濾器是 哈希函數單向收斂和 概率論的完美結合,
從上面的分析看,解決緩存穿透,我們在Cache前面預熱一個布隆過濾器,就可以阻止絕大部分非法的查詢鍵。
注意,布隆過濾器對刪除不友好,所以如果數據庫鍵有大量變更,需要重建布隆過濾器。