MapReduce定义
MapReduce是一个分布式运算的编程框架,是开发“基于Hadoop数据分析应用”的核心框架,他能将用户编写的业务逻辑代码和自带默认组件整合到分布式运算程序,并发运行在一个Hadoop集群上。
MapReduce优缺点
优点:易于编程、扩展,高容错、适合海量数据计算
缺点:不擅长实时计算、不擅长流式计算(Spark、Flink擅长流式计算)、不擅长DAG有向无环图计算(一个任务的结果作为另一个任务的输入,Spark擅长),注意不擅长不等于不行
MapReduce核心思想
MapReduce运算程序一般分为两个阶段:Map阶段和Reduce阶段,也叫MapTask和ReduceTask,两者都是并发运行的,且ReduceTask依赖MapTask的输出。MapReduce运算程序只包含一个MapTask和一个ReduceTask。
MapReduce进程
MapReduce运算程序有三个进程:
- MrAppMaster:负责整个程序的过程调度和状态协调
- MapTask:负责Map阶段的数据处理
- ReduceTask:负责Reduce阶段的数据处理
MapReduce WordCount源码
主要有两个静态内部类,分别继承了抽象类Mapper和Reducer。在Mapper类的定义中,有四个泛型,分别代表入参的key、value类型和出参的key、value类型,在Reducer类的定义中,也有四个泛型,也是分别代表入参的key、value类型和出参的key、value类型,其中,Mapper类的map方法的出参的key、value类型分别与Reducer类的入参的key、value类型相同。在WordCount案例中,Map阶段的入参的key类型实际为LongWritable(相当于Long),入参的value类型为Text(相当于String),出参的key类型为Text类型,出参value类型为IntWritable(相当于int)。Reduce阶段的入参的key类型为Text,入参的value类型为IntWritable类型,而reduce方法的入参的value类型为Iterable类型,类似于集合,出参的key类型为Text类型,出参value类型为IntWritable(相当于int)。Map阶段的map方法对于每一个<k,v>键值对都会调用一次,Reduce阶段的reduce方法对于每一个相同的key都会调用一次。以下是官方的WordCount的Mapper类和Reducer类(后续我们会自己写一份):
然后还有个main方法,用于设置job相关信息,这一步一般可细化为7步,后续会介绍。
他们的父类Mapper类和Reducer类中有个run方法,大体都是先setup设置初始化参数,然后while循环运行map方法或者reduce方法(父类中的这些方法都没有具体实现,等着子类实现),最后finnaly中运行cleanup方法做收尾工作,对于setup方法和cleanup方法,父类也都没实现,如果子类有需要也可以覆写。
举个例子,假设要统计如下文件的单词出现次数:
a a
b
c c
Map阶段的结果应为:
(a,1) (a,1)
(b,1)
(c,1) (c,1)
注意,map阶段不做reduce阶段的工作,因此不会产生如下结果:
(a,2)
(b,1)
(c,2)
然后reduce阶段才进行汇总,产生结果:
(a,2)
(b,1)
(c,2)
MapReduce WordCount自定义本地实现
Mapper代码:
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private Text text = new Text();
private IntWritable intWritable = new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = key.toString();
for (String str : line.split(" ")) {
text.set(str);
context.write(text, intWritable);
}
}
}
Reducer代码:
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable intWritable : values) {
sum += intWritable.get();
}
IntWritable intWritable = new IntWritable();
intWritable.set(sum);
context.write(key, intWritable);
}
}
Driver代码:
public class WordCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1. 获取配置信息生成job
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
// 2. 设置jar包class路径
job.setJarByClass(WordCountDriver.class);
// 3. 设置mapper和reducer
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 4. 设置mapper的输出的key、value类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputKeyClass(IntWritable.class);
// 5. 设置最终输出的key、value类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6. 设置输入路径和输出路径
FileInputFormat.setInputPaths(job, new Path("本地文件路径或者HDFS文件路径"));
FileOutputFormat.setOutputPath(job, new Path("本地文件路径或者HDFS文件路径"));
// 7. 提交job
job.waitForCompletion(true); // 监控并打印job相关的信息
}
}
job设置的七小步:
- 获取配置信息生成job
- 设置jar包class路径
- 设置mapper和reducer
- 设置mapper的输出的key、value类型
- 设置最终输出的key、value类型
- 设置输入路径和输出路径
- 提交job
注意导的类一定要是hadoop的mapreduce包下的。
MapReduce序列化
Hadoop集群肯定是多台机子互相协调工作组成的,因此这就涉及到他们之间的对象传输。传统的java序列化可以保证对象在内存和磁盘之间进行转化,那如何在Hadoop集群内进行对象的传输和转化呢,这就涉及到MapReduce序列化,并且这种序列化比java序列化传输速度更快。
Hadoop序列化的实现:实现Writable 接口。基本数据类型对应的序列化类型直接在其后加上Writable即可:
实现 Writable 接口(往往需求是要有一个自定义的bean能够序列化)即要实现其中的write和readFields方法,write方法也称为序列化方法,readField方法也称为反序列化方法。反序列化时,该bean必须要有空参构造函数。序列化顺序和反序列化顺序必须一致,即先写什么就先读什么。要想把结果显示在文件中,必须覆写toString方法。如果需要将自定义的bean放在key中传输,还必须实现Comparable接口,因为MapReduce的Shuffle过程要求能够对key进行排序。