目录
MapReduce运行时的mapTask和reduceTask
2.1 reduceTask & reduceTask并行度
MapReduce运行时的mapTask和reduceTask
1 mapTask任务
1.1 mapTask & mapTask并行度
mapTask任务的并行度:分布式运行mapTask任务的个数叫做并行度。
mapTask任务实际上就是分而治之中,分了多少个小任务,类似WordCount程序中map函数的调用次数。而最终mapTask的任务划分与HDFS底层存储的数据块有直接关系。
Hadoop源码中的FileInputFormat中的getSplits方法,决定每个mapTask的任务划分。该方法中有个split(切片)的概念。
切片(split)也称为逻辑切片,即只是对数据进行了逻辑划分,并没有真正的进行数据切分。例如:数据300M,mapTask01:0-100M、mapTask02:100-200M、mapTask03:200-300M。
这个逻辑切片,默认的大小是一个数据块的大小(BlockSize),那为什么是一个数据块的大小呢?
详细解释如下:
因为底层存储块的默认大小是128M,不同的数据块可能存储在不同的节点上,所以切片大小理论上128M合适,避免引切片过大、过小,而造成跨块计算,也避免了不必要的网络传输。
源码中的splitSize:
computeSplitSize方法就是求这三个参数的中间值大小,默认是blockSize大小(128M);
当自定义切片大小时,若想要切片大小小于BlockSize,改maxSize;若想要切片大小大于BlockSize,改minSize;
同时,getSplits方法实际上就是获取每一个逻辑切片。mapTask任务实际最终对应到数据上就是对应一个split切片。即一个切片,一个mapTask。也就是说:一个split切片 <----> 一个mapTask <----> 一个YarnChild
综上,任务划分就是对原始处理的数据进行任务切分(让不同的数据跑在不同的节点上),最终启动mapTask的个数与切片大小有关(切片默认大小是128M)
思考以下问题:
1)切片与数据块的关系?
答:没啥实际关系,切片是数据计算逻辑的划分,块是数据存储的物理划分。
2)MR统计3个300M的单词数据文件,需要启动几个mapTask任务?
答:9个
3)MR统计1个300M的单词数据文件,切片大小设置为200M,启动几个mapTask任务?
答:2个;mapTask-01:0-200M、mapTask-02:201-300M;但实际存储的是3个数据块。
4)MR统计8个300B的单词数据文件,切片大小设置为10M,启动几个mapTask?
答:8个;这种情况下切片的大小是实际处理的数据文件的大小。文件大小小于split大小,默认不会将文件进行合并。
在生产中,如果出现了大量小文件,可将原来默认的文件输入类FileInputFormat(TextInputFormat)修改为CombineTextInputFormat,该类可将文件进行合并,同时修改切片设置,可提高程序运行效率;
job.setInputFormatClass(CombineTextInputFormat.class);
CombineTextInputFormat.setMaxInputSplitSize(job,10*1024*1024);
这样,就会对文件进行合并读取,10M一个split(mapTask);即:合并文件后进行mapTask任务划分。
综上可知:块决定存储,切片决定mapTask!!!
1.2 如何修改mapTask并行度
想要修改mapTask的并行度,就需要修改split的大小,修改方式有如下两种:
(1)修改mapred-site.xml配置文件
修改配置文件中:mapreduce.input.fileinputformat.split.maxsize和mapreduce.input.fileinputformat.split.minsize两个配置项;
一般不采用该方法,因为修改配置项后,对别的MR程序也会造成影响,属于一变万变。
(2)修改代码
在FileInputFormat中使用以下方法,这种方式只对当前程序有效;
2 reduceTask任务
2.1 reduceTask & reduceTask并行度
reduceTask就是:reduce端进行任务分配的时候的每一个小任务;
reduceTask的并行度就是:reduceTask并行运行的个数;
reduceTask的并行度与分区个数有关;
reduceTask的并行度在显示上,就是最终输出结果的个数;若最后数据结果是N个,那么就是启动了N个reduceTask;
当数据量特别大的时候,若只有一个reduceTask,即只在一个节点中进行结果合并;这会严重导致性能不高等问题。
2.2 如何设置reduceTask的并行度
可以通过修改驱动类代码,设置reduceTask个数,传N就是N个reduceTask,如:part-r-00000、part-r-00001、…、part-0000n,生成的N个文件是各自统计不同的key的结果。最后,n个文件合并到一起就是我们最终的统计结果。
job.setNumReduceTasks(n);
该方法的内部是由MR的默认分区方式,决定了由不同的map输出的key,最终进入到不同的reduceTask中。
分区在map之后,reduce之前,即:shuffle阶段。shuffle阶段详细介绍地址:
MR中的分区方式包括:默认分区方法和自定义方法两种。下面对这两种分区方法别进行介绍:
1)默认分区方法
Partitioner(默认的分区类)<----> HashPartitioner(默认实现的分区)
public int getPartition(K key, V value,int numReduceTasks) {
return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}
key:map端输出的key;
value:map端输出的value;
numPartitions:reduceTask的个数;是由job.setNumReduceTask()方法设置。
默认分区方法是根据map端输出的Key的HashCode()值,进行取余分区的。
最终,模的结果通过这个函数得到不同的余数,进入到不同的reduceTask中进行计算。同时也可以保证,相同的key进入相同的reduceTask中。
综上,reduceTask并行度 <----> job.setNumReduceTask() <----> 设置了几个就有几个reduceTask <----> 最终对应输出。
2)自定义分区方法
工作中,我们会经常根据业务需求进行自定义分区,比如:统计各地区的手机流量,就需要按照各地区输出不同的统计结果。
思考该需求:进行单词统计时,将以a~j为首的单词存放到一个文件中,以k~z为首的单词的存放到另一个文件中。
具体实现自定义分区的流程为:
①创建自定义分区类,继承Partitioner类
②重写getPartition方法;返回的int值,就是part-r-0000x文件中x的值。
package com.ethan.mrpartition;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
/**
* 自定义分区规则
* @author Ethan
* Key:map输出key的类型
* value:map输出value的类型
*
*/
public class MyPartition extends Partitioner<Text, IntWritable>{
/**
* Text key:map输出的key
* IntWritable value:map输出的value
* int numPartitions:分区个数 由job.setNumReduceTask()设置
*/
@Override
public int getPartition(Text key, IntWritable value, int numPartitions) {
String keyString = key.toString();
char keyChar = keyString.charAt(0);
if(keyChar >= 'a' && keyChar <= 'j') {
return 0;
}else {
return 1;
}
}
}
类中的参数:
Key:map输出key的类型;
value:map输出value的类型
方法中的参数:
Text key:map输出的key;
IntWritable value:map输出的value;
int numPartitions:分区个数,通过job.setNumReduceTask()设置
③设置分区个数
job.setNumReduceTasks(2);
④在驱动类中添加自定义分区类
job.setPartitionerClass(MyPartition.class);
总结:
最终决定输出文件个数的是setNumReduceTasks,而分区的作用仅仅是规划每个reduceTask应该计算的数据的范围。
(1)默认情况下:(key.hashCode & Integer.Max) % numReduceTasks---Key哈希取余。这是一种比较均匀的数据划分方式。
(2)自定义情况下:根据需求,按照自定义的规则规划reduceTask应该处理的数据。
本例中,reduceTask-01:a~j、reduceTask-02:k~z。
(3)特别的,当你自定义分区的个数(partition)大于设置的分区个数(reduceTask)的时候,程序会报非法分区错误;
只有设置分区为1时除外,因为设置分区为1时,所有结果输出到1个文件中。
当自定义分区个数,小于设置的分区个数是,程序能正常运行,只不过是生成的文件大于分区个数文件中没有内容而已。
没有内容是因为多生成了reduceTask,在那空跑。
(4)自定义分区的返回值的含义:与reduceTask的id相对应;正常情况下,reduceTask从0开始编号。
如果分区中返回0,代表对应reduceTask0,最终的结果文件对应part-r-00000
如果分区中返回2,代表对应reduceTask0,最终的结果文件对应part-r-00002
2.3 数据倾斜问题
reduceTask的并行度最终会影响MR程序的整体运行效率,在生产环境中,mapTask(切片)基本不用改。但reduceTask全凭自己设置。所以在设计分区的时候,一定要足够的了解数据,如果设计分区不理想,容易产生数据倾斜。
(1)什么是数据倾斜?
每个reduceTask处理的数据不均匀。其造成的直接后果是影响代码的整体执行效率,比如:10个reduceTask,9个已经完成,但有1个始终完成不了。这9个就要一直等待那一个完成,极大的拉低了执行效率。
(2)如何避免数据倾斜?
合理设计分区,只能减少,无法完全消除。