OptimalSolution(7)--大数据和空间限制

  一、布隆过滤器

  问题:不安全网页的黑名单包含100亿个黑名单网页,每个网页的URL最多占用64B。现在想要实现一种网页过滤系统,可以根据网页的URL判断该网页是否在黑名单上,如何设计该系统。

  要求:允许有万分之一以下的判断失误率;使用的额外空间不能超过30GB

  思路:如果把黑名单中所有的URL通过数据库或者哈希表保存下来,至少需要640GB的空间。

  解答:如果遇到网页黑名单系统、垃圾邮件过滤系统、爬虫的网址判重系统等题目,并且系统容忍一定程度的失误率以及对空间要求比较严格,那么需要用布隆过滤器来解答。布隆过滤器精确地代表一个集合,并可以精确判断一个元素是否在集合中。但是想做到完全正确是不可能的。布隆过滤器的优势就在于使用很少的空间就可以将准确率做到很高的程度。

  基础知识:哈希函数的性质:(1)典型的哈希函数都有无限的输入值域(2)当给哈希函数传入相同的输入值时,返回值一样(3)当个哈希函数传入不同的输入值时,返回值可能一样,也可能不一样(4)最重要的性质是返回值会均匀地分布在固定范围的输出域上。第四点性质是评价一个哈希函数优劣的关键,不同输入值所得到的所有返回值越均匀地分布在输出域S上, 哈希函数越优秀,并且这种均匀分布与输入值出现的规律无关。哈希函数经典的实现包括MD5和SHA1算法。如果一个优秀的哈希函数能够做到很多不同的输入值所得到的返回值非常均匀地分布在S上,那么所有返回值对m取余也会均匀地分布在0~m-1的空间上。

  布隆过滤器的生成:(1)有一个长度为m的bit类型的数组。(2)有k个哈希函数,这些函数的输出域S都大于或等于m,并且这些哈希函数都足够优秀,彼此之间也完全独立。那么对同一个输入对象(假设是一个字符串记为URL),经过k个哈希函数算出来的结果也是独立的,可能相同,也可能不同,但彼此独立。对算出来的每一个结果都对m取余,然后在bit数组中把对应的位置设置成1。(3)把bit类型的数组记为bitMap,对于一个输入对象来说,对bitMap的影响就是把bitMap的一些位置设置成1(涂黑)。然后按照该规则处理所有的输入对象,每个对象都可能把bitMap中的一些白位置涂黑,也可能将黑位置继续涂黑。完成后,一个布隆过滤器就生成完毕,代表之前所有输入对象组成的集合。

  布隆过滤器的判断:假设一个对象为a,要想检查它是否之前已经输入到bitMap中,只需要把a通过k个哈希函数算出k个值并取余,就得到[0...m-1]范围上的k个值,然后查看bitMap中这些位置是否为黑,如果有一个不为黑,就说明a一定不在集合里。如果都为黑,说明在这个集合里,也可能会误判。误判的原因是:由于布隆过滤器在生成时输入对象过多同时bitMap过小,会导致bitMap大部分的位置都是黑的,这时不在集合里的a对应的k个位置可能都是黑的,从而错误地认为a在集合里。

  布隆过滤器的失误率:假设布隆过滤器的大小是m,哈希函数的个数是k,需要输入的对象个数为n,失误率是p,则经过推导可以得到

  

  带入到题目中,n=100亿,p=0.01%,(每个样本大小64B,这个信息不会影响布隆过滤器的大小,只和选择哈希函数有关,一般的哈希函数都可以接受64B的输入对象,所以使用布隆过滤器还有一个好处是不用顾忌单个样本的大小,丝毫不能影响布隆过滤器的大小)

  m=19.19n,向上取整为20n,即需要2000亿个bit,即25GB。再由k=0.7×(m/n)=14就确定了需要哈希函数的个数为14个。

  布隆过滤器误判解决:可以把确实不在集合中,但每次计算结果都在集合中的误报样本加入到白名单,以后就可以知道这个样本确实不在集合中了。

  二、只用2GB内存在20亿个整数中找到出现次数最多的数

  问题:有一个包含20亿个全是32位整数的大文件,在其中找到出现次数最多的数。内存限制为2GB。

  思路:想要在很多整数中找到出现次数最多的数,通常的做法是使用哈希表对出现的每一个数做词频统计,哈希表的key是某一个整数,value是这个数出现的次数。如果一个数出现了20亿次,用32位的整数也可以表示其出现的次数,而不会产生溢出,所以哈希表的key需要占用4B,value也是4B,一条哈希表的记录(key,value)需要占用8B,当哈希表记录数为2亿个时,至少需要1.6GB的内存。如果20亿个树中不同的数超过2亿种,,最极端的情况是20亿个数都不同,那么内存会不够用,所以一次性用哈希表统计20亿个数的办法是有风险的。

  解决办法:把包含20亿个数的大文件用哈希函数分成16个小文件,根据哈希函数的性质,同一种数不可能被哈希到不同的文件上,同时每个小文件中不同的数一定不会大于2亿种(20/2=10,而16>10)。然后对每一个小文件用哈希表来统计其中每种数出现的次数,这样就得到了16个小文件中各自出现次数最多的数,以及各自的次数统计。然后比较这16个文件中出现次数最多的数中出现次数最大的就行了。

  把一个大的集合通过哈希函数分配到多台机器中,或者分配到多个文件里,这种技巧是处理大数据面试题时最常用的技巧之一。

  三、40亿个非负整数中找到没出现的数

  32位无符号整数的范围是0~4294967295,现在正好有一个包含40亿个无符号整数的文件,所以在文件中必然没有出现过的数。

  题目1:最多使用1GB,找到所有每出现过的数。

  如果40亿个数都不同,则哈希表记录数为40亿条,所需要空间40亿×4B=16GB,显然不符合要求。

  正确思路:由于哈希表需要占用很多空间,因此可以使用bit map的方式来表示数出现的情况。即申请一个长度为4294967295的bit类型的数组bitArr,bitArr上每一个位置只可以表示0或1。由于1B=8bit,所以4294967295个bit占用空间为500MB。

  使用方法:遍历这40亿个无符号数,把bitArr对应位置上置1。例如,如果遍历到7000,就把bitArr[7000]置为1。遍历完成后,再遍历一遍,发现哪个位置上的值不是1,就说明这个数不在40亿个数中。

  问题2:最多使用1GB,找出所有出现了两次的数。

  使用bit map的方式来表示数出现的情况,即申请一个长度为4294967295×2的bit类型的数组bitArr,由于1B=8bit,所以所以4294967295 × 2个bit占用空间为1GB。遍历这40亿个数,如果第一次遇到num,就把bitArr[num*2+1]和bitArr[num*2]设置为01,第二次设置为10,第三次设置为11,只有就不再管了。然后依次遍历bitArr,如果发现bitArr[num*2+1]和bitArr[num*2]为10,那么i就是出现了两次的数。

  题目3:最多使用10MB,只用找到一个没出现过的数即可。

  思路:将0~4294967295区间平均分成64个区间,每个区间是67108864个数,由于只有40亿个数,因此至少有一个区间上的计数少于67108864。

  第一次遍历:申请长度为64的整形数组countArr[0...63],countArr[i]表示区间i上的数有多少个。例如,遍历到3422552090时,3422552090/67108864=51,因此countArr[51]++。遍历完40亿个数之后,一定存在count[i]小于67108864,找到第一个i即可。此时内存为64×4B。

  第二次遍历:假设第一步的i=37。申请长度为67108864的bit map,内存大小大约为8MB,记为bitArr[0...67108863]。然后再遍历一次40亿个数,此时只关注落在第37区间的数,即满足num/67108864=37的num。然后将bitArr[num - 67108864*37]=1,然后遍历bitArr,找到bitArr[i]不等于1的i值,然后67108864×37+i就是第一个没出现过的数。

  问题4:最多使用10MB,找到这40亿个整数的中位数。

  长度为2MB的无符号整型数组占用的空间为8MB,所以将区间的数量定位4294967295/2M=2148个。申请一个长度为2148的无符号整型数组arr[0...2147],arr[i]表示第i区间有多少个数。然后遍历这40亿个数,将对应的进行arr[num/2M]++操作,然后累加每个区间的出现次数,如果0~K-1区间上的数的个数为19.998亿,但是当加上第K个区间上的数的个数后就超过了20亿,因此第20亿个数是第K区间上第0.002亿个数。

  然后申请一个长度为2MB的无符号整型数组countArr[0...2M-1],占用空间是8MB。然后遍历这40亿个数,只对第K区间的数做统计,jicountArr[numi - K * 2M]++,然后只在第K区间上找到第0.002亿个数即可。

  四、找到100亿个URL中重复的URL以及搜索词汇的Top K问题

  问题1:有一个包含100亿个URL的大文件,每个URL占用64B,找出其中所有重复的URL。

  首先需要明确在资源上的限制有哪些,包括内存、计算时间等要求。总的思路就是将每条URL通过哈希函数分配到若干几区或者拆分成若干个小文件,这里的“若干”由具体的资源限制来计算出精确的数量。然后每一台机器或者每一个小文件分别统计是否有重复的URL出现。

  问题2:某搜索公司一天的用户搜索词汇是海量的(百亿数量级),设计一种求出每天最热top 100词汇的方法。

  先把包含百亿数据量的词汇文件分流到不同的机器上,然后对每一台机器来说,如果分到的数据量依然很大,可以再用哈希函数把每台机器上的分流文件拆分成更小的文件。处理每一个小文件的时候,使用哈希表统计词频信息。然后遍历哈希表,使用大小为100的小根堆来选出每一个小文件的top100,然后将小根堆(最小堆)里的词按照词频进行排序,就得到了每个小文件排序后的top 100。然后把各个小文件排序后的top 100进行外排序或者继续利用小根堆(最小堆),就可以选出每台机器上的top 100。不同机器之间的top 100在进行外排序或者继续利用小根堆(最小堆),最终求出整个百亿数据量中的top 100。(使用堆结构和外部排序

  

  五、一致性哈希算法的基本原理

  题目:常见的数据缓存策略是这样的:1.无论是添加、查询还是删除数据,都先将数据的id通过哈希函数转换成一个哈希值,记为key。2.如果当前机器有N台,则计算key%N的值,这个值就是该数据所属的机器编号,无论是添加、删除还是查询操作,都只能在这台机器上进行。分析这种缓存策略可能带来的问题,并提出改进方案。

  存在的问题:如果增加或者删除机器时(N变化),代价会很高,所有的数组都不得不根据id重新计算一遍哈希值,并将哈希值对新的机器进行取模计算,然后进行大规模的数据迁移。

  改进方法:一致性哈希算法是一种很好的数据缓存设计方案。

  第一步:假设数据的id通过哈希函数转换成的哈希值范围是在0~232-1的数字空间中,先将这些数字头尾相连,那么一个数据id在计算出哈希值之后认为对应到环中的一个位置。

  第二步:将机器添加到上面的环中,机器在环中的位置是根据机器id计算出来的哈希值决定。对于一条数据来说,首先把该数据的id用哈希函数计算出哈希值,并映射到环中相应位置,然后顺时针寻找离这个位置最近的机器。

  第三步:添加机器时,首先根据机器id得到机器处于原来哪两台机器的中间,然后把某一段数据迁移到新机器上就行了。删除机器时,只要把要删除机器的数据全部复制到顺时针找到的下一台机器上即可。

  第四步:机器负载不均时的处理。如果机器较少,很有可能造成机器在整个还上的分布不均匀,从而导致机器之间的负载不均衡。为了解决数据倾斜的问题,一致性哈希算法引入了虚拟节点机制,即对每一台机器通过不同的哈希函数计算出多个哈希值,对多个位置都放置一个服务节点,称为虚拟节点。具体做法可以在机器ip或主机名的后面增加编号或端口号来实现。例如,一个实际机器节点对应着两个虚拟节点,节点变多了,根据哈希函数的性质,平衡性自然会变好。同时数据定位算法不变,只是多了一步虚拟节点到实际节点的映射。

  

  

原文地址:https://www.cnblogs.com/BigJunOba/p/9646473.html