如何快速判斷某 URL 是否在 20 億的網(wǎng)址 URL 集合中敌蜂?

假設(shè)遇到這樣一個(gè)問題:

一個(gè)網(wǎng)站有 20 億 url 存在一個(gè)黑名單中,這個(gè)黑名單要怎么存津肛?若此時(shí)隨便輸入一個(gè) url章喉,你如何快速判斷該 url 是否在這個(gè)黑名單中?并且需在給定內(nèi)存空間(比如:500M)內(nèi)快速判斷出

可能很多人首先想到的會(huì)是使用 HashSet快耿,因?yàn)?HashSet基于 HashMap囊陡,理論上時(shí)間復(fù)雜度為:O(1)。達(dá)到了快速的目的掀亥,但是空間復(fù)雜度呢撞反?URL字符串通過Hash得到一個(gè)Integer的值,Integer占4個(gè)字節(jié)搪花,那20億個(gè)URL理論上需要:20億*4/1024/1024/1024=7.45G的內(nèi)存遏片,不滿足空間復(fù)雜度的要求。

這里就引出本文要介紹的“布隆過濾器”撮竿。

何為布隆過濾器

百科上對(duì)布隆過濾器的介紹是這樣的:

布隆過濾器(Bloom Filter)是1970年由布隆提出的吮便。它實(shí)際上是一個(gè)很長(zhǎng)的二進(jìn)制向量和一系列隨機(jī)映射函數(shù)。布隆過濾器可以用于檢索一個(gè)元素是否在一個(gè)集合中幢踏。它的優(yōu)點(diǎn)是空間效率和查詢時(shí)間都比一般的算法要好的多髓需,缺點(diǎn)是有一定的誤識(shí)別率和刪除困難。

是不是描述的比較抽象房蝉?那就直接了解其原理吧僚匆!

還是以上面的例子為例:

哈希算法得出的Integer的哈希值最大為:Integer.MAX_VALUE=2147483647,意思就是任何一個(gè)URL的哈希都會(huì)在0~2147483647之間搭幻。

那么可以定義一個(gè)2147483647長(zhǎng)度的byte數(shù)組咧擂,用來存儲(chǔ)集合所有可能的值。為了存儲(chǔ)這個(gè)byte數(shù)組檀蹋,系統(tǒng)只需要:2147483647/8/1024/1024=256M松申。

比如:某個(gè)URL(X)的哈希是2,那么落到這個(gè)byte數(shù)組在第二位上就是1俯逾,這個(gè)byte數(shù)組將是:000….00000010贸桶,重復(fù)的,將這20億個(gè)數(shù)全部哈希并落到byte數(shù)組中桌肴。

判斷邏輯:

如果byte數(shù)組上的第二位是1刨啸,那么這個(gè)URL(X)可能存在。為什么是可能识脆?因?yàn)橛锌赡芷渌黆RL因哈希碰撞哈希出來的也是2,這就是誤判。

但是如果這個(gè)byte數(shù)組上的第二位是0灼捂,那么這個(gè)URL(X)就一定不存在集合中离例。

多次哈希:

為了減少因哈希碰撞導(dǎo)致的誤判概率,可以對(duì)這個(gè)URL(X)用不同的哈希算法進(jìn)行N次哈希悉稠,得出N個(gè)哈希值宫蛆,落到這個(gè)byte數(shù)組上,如果這N個(gè)位置沒有都為1的猛,那么這個(gè)URL(X)就一定不存在集合中耀盗。

Guava的BloomFilter

Guava框架提供了布隆過濾器的具體實(shí)現(xiàn):BloomFilter,使得開發(fā)不用再自己寫一套算法的實(shí)現(xiàn)卦尊。

創(chuàng)建BloomFilter

BloomFilter提供了幾個(gè)重載的靜態(tài) create方法來創(chuàng)建實(shí)例:

public static <T> BloomFilter<T> create(Funnel<? super T> funnel, int expectedInsertions, double fpp);
public static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions, double fpp);
public static <T> BloomFilter<T> create(Funnel<? super T> funnel, int expectedInsertions);
public static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions);

最終還是調(diào)用:

static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions, double fpp, Strategy strategy);
// 參數(shù)含義:
// funnel 指定布隆過濾器中存的是什么類型的數(shù)據(jù)叛拷,有:IntegerFunnel,LongFunnel岂却,StringCharsetFunnel忿薇。
// expectedInsertions 預(yù)期需要存儲(chǔ)的數(shù)據(jù)量
// fpp 誤判率,默認(rèn)是0.03躏哩。

BloomFilter里byte數(shù)組的空間大小由 expectedInsertions署浩, fpp參數(shù)決定,見方法:

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)));
}

真正的byte數(shù)組維護(hù)在類:BitArray中扫尺。

使用:

最后通過:put和 mightContain方法筋栋,添加元素和判斷元素是否存在。

算法特點(diǎn)

1正驻、因使用哈希判斷弊攘,時(shí)間效率很高〔ν兀空間效率也是其一大優(yōu)勢(shì)肴颊。2、有誤判的可能渣磷,需針對(duì)具體場(chǎng)景使用婿着。3、因?yàn)闊o法分辨哈希碰撞醋界,所以不是很好做刪除操作竟宋。

使用場(chǎng)景

1、黑名單 2形纺、URL去重 3丘侠、單詞拼寫檢查 4、Key-Value緩存系統(tǒng)的Key校驗(yàn) 5逐样、ID校驗(yàn)蜗字,比如訂單系統(tǒng)查詢某個(gè)訂單ID是否存在打肝,如果不存在就直接返回。

