假設如今有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…