hbase之布隆過濾器

一、布隆過濾器
布隆過濾器(Bloom Filter)是1970由布隆提出的。通過一個很長的二進制向量於一系列隨即哈希函數生成。下面我就將通過以下小節來介紹布隆過濾器:

1、原因與結構解析

2、數學公式

1.1 原因與結構解析
首先,我們應當知道,hash是內存中使用的經典數據結構。

當我們需要判讀一個元素是否在一個集合當中時,我們可以用哈希表來判斷。在集合較小的情況下,hash是可行而且高效的。

然而數據量以PT計的大數據場景中,很多時候,hash便力有未逮。這是因爲在海量數據下hash要佔據鉅額內存空間,這遠遠超過我們能夠提供的內存大小。

例如在黑名單過濾當中,我們有100億的網站黑名單url需要過濾,假設一個url是64bytes。如果我們用hash表來做,那麼我們至少需要6400億字節即640G的內存空間(實際所需空間還遠大於此),空間消耗巨大,必須要多個服務器來同時分攤內存。

然而我們是否能用更加精簡的結構來做這件事呢?布隆過濾器就是這樣一個高度節省空間的結構,並且其時間也遠超一般算法,但是布隆過濾器存在一定的失誤率,例如在網頁URL黑名單過濾中,布隆過濾器絕不會將黑名單中網頁查錯,但是有可能將正常的網頁URL判定爲黑名單當中的,它的失誤可以說是寧可錯殺,不可放過。不過布隆過濾器的失誤率可以調節,下面我們會詳細介紹。

布隆過濾器實際就是一種集合。假設我們有一個數組,它的長度爲L,從0-(L-1)位置上,存儲的不是一個字符串或者整數,而是一個bit,這意味它的取值只能爲0或1.

例如我們實現如下的一個數組:


int[] array = new int[1000];
該數組中有1000個int類型的元素,而每一個int由有4個byte組成,一個byte又由8個bit組成,所以一個int就由32個bit所組成。

所以我們申請含1000整數類型的數組,它就包含32000個bit位。

但是我們如果想將第20001個bit位描黑,將其改爲1,我們需要怎樣做呢。

首先我們的需要定位,這第20001個bit位於哪個整數,接着我們需要定位該bit位於該整數的第幾個bit位。


int index = 20001;
int intIndex = index/32;
int bitIndex = index%32;
然後我們就將其描黑:


array[intIndex] =(array[intIndex] | (1 << bitIndex));
這段代碼可能有些同學不能理解,下面我們詳細解釋一下。我們的數組是0-999的整型數組,但是每個位置上實際保存的是32個bit,我們的intIndex就是代表第幾個整數,定位到625,而bitIndex定位到第625個整數中的第幾個bit,爲1。

所以,我們需要描黑的位置是第625號元素的第1個bit。

而(1 << bitIndex)代表將1左移到1位置,即是代表只有1位置爲1,而其他位置爲零。就相當於獲得了這樣一串二進制數:


00000000 00000000 00000000 00000001
然後我們將這樣的二進制數與array[indIndex]進行邏輯或運算,就能將第625號元素的第一個bit描黑變1,最後我們將描黑後的元素賦值給array[indIndex],完成運算。

這裏我們採用的是int類型的數組,如果我們想更加節省空間,我們就能創建long類型的數組,這樣申請1000個數組空間,我們就能得到64000個bit。

然後我們還能進一步擴展,將數組做成矩陣:


long[][] map = new long[1000][1000];
有了這些基礎以後,我們如何設計黑名單問題呢?

假設我們已經有了一個擁有m個bit的數組,然後我們將一個URL通過哈希函數計算出hashcode,然後hashcode%m將對應位置描黑。

然而這還不是真正的布隆過濾器,真正的布隆過濾器是通過多個哈希函數對一個URL進行計算,得到hashcode,然後在對不同位置的bit進行描黑。

注意:布隆過濾器採用的多個哈希函數必須是相互獨立的,前面我們已經介紹瞭如何通過一個哈希函數構造多個獨立哈希函數的方法。

