如何使用bloomfilter構建大型Java緩存系統

jopen 10年前發布 | 17K 次閱讀 Java緩存 緩存組件

在如今的軟件當中,緩存是解決很多問題的一個關鍵概念。你的應用可能會進行CPU密集型運算。你當然不想讓這些運算一邊又一邊的重復執行,相反,你可以只執行一次, 把這個結果放在內存中作為緩存。有時系統的瓶頸在I/O操作上,比如你不想重復的查詢數據庫,你想把結果緩存起來,只在數據發生變化時才去數據查詢來更新緩存。

與上面的情況類似,有些場合下我們需要進行快速的查找來決定如何處理新來的請求。例如,考慮下面這種情況,你需要確認一個URL是否指向一個惡意網站,這種需求可能會有很多。如果我們把所有惡意網站的URL緩存起來,那么會占用很大的空間。或者另一種情況,需要確認用戶輸入的字符串是包含了美國的地名。像“華盛頓的博物館”——在這個字符串中,華盛頓是美國的一個地名。我們應該把美國所有的地名保存在內存中然后再查詢嗎?那樣的話緩存會有多大?是否能在不使用數據庫的前提下來高效地完成?

這就是為什么我們要跨越基本的數據結構map,在更高級的數據結構像布隆過濾器(bloomfilter)中來尋找答案。你可以把布隆過濾器看做Java中的集合(collection),你可以往它里面添加元素,查詢某個元素是否存在(就像一個HashSet)。如果布隆過濾器說沒有這個元素,這個結果可能是錯誤的。如果我們在設計布隆過濾器時足夠細心,我們可以把這種出錯的概率控制在可接受范圍內。

解釋

如何使用bloomfilter構建大型Java緩存系統

布隆過濾器被設計為一個具有N的元素的位數組A(bit array),初始時所有的位都置為0.

添加元素

要添加一個元素,我們需要提供k個哈希函數。每個函數都能返回一個值,這個值必須能夠作為位數組的索引(可以通過對數組長度進行取模得到)。然后,我們把位數組在這個索引處的值設為1。例如,第一個哈希函數作用于元素I上,返回x。類似的,第二個第三個哈希函數返回y與z,那么:

A[x]=A[y]=A[z] = 1

查找元素

查找的過程與上面的過程類似,元素將會被會被不同的哈希函數處理三次,每個哈希函數都返回一個作為位數組索引值的整數,然后我們檢測位數組在x、y與z處的值是否為1。如果有一處不為1,那么就說明這個元素沒有被添加到這個布隆過濾器中。如果都為1,就說明這個元素在布隆過濾器里面。當然,會有一定誤判的概率。

算法優化

通過上面的解釋我們可以知道,如果想設計出一個好的布隆過濾器,我們必須遵循以下準則:

  • 好的哈希函數能夠盡可能的返回寬范圍的哈希值。
  • 位數組的大小(用m表示)非常重要:如果太小,那么所有的位很快就都會被賦值為1,這樣就增加了誤判的幾率。
  • 哈希函數的個數(用k表示)對索引值的均勻分配也很重要。
  • </ul>

    計算m的公式如下:

    m = - nlog p / (log2)^2;

    這里p為可接受的誤判率。

    計算k的公式如下:

    k = m/n log(2) ;

    這里k=哈希函數個數,m=位數組個數,n=待檢測元素的個數(后面會用到這幾個字母)。

    哈希算法

    哈希算法是影響布隆過濾器性能的地方。我們需要選擇一個效率高但不耗時的哈希函數,在論文《更少的哈希函數,相同的性能指標:構造一個更好的布隆過濾器》中,討論了如何選用2個哈希函數來模擬k個哈希函數。首先,我們需要計算兩個哈希函數h1(x)與h2(x)。然后,我們可以用這兩個哈希函數來模仿產生k個哈希函數的效果:

    gi(x) = h1(x) + ih2(x);

    這里i的取值范圍是1到k的整數。

    Google guava類庫使用這個技巧實現了一個布隆過濾器,哈希算法的主要邏輯如下:

    long hash64 = …; //calculate a 64 bit hash function
    //split it in two halves of 32 bit hash values  
    int hash1 = (int) hash64;  
    int hash2 = (int) (hash64 >>> 32);
    //Generate k different hash functions with a simple loop
    for (int i = 1; i <= numHashFunctions; i++) {
        int nextHash = hash1 + i * hash2;
    }

    應用

    從數學公式中,我們可以很明顯的知道使用布隆過濾器來解決問題。但是,我們需要很好地理解布隆過濾器所能解決問題的領域。像我們可以使用布隆過濾器來存放美國的所有城市,因為城市的數量是可以大概確定的,所以我們可以確定n(待檢測元素的個數)的值。根據需求來修改p(誤判概率)的值,在這種情況下,我們能夠設計出一個查詢耗時少,內存使用率高的緩存機制。

    實現

    Google Guava類庫有一個實現,查看這個類的構造函數,在這里面需要設置待檢測元素的個數與誤判率。

    import com.google.common.hash.BloomFilter;
    import com.google.common.hash.Funnels;

    //Create Bloomfilter int expectedInsertions = ….; double fpp = 0.03; // desired false positive probability BloomFilter bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("UTF-8")), expectedInsertions,fpp)</pre>

    更多資料

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!