MapReduce 浅析 (1)
本文目录
1 简介
2 模拟情景
3 MapReduce
4 代码示例分析(Hadoop)
1 简介
MapReduce是Google提出的一个软件架构,用于大规模数据集(大于1TB)的并行运算。概念“Map(映射)”和“Reduce(归纳)”,及他们的主要思想,都是从函数式编程语言借来的,还有从矢量编程语言借来的特性。[来源:维基百科]
当一个任务的规模超过了一台计算机自身的承受能力时,若想顺利完成该任务,就需要想办法将这个大规模的任务,分成若干个小规模的任务,分别交给不同的计算机处理,并将每个计算机的处理结果汇总并整理,得到该任务的最终结果。
但是,如何实现多台计算机共同处理一个大规模任务?MapReduce架构,就是用来解决这个问题的。
下面,我将从一个简单的模拟情景事例开始,阐述我个人对MapReduce的理解。
2 模拟情景
假设:一个客户找到某个教授,希望准确知道《红楼梦》里每个数字(0-9)出现的次数。
提取关键信息:
任务:统计每个数字出现的次数。
材料:《红楼梦》
需求人:客户
负责人:教授
由于《红楼梦》内容太多,教授自己无法及时完成该任务,所以,教授找来3个学生(A、B、C),将整本《红楼梦》(共120章)分成三份,每个学生负责统计40章内容里每个数字出现的次数。
提取关键信息:
任务:统计各自材料中,每个数字出现的次数。
材料:各自三分之一 《红楼梦》
负责人:3个学生(A、B、C)
当3个学生各自完成统计任务时,教授说,按照指定规则,将结果交给另外2个学生(甲、乙)。
规则:数字0-4的统计结果交给学生甲,数字5-9的统计结果交给学生乙。
提取关键信息:
任务:汇总统计结果
材料:学生A、B、C提供的统计结果。
负责人:2个学生(甲、乙)
当2个学生(甲、乙)各自完成汇总任务时,将汇总结果交给教授。至此,教授已经得到《红楼梦》中每个数字出现的次数,并将结果反馈给最初的客户。
3 MapReduce
将上述情景与MapReduce进行类比:
Job:统计每个数字出现的次数
Data:红楼梦
Mapper:学生A、学生B、学生C
Mapper Task:统计各自内容中,每个数字出现的次数。
Reducer:学生甲、学生乙
Reducer Task:汇总来自Mapper的数据。
注意:
(1) Hadoop 会将输入数据(Data)分为若干个 InputSplit (Data1, Data2, Data3),每个 InputSplit 交由一个独立的 Mapper 节点进行处理。在 Mapper 节点处理 InputSplit 时,每次读取其中的一行数据,并调用 map 方法进行处理,完成后,再读取下一行数据。
(2) 当所有 Mapper 都完成各自任务之后,数据才会被提交到 Reducer 节点。实际上,在 Mapper 和 Reducer 之间,还有一个 "Shuffle and Sort" 层。每个 Mapper 会将处理后的数据提交给"Shuffle and Sort"层,当所有 Mapper 都完成了各自的任务之后,"Shuffle and Sort" 层会对所有数据进行分类和排序,然后分派给不同的 Reducer 节点,并保证相同 Key 的数据会分派给同一个 Reducer 节点。
(3) 同一个主机,既可以是 Mapper 节点,也可以是 Reducer 节点。在 Hadoop 群组中,某个主机可以先作为 Mapper 进行工作,然后,再作为 Reducer 进行工作。类比模拟情景,学生A 和 学生甲 可以是同一个人。
4 代码示例分析(Hadoop)
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class NumberCount {
/**
* Mapper : 学生A、学生B、学生C
* Object : 输入数据 Key 类型
* Text : 输入数据 Value 类型
* Text : 输出数据 Key 类型(输出至 Reducer 的 Key 类型)
* IntWritable: 输出数据 Value 类型(输出至 Reducer 的 Value 类型)
*/
public static class StudentMapper extends Mapper<Object, Text, Text, IntWritable> {
/**
* Mapper Task: 统计各自内容中,每个数字出现的次数。
* key : 输入数据 Key (源自 Hadoop 分派的数据)
* value : 输入数据 Value (源自 Hadoop 分派的数据)
* context : 输出对象 Context (用于输出数据至 Reducer 的对象)
*/
public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
// 用数组记录数字[0-9]个数
int[] numberCount = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
// 将输入字符串中的非数字内容替换为空
String strNum = value.toString().replaceAll("[^0-9]", "");
// 遍历替换后的字符串中的每个字符
for (int index = 0; index < strNum.length(); index++) {
int arrayIndex = strNum.charAt(index) - 48;
numberCount[arrayIndex]++;
}
// 将统计结果以键值对<数字,个数>的形式提交
for (int index = 0; index < numberCount.length; index++) {
context.write(new Text(String.valueOf(index)), new IntWritable(numberCount[index]));
}
}
}
/**
* Reducer : 学生甲、学生乙
* Text : 输入数据 Key 类型(对应 Mapper 输出 Key 类型)
* IntWritable: 输入数据 Value 类型(对应 Mapper 输出 Value 类型)
* Text : 输出数据 Key 类型 (输出最终数据的 Key 类型)
* IntWritable: 输出数据 Value 类型 (输出最终数据的 Value 类型)
*/
public static class StudentReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
/**
* Reducer Task: 汇总来自Mapper的数据。
* key : 输入数据 Key (源自 Mapper 输出的 Key)
* value : 输入数据 Value (源自 Mapper 输出的一组 Value)
* context : 输出对象 Context (用于输出最终数据)
*/
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
IntWritable count = new IntWritable();
// 汇总每个 Key(数字) 对应的 Value(个数) 值之和
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
count.set(sum);
// 将汇总结果提交
context.write(key, count);
}
}
/**
* 入口函数
*/
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
// 创建 Job
Job job = new Job(conf, "Number Count");
// 设置 Mapper
job.setMapperClass(StudentMapper.class);
// 设置 Reducer
job.setReducerClass(StudentReducer.class);
// 设置 Mapper 的输出 Key 类型
job.setOutputKeyClass(Text.class);
// 设置 Mapper 的输出 Value 类型
job.setOutputValueClass(IntWritable.class);
// 设置 Data 输入路径
FileInputFormat.addInputPath(job, new Path("/user/hadoop/inputDemo"));
// 设置 Result 输出路径
FileOutputFormat.setOutputPath(job, new Path("/user/hadoop/output"));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}