當我們將一個URL通過n個哈希函數,得到hashcode,模以m,再將對應位置描黑之後。我們可以說該URL已經進入我們的布隆過濾器當中了。

接下來,我們將黑名單中所有URL都通過哈希函數,進入布隆過濾器中(布隆過濾器並不真正存儲實際的URL)。

對於一個新的URL,我們要查詢其是否在黑名單中,我們便通過同樣的n個哈希函數,計算出n個位置,然後我們查詢這n個位置是否都被描黑,如果都被描黑,我們就說該URL在黑名單當中。如果n個位置但凡有一個不爲黑,我們就說該URL不在該黑名單中。

注意:我們的數組不應該過小,不然很可能數組中的大多數位置都被描黑,這很容易將正常的URL判定爲不合法的,這也是布隆過濾器的失誤率來源。

3.2 數學公式
通過上一小節的介紹,我們可以直觀的感覺到,我們可以通過調整哈希函數n的大小以及數組m的大小來控制失誤率。

事實確實如此,下面我們就介紹有有關哈希函數數量,數組大小,以及失誤率的數學推論。

首先,我們要使用多少個哈希函數呢?

這一點只與我們黑名單中URL的數量及bit數組長度有關,而與單個URL的大小無關(哈希函數的輸入域是無窮的)。它只要求我們的哈希函數能夠接受URL這一參數類型。

而我們的數組或者矩陣的大小長度與什麼有關呢?

它同樣與我們黑名單中URL的數量有關,除此之外它還與我們能夠接受的失誤率有關。

下面我們給出有關公式:


 m= - n*lnp /(ln2)^2  #n爲樣本數量 p爲預計的失誤率
當我們的樣本量爲100億,而我們預計的失誤率爲萬分之一,根據這個公式我們便可以得到m 爲:


131571428572 bit
其單位爲bit,但我們實際要用的是byte,所以還要除以8,最後需要的空間爲23GB(向上取整)。

對比哈希表,原來我們需要640GB,而現在只需要23GB,大大節省了內存空間消耗。

而我們所需要的哈希函數的個數k的數學公式爲:


k = ln2 * m/k #m爲數組長度 n爲樣本數量 k向上取整
這裏經過計算n爲13。

因爲我們的m和n都經過了向上取整,所以我們的實際失誤率會變得更低。失誤率的計算公式爲:


p = (1-e^(-k*n/m))^k#n爲樣本數量,m爲數組長度,k爲哈希函數個數
經過我們向上取整後,計算出來的實際失誤率爲十萬分之六。

二、hbase中的布隆過濾器
布隆過濾器是hbase中的高級功能,它能夠減少特定訪問模式(get/scan)下的查詢時間。不過由於這種模式增加了內存和存儲的負擔,所以被默認爲關閉狀態。

hbase支持如下類型的布隆過濾器:

1、NONE          不使用布隆過濾器

2、ROW           行鍵使用布隆過濾器

3、ROWCOL    列鍵使用布隆過濾器

其中ROWCOL是粒度更細的模式。

2.1原因
在介紹爲什麼hbase要引入布隆過濾器之前,我們先來了解一下hbase存儲文件HFile的塊索引機制

我們知道hbase的實際存儲結構是HFile,它是位於hdfs系統中的,也就是在磁盤中。而加載到內存中的數據存儲在MemStore中,當MemStore中的數據達到一定數量時,它會將數據存入HFile中。

HFIle是由一個個數據塊與索引塊組成,他們通常默認爲64KB。hbase是通過塊索引來訪問這些數據塊的。而索引是由每個數據塊的第一行數據的rowkey組成的。當hbase打開一個HFile時,塊索引信息會優先加載到內存當中。

然後hbase會通過這些塊索引來查詢數據。

但是塊索引是相當粗粒度的,我們可以簡單計算一下。假設一個行佔100bytes的空間,所以一個數據塊64KB,所包含的行大概有:(64 * 1024)/100 = 655.53 = ~700行。而我們只能從索引給出的一個數據塊的起始行開始查詢。

