当需要判断某个数据在数据库是否存在的时候(比如判断某条记录的id是否存在),如果数据量小的时候,可以直接从数据库进行查询,当数据量非常大,而且查询又很频繁的时候,频繁的查询就会成为性能瓶颈。此时就可以考虑使用布隆过滤器来提高查询效率。布隆过滤器可以判断 “某样东西一定不存在或者可能存在”。
大致的思路是先保存数据的hash值的映射,判断数据是否存在时,先计算hash值,然后再判断对应的hash值的映射是否存在。
1. 单节点内存布隆过滤器
1.1 添加依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
1.2 测试代码
package com.demo;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
public class BloomFilterDemo {
private static int size = 1000000;//预计要插入多少数据
private static double fpp = 0.01;//期望的误判率
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);
public static void main(String[] args) {
//插入数据
for (int i = 0; i < 1000000; i++) {
bloomFilter.put(i);
}
int count = 0;
for (int i = 1000000; i < 2000000; i++) {
if (bloomFilter.mightContain(i)) {
count++;
System.out.println(i + "误判了");
}
}
System.out.println("总共的误判数:" + count);
}
}
1.3 执行测试
1999640误判了
1999697误判了
1999827误判了
1999942误判了
总共的误判数:10314
单节点布隆过滤器不适合多节点集群环境下的应用共享,将数据保存在内存中,系统重启也会导致数据丢失。
2. redis布隆过滤器
redis具有访问速度快等特点。
2.1 添加依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
2.2 测试代码
package com.demo;
import com.google.common.hash.Funnels;
import com.google.common.hash.Hashing;
import redis.clients.jedis.Jedis;
import java.nio.charset.Charset;
public class RedisBloomFilter {
static final int expectedInsertions = 1000000 * 100;//要插入多少数据
static final double fpp = 0.001;//期望的误判率
//bit数组长度
private static long numBits;
//hash函数数量
private static int numHashFunctions;
static {
numBits = optimalNumOfBits(expectedInsertions, fpp);
numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
}
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
for (int i = 0; i < 100000; i++) {
long[] indexs = getIndexs(String.valueOf(i));
for (long index : indexs) {
jedis.setbit("codebear:bloom", index, true);
}
}
long start = System.currentTimeMillis();
for (int i = 200000; i < 300001; i++) {
long[] indexs = getIndexs(String.valueOf(i));
boolean norepeat = true;
for (long index : indexs) {
Boolean isContain = jedis.getbit("codebear:bloom", index);
if (!isContain) {
norepeat = true;
// System.out.println(i + "肯定没有重复");
break;
} else {
norepeat = false;
}
}
if (norepeat == true) {
System.out.println(i + "肯定没有重复");
} else {
System.out.println(i + "可能重复");
}
}
long end = System.currentTimeMillis();
System.out.print("time spend is:" + (end - start));
}
/**
* 根据key获取bitmap下标
*/
private static long[] getIndexs(String key) {
long hash1 = hash(key);
long hash2 = hash1 >>> 16;
long[] result = new long[numHashFunctions];
for (int i = 0; i < numHashFunctions; i++) {
long combinedHash = hash1 + i * hash2;
if (combinedHash < 0) {
combinedHash = ~combinedHash;
}
result[i] = combinedHash % numBits;
}
return result;
}
private static long hash(String key) {
Charset charset = Charset.forName("UTF-8");
return Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asLong();
}
//计算hash函数个数
private static int optimalNumOfHashFunctions(long n, long m) {
return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
}
//计算bit数组长度
private static long optimalNumOfBits(long n, double p) {
if (p == 0) {
p = Double.MIN_VALUE;
}
return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
}
}
2.3 执行测试
299998肯定没有重复
299999肯定没有重复
300000肯定没有重复
time spend is:5774
10万个数据的判断,总共耗时5774毫秒,相当于1毫秒可以进行20次判断。
通过将数据保存到redis中,可以实现多个节点之间的数据共享,借助于redis的持久化机制,即使redis重启,仍然可以保证重启之后,数据不丢失。