布隆过滤器
简介
- 介绍:布隆过滤器是一种比较巧妙的概率型数据结构,是一个bit向量或者说是一个bit数组。用于判断一个元素是否在计划中。
- 特点:可以高效的插入和查询,不支持删除。查询结果:元素一定不存在,或者可能存在。
- 比较:优点:相对与传统的list,set,map等数据结构,更高效和占用空间更少;缺点:返回结果具有概率性,不确定。
算法描述(不写上PPT,用于描述)
- 增:为了添加一个元素,用k个hash function将它hash得到bloom filter中k个bit位,将这k个bit位置1。
- 删:不允许remove元素,因为那样的话会把相应的k个bits位置为0,而其中很有可能有其他元素对应的位。因此remove会引入false negative,这是绝对不被允许的。
- 查:为了query一个元素,即判断它是否在集合中,用k个hash function将它hash得到k个bit位。若这k bits全为1,则此元素在集合中;若其中任一位不为1,则此元素比不在集合中(因为如果在,则在add时已经把对应的k个bits位置为1)。
当add的元素过多时,即n/m在这里插入代码片
过大时(n是元素数,m是bloom filter的bits数),会导致false positive过高,此时就需要重新组建filter,但这种情况相对少见。
注意:如果一个位置多次被置为1,那么只有第一次会起作用,后面几次将没有任何效果。
如何选择哈希值和布隆过滤器长度?
k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率。
实际用处
- 减少磁盘IO
- 网络请求
- 垃圾邮件过滤的黑白名单方法
- 爬虫的网址判重
- 防止缓存击穿
哈希函数选择
选择性能较高的哈希函数,推荐 MurmurHash、Fnv 这些。
当k很大时,设计k个独立的hash function是不现实并且困难的。对于一个输出范围很大的hash function(例如MD5产生的128 bits数),如果不同bit位的相关性很小,则可把此输出分割为k份。或者可将k个不同的初始值(例如0,1,2, … ,k-1)结合元素,feed给一个hash function从而产生k个不同的数。
大Value拆分
Redis 因其支持 setbit 和 getbit 操作,且纯内存性能高等特点,因此天然就可以作为布隆过滤器来使用。但是布隆过滤器的不当使用极易产生大 Value,增加 Redis 阻塞风险,因此生成环境中建议对体积庞大的布隆过滤器进行拆分。
拆分的形式方法多种多样,但是本质是不要将 Hash(Key) 之后的请求分散在多个节点的多个小 bitmap (本质是Redis中的string)上,而是应该拆分成多个小 bitmap 之后,对一个 Key 的所有哈希函数都落在这一个小 bitmap 上。
使用例子
guava实现布隆过滤器
- 添加依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>22.0</version>
</dependency>
- 测试demo
public class TestDemo {
// 初始化大小
private static int size = 1000000;
// 创建布隆过滤器
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size);
public static void main(String[] args) {
// 初始化布隆过滤器
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());
}
}
redis实现布隆过滤器
- 引入依赖
<dependency>
<groupId>com.redislabs</groupId>
<artifactId>jrebloom</artifactId>
<version>1.0.1</version>
</dependency>
测试代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.util.Hashing;
public class RedisMain {
static final int expectedInsertions = 1000;//要插入多少数据
static final double fpp = 0.01;//期望的误判率
//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("127.0.0.1", 6379);
for (int i = 0; i < 1000; i++) {
long[] indexArray = getIndexArray(String.valueOf(i));
for (long index : indexArray) {
jedis.setbit("codebear:bloom", index, true);
}
}
int num = 0;
for (int i = 1000; i < 2000; i++) {
long[] indexArray = getIndexArray(String.valueOf(i));
for (long index : indexArray) {
if (!jedis.getbit("codebear:bloom", index)) {
System.out.println(i + "一定不存在");
num++;
break;
}
}
}
System.out.println("一定不存在的有" + num + "个");
}
/**
* 根据key获取bitmap下标
*/
private static long[] getIndexArray(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) {
return Hashing.MURMUR_HASH.hash(key);
}
//计算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)));
}
}
博客https://www.cnblogs.com/CodeBear/p/10911177.html
又有另外一种说法:
之前的布隆过滤器可以使用Redis中的位图操作实现,直到Redis4.0版本提供了插件功能,Redis官方提供的布隆过滤器才正式登场。布隆过滤器作为一个插件加载到Redis Server中,就会给Redis提供了强大的布隆去重功能。
博客https://www.cnblogs.com/heihaozi/p/12174478.html
问题:
- 什么时候初始化布隆过滤器数据?
在使用过滤器之前初始化过滤器。
- 如果一直有一个非法请求到后台数据库,当如何处理?
在缓存中设置一个null的值,如果是非法请求,则返回null。