在開發或者面試過程當中,時常遇到過海量數據須要查詢,秒殺時緩存擊穿怎麼避免等等這樣的問題呢?掌握好本篇介紹的知識點將有助於你在以後的工做、面試中策馬奔騰。java
Bloom Filter,即傳說中的布隆過濾器。它其實是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器能夠用於檢索一個元素是否在一個集合中。它的優勢是空間效率和查詢時間都遠遠超過通常的算法,缺點是有必定的誤識別率和刪除困難。
面試
布隆過濾器的原理是,<font color="red">當一個元素被加入集合時,經過K個散列函數將這個元素映射成一個位數組中的K個點,把它們置爲1。檢索時,咱們只要看看這些點是否是都是1就(大約)知道集合中有沒有它了:若是這些點有任何一個0,則被檢元素必定不在;若是都是1,則被檢元素極可能在。</font>這就是布隆過濾器的基本思想。算法
Bloom Filter跟單哈希函數Bit-Map不一樣之處在於:Bloom Filter使用了k個哈希函數,每一個字符串跟k個bit對應。從而下降了衝突的機率。
Bloom Filter在避免緩存擊穿中的應用方法:簡而言之就是先把咱們數據庫的數據都加載到咱們的過濾器中,好比數據庫的id如今有:1,2,3...,n,以上面的原理圖爲例,將id全部值 通過三次hash以後,將hash獲得的結果對應的地方由0修改成1。這樣作以後,每次請求過來經過id查詢數據,若是緩存沒有命中,再在過濾器中查詢,經過一樣的hash算法將請求的id值進行運算,得到三個索引值,若是有任何一個對應索引的值爲0,說明MySQL中也不存在該id,則直接報錯返回。
<font color="#E96900">試想一想這樣作的好處是什麼?假設這樣的一種場景,若是有1000個參數非法請求同時訪問(所謂參數非法是指數據庫也不存在這類的值,好比id全爲負值),緩存中都沒有命中,此時若是這1000個請求同時打到DB,數據庫層是扛不住的,因此此時Bloom Filter就顯得十分必要。</font>數據庫
Bloom Filter之因此能作到在時間和空間上的效率比較高,是由於犧牲了判斷的準確率、刪除的便利性數組
## Bloom Filter 實現
在實現Bloom Filter時,繞不過的兩點就是hash函數的選取以及bit數組的大小。
對於一個肯定的場景,咱們預估要存的數據量爲n,指望的誤判率爲fpp,而後須要計算咱們須要的Bit數組的大小m,以及hash函數的個數k,並選擇hash函數。
1 Bit數組大小選擇
根據預估數據量n以及誤判率fpp,bit數組大小的m的計算方式:
2 哈希函數選擇
由預估數據量n以及bit數組長度m,能夠獲得一個hash函數的個數k:
3 應用測試
本篇採用的是Google的Bloom Filter,首先須要引入jar包:緩存
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>23.0</version> </dependency>
測試分兩步:函數
一、往過濾器中放五千萬個數,而後去驗證這五千萬個數是否能順利經過過濾器;工具
二、另外找一萬個不在過濾器中的數,檢查Bloom Filter誤判的概率。測試
import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; /** * @author Carson Chu * @date 2020/3/15 14:48 * @description 布隆過濾器測試樣例 */ public class BloomFilterTest { private static int capacity = 50000000; private static BloomFilter<Integer> bf = BloomFilter.create(Funnels.integerFunnel(), capacity); // private static BloomFilter<Integer> bf = BloomFilter.create(Funnels.integerFunnel(), total, 0.001); public static void main(String[] args) { // 初始化50000000條數據到過濾器中 for (int i = 0; i < capacity; i++) { bf.put(i); } // 匹配已在過濾器中的值,是否有匹配不上的 for (int i = 0; i < capacity; i++) { if (!bf.mightContain(i)) { System.out.println("有壞人逃脫了~~~"); } } // 匹配不在過濾器中的10000個值,有多少匹配出來 int count = 0; for (int i = capacity; i < capacity + 10000; i++) { if (bf.mightContain(i)) { count++; } } System.out.println("誤命中的數量:" + count); } }
運行結果表示,遍歷這五千萬個在過濾器中的數時,都被識別出來了。一萬個不在過濾器中的數,誤傷了297個,誤判率是2.9%左右。
若是想要下降誤判率該怎麼作呢,不要急,源碼爲咱們提供了這一機制:google
@CheckReturnValue public static <T> BloomFilter<T> create(Funnel<? super T> funnel, int expectedInsertions) { return create(funnel, (long)expectedInsertions); } @CheckReturnValue public static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions) { return create(funnel, expectedInsertions, 0.03D); } @CheckReturnValue public static <T> BloomFilter<T> create(Funnel<? super T> funnel, int expectedInsertions, double fpp) { return create(funnel, (long)expectedInsertions, fpp); } @CheckReturnValue public static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions, double fpp) { return create(funnel, expectedInsertions, fpp, BloomFilterStrategies.MURMUR128_MITZ_64); } /* create()方法的最底層實現 */ @VisibleForTesting static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions, double fpp, BloomFilter.Strategy strategy) { Preconditions.checkNotNull(funnel); Preconditions.checkArgument(expectedInsertions >= 0L, "Expected insertions (%s) must be >= 0", new Object[]{expectedInsertions}); Preconditions.checkArgument(fpp > 0.0D, "False positive probability (%s) must be > 0.0", new Object[]{fpp}); Preconditions.checkArgument(fpp < 1.0D, "False positive probability (%s) must be < 1.0", new Object[]{fpp}); Preconditions.checkNotNull(strategy); if (expectedInsertions == 0L) { expectedInsertions = 1L; } long numBits = optimalNumOfBits(expectedInsertions, fpp); int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits); try { return new BloomFilter(new BitArray(numBits), numHashFunctions, funnel, strategy); } catch (IllegalArgumentException var10) { throw new IllegalArgumentException("Could not create BloomFilter of " + numBits + " bits", var10); } }
BloomFilter一共四個create方法,不過最終都是走向第四個。看一下每一個參數的含義:funnel
:數據類型(通常是調用Funnels
工具類中的)expectedInsertions
:指望插入的值的個數fpp
:錯誤率(默認值爲0.03)strategy
:Bloom Filter的算法策略
錯誤率越大,所需空間和時間越小,錯誤率越小,所需空間和時間約大。
布隆過濾器主要是在解決緩存穿透問題的時候引出來的,瞭解他的原理並能實習運用,在開發和麪試中都是大有裨益的。