如果用戶隨機查找一個行鍵,則這個行鍵很可能位於兩個開始鍵(即索引)之間的位置。對於hbase來說,它判斷這個行鍵是否真實存在的唯一方法就是加載這個數據塊,並且掃描它是否包含這個鍵。

同時,還存在很多情況使得這種情況更加複雜。

對於一個應用來說,用戶通常會以一定的速率進行更新數據,這就將導致內存中的數據被刷寫到磁盤中,並且之後系統會將他們合併成更大的存儲文件。在hbase的合併存儲文件的時候,它僅僅會合並最近幾個存儲文件,直至合併的存儲文件到達配置的最大大小。最終系統中會有很多的存儲文件,所有的存儲文件都是候選文件,其可能包含用戶請求行鍵的單元格。如下圖所示:

我們可以看到,這些不同的文件都來着同一個列族,所以他們的行鍵分佈類似。所以,雖然我們要查詢更新的特定行只在某個或者某幾個文件中,但是採用塊索引方式,還是會覆蓋整個行鍵範圍。當塊索引確定那些塊可能含有某個行鍵後,regionServer需要加載每一個塊來檢查該塊中是否真的包含該行的單元格。

2.2作用
從4.1小節當中我們可以知道,當我們隨機讀get數據時,如果採用hbase的塊索引機制,hbase會加載很多塊文件。如果採用布隆過濾器後,它能夠準確判斷該HFile的所有數據塊中,是否含有我們查詢的數據,從而大大減少不必要的塊加載,從而增加hbase集羣的吞吐率。

這裏有幾點細節:

1、布隆過濾器的存儲在哪?
對於hbase而言,當我們選擇採用布隆過濾器之後,HBase會在生成StoreFile(HFile)時包含一份布隆過濾器結構的數據,稱其爲MetaBlock;MetaBlock與DataBlock(真實的KeyValue數據)一起由LRUBlockCache維護。所以,開啓bloomfilter會有一定的存儲及內存cache開銷。但是在大多數情況下,這些負擔相對於布隆過濾器帶來的好處是可以接受的。

2、採用布隆過濾器後,hbase如何get數據?
在讀取數據時,hbase會首先在布隆過濾器中查詢,根據布隆過濾器的結果,再在MemStore中查詢,最後再在對應的HFile中查詢。

3、採用ROW還是ROWCOL布隆過濾器?
這取決於用戶的使用模式。如果用戶只做行掃描,使用更加細粒度的行加列布隆過濾器不會有任何的幫助,這種場景就應該使用行級布隆過濾器。當用戶不能批量更新特定的一行,並且最後的使用存儲文件都含有改行的一部分時,行加列級的布隆過濾器更加有用。

例如:ROW 使用場景假設有2個Hfile文件hf1和hf2, hf1包含kv1(r1 cf:q1 v)、kv2(r2 cf:q1 v) hf2包含kv3(r3 cf:q1 v)、kv4(r4 cf:q1 v) 如果設置了CF屬性中的bloomfilter(布隆過濾器)爲ROW,那麼get(r1)時就會過濾hf2,get(r3)就會過濾hf1 。

ROWCOL使用場景假設有2個Hfile文件hf1和hf2, hf1包含kv1(r1 cf:q1 v)、kv2(r2 cf:q1 v) hf2包含kv3(r1 cf:q2 v)、kv4(r2 cf:q2 v) 如果設置了CF屬性中的bloomfilter爲ROW,無論get(r1,q1)還是get(r1,q2),都會讀取hf1+hf2;而如果設置了CF屬性中的bloomfilter爲ROWCOL,那麼get(r1,q1)就會過濾hf2,get(r1,q2)就會過濾hf1。

注意:ROW和ROWCOL只是名字上有聯繫,但是ROWCOL並不是ROW的擴展,也不能取代ROW


文章出處:https://blog.csdn.net/qq_38180223/article/details/80922114

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章