海量数据的处理算法
海量数据处理,就是基于海量数据上的存储、处理、操作。海量就是数据量太大,所以导致要么是无法在较短时间内迅速解决,要么是无法一次性装入内存。
解决办法:
(1)针对时间,可以采用巧妙的算法搭配合适的数据结构,如Hash/bitmap/堆/倒排索引/trie树;
(2)针对空间,大而化小:分而治之/hash映射,把规模大化为规模小的,各个击破。
一、海量数据中的最值问题:
1、海量日志数据,提取出某日访问百度次数最多的那个IP。
思路:分而治之/hash映射 + hash统计 + 堆/快速/归并排序,就是先映射,后统计,最后排序。
a. 分而治之/hash映射:针对数据太大,内存受限,只能是:把大文件化成(取模映射)小文件,即16字方针:大而化小,各个击破,缩小规模,逐个解决
b. hash统计:当大文件转化了小文件,那么我们便可以采用常规的hash_map(ip,value)来进行频率统计。
c. 堆/快速排序:统计完了之后,便进行排序(可采取堆排序),获取每个小文件的最值。再归并每个小文件的最值进而获取最大的值。
实现: 首先是这一天,并且是访问百度的日志中的IP取出来,逐个写入到一个大文件中。注意到IP是32位的,最多有个2^32个IP。同样可以采用映射的方法,比如模1000,把整个大文件映射为1000个小文件,再找出每个小文中出现频率最大的IP(可以采用hash_map对那1000个文件中的所有IP进行频率统计,然后依次找出各个文件中频率最大的那个IP)及相应的频率。然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。
二、海量数据的top K问题。
海量数据存储于多个文件,任何一条数据都可能存在于任何一个文件当中,现需要筛选出现的次数最多的k条数据。 一般思路:
1)、 依次遍历这些文件,通过hash映射,将每个文件的每条数据映射到新构造的多个小文件中(设生成了n个小文件);
2)、依次统计每个小文件中出现次数最多的k条数据,构成hash表,hash表中每个键值对的形式为 dataItem: count;
3)、利用堆排序,依次遍历这些hash表,在n∗k条数据中,找出count值最大的k个;
1、如何在1亿个数中找出最大的100个数
a. 如果这1亿个书里面有很多重复的数,先通过Hash法,把这1亿个数字去重复,这样如果重复率很高的话,会减少很大的内存用量,从而缩小运算空间。
b. 将1亿个数据分成100份,每份100万个数据,
c. 统计每份文件中数据出现的kv值,找到每份数据中最大的100个。
d. 堆/快速排序归并获取最大的100个数
2、寻找热门查询,300万个查询字符串中统计最热门的10个查询。
思路: 我们知道,数据大则划为小的,如一亿个数求Top10,可先%1000将ip分到1000个小文件中去,并保证一种ip只出现在一个文件中,再对每个小文件中的ip进行hashmap计数统计并按数量排序,最后归并或者最小堆依次处理每个小文件的top10以得到最后的结果。但如果数据规模比较小,能一次性装入内存呢? 虽然有一千万个Query,但是由于重复度比较高,因此事实上只有300万的Query,每个Query255Byte,因此我们可以考虑把他们都放进内存中去(300万个字符串假设没有重复,都是最大长度,那么最多占用内存3M*1K/4=0.75G。所以可以将所有字符串都存放在内存中进行处理),而现在只是需要一个合适的数据结构,在这里,HashTable绝对是我们优先的选择。所以我们放弃分而治之/hash映射的步骤,直接上hash统计,然后排序。So,针对此类典型的TOP K问题,采取的对策往往是:hashmap + 堆。
a. hash_map统计: 先对这批海量数据预处理。具体方法是:维护一个Key为Query字串,Value为该Query出现次数的HashTable,即hash_map(Query,Value),每次读取一个Query,如果该字串不在Table中,那么加入该字串,并且将Value值设为1;如果该字串在Table中,那么将该字串的计数加一即可。最终我们在O(N)的时间复杂度内用Hash表完成了统计;
b. 堆排序: 第二步、借助堆这个数据结构,找出Top K,时间复杂度为N‘logK。即借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比。所以,我们最终的时间复杂度是:O(N) + N’ * O(logK),(N为1000万,N’为300万)。
3、海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10。
非重复数据的思路:如果每个数据元素只出现一次,而且只出现在某一台机器中,那么可以采取以下步骤统计出现次数TOP10的数据元素。
实现:
a. 直接在每台电脑上堆排序求出TOP10,可以采用包含10个元素的堆完成(TOP10小,用最大堆,TOP10大,用最小堆,比如求TOP10大,我们首先取前10个元素调整成最小堆,如果发现,然后扫描后面的数据,并与堆顶元素比较,如果比堆顶元素大,那么用该元素替换堆顶,然后再调整为最小堆。最后堆中的元素就是TOP10大)。
b. 求出每台电脑上的TOP10后,然后把这100台电脑上的TOP10组合起来,共1000个数据,
c. 再利用利用堆排序的方法求出TOP10就可以了。
重复数据的思路: 如果同一个元素重复出现在不同的电脑中呢,这个时候,你可以有两种方法:遍历一遍所有数据,重新hash取摸,如此使得同一个元素只出现在单独的一台电脑中,然后采用上面所说的方法,统计每台电脑中各个元素的出现次数找出TOP10,继而组合100台电脑上的TOP10,找出最终的TOP10。
三、海量数据的某个数据是否存在或重复存在的问题。
1、给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中?
思路: 一个bit位代表一个unsigned int值。读入40亿个数,设置相应的bit位。由于2^32=42.9+亿,那么2^32bit才能存下40亿个数,也就需要2^32=4Gb=0.5GB=512M内存。读入40亿个数,设置相应的bit位,读入要查询的数,查看相应bit位是否为1,为1表示存在,为0表示不存在。
2、在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数。
思路:采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)进行,共需内存2^32 * 2 bit=1 GB内存,还可以接受。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。
四、海量数据中找出第K大的数。(如中位数)
1、现在 有10亿个int型的数字(假设int 型占4B),以及一台可用内存为1GB的机器,如何找出这10亿个数字的中位数?
思路:采用基于二进制位比较和快速排序算法中的“分割思想”来寻找中位数。
具体实现: 假设10亿个数字保存在一个大文件中,依次读一部分文件到内存(不超过内存的限制:1GB),将每个数字用二进制表示,比较二进制的最高位(第32位),如果数字的最高位为0,则将这个数字写入 file_0文件中;如果最高位为 1,则将该数字写入file_1文件中。【这里的最高位类似于快速排序中的枢轴元素】。从而将10亿个数字分成了两个文件(几乎是二分的),假设 file_0文件中有 6亿 个数字,file_1文件中有 4亿 个数字。那么中位数就在 file_0 文件中,并且是 file_0 文件中所有数字排序之后的第 1亿 个数字。
为什么呢?因为10亿个数字的中位数是10亿个数排序之后的第5亿个数。现在file_0有6亿个数,file_1有4亿个数,file_0中的数都比file_1中的数要大(最高位为符号位,file_1中的数都是负数,file_0中的数都是正数,也即这里一共只有4亿个负数,排序之后的第5亿个数一定是正数,那么排序之后的第5亿个数一定位于file_0中)。除去4亿个负数,中位数就是6亿个正数从小到大排序之后 的第 1 亿个数。现在,我们只需要处理 file_0 文件了(不需要再考虑file_1文件)。对于 file_0 文件,同样采取上面的措施处理:将file_0文件依次读一部分到内存(不超内存限制:1GB),将每个数字用二进制表示,比较二进制的 次高位(第31位),如果数字的次高位为0,写入file_0_0文件中;如果次高位为1,写入file_0_1文件 中。
现假设 file_0_0文件中有3亿个数字,file_0_1中也有3亿个数字,则中位数就是:file_0_0文件中的数字从小到大排序之后的第1亿个数字。抛弃file_0_1文件,继续对 file_0_0文件 根据 次次高位(第30位) 划分,假设此次划分的两个文件为:file_0_0_0中有0.5亿个数字,file_0_0_1中有2.5亿个数字,那么中位数就是 file_0_0_1文件中的所有数字排序之后的 第 0.5亿 个数。
…
按照上述思路,直到划分的文件可直接加载进内存时(比如划分的文件中只有5KW个数字了),就可以直接对数字进行快速排序,找出中位数了。
总结: 上面的海量数据寻找中位数,其实就是利用了“分割”思想,每次将 问题空间 大约分解成原问题空间的一半左右。(划分成两个文件,直接丢弃其中一个文件),故总的复杂度可视为O(logN) N=10亿。
五、多个海量文件之间对比查重
A和B两个大文件,每个文件都存储着海量数据,要求给出A,B中重复的数据。 一般思路:
1)、遍历A中的所有数据,通过hash映射将他们分布存储在n个小文件中,记为{a1,a2,…,an};
2)、遍历B中的所有数据,通过hash映射将他们分布存储在n个小文件中,记为{b1,b2,…,bn};
3)、根据hash函数性质可知,A和B中的相同数据一定被映射到序号相同的小文件,所以我们依次比较{ai,bi}即可;
4)、如果问题更进一步,要求返回重复次数最多的k条数据,则可以将对比小文件找到的数据存入hash表,键为数据,值为该数据出现的次数。再用大小为k的堆,排序找出即可。
1、给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url?
思路: 假如每个url大小为10bytes,那么可以估计每个文件的大小为50G×64=320G,远远大于内存限制的4G,所以不可能将其完全加载到内存中处理,可以采用分治的思想来解决。
Step1:遍历文件a,对每个url求取hash(url)%1000,然后根据所取得的值将url分别存储到1000个小文件(记为a0,a1,…,a999,每个小文件约300M);
Step2:遍历文件b,采取和a相同的方式将url分别存储到1000个小文件(记为b0,b1,…,b999);
巧妙之处:这样处理后,所有可能相同的url都被保存在对应的小文件(a0vsb0,a1vsb1,…,a999vsb999)中,不对应的小文件不可能有相同的url。然后我们只要求出这个1000对小文件中相同的url即可。
Step3:求每对小文件ai和bi中相同的url时,可以把ai的url存储到hash_set/hash_map中。然后遍历bi的每个url,看其是否在刚才构建的hash_set中,如果是,那么就是共同的url,存到文件里面就可以了。
六、cdn加速
在HTTP请求的资源,请求可以分为静态请求和动态请求。
- 静态请求:静态请求是指在不同请求中访问到的数据都相同的静态文件。例如:图片、视频、网站中的文件(html、css、js)、软件安装包、apk文件、压缩包文件等。
CDN加速的本质是缓存加速,将您服务器上存储的静态内容缓存在CDN节点上,当您访问这些静态内容时,无需访问服务器源站,就近访问CDN节点即可获取相同内容,从而达到加速的效果,同时减轻服务器源站的压力。 - 动态请求:动态请求是指在不同请求中访问到的数据不相同的动态内容。例如:网站中的文件(asp、jsp、php、perl、cgi)、API接口、数据库交互请求等。当客户端访问这些动态内容时,每次都需要访问用户的服务器,由服务器动态生成实时的数据并返回给客户端。因此CDN的缓存加速不适用于加速动态内容,CDN无法缓存实时变化的动态内容。对于动态内容请求,CDN节点只能转发回源站服务器,没有加速效果。
- 全站加速:如果用户的网站或App应用有较多动态内容,例如需要对各种API接口进行加速,则需要使用全站加速。全站加速能同时加速动态和静态内容,加速方式如下:
静态内容使用CDN加速。动态内容通过路由优化、传输优化等动态加速技术以最快的速度访问您的服务器源站获取数据。从而达到全站加速的效果。