前言
Bloom Filter是一个占用空间很小、效率很高的随机数据结构,它由一个bit数组和一组Hash算法构成。可用于判断一个元素是否在一个集合中,查询效率很高(1-N,最优能逼近于1)。
布隆过滤器概念详解可参考:https://blog.csdn.net/jiaomeng/article/details/1495500
常见使用场景
1、缓存击穿
将已存在的缓存放到布隆中,当黑客访问不存在的缓存时迅速返回避免缓存及DB挂掉
比如说:活动详情接口,我们可以缓存所有活动id,每一次调用活动详情接口时先通过布隆过滤器进行一次判断,通过后在查询活动详情的缓存。否则直接将此请求拦截。比如说:活动详情接口,我们可以缓存所有活动id,每一次调用活动详情接口时先通过布隆过滤器进行一次判断,通过后在查询活动详情的缓存。否则直接将此请求拦截。
2、网页爬虫对URL的去重,避免爬取相同的URL地址
3、垃圾邮件地址过滤
优缺点
优点
- 全量存储单不存储数据本身(数据通过k个hash算法计算出来的),对于数据保密有优势
- 空间高效率
- 插入/查询 的时间是常数O(k)
缺点
- 存在误算率,数据越大,误算越高
- 不能从bloomfilter中删除数据,因为不能判断是否真正存在
- 数组长度和hash 个数确认复杂
使用
第一步跑不开是引用maven包~。
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
然后定义我们的布隆过滤器
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;
// 计算bit数组长度
bitSize = optimalNumOfBits(expectedInsertions, fpp);
// 计算hash方法执行次数
numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
}
public 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)));
}
}
定义redisService 结合redis使用
public class BloomRedisService<T> {
private RedisTemplate<String, String> redisTemplate;
private BloomFilterHelper<T> bloomFilterHelper;
public void setBloomFilterHelper(BloomFilterHelper<T> bloomFilterHelper) {
this.bloomFilterHelper = bloomFilterHelper;
}
public void setRedisTemplate(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 根据给定的布隆过滤器添加值
*/
public void addByBloomFilter(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 boolean includeByBloomFilter(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;
}
}
接下来将其塞入到springboot中让ioc管理就可以愉快到使用啦
@Configuration
public class BloomFilterConfig {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Bean
public BloomFilterHelper<String> initBloomFilterHelper() {
return new BloomFilterHelper<>((Funnel<String>) (from, into) -> into.putString(from, Charsets.UTF_8)
.putString(from, Charsets.UTF_8), 1000000, 0.01);
}
/**
* 布隆过滤器bean注入
* @return
*/
@Bean
public BloomRedisService<String> bloomRedisService(){
BloomRedisService<String> bloomRedisService = new BloomRedisService<>();
bloomRedisService.setBloomFilterHelper(initBloomFilterHelper());
bloomRedisService.setRedisTemplate(redisTemplate);
return bloomRedisService;
}
}
使用方式:
初始化布隆过滤器
@Autowired
private BloomRedisService<String> bloomRedisService;
塞入数据, 多条数据循环塞入即可.
bloomRedisService.addByBloomFilter("test","123");
判断数据是否存在.
if (!bloomRedisService.includeByBloomFilter("test","321")){
return;
}
注意KEY需要一致噢~
文章到此结束啦,希望对你有帮助