浅谈布隆过滤器Bloom Filter
先从一道面试题开始:
给A,B两个文件,各存放50亿条URL,每条URL占用64字节,内存限制是4G,让你找出A,B文件共同的URL。
这个问题的本质在于判断一个元素是否在一个集合中。哈希表以O(1)的时间复杂度来查询元素,但付出了空间的代价。在这个大数据问题中,就算哈希表有100%的空间利用率,也至少需要50亿*64Byte的空间,4G肯定是远远不够的。
当然我们可能想到使用位图,每个URL取整数哈希值,置于位图相应的位置上。4G大概有320亿个bit,看上去是可行的。但位图适合对海量的、取值分布很均匀的集合去重。位图的空间复杂度是随集合内最大元素增大而线性增大的。要设计冲突率很低的哈希函数,势必要增加哈希值的取值范围,假如哈希值最大取到了264,位图大概需要23亿G的空间。4G的位图最大值是320亿左右,为50亿条URL设计冲突率很低、最大值为320亿的哈希函数比较困难。
题目的一个解决思路是将文件切割成可以放入4G空间的小文件,重点在于A与B两个文件切割后的小文件要一一对应。
分别切割A与B文件,根据hash(URL) % k值将URL划分到k个不同的文件中,如A1,A2,...,Ak和B1,B2,...,Bk,同时可以保存hash值避免重复运算。这样Bn小文件与A文件共同的URL肯定会分到对应的An小文件中。读取An到一个哈希表中,再遍历Bn,判断是否有重复的URL。
另一个解决思路就是使用Bloom Filter布隆过滤器了。
Bloom Filter简介
布隆过滤器(Bloom-Filter)是1970年由Bloom提出的。它可以用于检索一个元素是否在一个集合中。
布隆过滤器其实是位图的一种扩展,不同的是要使用多个哈希函数。它包括一个很长的二进制向量(位图)和一系列随机映射函数。
首先建立一个m位的位图,然后对于每一个加入的元素,使用k个哈希函数求k个哈希值映射到位图的k个位置,然后将这k个位置的bit全设置为1。下图是k=3的布隆过滤器:


检索时,我们只要检索这些k个位是不是都是1就可以了:如果这些位有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。
可以看出布隆过滤器在时间和空间上的效率比较高,但也有缺点:
- 存在误判。布隆过滤器可以100%确定一个元素不在集合之中,但不能100%确定一个元素在集合之中。当k个位都为1时,也有可能是其它的元素将这些bit置为1的。
- 删除困难。一个放入容器的元素映射到位图的k个位置上是1,删除的时候不能简单的直接全部置为0,可能会影响其他元素的判断。
Bloom Filter实现
要实现一个布隆过滤器,我们需要预估要存储的数据量为n,期望的误判率为P,然后计算位图的大小m,哈希函数的个数k,并选择哈希函数。
求位图大小m公式:

哈希函数数目k公式:

