Redis-避免緩存穿透的利器之BloomFilter

你知道的越多,你不知道的也越多html

點贊再看,養成習慣java

前言

你在開發或者面試過程當中,有沒有遇到過海量數據須要查重緩存穿透怎麼避免等等這樣的問題呢?下面這個東西超屌,好好了解下,面試過關斬將,凸顯你的不同。node

Bloom Filter 概念

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

Bloom Filter 原理

布隆過濾器的原理是,當一個元素被加入集合時,經過K個散列函數將這個元素映射成一個位數組中的K個點,把它們置爲1。檢索時,咱們只要看看這些點是否是都是1就(大約)知道集合中有沒有它了:若是這些點有任何一個0,則被檢元素必定不在;若是都是1,則被檢元素極可能在。這就是布隆過濾器的基本思想。redis

Bloom Filter跟單哈希函數Bit-Map不一樣之處在於:Bloom Filter使用了k個哈希函數,每一個字符串跟k個bit對應。從而下降了衝突的機率。算法

img

緩存穿透

每次查詢都會直接打到DB數據庫

簡而言之,言而簡之就是咱們先把咱們數據庫的數據都加載到咱們的過濾器中,好比數據庫的id如今有:一、二、3api

那就用id:1 爲例子他在上圖中通過三次hash以後,把三次本來值0的地方改成1數組

下次數據進來查詢的時候若是id的值是1,那麼我就把1拿去三次hash 發現三次hash的值,跟上面的三個位置徹底同樣,那就能證實過濾器中有1的緩存

反之若是不同就說明不存在了

那應用的場景在哪裏呢?通常咱們都會用來防止緩存擊穿

簡單來講就是你數據庫的id都是1開始而後自增的,那我知道你接口是經過id查詢的,我就拿負數去查詢,這個時候,會發現緩存裏面沒這個數據,我又去數據庫查也沒有,一個請求這樣,100個,1000個,10000個呢?你的DB基本上就扛不住了,若是在緩存裏面加上這個,是否是就不存在了,你判斷沒這個數據就不去查了,直接return一個數據爲空不就行了嘛。

這玩意這麼好使那有啥缺點麼?有的,咱們接着往下看

Bloom Filter的缺點

bloom filter之因此能作到在時間和空間上的效率比較高,是由於犧牲了判斷的準確率、刪除的便利性

  • 存在誤判,可能要查到的元素並無在容器中,可是hash以後獲得的k個位置上值都是1。若是bloom filter中存儲的是黑名單,那麼能夠經過創建一個白名單來存儲可能會誤判的元素。

  • 刪除困難。一個放入容器的元素映射到bit數組的k個位置上是1,刪除的時候不能簡單的直接置爲0,可能會影響其餘元素的判斷。能夠採用Counting Bloom Filter

Bloom Filter 實現

布隆過濾器有許多實現與優化,Guava中就提供了一種Bloom Filter的實現。

在使用bloom filter時,繞不過的兩點是預估數據量n以及指望的誤判率fpp,

在實現bloom filter時,繞不過的兩點就是hash函數的選取以及bit數組的大小。

對於一個肯定的場景,咱們預估要存的數據量爲n,指望的誤判率爲fpp,而後須要計算咱們須要的Bit數組的大小m,以及hash函數的個數k,並選擇hash函數

(1)Bit數組大小選擇

  根據預估數據量n以及誤判率fpp,bit數組大小的m的計算方式:

img

(2)哈希函數選擇

​ 由預估數據量n以及bit數組長度m,能夠獲得一個hash函數的個數k:

img

​ 哈希函數的選擇對性能的影響應該是很大的,一個好的哈希函數要能近似等機率的將字符串映射到各個Bit。選擇k個不一樣的哈希函數比較麻煩,一種簡單的方法是選擇一個哈希函數,而後送入k個不一樣的參數。

哈希函數個數k、位數組大小m、加入的字符串數量n的關係能夠參考Bloom Filters - the mathBloom_filter-wikipedia

要使用BloomFilter,須要引入guava包:

<dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
 </dependency>    
複製代碼

測試分兩步:

一、往過濾器中放一百萬個數,而後去驗證這一百萬個數是否能經過過濾器

二、另外找一萬個數,去檢驗漏網之魚的數量

/** * 測試布隆過濾器(可用於redis緩存穿透) * * @author 敖丙 */
public class TestBloomFilter {

    private static int total = 1000000;
    private static BloomFilter<Integer> bf = BloomFilter.create(Funnels.integerFunnel(), total);
// private static BloomFilter<Integer> bf = BloomFilter.create(Funnels.integerFunnel(), total, 0.001);

    public static void main(String[] args) {
        // 初始化1000000條數據到過濾器中
        for (int i = 0; i < total; i++) {
            bf.put(i);
        }

        // 匹配已在過濾器中的值,是否有匹配不上的
        for (int i = 0; i < total; i++) {
            if (!bf.mightContain(i)) {
                System.out.println("有壞人逃脫了~~~");
            }
        }

        // 匹配不在過濾器中的10000個值,有多少匹配出來
        int count = 0;
        for (int i = total; i < total + 10000; i++) {
            if (bf.mightContain(i)) {
                count++;
            }
        }
        System.out.println("誤傷的數量:" + count);
    }

}
複製代碼

