Bloom Filter
布隆过滤器是什么
⼀句话解释:⼀种快速检索⼀个元素是否在⼀个海量集合中的算法。
⽐如现在有⼀个签到活动,要求每个⽤户只能签到⼀次,重复签到⽆效。这种常⻅的需求怎么做?如果不考虑数量级,那么非常简单。把所有签到的⽤户ID存到⼀个集合⾥。签到前到集合⾥检查⼀下⽤户ID有没有就可以了。
但是,如果你要⾯对的是淘宝的海量用户信息呢?这个集合得要多⼤?在⼀个海量集合⾥检索⼀个数据,是不是很慢?这就需要⼀个更节省空间同时更⾼效的算法,能够在海量数据集合中快速判断⼀个元素存不存在。这就可以⽤布隆过滤器。
布隆过滤器的使用场景⾮常多,最典型的应⽤是作为缓存数据的前端过滤缓存。快速⽐如,在淘宝这种海量⽤户的登录场景,也可以⽤布隆过滤器,快速判断⽤户输⼊的⽤户名是不是存在。如果⽤户名不存在,那么就可以直接拒绝,不再需要去数据库⾥查了。这样就可以防⽌⼤部分⽆效数据查询,屏蔽很多恶意的请求。
布隆过滤器使⽤⼀个很⻓的⼆进制位数组和⼀系列哈希函数来保存元素。优点是⾮常节省空间,并且查询时间也⾮常快。缺点是有⼀定的误失败概率以及⽆法删除元素 ,也无法给元素计数。
- 位数组(Bit Array):布隆过滤器使⽤⼀个⻓度固定的位数组来存储数据。每个位置只占⽤⼀个⽐特(0或1),初始时所有位都设置为0。位数组的⻓度和哈希函数的数量决定了过滤器的误报率和容量。
- 哈希函数集合:布隆过滤器使⽤多个哈希函数,每个函数都会将输⼊数据映射到位数组的⼀个不同位置。哈希函数的选择对过滤器的性能有很⼤影响,理想的哈希函数应该具有良好的散列性,使得不同的输⼊尽可能均匀地映射到位数组的不同位置。
Guava的布隆过滤器示例
布隆过滤器中,将⼀个原本不在集合中的元素判断成为在集合中,这就是误判。⽽误判率是布隆过滤器⼀个很重要的控制指标。
在算法实现时,误判率是可以通过设定更复杂的哈希函数组合以及做更⼤的位数组来进⾏控制的。所以,在布隆过滤器的初始化过程中,通常只需要指定过滤器的容量和误判率,就⾜够了。
pom.xml引⼊Guava
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.1.0-jre</version>
</dependency>
使⽤Guava提供的布隆过滤器实现
public static void main(String[] args) {
BloomFilter<String> bloomFilter =
BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8),10000,0.01);
//把 A~Z 放⼊布隆过滤器
for (int i = 64; i <= 90 ; i++) {
bloomFilter.put(String.valueOf((char) i));
}
System.out.println(bloomFilter.mightContain("A")); //true
System.out.println(bloomFilter.mightContain("a")); //false
}
Redis的BloomFilter使⽤示例
布隆过滤器是⽤的⼆进制数组来保存数据,所以,Redis的BitMap数据结构天⽣就⾮常适合做⼀个分布式的布隆过滤器底层存储。只是算法还是需要⾃⼰实现。有很多企业实际上也是这么做的。
现在Redis提供了BloomFilter模块后,BloomFilter的使⽤⻔槛就更低了。
Cuckoo Filter
CuckooFilter是什么?
布隆过滤器最⼤的问题是无法删除数据。因此,后续诞生了很多布隆过滤器的改进版本。Cuckoo Filter 布⾕⻦过滤器就是其中一种。
相⽐于布隆过滤器,Cuckoo Filter可以删除数据。⽽且基于相同的集合和误报率,Cuckoo Filter通常占⽤空间更少。相对的,算法实现也就更复杂。
不过他同样有误判率。即有可能将⼀个不在集合中的元素错误的判断成在集合中。布隆过滤器的误报率通过调整位数组的⼤⼩和哈希函数来控制,⽽CuckooFilter的误报率受指纹⼤⼩和桶⼤⼩控制。
BUSKETSIZE,表示每个桶Busket中存放的元素个数。Cuckoo Filter的数组⾥存的不是位,⽽是桶busket,每个桶⾥可以存放多个数据。同⼀个桶中存放的数据越多,空间利⽤率更⾼,相应的误判率也就越⾼,性能也更慢。Redis的CuckooFilter实现中,BUSKETSIZE应该是⼀个在1到255之间的整数,默认的BUSKETSIZE是2。
桶Busket中并不实际保存数据本身,⽽是保存数据的指纹(可以认为是压缩后的数据,实际上是数据对象的⼏个低位数据)。指纹越⼩,HASH冲突造成误判的⼏率就越⼩。这个参数的调整⽐较复杂,Redis的CuckooFilter中不支持调整这个参数。
CuckooFilter使⽤示例
-- 创建默认值
## 容量1000,这个是必填参数。后⾯⼏个都是可选参数。这⾥填的⼏个就是Redis中的CuckooFilter的默认值
## BUSKETSIZE越⼤,空间利⽤率更⾼,但是误判率也更⾼,性能更差
## MAXITARATIONS越⼩,性能越好。如果设置越⼤,空间利⽤率就越好。
## EXPANSION 是指空间扩容的⽐例。
CF.RESERVE cf 1000 BUSKETSIZE 2 MAXITERATIONS 20 EXPANSION 1