Python中已经有实现布隆过滤器的包:pybloom
安装
pip install pybloom
简单的看一下实现:
class BloomFilter(object):
FILE_FMT = b'<dQQQQ' def __init__(self, capacity, error_rate=0.001):
"""Implements a space-efficient probabilistic data structure
capacity
this BloomFilter must be able to store at least *capacity* elements
while maintaining no more than *error_rate* chance of false
positives
error_rate
the error_rate of the filter returning false positives. This
determines the filters capacity. Inserting more than capacity
elements greatly increases the chance of false positives.
>>> b = BloomFilter(capacity=100000, error_rate=0.001)
>>> b.add("test")
False
>>> "test" in b
True
"""
if not (0 < error_rate < 1):
raise ValueError("Error_Rate must be between 0 and 1.")
if not capacity > 0:
raise ValueError("Capacity must be > 0")
# given M = num_bits, k = num_slices, P = error_rate, n = capacity
# k = log2(1/P)
# solving for m = bits_per_slice
# n ~= M * ((ln(2) ** 2) / abs(ln(P)))
# n ~= (k * m) * ((ln(2) ** 2) / abs(ln(P)))
# m ~= n * abs(ln(P)) / (k * (ln(2) ** 2))
num_slices = int(math.ceil(math.log(1.0 / error_rate, 2)))
bits_per_slice = int(math.ceil(
(capacity * abs(math.log(error_rate))) /
(num_slices * (math.log(2) ** 2))))
self._setup(error_rate, num_slices, bits_per_slice, capacity, 0)
self.bitarray = bitarray.bitarray(self.num_bits, endian='little')
self.bitarray.setall(False) def _setup(self, error_rate, num_slices, bits_per_slice, capacity, count):
self.error_rate = error_rate
self.num_slices = num_slices
self.bits_per_slice = bits_per_slice
self.capacity = capacity
self.num_bits = num_slices * bits_per_slice
self.count = count
self.make_hashes = make_hashfuncs(self.num_slices, self.bits_per_slice) def __contains__(self, key):
"""Tests a key's membership in this bloom filter.
>>> b = BloomFilter(capacity=100)
>>> b.add("hello")
False
>>> "hello" in b
True
"""
bits_per_slice = self.bits_per_slice
bitarray = self.bitarray
hashes = self.make_hashes(key)
offset = 0
for k in hashes:
if not bitarray[offset + k]:
return False
offset += bits_per_slice
return True
计算公式基本一致。
算法将位图分成了k段(代码中的num_slices,也就是哈希函数的数量k),每段长度为代码中的bits_per_slice,每个哈希函数只负责将对应的段中的bit置为1:
for k in hashes:
if not skip_check and found_all_bits and not bitarray[offset + k]:
found_all_bits = False
self.bitarray[offset + k] = True
offset += bits_per_slice
当期望误判率为0.001时,m与n的比率大概是14:
>>> import math
>>> abs(math.log(0.001))/(math.log(2)**2)
14.37758756605116
当期望误判率为0.05时,m与n的比率大概是6:
>>> import math
>>> abs(math.log(0.05))/(math.log(2)**2)
6.235224229572683
上述题目中,m最大为320亿,n为50亿,误判率大概为0.04,在可以接受的范围:
>>> math.e**-((320/50.0)*(math.log(2)**2))
0.04619428041606246
应用
布隆过滤器一般用于在大数据量的集合中判定某元素是否存在:
1. 缓存穿透:
缓存穿透,是指查询一个数据库中不一定存在的数据。正常情况下,查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并将查询到的对象放进缓存。如果每次都查询一个数据库中不存在的key,由于缓存中没有数据,每次都会去查询数据库,很可能会对数据库造成影响。
缓存穿透的一种解决办法是为不存在的key缓存一个空值,直接在缓存层返回。这样做的弊端就是缓存太多空值占用了太多额外的空间,这点可以通过给缓存层空值设立一个较短的过期时间来解决。
另一种解决办法就是使用布隆过滤器,查询一个key时,先使用布隆过滤器进行过滤,如果判断请求查询key值存在,则继续查询数据库;如果判断请求查询不存在,直接丢弃。
2. 爬虫:
在网络爬虫中,用于URL去重策略。
3. 垃圾邮件地址过滤
由于垃圾邮件发送者可以不停地注册新地址,垃圾邮件的Email地址是一个巨量的集合。使用哈希表存贮几十亿个邮件地址可能需要上百GB的内存,而布隆过滤器只需要哈希表1/8到1/4的大小就能解决问题。布隆过滤器决不会漏掉任何一个在黑名单中的可疑地址。至于误判问题,常见的补救办法是在建立一个小的白名单,存储那些可能被误判的清白邮件地址。
4. Google的BigTable
Google的BigTable也使用了布隆过滤器,以减少不存在的行或列在磁盘上的I/O。
5. Summary Cache
Summary Cache是一种用于代理服务器Proxy之间共享Cache的协议。可以使用布隆过滤器构建Summary Cache,每一个Cache的网页由URL唯一标识,因此Proxy的Cache内容可以表示为一个URL列表。进而我们可以将URL列表这个集合用布隆过滤器表示。
扩展
要实现删除元素,可以采用Counting Bloom Filter。它将标准布隆过滤器位图的每一位扩展为一个小的计数器(Counter),插入元素时将对应的k个Counter的值分别加1,删除元素时则分别减1:


代价就是多了几倍的存储空间。
浅谈布隆过滤器Bloom Filter的更多相关文章
- [转载]布隆过滤器(Bloom Filter)
[转载]布隆过滤器(Bloom Filter) 这部分学习资料来源:https://www.youtube.com/watch?v=v7AzUcZ4XA4 Filter判断不在,那就是肯定不在:Fil ...
- 布隆过滤器(Bloom Filter)详解——基于多hash的概率查找思想
转自:http://www.cnblogs.com/haippy/archive/2012/07/13/2590351.html 布隆过滤器[1](Bloom Filter)是由布隆(Burton ...
- 布隆过滤器(Bloom Filter)的原理和实现
什么情况下需要布隆过滤器? 先来看几个比较常见的例子 字处理软件中,需要检查一个英语单词是否拼写正确 在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上 在网络爬虫里,一个网址是否被访问过 yahoo, ...
- [转载] 布隆过滤器(Bloom Filter)详解
转载自http://www.cnblogs.com/haippy/archive/2012/07/13/2590351.html 布隆过滤器[1](Bloom Filter)是由布隆(Burton ...
- 布隆过滤器(Bloom Filter)详解
直观的说,bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中.和一般的hash set不同的是,这个算法无需存储key的值,对于每个key,只需要k个比特位,每个存储一 ...
- 【面试突击】-缓存击穿(布隆过滤器 Bloom Filter)
原文地址:https://blog.csdn.net/fouy_yun/article/details/81075432 前面的文章介绍了缓存的分类和使用的场景.通常情况下,缓存是加速系统响应的一种途 ...
- 布隆过滤器 Bloom Filter 2
date: 2020-04-01 17:00:00 updated: 2020-04-01 17:00:00 Bloom Filter 布隆过滤器 之前的一版笔记 点此跳转 1. 什么是布隆过滤器 本 ...
- 布隆过滤器(Bloom Filter)-学习笔记-Java版代码(挖坑ing)
布隆过滤器解决"面试题: 如何建立一个十亿级别的哈希表,限制内存空间" "如何快速查询一个10亿大小的集合中的元素是否存在" 如题 布隆过滤器确实很神奇, 简单 ...
- 探索C#之布隆过滤器(Bloom filter)
阅读目录: 背景介绍 算法原理 误判率 BF改进 总结 背景介绍 Bloom filter(后面简称BF)是Bloom在1970年提出的二进制向量数据结构.通俗来说就是在大数据集合下高效判断某个成员是 ...
随机推荐
- 【数论&线段树】【P4140】[清华集训2015]奇数国
Description 有一个长为 \(n\) 的序列,保证序列元素不超过 \(10^6\) 且其质因数集是前60个质数集合的子集.初始时全部都是 \(3\),有 \(m\) 次操作,要么要求支持单点 ...
- CF264D - Colorful Stones 题解
题面 官方题解 模拟赛题解 题解概述: 定义符号A~B表示序列A是序列B的子序列,A!~B反之. 设操作序列为I,则有A~I,B!~I,C~I,D!~I. 可得出条件①B!~C且D!~A,所以我们只要 ...
- 使用docker部署nginx+tomcat架构(2):访问mysql数据库
上一篇完成了通过docker部署nginx+tomcat的基础软件架构,但是距离一个真正可用的软件架构还差得很远.其中最重要的一点是缺少数据库这个角色的存在,那么本篇就来完善这一点. 废话少说,直接进 ...
- Debian使用小计
1. Debian无法apt install debian安装完成后,如果运行apt install,提示 Media change: please insert the disc labeled ' ...
- 简单说说JavaScript的Generator 实现(ES6)
Generator 是 ES6 中新增的语法,和 Promise 一样,都可以用来异步编程 // 使用 * 表示这是一个 Generator 函数 // 内部可以通过 yield 暂停代码 // 通过 ...
- elasticsearch 官方监控文档 老版但很有用
https://zhaoyanblog.com/page/1?s=elasticsearch 监控每个节点(jvm部分) 操作系统和进程部分 操作系统和进程部分的含义是很清楚的,这里不会描述的很详细. ...
- jQuery学习路线。
通过jQuery思维导图,来进行计划的温习/掌握 jQuery技能. 通过思维导图的思路学习,是很好的学习方法之一,思路清晰.跟上环节,易于贯通,重要的是少走弯路. 这里一共有6张图,第1张是大纲路线 ...
- 【剑指offer】数据流中的中位数
题目描述 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值.我们 ...
- Istio 1.4 更新了 | 感觉学不完
Istio 1.4 更新了 官网 https://istio.io/news/2019/announcing-1.4/ Istio 评选 为GitHub上增长最快的五个 开源项目之一 变更说明获取更改 ...
- 【mysql】mysql5.7支持的json字段查询【mybatis】
mysql5.7支持的json字段查询 参考:https://www.cnblogs.com/ooo0/p/9309277.html 参考:https://www.cnblogs.com/pfdltu ...