優(yōu)化背景:查詢訂單需要關(guān)聯(lián)預(yù)警訂單數(shù)據(jù)挪捕,由于每查詢一筆預(yù)警就要查詢一次預(yù)警表粗梭,效率低,即是判斷該訂單是否預(yù)警
可以先將預(yù)警的訂單放到布隆過濾器中存放一份级零,則查詢訂單的時(shí)候可以用于關(guān)聯(lián)
應(yīng)用該場(chǎng)景的原因:大部分訂單還是正常的断医,所以沒不要每次去關(guān)聯(lián)
先去布隆過濾器查詢?cè)撚唵问欠翊嬖冢淮嬖趧t直接返回正常奏纪,存在則去預(yù)警表查詢鉴嗤,允許一定的誤差率

Bloom Filter實(shí)戰(zhàn)

使用goole guava輕松實(shí)現(xiàn)bloom filter
  源碼分析 bitArray,numHashFunction,funnel,Strategy,put(),
  Demo實(shí)例
    場(chǎng)景描述:100w字符串放入布隆過濾器,另外隨機(jī)生成1w字符串序调,判斷他們?cè)?00w里面是否存在
    目的醉锅,了解布隆過濾器的簡(jiǎn)單使用;
    了解誤判率對(duì)hash函數(shù)個(gè)數(shù)以及bit數(shù)組長(zhǎng)度的影響
    使用bloom filter解決緩存擊穿的問題

public class BloomFilterTest {
    
    private static final int insertions = 1000000; //100w
    
    @Test
    public void bfTest(){
        //初始化一個(gè)存儲(chǔ)string數(shù)據(jù)的布隆過濾器炕置,初始化大小100w,不能設(shè)置為0
        BloomFilter<String> bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), insertions,0.001);
        //初始化一個(gè)存儲(chǔ)string數(shù)據(jù)的set荣挨,初始化大小100w
        Set<String> sets = new HashSet<>(insertions);
        //初始化一個(gè)存儲(chǔ)string數(shù)據(jù)的set,初始化大小100w
        List<String> lists = new ArrayList<String>(insertions);
        
        //向三個(gè)容器初始化100萬個(gè)隨機(jī)并且唯一的字符串---初始化操作
        for (int i = 0; i < insertions; i++) {
            String uuid = UUID.randomUUID().toString();
            bf.put(uuid);
            sets.add(uuid);
            lists.add(uuid);
        }
        
        int wrong = 0;//布隆過濾器錯(cuò)誤判斷的次數(shù)
        int right = 0;//布隆過濾器正確判斷的次數(shù)
        for (int i = 0; i < 10000; i++) {
            String test = i%100==0?lists.get(i/100):UUID.randomUUID().toString();//按照一定比例選擇bf中肯定存在的字符串
            if(bf.mightContain(test)){
                if(sets.contains(test)){
                    right ++;
                }else{
                    wrong ++;
                }
            }
        }
        
        System.out.println("=================right====================="+right);//100
        System.out.println("=================wrong====================="+wrong);
    }
    
}

解決緩存擊穿

image.png
private BloomFilter<String> bf;

@postConstruct  ------------->初始化的方法
private void init(){
    //將唯一編碼加進(jìn)來
    //初始化布隆過濾器
    bf = BloomFiler.create(Funnels.stringFunner(Charsets.UTF_8),編碼.size()*1.2);
    for(String str:ucodes){
    bf.put(str);
}
========將布隆過濾器的數(shù)據(jù)放到單個(gè)服務(wù),和業(yè)務(wù)代碼分開
使用多線程放進(jìn)去
if(bf.mightContain(usercode)){
    return null;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市慧库,隨后出現(xiàn)的幾起案子霜幼,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鹃操,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門春哨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荆隘,“玉大人,你說我怎么就攤上這事赴背∫埽” “怎么了?”我有些...
    開封第一講書人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵凰荚,是天一觀的道長(zhǎng)燃观。 經(jīng)常有香客問我,道長(zhǎng)便瑟,這世上最難降的妖魔是什么缆毁? 我笑而不...
    開封第一講書人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮到涂,結(jié)果婚禮上脊框,老公的妹妹穿的比我還像新娘颁督。我一直安慰自己,他們只是感情好缚陷,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開白布适篙。 她就那樣靜靜地躺著,像睡著了一般箫爷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上聂儒,一...
    開封第一講書人閱讀 49,842評(píng)論 1 290
  • 那天虎锚,我揣著相機(jī)與錄音,去河邊找鬼衩婚。 笑死窜护,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的非春。 我是一名探鬼主播柱徙,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼奇昙!你這毒婦竟也來了护侮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤储耐,失蹤者是張志新(化名)和其女友劉穎羊初,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體什湘,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡长赞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闽撤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片得哆。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖哟旗,靈堂內(nèi)的尸體忽然破棺而出贩据,到底是詐尸還是另有隱情,我是刑警寧澤热幔,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布乐设,位于F島的核電站,受9級(jí)特大地震影響绎巨,放射性物質(zhì)發(fā)生泄漏近尚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一场勤、第九天 我趴在偏房一處隱蔽的房頂上張望戈锻。 院中可真熱鬧歼跟,春花似錦、人聲如沸格遭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拒迅。三九已至骚秦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間璧微,已是汗流浹背作箍。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留前硫,地道東北人胞得。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像屹电,于是被迫代替她去往敵國(guó)和親阶剑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容