MapReduce
MapReduce定义
MapReduce是一个分布式运算程序的编程框架,是用户开发“基于hadoop数据分析应用”的核心框架。MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在Hadoop集群上
MapReduce优缺点
优点
-
MapReduce易于编程
它简单的实现了一些接口,就可以完成分布式程序,这个分布式程序可以分布到大量廉价的PC机器上运行。也就是说你写一个分布式程序,跟写一个简单的串行程序是一摸一样的。就是因为这个特点使得MapReduce编程变得非常流行。
-
良好的扩展性
当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力
-
高容错性
MapReduce设计的初衷就是使程序能够部署在廉价的PC机器上,这就要求它具有很高的容错性。比如一台机器挂了,它可以把任务转移到另一个节点运行,不至于任务失败,而这个过程不需要人工参与,而完全由hadoop内部完成
-
适合PB级以上海量数据的离线处理
可以实现上千台服务器集群并发工作,提供数据处理能力。
缺点
-
不擅长实时计算
MapReduce无法向MySQL一样,在毫秒或者秒级内返回结果
-
不擅长流式计算
流式计算的输入数据是动态的,而MapReduce的输入数据集是静态的,不能动态变化。这是因为MapReduce自身设计特点决定了数据源必须是静态的。
注意:静态数据到动态数据并非不可跨越,根本原因是速度比较慢。
-
不擅长DAG(有向图)计算
多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下,MapReduce并不是不能做,而是使用后,每个MapReduce作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常低下
总之一个字:慢
MapReduce进程
一个完整的MapReduce程序在分布式运行时有三类实例进程
- MrAppMaster:负责整个程序的过程调度以及状态协调
- MapTask:负责Map阶段的整个数据处理流程
- ReduceTask:负责Reduce阶段的整个数据处理流程
WordCount案例编写(入门)
WcMapper类
public class WcMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private Text word = new Text();
private IntWritable one = new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//拿到一行数据
String line = value.toString();
//按照空格切分数据
String[] words = line.split(" ");
//遍历数组将数据变成(word,1)
for (String word : words) {
this.word.set(word);
context.write(this.word,one);
}
}
}
WcReducer类
public class WcReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable total = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//做累加
int sum = 0;
for (IntWritable value : values) {
sum += value.get();
}
//包装结果并输出
total.set(sum);
context.write(key,total)
}
}
WcDriver类
public class WcDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//获取一个job实例
Job job = Job.getInstance(new Configuration());
//设置类路径(Classpath)
job.setJarByClass(WcDriver.class);
//设置Mapper和Reducer
job.setMapperClass(WcMapper.class);
job.setReducerClass(WcReducer.class);
//设置Mapper和Reducer输出类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//设置输入输出数据
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//提交Job
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
MapReduce的数据流
思考:当我们对一个文件大小为300M的数据进行计算,job进入集群的时候,是一台机器计算效率高,还是将文件切分成多份,分配给集群并行执行效率高。答案是肯定的。
那么问题又来了,如果文件大小只有1kb呢?
数据切片与MapTask并行度决定机制
切片由InputFormat负责
MapTask的数量由切片的数量来决定
- 一个job的map阶段并行度由客户端在提交job时的切片数决定
- 每个split切片分配一个MapTask并行处理
- 默认情况下,切片大小=BlockSize
- 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片
shuffle机制
分区
shuffle过程
- 我们mapper方法出来之后,数据会进入环形缓冲区,环形缓冲区满了之后会落盘
- 在落盘前会完成分区和排序的工作(区内有序的文件)
- 分区后的多个文件会按照分区归并
- 多个MapTask都会生产归并后的文件:分区切有序
- 于是多个MapTask中的相同分区的数据会进入到同一个reduce中进行处理:
- reduce会对区内数据进行归并排序,分组等,最终将结果输出
注意:
-
reduce的并行度由ReduceTask个数决定:在Driver中设置 job.setNumReduceTasks(int num)
-
分区的目的,告诉某一条数据应该被那一条ReduceTask处理
-
如果分区的数量比ReduceTask数量多:有一部分数据分不到ReduceTask,就会报错
-
如果分区数量比ReduceTask数量少:程序是不会报错的,但是会浪费资源
-
分区号要从0开始逐一累加
排序WritableComparable
排序是MapReduce框架中最重要的操作之一
MapTask和ReduceTask均会对数据按照key进行排序。该操作属于Hadoop的默认行为。任何程序中的数据都会被排序,而不管逻辑上是否需要(默认排序是按照字典顺序,且实现该排序的方法是快速排序)
可以实现WritableComparable接口重写对应方法来自定义排序规则
合并Combiner
- Combiner是MR程序中Mapper和Reducer之外的一种组件
- Combiner组件的父类就是Reducer
- Combiner和Reducer的区别在于运行位置
- Combiner是在每一个MapTask所在的节点运行
- Reducer是接受全局所有Mapper的输出结果
- Combiner的意义就是对每一个MapTask的输出进行局部汇总,减小网络传输量
- Combiner能应用的前提是不能影响最终的业务逻辑,而且,Combiner的输出kv应该跟Reducer的输入kv对应
MapReduce详细工作流程
- 首先我们有一个文件准备处理,处理的第一步,在客户端对文件完成切片工作生成一些相应的信息,放在提交目录中的临时目录中,这些信息是给yarn看的
- yarn拿到这些信息,计算出MapTask数量
- MapTask拿到自己的切片后,生成TextInputFormat,通过这个Format获取到RecorderReader,这个RecorderReader就负责将切片转换成(k,v)值
- 然后这个(k,v)值通过序列化方式传给Mapper的map方法进行逻辑运算,将处理完的(k,v)同样以序列化方式收集到outputCollector对象中,(在进入环形缓冲区之前先确定partitioner分区)然后向环形缓冲区写入数据
- 环形缓冲区写到80%(默认情况下)时会发生溢写的过程,在一些的过程中,这些数据(缓冲区80%的数据)会发生一次全排序,(如果启用Combiner就会将相同key合并为一组)最终写到磁盘(有序)
- 环形缓冲区进行多次溢写之后将多个文件通过归并排序合并成一个文件(如果之前启用Combiner,会在归并过程中再次启用Combiner,然后再落盘),到此为止就是map阶段最终的执行结果,MapTask随之就结束了。
- 所有MapTask任务完成后,启动相应数量的ReduceTask,并且告知ReduceTask处理数据范围(数据分区),每个ReduceTask都会从所有MapTask获取相应分区的结果数据。注意这时就不再分区(分区的目的就是就是不同的切片送往指定的ReduceTask,所有去往同一个ReduceTask就是同一个分区)
- 那么多个来自MapTask的数据结果,在ReduceTask中会发生归并排序合并成为一个数据整体,然后将数据传给Reducer,在传的过程中一次读取一组数据(GroupingComparator注意分区与分组,这两个是完全两个东西,分组是针对同一个分区内的数据进行分组)。
- Reducer处理后的数据(reduce()方法)最终会走到OutPutFormat(默认为TextOutPutFormat),将每一个KV对,向目标文本输出一行。然后经过RecordWrite输出,生成最终结果文件