運行結果:

img

運行結果表示,遍歷這一百萬個在過濾器中的數時,都被識別出來了。一萬個不在過濾器中的數,誤傷了320個,錯誤率是0.03左右。

看下BloomFilter的源碼:

public static <T> BloomFilter<T> create(Funnel<? super T> funnel, int expectedInsertions) {
        return create(funnel, (long) expectedInsertions);
    }  

    public static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions) {
        return create(funnel, expectedInsertions, 0.03); // FYI, for 3%, we always get 5 hash functions
    }

    public static <T> BloomFilter<T> create( Funnel<? super T> funnel, long expectedInsertions, double fpp) {
        return create(funnel, expectedInsertions, fpp, BloomFilterStrategies.MURMUR128_MITZ_64);
    }

    static <T> BloomFilter<T> create( Funnel<? super T> funnel, long expectedInsertions, double fpp, Strategy strategy) {
     ......
    }
複製代碼

BloomFilter一共四個create方法,不過最終都是走向第四個。看一下每一個參數的含義:

funnel:數據類型(通常是調用Funnels工具類中的)

expectedInsertions:指望插入的值的個數

fpp 錯誤率(默認值爲0.03)

strategy 哈希算法(我也不懂啥意思)Bloom Filter的應用

在最後一個create方法中,設置一個斷點:

img

img

上面的numBits,表示存一百萬個int類型數字,須要的位數爲7298440,700多萬位。理論上存一百萬個數,一個int是4字節32位,須要481000000=3200萬位。若是使用HashMap去存,按HashMap50%的存儲效率,須要6400萬位。能夠看出BloomFilter的存儲空間很小,只有HashMap的1/10左右

上面的numHashFunctions,表示須要5個函數去存這些數字

使用第三個create方法,咱們設置下錯誤率:

private static BloomFilter<Integer> bf = BloomFilter.create(Funnels.integerFunnel(), total, 0.0003);
複製代碼

再運行看看:

img

此時誤傷的數量爲4,錯誤率爲0.04%左右。

img

當錯誤率設爲0.0003時,所須要的位數爲16883499,1600萬位,須要12個函數

和上面對比能夠看出,錯誤率越大,所需空間和時間越小,錯誤率越小,所需空間和時間約大

常見的幾個應用場景:

  • cerberus在收集監控數據的時候, 有的系統的監控項量會很大, 須要檢查一個監控項的名字是否已經被記錄到db過了, 若是沒有的話就須要寫入db.

  • 爬蟲過濾已抓到的url就再也不抓,可用bloom filter過濾

  • 垃圾郵件過濾。若是用哈希表,每存儲一億個 email地址,就須要 1.6GB的內存(用哈希表實現的具體辦法是將每個 email地址對應成一個八字節的信息指紋,而後將這些信息指紋存入哈希表,因爲哈希表的存儲效率通常只有 50%,所以一個 email地址須要佔用十六個字節。一億個地址大約要 1.6GB,即十六億字節的內存)。所以存貯幾十億個郵件地址可能須要上百 GB的內存。而Bloom Filter只須要哈希表 1/8到 1/4 的大小就能解決一樣的問題。

總結

布隆過濾器主要是在回答道緩存穿透的時候引出來的,文章裏面仍是寫的比較複雜了,不少都是網上我看到就複製下來了,你們只要知道他的原理,還有就是知道他的場景能在面試中回答出他的做用就行了。

說到服務器,最近雙十一,阿里雲的服務器對新用戶很友好(老用戶悄悄用爸媽手機註冊),賊便宜!

經過個人連接購買,一年最低僅需86塊(新用戶專享,若是不是新用戶的能夠用家裏人的帳號購買),80一年,200三年我以爲跟白嫖差很少,我直接用老媽帳號買了三年的,準備搭建一個小項目玩玩。你們能夠買來玩玩,想看服務器文章的朋友留言給我,能夠的話我會寫一期怎麼利用服務器的文章。

點個人連接去買有優惠喲

好了 以上就是這篇文章的所有內容了,你們能夠看一下我更新的Java面試吊打系列和Java技術棧相關的文章。若是你有什麼想知道的,也能夠點留言給我,我一有時間就會寫出來,咱們共同進步。

很是感謝您能看到這裏,若是這個文章寫得還不錯的話 求點贊 求關注 求分享 求留言 各位的支持和承認,就是我創做的最大動力,OK各位咱們下期見!

敖丙 | 文


後面會持續更新《吊打面試官》系列能夠關注個人公衆號 JavaFamily 第一時間閱讀,也會有朋友一線大廠的內推機會不按期推出(字節跳動,阿里,網易,PDD,滴滴,蘑菇街等),就業上有什麼問題也能夠直接微信滴滴我,我也是個新人,不過不影響咱們一塊兒進步,做爲渣男,我給不了你工做,還給不了你溫暖嘛?

相關文章
相關標籤/搜索