假设如今有50亿个电话号码,如今有1万个电话号码,须要快速判断这些电话号码是否已经存在?java
如今有3中途径git
相似的问题:github
而布隆过滤器则能够解决上述问题redis
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它其实是一个很长的二进制向量和一系列随机映射函数。布隆过滤器能够用于检索一个元素是否在一个集合中。它的优势是空间效率和查询时间都远远超过通常的算法,缺点是有必定的误识别率和删除困难。算法
当一个元素被加入集合时,经过 K 个 Hash 函数将这个元素映射成一个位阵列(Bit array)中的 K 个点,把它们置为 1。检索时,咱们只要看看这些点是否是都是 1 就(大约)知道集合中有没有它了:数据库
若是这些点有任何一个 0,则被检索元素必定不在; 若是都是 1,则被检索元素极可能在。数组
添加元素的原理缓存
1 将要添加的元素给k个hash函数安全
2 获得对应于位数组上的k个位置bash
3 将这k个位置设置成 1
查询元素原理 1 将要查询的元素给k个hash函数
2 获得对应数组的k个元素
3 若是k个位置中有一个为0,则确定不在集合中
4.若是k个位置所有为1,则有可能在集合中
它的优势是空间效率和查询时间都远远超过通常的算法,布隆过滤器存储空间和插入 / 查询时间都是常数O(k)。另外, 散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不须要存储元素自己,在某些对保密要求很是严格的场合有优点。
随着数据的增长,误判率随之增长;;只能判断数据是否必定不存在,而没法判断数据是否必定存在。
若是数据A,通过hash1(A)、hash2(A)、hash3(A),获得其hash值一、三、5,而后咱们在其二进制向量位置一、三、5设置1,而后数据B,通过hash1(B)、hash2(B)、hash3(B),其实hash值也是一、三、5,咱们在作业务处理的时候判断B是否存在的时候发现 其二进制向量位置返回1,认为其已经存在,就跳过相关业务处理,实际上根本不存在,这就是因为hash碰撞引发的问题。也就存在了偏差率。
没法作到删除数据
通常状况下不能从布隆过滤器中删除元素. 咱们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加 1, 这样删除元素时将计数器减掉就能够了。然而要保证安全地删除元素并不是如此简单。首先咱们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是没法保证的。
引入guava pom.xml
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
复制代码
基于Mur3Mur3 hash算法(低碰撞,高性能)
package jedis.bloomFilter;
import com.google.common.hash.Funnels;
import com.google.common.hash.BloomFilter;
import java.util.ArrayList;
import java.util.List;
public class GuavaBloomFilter {
private static int size = 10000;
public static void main(String[] args) {
/**
* 默认偏差率3%。确定不存在以及可能存在
* 可经过构造函数去设置偏差率
* create(
* Funnel<? super T> funnel, int expectedInsertions, double fpp)
*
*/
BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size);
for (int i = 0; i < size; i++) {
bloomFilter.put(i);
}
for (int i = 0; i < size; i++) {
if (!bloomFilter.mightContain(i)) {
System.out.println("有人逃脱了");
}
}
List<Integer> list = new ArrayList<Integer>(1000);
for (int i = size + 10000; i < size + 20000; i++) {
if (bloomFilter.mightContain(i)) {
list.add(i);
}
}
System.out.println("误伤的数量:" + list.size());
}
}
复制代码
误伤的数量:320
复制代码
BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size,0.01);
误伤的数量:100
复制代码
1 基于本地缓存,容量受限制 2 多个应用就有多个布隆过滤器,多应用同步复杂。
主要是把guava布隆过滤器的相关源码提取了出来,用于分布式redis布隆过滤器。
package jedis.bloomFilter.bloomFilterGuava;
import com.google.common.base.Preconditions;
import com.google.common.hash.Funnel;
import com.google.common.hash.Hashing;
//@Configurable
public class BloomFilterHelper<T> {
private int numHashFunctions;//hash循环次数
private int bitSize;//bitsize长度
private Funnel<T> funnel;
/**
* @param funnel
* @param expectedInsertions 指望插入长度
* @param fpp 偏差率
*/
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);
}
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)));
}
}
复制代码
package jedis.bloomFilter.bloomFilterGuava;
import com.google.common.base.Preconditions;
import redis.clients.jedis.JedisCluster;
//@Component
public class RedisBloomFilter {
private JedisCluster cluster;
public RedisBloomFilter(JedisCluster jedisCluster) {
this.cluster = jedisCluster;
}
/**
* 根据给定的布隆过滤器添加值
*/
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) {
cluster.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 (!cluster.getbit(key, i)) {
return false;
}
}
return true;
}
}
复制代码
package jedis.bloomFilter.bloomFilterGuava;
import com.google.common.hash.Funnels;
import redis.clients.jedis.JedisCluster;
import java.nio.charset.Charset;
/**
* 基于guava分布式布隆过滤器
*/
public class Test {
public static void main(String[] args) {
BloomFilterHelper bloomFilterHelper = new BloomFilterHelper<>(Funnels.stringFunnel(Charset.defaultCharset()), 1000, 0.1);
JedisCluster cluster = null;
RedisBloomFilter redisBloomFilter = new RedisBloomFilter( cluster);
int j = 0;
for (int i = 0; i < 100; i++) {
redisBloomFilter.addByBloomFilter(bloomFilterHelper, "bloom", i+"");
}
for (int i = 0; i < 1000; i++) {
boolean result = redisBloomFilter.includeByBloomFilter(bloomFilterHelper, "bloom", i+"");
if (!result) {
j++;
}
}
System.out.println("漏掉了" + j + "个");
}
}
复制代码
redis4.0 以后支持插件支持布隆过滤器 git: 开源项目:github.com/RedisBloom/…
(也可参考)RedisBloom的客户端:github.com/RedisBloom/…
1 下载并编译
$ git clone git://github.com/RedisLabsModules/rebloom
$ cd rebloom
$ make
复制代码
loadmodule /path/to/rebloom.so
复制代码
BF.ADD bloom redis
BF.EXISTS bloom redis
BF.EXISTS bloom nonxist
复制代码
cd /usr/redis-4.0.11
./src/redis-server redis.conf --loadmodule /usr/rebloom/rebloom.so INITIAL_SIZE 1000000 ERROR_RATE 0.0001
# 容量100万, 容错率万分之一
复制代码
bloomFilterAdd.lua
local bloomName = KEYS[1]
local value = KEYS[2]
-- bloomFilter
local result_1 = redis.call('BF.ADD', bloomName, value)
return result_1
复制代码
bloomFilterExist.lua
local bloomName = KEYS[1]
local value = KEYS[2]
-- bloomFilter
local result_1 = redis.call('BF.EXISTS', bloomName, value)
return result_1
复制代码
krisives.github.io/bloom-calcu…