为什么
就当前互联网环境来说,头部的互联网生态越来越往高并发、分布式的形态发展。举例来说,各大网页的黑名单系统,爬虫的重复率判断。这些场景越来越多。
举例来说,实时状态下可能会对超过百亿级别的 URL 需要进行判断是否符合规范或者存在于系统中,能否正常使用。
- 通常情况下,每个 URL 的大小为 64B(字节),那么就按照100亿的 URL 数量来看,大概需要640GB的内存容量【】,对于当前线上服务器来说,… 这个值依然还是很大的!但如果利用布隆过滤器的优势,在没有失误率的情况下只需要100亿个比特,即:1.2GB,即使为了降低失误率,也不会超过几十GB的空间【失误率后面会谈到】
布隆过滤器的主要作用:判断一个元素是否在集合中
这样的场景会有很多。会去判断,要查询的元素是否存在于集合当中。【该网站是否允许该用户登录、该网站共是否接受这样的 url 请求】
通常在查询的时候,一般会先从 cache 中进行查询,如果没有的话会直接到磁盘或者数据库查询,这样的方式看起来很合理,但是如果在中间再加一层布隆顾虑器,这样就会更加合理了!为什么?
假设要查询的一个元素,而该元素不存在
-
如果没有 BloomFilter,从cache中查询完就会直接到数据库做查询了,这样带来的现象是“慢”,毕竟从库中查耗时是比较长的,很大程度上对服务的性能产生影响。
-
中间存在 BloomFilter,从cache中查询完就会首先查询 BloomFilter,就会发现该元素不存在,就可以不往后面进行查询了,而 BloomFilter 的性能是极其优越的。这样,对于机器或者说服务性能避免了很大不必要的消耗。
是什么
Bloom Filter是一种是一种空间效率高的概率型数据结构,专门用于判断某个元素是否存在于一个超大集合。集合载体是由01组成的向量图(bitMap),载体索引位置由一系列随机哈希函数计算而出。
判断规则:通过多个hash算法,为数据算出多个在bitmap中的索引位置,并将这多个索引位置的值都置为1。后续如果判断数据是否存在时,也判断这多个索引位的值是否都为1。如果都为1,则存在,有一个不为1,就说明不存在。
布隆过滤器用于检索一个元素是否存在于一个集合中,他的优点是空间占用和查询效率都比一般的算法好太多。并且因为布隆过滤器只存储01而不是具体的原值,在某些保密场合具有先天的优势。
bitmap的每一位都是一个bit,那么通过bitmap来存在10亿个位置,bitmap的大小为10亿/8/1024/1024 = 0.12G
插入、查询的时间复杂度都是O(k),k为hash函数数量
布隆过滤器的缺点也相当明显:那就是他的准确率受制于他的算法限制,只能判断一个元素一定不存在于该集合,因为hash函数的碰撞,有些元素的所有hash下标位置和海量元素的共同hash计算下标位置结果相同而重置了所有位置,导致该元素不存在于该集合却误判为存在。
1、增大位图数组的大小(位图数组越大,占用的内存越大),减少hash碰撞
2、增加哈希函数的次数,而减少哈希碰撞:同一个key值经过1个函数相等了,
那么经过2个或者更多个哈希函数的计算,都得到相等结果的概率就自然会降低了。
需要注意的是办法1会增加内存空间,而办法二会影响计算效率,因为时间复杂度为O(hash次数)
布隆过滤器的缺点二是:不能很轻易的删除掉元素
因为布隆过滤器的特性我知道,它们的下标位置计算可能是重复的,所以在删除下标位置的时候必须保证
该位置为该元素独享。而我们也可以把位列图转变为整数数组,每次下标的时候计算器+1,在删除一个元素
时在进行相应的扣减。但是我们必须保证这个元素是一定存在于这个集合中的,而布隆过滤器不能验证这点。
怎么用
java
- 引入google的Guava
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>22.0</version>
</dependency>
- java
import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
public class GuavaBloomFilter {
public static void main(String[] args) {
// 总数量
int total = 1000000;
// 默认误判率fpp0.03
//fpp:因为布隆过滤器中总是会存在误判率,因为哈希碰撞是不可能百分百避免的。布隆过滤器对这种误判率称之为假阳性概率,即:False Positive Probability,简称为fpp。
BloomFilter<CharSequence> bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), total);
// 初始化 total 条数据到过滤器中
for (int i = 0; i < total; i++) {
bf.put("" + i);
}
// 判断值是否存在过滤器中
int count = 0;
for (int i = 0; i < total + 10000; i++) {
if (bf.mightContain("" + i)) {
count++;
}
}
System.out.println("已匹配数量 " + count);// (1000309 - 1000000)/(1000000 + 10000) * 100 ≈ 0.030594059405940593
//指定误判率:万分之一,提高匹配精度
BloomFilter<CharSequence> bfWithFpp = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), total, 0.0001);
for (int i = 0; i < total; i++) {
bfWithFpp.put("" + i);
}
int countFpp = 0;
for (int i = 0; i < total + 10000; i++) {
if (bfWithFpp.mightContain("" + i)) {
countFpp++;
}
}
//误判率 fpp 的值越小,匹配的精度越高。当减少误判率 fpp 的值,需要的存储空间也越大,所以在实际使用过程中需要在误判率和存储空间之间做个权衡。
System.out.println("指定误判率已匹配数量 " + countFpp);// (1000001 - 1000000)/(1000000 + 10000) * 100 ≈ 0.0001
}
}
fpp:
因为布隆过滤器中总是会存在误判率,因为哈希碰撞是不可能百分百避免的。布隆过滤器对这种误判率称之为假阳性概率,即:False Positive Probability,简称为fpp。
redis上的布隆过滤器(rebloom)
- 下载
git clone git://github.com/RedisLabsModules/rebloom
cd rebloom
make
- redis.conf配置文件中配置reloom文件下的rebloom.so 路径:loadmodule /path/rebloom.so
- 重启redis,操作rebloom
[root@mysql01 ~]# redis-cli
127.0.0.1:6379> BF.add myBloom 1
127.0.0.1:6379> BF.add myBloom 2
127.0.0.1:6379> BF.exists myBloom 1