一、MapTask的并行度
运行map部分的任务叫做maptask,并行度是指同时运行的maptask的任务个数。maptask处理的数据量对应于一个文件切片,每个maptask处理一个文件切片大小的数据。
MapReducer任务运行时通过 FileInputFormat 类传入输入数据文件,该类在读取文件时会调用 getSplit() 方法对文件进行逻辑切片。
getSplit() 部分源码如下,当使用默认配置时切片大小默认等于HDFS分块大小128M
/**
* Generate the list of files and make them into FileSplits.
* @param job the job context
* @throws IOException
*/
public List<InputSplit> getSplits(JobContext job) throws IOException {
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job)); // 1
long maxSize = getMaxSplitSize(job); // LONG_MAX_VALUE
// 块的切分标准,Hadoop2.x 默认128M
long blockSize = file.getBlockSize();
// 计算切片大小
long splitSize = computeSplitSize(blockSize, minSize, maxSize);
}
// 返回三个值得中间值
protected long computeSplitSize(long blockSize, long minSize, long maxSize) {
return Math.max(minSize, Math.min(maxSize, blockSize));
}
切片和分块的关系:
默认情况下大小相同。切块是物理概念,针对于HDFS的数据存储。切片是逻辑概念,针对于maptask的并行度。
由于HDFS存储时以块为单位,数据文件不足128M也会单独存储一个块,默认情况下maptask处理的数据量与一个块的数据量相同。对于多个小文件的情况,如共100个小文件,每个文件1KB。虽然所有文件一共才100KB,但默认要启动100maptask。此时可使用CombineFileInputFormat对小文件进行合并再做mapreduce处理。
二、ReduceTask的并行度
reducetask设置为多个就是将原来的一个大任务分成多个小任务,每一个任务负责一部分数据。既然要将数据进行拆分,就要遵循一个拆分的规则,避免每个小任务只对部分数据可见。数据的拆分就是分区,每个reducetask对应处理一个分区的数据。
若不指定分区规则,默认采用Hash分区,源码如下
public class HashPartitioner<K, V> extends Partitioner<K, V> {
/** Use {@link Object#hashCode()} to partition. */
/**
* K key : map输出的key
* V Value : map输出的value
* int numReduceTasks : 用户指定的ReduceTask任务个数
public int getPartition(K key, V value, int numReduceTasks) {
return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
// 避免hashCode取值超过Integer.MAX_VALUE,求与后超出int部分全为0
}
}
自定义分区:按照业务逻辑定义分区规则。
1. 继承Partitioner
2. 重写getPartition()方法
/**
* KEY : map输出key的类型
* VALUE : map输出value的类型
/
class MyPartition extends Partitioner<KEY, VALUE> {
public int getPartition(Text s, IntWritable integer, int numPartitions) {
s = s.toString();
if (s.startsWith("a")) return 0;
else if (s.startsWith("b")) return 1;
else return 2;
}
}
Driver类中指定分区类和分区个数。如不指定分区个数,默认按照一个分区处理。若指定分区个数小于自定义分区个数报错,大于自定义分区个数,未指定分区生成空的结果文件。
job.setPartitionerClass(MyPartition.class);
job.setNumReduceTask(3);
说明:一个节点可以运行多个maptask和reducetask任务,但一个maptask或reducetask任务只能在一个节点上运行。
由于两种任务对于数据分配的规则不同,maptask采用分片对数据进行切分,每个部分的数据大小都是相同的。而reducetask处理的数据量大小与分区规则有关,若分区规则设置不合理,造成某个分区数据量过大,将会造成数据倾斜问题,即一个reducetask要处理大量数据,而其它reducetask只处理少部分数据。
解决办法:合理设计分区算法。针对业务数据可采用字符串反转、加盐、加时间戳等方式避免数据倾斜。