1 布隆过滤器的实现
1-1 布隆过滤器基础知识
需求:不安全的网页黑名单包含100亿个黑名单网页,每个网页的URL最多占用64B,现在实现一个网页过滤系统,可以根据网页URL判断该网页是否在黑名单上。
注意:
1)允许有万分之一以下的判断失误率
2)额外空间不超过30G 约等于 30 x 10^9 B
分析:所有网页存储需要 64 x 10^10 B 即640G空间
hash函数的基本特征:
1) 有无限的输入值区域
2) 有限范围的输出区域S,因此传入相同的输入值,返回值相同,不同值,返回值也可能相同(无限对有限,也就是所谓的hash碰撞)
3) 优秀hash函数应保证输出S分布均匀(相似的输入hash碰撞概率低)
布隆过滤器的组成
1)大小为m的bitMap(位数组)
2)k个独立并优秀的hash函数,其输出区域S确保都大于等于m.
总结:因此布隆过滤器的实现核心就是确定其位数组的大小m和hash函数的个数。
更多关于布隆过滤器原理参考:
1-2 布隆过滤器的设计步骤
step1:根据实际需求中输入的总数n,允许错误率p,按照公式计算bit数组的大小m
step2:根据n,m的值,按照公式计算hash函数的个数k
step3:根据hash函数个数k以及bit数组大小m,计算实际错误率确保满足需求。
布隆过滤器bit数组大小m与输入总数关系
\[m=-\frac{n \times \ln p}{(\ln 2)^{2}}
\]
n:布隆过滤器的输入总数
p: 布隆过滤器允许的判断失误率
实例:
n = 100亿 , p = 0.0001 ln2 = 0.693147 ln(0.0001) = -9.21034
代入公式为约为:m = 19.19n
即bit数组的大小m至少为19.19 x 100亿 bit 取整则 2000亿 bit = 25GB左右
hash函数个数k的计算
\[k=\ln 2 \times \frac{m}{n}=0.7 \times \frac{m}{n}
\]
实例: k = ln2 * 19.19 约等于 0.7*20 = 14 个hash函数
布隆过滤真实失误率计算
\[\left(1-e^{-\frac{n k}{m}}\right)^{k}
\]
由于之间采用向上取整(k与m的大小都留有余量),因此n=100亿,m=2000亿bit,k = 14,得到的错误率约为0.006%,
小于需求的0.01%
布隆过滤器公式的推导
2 哈希函数应用
2-1 有限内存统计大规模数据
需求:有一个包含20亿个全是32位整数(4B)的大文件,找到其中出现次数最多的数
内存限制:2GB
基本思路:
hash表统计键值对,key是4B,value是4B(4B有符号整数能够表示20亿),因此每个键值对为8B
最钟需要的存储为 8B*20亿 = 16x10^9B 约为16GB,显然超过2GB限制
进阶思路:
目标:对文件进行切分,确保单个文件的数据量不超过内存限制。
题目中文件存储需要16GB,因此可以将文件切分为16个小文件,每个小文件单独统计各个字符的分布。最后获得16个结果
问题:为什么划分16个?
目的是限制每个文件的大小为1GB,这样极端情况下即便每个文件的数字都各不相同,也至多占用2GB
问题:hash函数如何设计?
选择一个性能比较号的hash函数,输出值去模上目标文件数目
问题:上面方法存在的问题?
必须确保相同的数都在同一个文件内部,如果数据分布非常不均匀,会存在某个区间的映射的数字超过1GB,这样该如何处理?
极端情况如何处理?
1)20亿个数都不同 2)20亿个数都相同 3) 9亿9999万个数相同,剩余的数也相同。
获取:每个小文件中出现频率最高的数
个人思路:
1)用bitMap先统计不同的数字个数(如果不同数据个数使用hashmap能够承载的话,直接用hashmap统计)
2)可以采用基数排序的方式,比如本题中可以使用2个位数对20取模的方式将文件均匀分配到各个文件,需要进行16次遍历
3)排完序再顺序遍历的统计出现次
2-2 海量数据TopK问题(摘录)
针对top k类问题,通常比较好的方案是【分治+trie树/hash+小顶堆】,即先将数据集按照hash方法分解成多个小数据集,然后使用trie树或者hash统计每个小数据集中的query词频,之后用小顶堆求出每个数据集中出频率最高的前K个数,最后在所有top K中求出最终的top K。
结合限制条件考虑:
1 单机+单核+足够大内存
设每个查询词平均占8Byte,则10亿个查询词所需的内存大约是10^9*8=8G内存。如果你有这么大的内存,直接在内存中对查询词进行排序,顺序遍历找出10个出现频率最大的10个即可。这种方法简单快速,更加实用。当然,也可以先用HashMap求出每个词出现的频率,然后求出出现频率最大的10个词。
2 单机+多核+足够大内存
这时可以直接在内存中实用hash方法将数据划分成n个partition,每个partition交给一个线程处理,线程的处理逻辑是同1类似,最后一个线程将结果归并。
该方法存在一个瓶颈会明显影响效率,即数据倾斜,每个线程的处理速度可能不同,快的线程需要等待慢的线程,最终的处理速度取决于慢的线程。解决方法是,将数据划分成c*n个partition(c>1),每个线程处理完当前partition后主动取下一个partition继续处理,直到所有数据处理完毕,最后由一个线程进行归并。
3 单机+单核+受限内存
这种情况下,需要将原数据文件切割成一个一个小文件,如,采用hash(x)%M,将原文件中的数据切割成M小文件,如果小文件仍大于内存大小,继续采用hash的方法对数据文件进行切割,直到每个小文件小于内存大小,这样,每个文件可放到内存中处理。采用1的方法依次处理每个小文件。
4 多机+受限内存
这种情况下,为了合理利用多台机器的资源,可将数据分发到多台机器上,每台机器采用3中的策略解决本地的数据。可采用hash+socket方法进行数据分发。
从实际应用的角度考虑,1~4的方案并不可行,因为在大规模数据处理环境下,作业效率并不是首要考虑的问题,算法的扩展性和容错性才是首要考虑的。算法应该具有良好的扩展性,以便数据量进一步加大(随着业务的发展,数据量加大是必然的)时,在不修改算法框架的前提下,可达到近似的线性比;算法应该具有容错性,即当前某个文件处理失败后,能自动将其交给另外一个线程继续处理,而不是从头开始处理。
Top k问题很适合采用MapReduce框架解决,用户只需编写一个map函数和两个reduce 函数,然后提交到Hadoop(采用mapchain和reducechain)上即可解决该问题。对于map函数,采用hash算法,将hash值相同的数据交给同一个reduce task;对于第一个reduce函数,采用HashMap统计出每个词出现的频率,对于第二个reduce 函数,统计所有reduce task输出数据中的top k即可。