我們知道,布隆過濾器是不可變的,但如果布隆過濾器容量確實不夠了,該怎么辦呢?或者如果要每個月都刪除幾個月前的去重數據,該如何處理呢?這邊要記錄一種布隆過濾器的巧用,多個布隆過濾器組成的循環布隆過濾器。
布隆過濾器
布隆過濾器的細節這邊不做贅述,他在創建的時候就確定了容量以及錯誤率(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)
每次的插入操作,我們對所有的過濾器都執行,而查詢是否重復,只需要查詢最早的過濾器是否存在即可(包含了全部的數據)