讲个使用场景,比如我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容。
问题来了,新闻客户端推荐系统如何实现推送去重的?
Redis 官方提供的布隆过滤器到了 Redis 4.0 提供了插件功能之后才正式登场。
倘若数据量较小的情况下可以通过 查询数据库,过滤掉已经推送过的信息,剩下的就是没有被推送的了。
当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录
问题是当用户量很大,每个用户看过的新闻又很多的情况下,这种方式,推荐系统的去重工作在性能上跟的上么?
如果历史记录存储在关系数据库里,去重就需要频繁地对数据库进行 exists 查询,当系统并发量很高时,数据库是很难扛住压力的。
你可能又想到了缓存,但是如此多的历史记录全部缓存起来,那得浪费多大存储空间啊?而且这个存储空间是随着时间线性增长,你撑得住一个月,你能撑得住几年么?但是不缓存的话,性能又跟不上,这该怎么办?
这时,布隆过滤器 (Bloom Filter) 闪亮登场了,它就是专门用来解决这种去重问题的。
它在起到去重的同时,在空间上还能节省 90% 以上,只是稍微有那么点不精确,也就是有一定的误判概率。
布隆过滤器是什么?
布隆过滤器可以理解为一个不怎么精确的 set 结构,当你使用它的 contains 方法判断某个对象是否存在时,
它可能会误判。但是布隆过滤器也不是特别不精确,只要参数设置的合理,它的精确度可以控制的相对足够精确,只会有小小的误判概率。
当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。
布隆过滤器(Bloom filter)是一个高空间利用率的概率性数据结构,被用于测试一个元素是否在集合中(由于集合无重复元素的性质,可用来判重)
数据量大到传统无错误散列(hash)方法需要使用的内存量是不可满足时使用
算法描述
一个空的布隆过滤器是一串被置为0的bit数组。
同时声明k个不同的散列函数生成一个统一随机分布,每一个散列函数都将元素映射到m个bit中的一个。(k是一个小于m的常数)
向过滤器中添加元素时,通过k个散列函数得到该元素对应的m个位置,并将这些位置置为1
核心思想
BloomFilter的核心思想有两点:
多个hash,增大随机性,减少hash碰撞的概率
扩大数组范围,使hash值均匀分布,进一步减少hash碰撞的概率。
BloomFilter的准确性
尽管BloomFilter已经尽可能的减小hash碰撞的概率了,但是,并不能彻底消除,因此正如上面提到的:
如果对应的bit位值都为1,那么也不能肯定这个url一定存在
也就是说,BloomFilter其实是存在一定的误判的,
这个误判的概率显然和数组的大小以及hash函数的个数以及每个hash函数本身的好坏有关。
布隆过滤器基本使用
每次添加一个值到布隆过滤器
bf.add key val1
bf.add key val2
添加多个值到布隆过滤器
bf.madd key val3 val4
bf.exists key val1
1
bf.exists key val88
0
我们上面使用的布隆过滤器只是默认参数的布隆过滤器,它在我们第一次 add 的时候自动创建。Redis 其实还提供了自定义参数的布隆过滤器,需要我们在 add 之前使用bf.reserve指令显式创建。
如果对应的 key 已经存在,bf.reserve会报错。
bf.reserve有三个参数,分别是 key, error_rate和initial_size。
error_rate错误率越低,需要的空间越大。initial_size参数表示预计放入的元素数量,当实际数量超出这个数值时,误判率会上升。
所以需要提前设置一个较大的数值避免超出导致误判率升高。
如果不使用 bf.reserve,默认的error_rate是 0.01,默认的initial_size是 100。
注意事项
布隆过滤器的initial_size估计的过大,会浪费存储空间,估计的过小,就会影响准确率,用户在使用之前一定要尽可能地精确估计好元素数量,还需要加上一定的冗余空间以避免实际元素可能会意外高出估计值很多。
布隆过滤器的error_rate越小,需要的存储空间就越大,对于不需要过于精确的场合,error_rate设置稍大一点也无伤大雅。比如在新闻去重上而言,误判率高一点只会让小部分文章不能让合适的人看到,文章的整体阅读量不会因为这点误判率就带来巨大的改变。
使用时不要让实际元素远大于初始化大小,当实际元素开始超出初始化大小时,应该对布隆过滤器进行重建,重新分配一个 size 更大的过滤器,再将所有的历史元素批量 add 进去 (这就要求我们在其它的存储器中记录所有的历史元素)。
因为 error_rate 不会因为数量超出就急剧增加,这就给我们重建过滤器提供了较为宽松的时间。
布隆过滤器的其它应用
在爬虫系统中,我们需要对 URL 进行去重,已经爬过的网页就可以不用爬了。但是 URL 太多了,几千万几个亿,如果用一个集合装下这些 URL 地址那是非常浪费空间的。这时候就可以考虑使用布隆过滤器。它可以大幅降低去重存储消耗,只不过也会使得爬虫系统错过少量的页面。
邮箱系统的垃圾邮件过滤功能也普遍用到了布隆过滤器,因为用了这个过滤器,所以平时也会遇到某些正常的邮件被放进了垃圾邮件目录中,这个就是误判所致,概率很低。