說一說布隆過濾器

介紹

布隆過濾器在wiki上的介紹:java

布隆過濾器(Bloom Filter)是1970年由布隆提出的。它其實是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器能夠用於檢索一個元素是否在一個集合中。它的優勢是空間效率和查詢時間都遠遠超過通常的算法,缺點是有必定的誤識別率和刪除困難

爲何要用布隆過濾器?

事實上,布隆過濾器被普遍用於網頁黑名單系統、垃圾郵件過濾系統、爬蟲的網址判重系統以及解決緩存穿透問題。經過介紹已經知曉布隆過濾器的做用是檢索一個元素是否在集合中。可能有人認爲這個功能很是簡單,直接放在redis中或者數據庫中查詢就行了。又或者當數據量較小,內存又足夠大時,使用hashMap或者hashSet等結構就行了。可是若是當這些數據量很大,數十億甚至更多,內存裝不下且數據庫檢索又極慢的狀況,咱們應該如何去處理?這個時候咱們不妨考慮下布隆過濾器,由於它是一個空間效率佔用極少和查詢時間極快的算法,可是須要業務能夠忍受一個判斷失誤率。redis

哈希函數

布隆過濾器離不開哈希函數,因此在這裏有必要介紹下哈希函數的概念,若是你已經掌握了,能夠直接跳到下一小節。
哈希函數的性質:算法

  1. 經典的哈希函數都有無限大的輸入值域(無窮大)。
  2. 經典的哈希函數的輸出域都是固定的範圍(有窮大,假設輸出域爲S)
  3. 當給哈希函數傳入相同的值時,返回值必同樣
  4. 當給哈希函數傳入不一樣的輸入值時,返回值可能同樣,也可能不同。
  5. 輸入值會盡量均勻的分佈在S上

前三點都是哈希函數的基礎,第四點描述了哈希函數存在哈希碰撞的現象,由於輸入域無限大,輸出域有窮大,這是必然的,輸入域中會有不一樣的值對應到輸入域S中。第五點事評價一個哈希函數優劣的關鍵,哈希函數越優秀,分佈就越均勻且與輸入值出現的規律無關。好比存在"hash1","hash2","hash3"三個輸入值比較相似,通過哈希函數計算後的結果應該相差很是大,能夠經過常見的MD5和SHA1算法來驗證這些特性。若是一個優秀的函數可以作到不一樣的輸入值所獲得的返回值能夠均勻的分佈在S中,將其返回值對m取餘(%m),獲得的返回值能夠認爲也會均勻的分佈在0~m-1位置上。數據庫

基於緩存業務分析布隆過濾器原理

在大多應用中,當業務系統中發送一個請求時,會先從緩存中查詢;若緩存中存在,則直接返回;若返回中不存在,則查詢數據庫。其流程以下圖所示:
圖片描述api

緩存穿透:當請求數據庫中不存在的數據,這時候全部的請求都會打到數據庫上,這種狀況就是緩存穿透。若是當請求較多的話,這將會嚴重浪費數據庫資源甚至致使數據庫假死。數組


接下來開始介紹布隆過濾器。有一個長度爲m的bit型數組,如咱們所知,每一個位置只佔一個bit,每一個位置只有0和1兩種狀態。假設一共有k個哈希函數相互獨立,輸入域都爲s且都大於等於m,那麼對同一個輸入對象(能夠想象爲緩存中的一個key),通過k個哈希函數計算出來的結果也都是獨立的。對算出來的每個結果都對m取餘,而後在bit數組上把相應的位置設置爲1(描黑),以下圖所示:緩存

clipboard.png

至此一個輸入對象對bit array集合的影響過程就結束了,咱們能夠看到會有多個位置被描黑,也就是設置爲1.接下來全部的輸入對象都按照這種方式去描黑數組,最終一個布隆過濾器就生成了,它表明了全部輸入對象組成的集合。
那麼如何判斷一個對象是否在過濾器中呢?假設一個輸入對象爲hash1,咱們須要經過看k個哈希函數算出k個值,而後把k個值取餘(%m),就獲得了k個[0,m-1]的值。而後咱們判斷bit array上這k個值是否都爲黑,若是有一個不爲黑,那麼確定hash1確定不在這個集合裏。若是都爲黑,則說明hash1在集合裏,但有可能誤判。由於當輸入對象過多,而集合太小,會致使集合中大多位置都會被描黑,那麼在檢查hash1時,有可能hash1對應的k個位置正好被描黑了,而後錯誤的認爲hash1存在集合裏。less

