我们知道,布隆过滤器是不可变的,但如果布隆过滤器容量确实不够了,该怎么办呢?或者如果要每个月都删除几个月前的去重数据,该如何处理呢?这边要记录一种布隆过滤器的巧用,多个布隆过滤器组成的循环布隆过滤器。
布隆过滤器
布隆过滤器的细节这边不做赘述,他在创建的时候就确定了容量以及错误率(false postive),为了后续的方便,这边假设我们有了一个可靠的布隆过滤器。
class BloomFilter(object):
def __init__(self, capacity, error_rate):
pass
def add(self,key):
pass
def exists(self,key):
pass
def __len__(self):
pass
很简单的一个结构,接下去我们会用基础的布隆过滤器去实现最开始说的两个需求。
布隆过滤器扩容
因为布隆过滤器的不可逆,我们没法重新建一个更大的布隆过滤器然后去把数据重新导入。这边采取的扩容的方法是,保留原有的布隆过滤器,建立一个更大的,新增数据都放在新的布隆过滤器中,去重的时候检查所有的布隆过滤器。
class BloomFilterAdapter(object):
def __init__(self, old_filters, new_filter):
self.old_filters = old_filters
self.new_filter = new_filter
def add(self, key):
self.new_filter.add(key)
def exists(self, key):
return any([f.exists(key) for f in self.old_filters]) or self.new_filter.exists(key)
def __len__(self):
return sum([len(f) for f in self.old_filters]) + len(self.new_filter)
非常巧妙的方法,用一个新的布隆过滤器和多个老的布隆过滤器共同组成一个新的过滤器,提供相同的接口。
附带时效的布隆过滤器
为了实现这么一个需求:使用布隆过滤器对url去重,但是每五个月要重新爬取一次。这边介绍一种循环的布隆过滤器,类似于之前的思路,由多个布隆过滤器组成,每个月都清空最早的那个过滤器。demo如下。
class CircleBloomFilter(object):
def __init__(self, filter_num):
"""
:param filter_num: 预期包含的filter数量
"""
self.filter_num = filter_num
self.filters = [new_bloomfilter()]
def do_circle(self):
"""
执行循环逻辑
:return:
"""
if len(self.filters) >= self.filter_num:
self.filters.pop(0)
self.filters.append(new_bloomfilter())
def add(self, key):
self.filters[-1].add(key)
def exists(self,key):
return any([f.exists(key) for f in self.filters])
def __len__(self):
return sum([len(f) for f in self.filters])
一样非常简单的逻辑,只要定期执行do_circle
即可。
另外,我们可以看到,上边的实现add
方法只对一个过滤器执行,而exists
方法对所有过滤器都要执行,比较适用于插入多,但是判断是否重复少的场景。我们还可以换一种方式,应对查询是否重复大于添加操作的场景。
def add(self,key):
[f.add(key) for f in self.filters]
def exists(self,key):
return self.filters[0].exists(key)
每次的插入操作,我们对所有的过滤器都执行,而查询是否重复,只需要查询最早的过滤器是否存在即可(包含了全部的数据)