最近在研究布隆過濾器(若是不瞭解什麼是布隆過濾器的,推薦看這篇 如何判斷一個元素在億級數據中是否存在?瞭解),發現Guava提供了封裝好的類,可是隻能單機使用,通常如今的應用都是部署在分佈式系統的,因此想找個能夠在分佈式系統下使用的布隆過濾器,找了半天只找到一個基於redis開發的模塊項目 ReBloom,可是這個是須要額外安裝的,並且文檔裏只說了怎麼在docker下運行,沒研究過docker因此放棄了。後來找到一篇博客講怎麼利用布隆過濾器統計消息未讀數的(博客地址不記得了,是一位淘寶同窗寫的),博客最後放了一份整合redis和bloomFilter的代碼demo,詳見 BloomFilter.java,看了下實現比較簡單,可是使用方式不是我想要的,因此參考着本身整理了一份。
package com.doodl6.springmvc.service.cache.redis; import com.google.common.base.Preconditions; import com.google.common.hash.Funnel; import com.google.common.hash.Hashing; public class BloomFilterHelper<T> { private int numHashFunctions; private int bitSize; private Funnel<T> funnel; public BloomFilterHelper(Funnel<T> funnel, int expectedInsertions, double fpp) { Preconditions.checkArgument(funnel != null, "funnel不能爲空"); this.funnel = funnel; bitSize = optimalNumOfBits(expectedInsertions, fpp); numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize); } int[] murmurHashOffset(T value) { int[] offset = new int[numHashFunctions]; long hash64 = Hashing.murmur3_128().hashObject(value, funnel).asLong(); int hash1 = (int) hash64; int hash2 = (int) (hash64 >>> 32); for (int i = 1; i <= numHashFunctions; i++) { int nextHash = hash1 + i * hash2; if (nextHash < 0) { nextHash = ~nextHash; } offset[i - 1] = nextHash % bitSize; } return offset; } /** * 計算bit數組長度 */ private int optimalNumOfBits(long n, double p) { if (p == 0) { p = Double.MIN_VALUE; } return (int) (-n * Math.log(p) / (Math.log(2) * Math.log(2))); } /** * 計算hash方法執行次數 */ private int optimalNumOfHashFunctions(long n, long m) { return Math.max(1, (int) Math.round((double) m / n * Math.log(2))); } }
BloomFilterHelper是實現功能的關鍵,包含了計算bitmap的核心算法,其實大部分代碼都是來源於Guava庫裏面的BloomFilterStrategies類,可是由於這個類是專門爲Guava的BloomFilter類使用的,因此沒有對外暴露一些重要的算法邏輯。java
再來看怎麼結合redis一塊兒使用BloomFilterHelpergit
package com.doodl6.springmvc.service.cache.redis; import com.google.common.base.Preconditions; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Collection; import java.util.Map; import java.util.concurrent.TimeUnit; @Service public class RedisService { @Resource private RedisTemplate<String, Object> redisTemplate; /** * 根據給定的布隆過濾器添加值 */ public <T> void addByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) { Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能爲空"); int[] offset = bloomFilterHelper.murmurHashOffset(value); for (int i : offset) { redisTemplate.opsForValue().setBit(key, i, true); } } /** * 根據給定的布隆過濾器判斷值是否存在 */ public <T> boolean includeByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) { Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能爲空"); int[] offset = bloomFilterHelper.murmurHashOffset(value); for (int i : offset) { if (!redisTemplate.opsForValue().getBit(key, i)) { return false; } } return true; } }
RedisService很簡單,只有兩個方法github
addByBloomFilter,往redis裏面添加元素redis
includeByBloomFilter,檢查元素是否在redis bloomFilter裏面算法
這裏redis的客戶端使用的是spring-data-redis封裝的,能夠在個人項目SpringMVC-Project中查看完整的使用代碼。spring