海量数据处理(1):MapReduce
海量数据处理,就是基于海量数据的存储、删除、搜索等操作。由于数据量太大,导致要么无法在短时间内迅速处理,要么无法一次性装入内存。针对面试过程中的问题,主要有以下解决步骤
-
针对时间,可以采用更加迅速的数据结构和算法,比如BloomFilter、Hash、堆、Bitmap等
-
针对空间,无非就是大而化小,分而治之。
-
算法方面:
- 外排序算法(External Sorting)
- Map Reduce
- 非精确算法
- 概率算法
- 哈希算法与哈希函数(Hash Function)
数据结构方面:
- 哈希表(Hash Table)
- 堆(Heap)
- 布隆过滤器(BloomFilter)
- 位图(Bitmap)
一、MapReduce简介
给定一个有海量单词的集合,统计每个词的出现次数,最简单的就是for循环,放进map,一台机器这样做,明显是不合适的,所以我们想到了用两台机器for循环,两台服务器去处理数据,但是到了merge环节,还是一台机器啊,这也是个瓶颈。
合并时候也可以采用并行的方式,有两种选择
- 一是以机器来划分(比如第i台到第j台由某台机器合并)
- 二是根据key来划分(比如key由a、b、c、d,让一台机器合并key a和key b)
很显然以机器作为划分并不好,因为进行了第一轮合并了后,还要再进行合并…最后形成一个类似树状结构的,后面的机器得要等前面机器合并完了才能继续工作,依赖性较强。所以才用根据key来划分(没有前后依赖性,系统复杂度低),这就有了MapReduce。
注意,上面的map中采用了合并,这实际上是不对的,如果要合并,需要hashmap,机器里开一个hashmap肯定是放不下的,map的主要任务是打乱,不做合并。
二、Map函数和Reduce函数
Map函数和Reduce函数的输入输出必须是key-value
的形式
- Map 输入 key:文章存储地址,value:文章内容
- Reduce 输入key:Map输出的key,Value:Map输出的value
实现map和reduce函数
//OutputCollector是辅助输出的工具
class OutputCollector<K, V> {
public void collect(K key, V value) {
}
}
public class WordCount {
public static class Map {
//value是文章
public void map(String key, String value, OutputCollector<String, Integer> output) {
StringTokenizer tokenizer = new StringTokenizer(value);
while (tokenizer.hasMoreTokens()) {
String outputKey = tokenizer.nextToken();
output.collect(outputKey, 1); //不做merge
}
}
}
public static class Reduce {
//key和value都是map输出的,某个key出现n此就有n个1,只要把Iterator中所有的1加起来即可
public void reduce(String key, Iterator<Integer> values, OutputCollector<String, Integer> output) {
int sum = 0;
while (values.hasNext()) {
sum += values.next();
}
output.collect(key, sum);
}
}
}
三、传输整理
在Map到Reduce的过程中有个传输整理,这个流程如图所示
Partition和Sort就是传输整理,在这个过程中ab分为一组,cd分为一组,这个分组是由master的一致性hash来进行分组,由于分组存在硬盘上,采用外部排序。排序时key作为第一关键字,value作为第二关键字。接下来是Reduce的工作,通过Fetch把排好序的文件拿到自己的机器上,并对这些文件进行k路归并成Reduce的输入。
下面介绍两个应用
四、利用MapReduce建立倒排索引
正排:序号->关键字,倒排:给定文章关键词,返回文章序号,就比如google上搜索关键词,返回网页。
map输出的key是文章的关键词,value是文章的编号。在reduce中需要进行去重操作
//OutputCollector是辅助输出的工具
class OutputCollector<K, V> {
public void collect(K key, V value) {
}
}
class Document {
public int id;
public String content;
}
public class InvertedIndex {
public static class Map {
// value是对应文章的id
public void map(String _, Document value, OutputCollector<String, Integer> output) {
StringTokenizer tokenizer = new StringTokenizer(value.content);
while (tokenizer.hasMoreTokens()) {
String word = tokenizer.nextToken();
output.collect(word, value.id);
}
}
}
public static class Reduce {
public void reduce(String key, Iterator<Integer> values, OutputCollector<String, List<Integer>> output) {
ArrayList<Integer> res = new ArrayList<>();
int left = -1; //用于去重
while (values.hasNext()) {
Integer now = values.next();
if (now != left) {
res.add(now);
}
left = now;
}
output.collect(key, res); //key->包含该key的文章
}
}
}
五、利用MapReduce对Anagram进行分组
Anagram的特性就是能够随意改变单词中字母顺序,如果把内部字母排个序,那么只要是Anagram就会变成同一个单词。经过内部排序的单词就能够作为key,而原单词就是value。
//OutputCollector是辅助输出的工具
class OutputCollector<K, V> {
public void collect(K key, V value) {
}
}
public class Anagram {
public static class Map {
public void map(String key, String value, OutputCollector<String, String> output) {
StringTokenizer tokenizer = new StringTokenizer(value);
while (tokenizer.hasMoreTokens()) {
String word = tokenizer.nextToken();
char[] chs = word.toCharArray();
Arrays.sort(chs);
output.collect(new String(chs), word);
}
}
}
public static class Reduce {
public void reduce(String key, Iterator<String> values, OutputCollector<String, List<String>> output) {
List<String> res = new ArrayList<>();
while (values.hasNext()) {
res.add(values.next());
}
output.collect(key, res);
}
}
}
来源:CSDN
作者:0xac001d09
链接:https://blog.csdn.net/weixin_44424668/article/details/103914239