5分鐘掌握布隆過濾器

程式設計碼農發表於2021-11-06

布隆過濾器

布隆過濾器是一種由位陣列多個雜湊函式組成概率資料結構,返回兩種結果 可能存在一定不存在

布隆過濾器裡的一個元素由多個狀態值共同確定。位陣列儲存狀態值,雜湊函式計算狀態值的位置。

根據它的演算法結構,有如下特徵:

  • 使用有限位陣列表示大於它長度的元素數量,因為一個位的狀態值可以同時標識多個元素。
  • 不能刪除元素。因為一個位的狀態值可能同時標識著多個元素。
  • 新增元素永遠不會失敗。只是隨著新增元素增多,誤判率會上升。
  • 如果判斷元素不存在,那麼它一定不存在。

比如下面,X,Y,Z 分別由 3個狀態值共同確定元素是否存在,狀態值的位置通過3個雜湊函式分別計算。

bloom

數學關係

誤判概率

關於誤判概率,因為每個位的狀態值可能同時標識多個元素,所以它存在一定的誤判概率。如果位陣列滿,當判斷元素是否存在時,它會始終返回true,對於不存在的元素來說,它的誤判率就是100%。

那麼,誤判概率和哪些因素有關,已新增元素的數量,布隆過濾器長度(位陣列大小),雜湊函式數量。

根據維基百科推理誤判概率 \( P_{fp} \)有如下關係:

$$ { P_{fp} =\left(1-\left[1-{\frac {1}{m}}\right]^{kn}\right)^{k}\approx \left(1-e^{{-\frac {kn}{m}}}\right)^{k}} $$

  • $m$ 是位陣列的大小;
  • $n$ 是已經新增元素的數量;
  • $k$ 是雜湊函式數量;
  • $e$ 數學常數,約等於2.718281828。

由此可以得到,當新增元素數量為0時,誤報率為0;當位陣列全都為1時,誤報率為100%。

不同數量雜湊函式下,$ P_{fp}$ 和 $ n$ 的關係如下圖:

Bloom_filter_fp_probability

根據誤判概率公式可以做一些事

  • 估算最佳布隆過濾器長度。
  • 估算最佳雜湊函式數量。

最佳布隆過濾器長度

當 \(n \) 新增元素和 \( P_{fp} \)誤報概率確定時,\( m \) 等於:

$$ {\displaystyle m=-{\frac {n\ln P_{fp}}{(\ln 2)^{2}}} \approx -1.44\cdot n\log _{2}P_{fp}} $$

最佳雜湊函式數量

當 \( n \) 和 \(P_{fp} \) 確定時,\( k \) 等於:

$$ {\displaystyle k=-{\frac {\ln P_{fp} }{\ln 2}}=-\log _{2}P_{fp} } $$

當 \( n \) 和 \( m \) 確定時,\( k \) 等於:

$$ {\displaystyle k={\frac {m}{n}}\ln 2} $$

實現布隆過濾器

使用布隆過濾器前,我們一般會評估兩個因素。

  • 預期新增元素的最大數量。
  • 業務對錯誤的容忍程度。比如1000個允許錯一個,那麼誤判概率應該在千分之一內。

很多布隆過濾工具都提供了預期新增數量誤判概率配置引數,它們會根據配置的引數計算出最佳的長度雜湊函式數量

Java中有一些不錯的布隆過濾工具包。

  • GuavaBloomFilter
  • redissonRedissonBloomFilter 可以redis 中使用。

看下 GuavaBloomFilter 的簡單實現,建立前先計算出位陣列長度雜湊函式數量

 static <T> BloomFilter<T> create(
      Funnel<? super T> funnel, long expectedInsertions, double fpp, Strategy strategy) {
    /**
     * expectedInsertions:預期新增數量
     * fpp:誤判概率
     */
    long numBits = optimalNumOfBits(expectedInsertions, fpp);
    int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
    try {
      return new BloomFilter<T>(new BitArray(numBits), numHashFunctions, funnel, strategy);
    } catch (IllegalArgumentException e) {
      throw new IllegalArgumentException("Could not create BloomFilter of " + numBits + " bits", e);
    }
  }

根據最佳布隆過濾器長度公式,計算最佳位陣列長度。


static long optimalNumOfBits(long n, double p) {
    if (p == 0) {
      p = Double.MIN_VALUE;
    }
    return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
  }

根據最佳雜湊函式數量公式,計算最佳雜湊函式數量。

static int optimalNumOfHashFunctions(long n, long m) {
    return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
  }

redissonRedissonBloomFilter 計算方法也是一致。

    private int optimalNumOfHashFunctions(long n, long m) {
        return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
      }

    private long optimalNumOfBits(long n, double p) {
        if (p == 0) {
            p = Double.MIN_VALUE;
        }
        return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
    }

記憶體佔用

設想一個手機號去重場景,每個手機號佔用22 Byte,估算邏輯記憶體如下。

expectedHashSetfpp=0.0001fpp=0.0000001
100萬18.28MB2.29MB4MB
1000萬182.82MB22.85MB40MB
1億1.78G228.53MB400MB
注:實際實體記憶體佔用大於邏輯記憶體。

誤判概率 \( p \) 和已新增的元素 \( n \),位陣列長度 \( m \),雜湊函式數量 \( k \) 關係如下:

關係圖

應用場景

  1. 弱密碼檢測;
  2. 垃圾郵件地址過濾。
  3. 瀏覽器檢測釣魚網站;
  4. 快取穿透。

弱密碼檢測

維護一個雜湊過弱密碼列表。當使用者註冊或更新密碼時,使用布隆過濾器檢查新密碼,檢測到提示使用者。

垃圾郵件地址過濾

維護一個雜湊過垃圾郵件地址列表。當使用者接收郵件,使用布隆過濾器檢測,檢測到標識為垃圾郵件。

瀏覽器檢測釣魚網站

使用布隆過濾器來查詢釣魚網站資料庫中是否存在某個網站的 URL。

快取穿透

快取穿透是指查詢一個根本不存在的資料,快取層和資料庫都不會命中。當快取未命中時,查詢資料庫

  1. 資料庫不命中,空結果不會寫回快取並返回空結果。
  2. 資料庫命中,查詢結果寫回快取並返回結果。

一個典型的攻擊,模擬大量請求查詢不存在的資料,所有請求落到資料庫,造成資料庫當機。

其中一種解決方案,將存在的快取放入布隆過濾器,在請求前進行校驗過濾。

快取

小結

對於千萬億級別的資料來說,使用布隆過濾器具有一定優勢,另外根據業務場景合理評估預期新增數量誤判概率是關鍵。

參考

https://en.wikipedia.org/wiki...

https://hur.st/bloomfilter

相關文章