MapReduce大致流程:
inputformat -> Mapper -> Shuffle(各种排序,分区排序,归并排序) -> Reducer -> outputformat
shuffle过程位于map()方法之后,reduce()方法之前。
MapTask的工作机制(包括shuffle过程,包括ReduceTask):
【MapTask通过InputFormat获得RecordReader,输出keyValue,再交给mapper中用户定义的Map()方法中处理,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。在这个方法内部,它会调用Partitioner对key/value数据进行分区;然后就开始shuffle过程,【ouputCollector将分区后的数据写入环形缓冲区中,环形缓冲区底层是一个100M大小的数组,可以两边写数据,右边写keyvalue数据,左边写元数据,元数据包括:index,partition,keystart , valstart。当数据达到环形缓冲区的80%时候,会进行分区排序(快排,排序的过程中是对索引进行排序),如果设置了combiner会进行分区内汇总。再溢写文件到本地磁盘,在溢写的过程中,缓冲区中的那20%也会反向向两边写数据。溢写的文件中是分区且有序的,而且可能有多个,这些文件会归并排序到一个大文件中,如果设置了combiner还会进行分区汇总。】,等到maptask任务完成后,【就会启动相应数量的reducetask(具体多少需要进行实验,设置reducetask的数量,调查相应的执行效率),reducetask会根据分区号主动去抓取不同的maptask机器上的相同分区的结果文件到内存中,如果内存不够,会溢写磁盘中,然后reducetask再将这些文件归并排序成一个大文件,交给Reducer()方法。到这里Shuffle过程就结束了。】
后面就到了reducetask的逻辑运算过程,也就是调用用户自定义的reduce方法,对每组key value进行统计处理。】
(这里的value是一个集合,什么样的集合?看需求!如果是单词统计案例,value就是一个或多个1组成的集合,如果是统计手机的上下行总流量,那就是一个或多个flowbean类型的对象集合。)
Maptask工作机制流程图:
Shuffle机制
ReduceTask工作机制(包括shuffle过程):
reducetask会根据分区号主动去抓取不同的maptask机器上的相同分区的结果文件,然后每个reducetask再将这些文件归并排序成一个大文件,到这里***Shuffle过程***就结束了。
注意事项
1. ReduceTask=0,表示没有Reduce阶段,输出文件个数和Map个数一致。
2. ReduceTask默认值就是1,所以输出文件个数为一个。
3. 如果数据分布不均匀,就有可能在Reduce阶段产生数据倾斜。
4. ReduceTask数量并不是任意设置,还要考虑业务逻辑需求,有些情况下,需要计算全局汇总结果,就只能有1个ReduceTask。
5. 具体多少个ReduceTask,需要根据集群性能而定。
6. 如果分区数不是1,但是ReduceTask为1,是否执行分区过程。答案是:不执行分区过程。因为在MapTask的源码中,执行分区的前提是先判断ReduceNum个数是否大于1。不大于1肯定不执行。
OutCollector中Partitioner源码分析
Job.setNumReduceTasks(1) 结果会有一个分区文件part-r-00000,不会走到
Job.setNumReduceTasks(0) 不会到shuffle阶段,直接执行完Map阶段就结束了。最后输出的文件为part-m-00000
// get an output object
if (job.getNumReduceTasks() == 0) {
//如果不设置走默认的
output =
new NewDirectOutputCollector(taskContext, job, umbilical, reporter);
} else {
output = new NewOutputCollector(taskContext, job, umbilical, reporter);
}
NewOutputCollector(org.apache.hadoop.mapreduce.JobContext jobContext,
JobConf job,
TaskUmbilicalProtocol umbilical,
TaskReporter reporter
) throws IOException, ClassNotFoundException {
collector = createSortingCollector(job, reporter);
partitions = jobContext.getNumReduceTasks(); //job对象中获取ReduceTasks的数量
if (partitions > 1) {
// 如果设置的ReduceTask数据大于1,就会走我们自定义的分区类
partitioner = (org.apache.hadoop.mapreduce.Partitioner<K,V>)
ReflectionUtils.newInstance(jobContext.getPartitionerClass(), job);
} else {
// 如果设置为1时,partitioner分区类就是一个匿名内部类对象,就只有一个分区0000
partitioner = new org.apache.hadoop.mapreduce.Partitioner<K,V>() {
@Override
public int getPartition(K key, V value, int numPartitions) {
return partitions - 1;
}
};
}
}
//Partitioner会对key/value数据进行分区
@Override
public void write(K key, V value) throws IOException, InterruptedException {
collector.collect(key, value,
partitioner.getPartition(key, value, partitions));
}