控制布隆過濾器的誤判率

若是bit array集合的大小m相比於輸入對象的個數太小,失誤率就會變高。這裏直接引入一個已經獲得證實的公式,根據輸入對象數量n和咱們想要達到的誤判率爲p計算出布隆過濾器的大小m和哈希函數的個數k.
布隆過濾器的大小m公式:ide

clipboard.png

哈希函數的個數k公式:函數

clipboard.png

布隆過濾器真實失誤率p公式:

clipboard.png

假設咱們的緩存系統,key爲userId,value爲user。若是咱們有10億個用戶,規定失誤率不能超過0.01%,經過計算器計算可得m=19.17n,向上取整爲20n,也就是須要200億個bit,換算以後所需內存大小就是2.3G。經過第二個公式可計算出所需哈希函數k=14.由於在計算m的時候用了向上取整,因此真是的誤判率絕對小於等於0.01%。

快速集成BloomFilter

關於布隆過濾器,咱們不須要本身實現,谷歌已經幫咱們實現好了。

  • pom引入依賴
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>25.1-jre</version>
</dependency>
  • 核心api
/**
   * Creates a {@link BloomFilter BloomFilter<T>} with the expected number of
   * insertions and expected false positive probability.
   *
   * <p>Note that overflowing a {@code BloomFilter} with significantly more elements
   * than specified, will result in its saturation, and a sharp deterioration of its
   * false positive probability.
   *
   * <p>The constructed {@code BloomFilter<T>} will be serializable if the provided
   * {@code Funnel<T>} is.
   *
   * <p>It is recommended that the funnel be implemented as a Java enum. This has the
   * benefit of ensuring proper serialization and deserialization, which is important
   * since {@link #equals} also relies on object identity of funnels.
   *
   * @param funnel the funnel of T's that the constructed {@code BloomFilter<T>} will use
   * @param expectedInsertions the number of expected insertions to the constructed
   *     {@code BloomFilter<T>}; must be positive
   * @param fpp the desired false positive probability (must be positive and less than 1.0)
   * @return a {@code BloomFilter}
   */
  public static <T> BloomFilter<T> create(
      Funnel<T> funnel, int expectedInsertions /* n */, double fpp) {
    checkNotNull(funnel);
    checkArgument(expectedInsertions >= 0, "Expected insertions (%s) must be >= 0",
        expectedInsertions);
    checkArgument(fpp > 0.0, "False positive probability (%s) must be > 0.0", fpp);
    checkArgument(fpp < 1.0, "False positive probability (%s) must be < 1.0", fpp);
    if (expectedInsertions == 0) {
      expectedInsertions = 1;
    }
    /*
     * TODO(user): Put a warning in the javadoc about tiny fpp values,
     * since the resulting size is proportional to -log(p), but there is not
     * much of a point after all, e.g. optimalM(1000, 0.0000000000000001) = 76680
     * which is less than 10kb. Who cares!
     */
    long numBits = optimalNumOfBits(expectedInsertions, fpp);
    int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
    try {
      return new BloomFilter<T>(new BitArray(numBits), numHashFunctions, funnel,
          BloomFilterStrategies.MURMUR128_MITZ_32);
    } catch (IllegalArgumentException e) {
      throw new IllegalArgumentException("Could not create BloomFilter of " + numBits + " bits", e);
    }
  }
/**
   * Returns {@code true} if the element <i>might</i> have been put in this Bloom filter,
   * {@code false} if this is <i>definitely</i> not the case.
   */
  public boolean mightContain(T object) {
    return strategy.mightContain(object, funnel, numHashFunctions, bits);
  }
  • 一個小例子
public static void main(String... args){
        /**
         * 建立一個插入對象爲一億,誤報率爲0.01%的布隆過濾器
         */
        BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("utf-8")), 100000000, 0.0001);
        bloomFilter.put("121");
        bloomFilter.put("122");
        bloomFilter.put("123");
        System.out.println(bloomFilter.mightContain("121"));
    }
相關文章
相關標籤/搜索