如何使用bloomfilter構建大型Java快取系統
背景
在如今的軟體當中,快取是解決很多問題的一個關鍵概念。你的應用可能會進行CPU密集型運算。你當然不想讓這些運算一邊又一邊的重複執行,相反,你可以只執行一次, 把這個結果放在記憶體中作為快取。有時系統的瓶頸在I/O操作上,比如你不想重複的查詢資料庫,你想把結果快取起來,只在資料發生變化時才去資料查詢來更新快取。
與上面的情況類似,有些場合下我們需要進行快速的查詢來決定如何處理新來的請求。例如,考慮下面這種情況,你需要確認一個URL是否指向一個惡意網站,這種需求可能會有很多。如果我們把所有惡意網站的URL快取起來,那麼會佔用很大的空間。或者另一種情況,需要確認使用者輸入的字串是包含了美國的地名。像“華盛頓的博物館”——在這個字串中,華盛頓是美國的一個地名。我們應該把美國所有的地名儲存在記憶體中然後再查詢嗎?那樣的話快取會有多大?是否能在不使用資料庫的前提下來高效地完成?
這就是為什麼我們要跨越基本的資料結構map,在更高階的資料結構像布隆過濾器(bloomfilter)中來尋找答案。你可以把布隆過濾器看做Java中的集合(collection),你可以往它裡面新增元素,查詢某個元素是否存在(就像一個HashSet)。如果布隆過濾器說沒有這個元素,這個結果可能是錯誤的。如果我們在設計布隆過濾器時足夠細心,我們可以把這種出錯的概率控制在可接受範圍內。
解釋
布隆過濾器被設計為一個具有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表示)對索引值的均勻分配也很重要。
計算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<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("UTF-8")), expectedInsertions,fpp)
更多資料
- Bloom Filter Implementation in Java on GitHub
- Google Guava BloomFilter
- See Your Solr Cache Sizes: Eclipse Memory Analyzer
相關文章
- 使用Java和Redis構建高效能的快取系統JavaRedis快取
- 微信小程式大型系統架構中應用Redis快取要點微信小程式架構Redis快取
- 系統架構設計:程式快取和快取服務,如何抉擇?架構快取
- 大型分散式網站架構:快取在分散式系統中的應用分散式網站架構快取
- 如何構建Vue大型應用Vue
- 如何設計快取系統:快取穿透,快取擊穿,快取雪崩解決方案分析快取穿透
- 探討下如何更好的使用快取 —— Redis快取的特殊用法以及與本地快取一起構建多級快取的實現快取Redis
- 如何構建大型的前端專案前端
- Java進階專題(十八) 系統快取架構設計 (下)Java快取架構
- Java進階專題(十七) 系統快取架構設計 (上)Java快取架構
- 如何快速清除 Ubuntu 的系統快取Ubuntu快取
- 如何構建推薦系統
- 如何構建一個系統?
- 大型系統的重構
- 使用 JavaFX 構建 Reactive 系統JavaReact
- Java高併發快取架構,快取雪崩、快取穿透之謎Java快取架構穿透
- 如何使用 Redis 快取Redis快取
- 使用AngularJS構建大型Web應用AngularJSWeb
- 常用快取系統使用經驗總結快取
- 系統效能提升利刃 | 快取技術使用快取
- 億級流量電商詳情頁系統的大型高併發與高可用快取架構實戰快取架構
- 【Django】Django快取系統Django快取
- 理解分散式系統中的快取架構(下)分散式快取架構
- 理解分散式系統中的快取架構(上)分散式快取架構
- 億級系統的Redis快取如何設計???Redis快取
- 為構建大型複雜系統而生的微服務框架 Erda Infra微服務框架
- win10系統如何更改系統快取檔案路徑Win10快取
- 如何構建設計語言系統
- 如何構建自己的筆記系統?筆記
- Java分散式鍵-值快取系統VoldemortJava分散式快取
- 大型網站系統架構演化網站架構
- 分散式系統快取系列一 認識快取分散式快取
- 使用LangGraph構建多Agent系統架構!架構
- 如何使用NodeJS構建基於RPC的API系統NodeJSRPCAPI
- 使用 Proxy 構建響應式系統
- 鵝廠如何構建大型基礎網路平臺
- 系統快取全解析2:頁面輸出快取快取
- 系統快取全解析5:檔案快取依賴快取