BloomFilter算法及其適用場景
BloomFilter是利用類似位圖或者位集合數據結構來存儲數據,利用位數組來簡潔的表示一個集合,並且能夠快速的判斷一個元素是不是已經存在於這個集合。因為基於Hash來計算數據所在位置,所以BloomFilter的添加和查詢操作都是O(1)的。因為存儲簡潔,這種數據結構能夠利用較少的內存來存儲海量的數據。那么,還有這種時間和空間兩全其美的算法?當然不是,BloomFilter正是它的高效(使用Hash)帶來了它的判斷不一定是正確的,也就是說准確率不是100%。因為再好的Hash都是存在沖突的,這樣的話同一個位置可能被多次置1。這樣再判斷的時候,有可能一個不存在的數據就會誤判成存在。但是判斷存在的數據一定是存在的。這里需要注意的是這里的Hash和HashMap不同,HashMap可以使用開放定址發、鏈地址法來解決沖突,因為HashMap是有Key-Value結構的,是可逆的,可以定位。但是Hash是不可逆的,所以不能夠解決沖突。雖然BloomFilter不是100%准確,但是可以通過調節參數,使用Hash函數的個數,位數組的大小來降低失誤率。這樣調節完全可以把失誤率降低到接近於0。可以滿足大部分場景了。
關於BloomFilter的理論請參考:
http://blog.csdn.net/jiaomeng/article/details/1495500
https://en.wikipedia.org/wiki/Bloom_filter
適用場景:BloomFilter一般適用於大數據量的對精確度要求不是100%的去重場景。
爬蟲鏈接的去重:大的爬蟲系統有成千上萬的鏈接需要去爬,而且需要保證爬蟲鏈接不能循環。這樣就需要鏈接列表的去重。把鏈接Hash后存放在BitSet中,然后在爬取之前判斷是否存在。
網站UV統計:一般同一個用戶的多次訪問是要過濾掉的,一般大型網站的UV是巨大的,這樣使用BloomFilter就能較高效的實現。
結合Redis
前面說的BloomFilter算法是單機的,可以使用JDK自帶的BitSet來實現。但是擁有大數據量的系統絕不是一台服務器,所以需要多台服務器共享。結合Redis的BitMap就能夠完美的實現這一需求。利用redis的高性能以及通過pipeline將多條bit操作命令批量提交,實現了多機BloomFilter的bit數據共享。唯一需要注意的是redis的bitmap只支持2^32大小,對應到內存也就是512MB,數組的下標最大只能是2^32-1。不過這個限制我們可以通過構建多個redis的bitmap通過hash取模的方式分散一下即可。萬分之一的誤判率,512MB可以放下2億條數據。
實踐
使用了Github上兩個開源的實現測試了一下,是基於JDK BitSet實現的。
開源代碼:https://github.com/MagnusS/Java-BloomFilter
https://github.com/Baqend/Orestes-Bloomfilter
測試結果(在本地測試,耗時是每條數據的耗時):
然后在java-bloomFilter的基礎上修改了源代碼,在有5個節點的Redis集群上做了一下測試。
測試結果:
初始化:173070
插入數據:173070
查詢數據:173070
耗時:350261ns
內存:326KB
失誤率:0.00%
可以看到結合Redis的BloomFilter算法的性能還是比較好的。
Redis+BloomFilter測試源代碼:https://github.com/wxisme/redis-bloomFilter