BloomFilter–大规模数据处理利器(转)
BloomFilter–大規模數據處理利器
Bloom Filter是由Bloom在1970年提出的一種多哈希函數映射的快速查找算法。通常應用在一些需要快速判斷某個元素是否屬于集合,但是并不嚴格要求100%正確的場合。
一.?實例
為了說明Bloom Filter存在的重要意義,舉一個實例:
假設要你寫一個網絡爬蟲程序(web crawler)。由于網絡間的鏈接錯綜復雜,爬蟲在網絡間爬行很可能會形成“環”。為了避免形成“環”,就需要知道爬蟲程序已經訪問過那些URL。給一個URL,怎樣知道爬蟲程序是否已經訪問過呢?稍微想想,就會有如下幾種方案:
1.?將訪問過的URL保存到數據庫。
2.?用HashSet將訪問過的URL保存起來。那只需接近O(1)的代價就可以查到一個URL是否被訪問過了。
3. URL經過MD5或SHA-1等單向哈希后再保存到HashSet或數據庫。
4. Bit-Map方法。建立一個BitSet,將每個URL經過一個哈希函數映射到某一位。
方法1~3都是將訪問過的URL完整保存,方法4則只標記URL的一個映射位。
以上方法在數據量較小的情況下都能完美解決問題,但是當數據量變得非常龐大時問題就來了。
方法1的缺點:數據量變得非常龐大后關系型數據庫查詢的效率會變得很低。而且每來一個URL就啟動一次數據庫查詢是不是太小題大做了?
方法2的缺點:太消耗內存。隨著URL的增多,占用的內存會越來越多。就算只有1億個URL,每個URL只算50個字符,就需要5GB內存。
方法3:由于字符串經過MD5處理后的信息摘要長度只有128Bit,SHA-1處理后也只有160Bit,因此方法3比方法2節省了好幾倍的內存。
方法4消耗內存是相對較少的,但缺點是單一哈希函數發生沖突的概率太高。還記得數據結構課上學過的Hash表沖突的各種解決方法么?若要降低沖突發生的概率到1%,就要將BitSet的長度設置為URL個數的100倍。
二. Bloom Filter的算法
???廢話說到這里,下面引入本篇的主角–Bloom Filter。其實上面方法4的思想已經很接近Bloom Filter了。方法四的致命缺點是沖突概率高,為了降低沖突的概念,Bloom Filter使用了多個哈希函數,而不是一個。
?? Bloom Filter算法如下:
???創建一個m位BitSet,先將所有位初始化為0,然后選擇k個不同的哈希函數。第i個哈希函數對字符串str哈希的結果記為h(i,str),且h(i,str)的范圍是0到m-1?。
(1)?加入字符串過程
下面是每個字符串處理的過程,首先是將字符串str“記錄”到BitSet中的過程:
對于字符串str,分別計算h(1,str),h(2,str)…… h(k,str)。然后將BitSet的第h(1,str)、h(2,str)…… h(k,str)位設為1。
?
很簡單吧?這樣就將字符串str映射到BitSet中的k個二進制位了。
(2)?檢查字符串是否存在的過程
下面是檢查字符串str是否被BitSet記錄過的過程:
對于字符串str,分別計算h(1,str),h(2,str)…… h(k,str)。然后檢查BitSet的第h(1,str)、h(2,str)…… h(k,str)位是否為1,若其中任何一位不為1則可以判定str一定沒有被記錄過。若全部位都是1,則“認為”字符串str存在。
若一個字符串對應的Bit不全為1,則可以肯定該字符串一定沒有被Bloom Filter記錄過。(這是顯然的,因為字符串被記錄過,其對應的二進制位肯定全部被設為1了)
但是若一個字符串對應的Bit全為1,實際上是不能100%的肯定該字符串被Bloom Filter記錄過的。(因為有可能該字符串的所有位都剛好是被其他字符串所對應)這種將該字符串劃分錯的情況,稱為false positive?。
(3)?刪除字符串過程
???字符串加入了就被不能刪除了,因為刪除會影響到其他字符串。實在需要刪除字符串的可以使用Counting bloomfilter(CBF),這是一種基本Bloom Filter的變體,CBF將基本Bloom Filter每一個Bit改為一個計數器,這樣就可以實現刪除字符串的功能了。
Bloom Filter跟單哈希函數Bit-Map不同之處在于:Bloom Filter使用了k個哈希函數,每個字符串跟k個bit對應。從而降低了沖突的概率。
三. Bloom Filter參數選擇
?(1)哈希函數選擇
??? 哈希函數的選擇對性能的影響應該是很大的,一個好的哈希函數要能近似等概率的將字符串映射到各個Bit。選擇k個不同的哈希函數比較麻煩,一種簡單的方法是選擇一個哈希函數,然后送入k個不同的參數。
(2) m,n,k值,我們如何取值
我們定義:
可能把不屬于這個集合的元素誤認為屬于這個集合(False Positive)
不會把屬于這個集合的元素誤認為不屬于這個集合(False Negative)。
?
哈希函數的個數k、位數組大小m、加入的字符串數量n的關系。哈希函數個數k取10,位數組大小m設為字符串個數n的20倍時,false positive發生的概率是0.0000889?,即10萬次的判斷中,會存在9次誤判,對于一天1億次的查詢,誤判的次數為9000次。?
?
算法分析:
我們假設kn<m且各個哈希函數是完全隨機的。當集合S={x1, x2,…,xn}的所有元素都被k個哈希函數映射到m位的位數組中時,這個位數組中某一位還是0的概率是:
False Positive的概率是:
p’表示1的概率,k次方表示8次hash都為1的概率。
?當?k = ln 2 * m/n 時,右邊的等式值最小,此時等式轉變成:
?
?四. Bloom Filter實現代碼(簡易版)
?? 下面給出一個簡單的Bloom Filter的Java實現代碼:
package org.magnus.utils; import java.util.BitSet; //傳統的Bloom filter 不支持從集合中刪除成員。 //Counting Bloom filter由于采用了計數,因此支持remove操作。 //基于BitSet來實現,性能上可能存在問題 public class SimpleBloomFilter {//DEFAULT_SIZE為2的25次方private static final int DEFAULT_SIZE = 2 << 24;/* 不同哈希函數的種子,一般應取質數,seeds數據共有7個值,則代表采用7種不同的HASH算法 */private static final int[] seeds = new int[] { 5, 7, 11, 13, 31, 37, 61 };//BitSet實際是由“二進制位”構成的一個Vector。假如希望高效率地保存大量“開-關”信息,就應使用BitSet.//BitSet的最小長度是一個長整數(Long)的長度:64位private BitSet bits = new BitSet(DEFAULT_SIZE);/* 哈希函數對象 */private SimpleHash[] func = new SimpleHash[seeds.length];public static void main(String[] args) {String value = "stone2083@yahoo.cn";//定義一個filter,定義的時候會調用構造函數,即初始化七個hash函數對象所需要的信息。SimpleBloomFilter filter = new SimpleBloomFilter();//判斷是否包含在里面。因為沒有調用add方法,所以肯定是返回falseSystem.out.println(filter.contains(value));filter.add(value);System.out.println(filter.contains(value));}//構造函數public SimpleBloomFilter() {for (int i = 0; i < seeds.length; i++) {//給出所有的hash值,共計seeds.length個hash值。共7位。//通過調用SimpleHash.hash(),可以得到根據7種hash函數計算得出的hash值。//傳入DEFAULT_SIZE(最終字符串的長度),seeds[i](一個指定的質數)即可得到需要的那個hash值的位置。func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);}}// 將字符串標記到bits中,即設置字符串的7個hash值函數為1public void add(String value) {for (SimpleHash f : func) {bits.set(f.hash(value), true);}}//判斷字符串是否已經被bits標記public boolean contains(String value) {//確保傳入的不是空值if (value == null) {return false;}boolean ret = true;//計算7種hash算法下各自對應的hash值,并判斷for (SimpleHash f : func) {//&&是boolen運算符,只要有一個為0,則為0。即需要所有的位都為1,才代表包含在里面。//f.hash(value)返回hash對應的位數值//bits.get函數返回bitset中對應position的值。即返回hash值是否為0或1。ret = ret && bits.get(f.hash(value));}return ret;}/* 哈希函數類 */public static class SimpleHash {//cap為DEFAULT_SIZE的值,即用于結果的最大的字符串長度。//seed為計算hash值的一個給定key,具體對應上面定義的seeds數組private int cap;private int seed;public SimpleHash(int cap, int seed) {this.cap = cap;this.seed = seed;}//計算hash值的具體算法,hash函數,采用簡單的加權和hashpublic int hash(String value) {//int的范圍最大是2的31次方減1,或超過值則用負數來表示int result = 0;int len = value.length();for (int i = 0; i < len; i++) {//數字和字符串相加,字符串轉換成為ASCII碼result = seed * result + value.charAt(i);//System.out.println(result+"--"+seed+"*"+result+"+"+value.charAt(i));}// System.out.println("result="+result+";"+((cap - 1) & result));// System.out.println(414356308*61+'h'); 執行此運算結果為負數,為什么?//&是java中的位邏輯運算,用于過濾負數(負數與進算轉換成反碼進行)。return (cap - 1) & result;}} }五:Bloom Filter的優點及應用。
1.2????優缺點分析
1.2.1????????優點:
節約緩存空間(空值的映射),不再需要空值映射。
減少數據庫或緩存的請求次數。
提升業務的處理效率以及業務隔離性。
?
1.2.2????????缺點:
存在誤判的概率。
傳統的Bloom Filter不能作刪除操作。
?
1.3????使用場景
?????????? 適用于特定場景,能夠有效的解決數據庫空查問題。
?????????以公司的某小表查詢為例,該表每天查詢量20億次左右,且數據庫中存在大量的下面的空查:
???????? 目前表中的記錄為8w,即n的值為8w, m=20*n=160w,占用空間大小195KB。以type||CONTENT復合鍵作為key值,假設HASH次數k取值為6,誤判率為:0.0303%(10000次中存在3次誤判)。HASH次數的最優解為14,當k=14時,誤判率為:0.014%(10000次中存在1-2次誤判)。
測試過程及結果如下(源代碼見附件):
| 測試場景1:m=1600000;n=80000;最優解k=14;m/n=20;k的次數為:6;對1000w數據進行判定: ? ??????? ?測試結果: 2000w數據誤判的記錄為:3035,誤判率約為0.03035%(和理論值0.0303%相差不大)。判斷2000萬數據的時間為25秒。平均一次判斷時間為:2.5微秒。平均一次hash時間為0.417微秒。 |
| 測試場景2:m=1600000;n=80000;最優解k=14;m/n=20;k的次數為:6;對2000w數據進行判定:????測試結果:2000w數據誤判的記錄為:5839,誤判率約為0.029%(理論值為0. 0303%)。判斷1000萬數據的時間為51秒。平均一次判斷時間為:2.55微秒。平均一次hash時間為0.425微秒。 |
| 測試場景3:m=1600000;n=80000;最優解k=14;m/n=20;k的次數為:14;對1000w數據進行判定?:??測試結果:1000w數據誤判的記錄為:605,誤判率約為0.00605%(和理論值0. 014%相差不大)。判斷1000萬數據的時間為37秒。平均一次判斷時間為:3.7微秒。平均一次hash時間為0.265微秒。? |
?
| 測試場景4:m=1600000;n=80000;最優解k=14;m/n=20;k的次數為:14;對2000w數據進行判定:?測試結果:2000w數據誤判的記錄為:1224,誤判率約為0.00612%(理論值為0.014%)。判斷1000萬數據的時間為84秒。平均一次判斷時間為:4.2微秒。平均一次hash時間為0.3微秒。 |
?
?????????其它測試略。
?
結論:
| 測試 | m/n | K(括號內為最優解) | 數據基數 | 誤判數 | 誤判率 | 理論值 | 用時(單位:秒) | 一次判定時間(單位:微秒) | 一次Hash時間(單位:微秒.估參考) |
| 1 | 20 | 6(14) | 1000W | 3035 | 0.03035% | 0.0303% | 25 | 2.5 | 0.417 |
| 2 | 20 | 6(14) | 2000W | 5839 | 0.029% | 0.0303% | 51 | 2.55 | 0.425 |
| 3 | 20 | 14(14) | 1000W | 605 | 0.00605% | 0.014% | 37 | 3.7 | 0.265 |
| 4 | 20 | 14(14) | 2000W | 1224 | 0.00612% | 0.014% | 84 | 4.2 | 0.3 |
| 5 | 20 | 20(14) | 1000W | 914 | 0.00914% | 不計算 | 48 | 4.8 | 0.24 |
| 6 | 20 | 20(14) | 2000W | 1881 | 0.00941% | 不計算 | 99 | 4.95 | 0.2475 |
| 7 | 10 | 7(7) | 1000w | 517854 | 0.786% | 0.819% | 41 | 4.1 | 0.59 |
| 8 | 5 | 3(3) | 1000w | 901411 | 9.014% | 9.2% | 31 | 3.1 | 1.033 |
| 9 | 2 | 1(1) | 1000w | 3910726 | 39.107% | 39.3% | 29 | 2.9 | 2.9 |
| 10 | 2 | 2(1) | 1000w | 3961065 | 39.61% | 40% | 30 | 3.0 | 3.0 |
| 11 | 2 | 5(1) | 1000w | 6436696 | 64.37% | 不計算 | 76 | 7.6 | 1.52 |
?
一次判斷時間計算方式為:總時間/總次數
一次HASH所需時間計算方式為:一次判定時間/每次判斷需要的hash數。
一次HASH所需時間,當執行hash次數越少,基數越小,誤差越大。當一次判斷所需的hash次數越大時,一次hash時間越精確。
?
結論:
m/n的比值越大越好,比較越大,誤判率會越代,但同時會使用更多的空間成本。
???????? Hash次數增加帶來的收益并不大。需要在條件允許的情況下,盡量的擴大m/n的值。
?
六:實施方案思考
?????????適用于一些黑名單,垃圾郵件等的過濾。
?????????當位數組較小時,可以作本地jvm緩存。
當位數組較大時,可以做基于tair的緩存,此時可能需要開辟單獨的應用來提供查詢支持。
?????????此方案,適用的應用場景需要能夠容忍,位數組和的延時。
轉載于:https://www.cnblogs.com/bendantuohai/p/4708039.html
總結
以上是生活随笔為你收集整理的BloomFilter–大规模数据处理利器(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hdu5348 MZL's endles
- 下一篇: dede_arctype|栏目表