一 MapTask个数的决定因素
首先,我们需要明确以下几点:
1Map Task个数不能通过配置文件指定
2Map Task个数是在进行文件的切分时动态计算的
3FileInputFormat负责切分文件进行split操作
1.1分析源码:
intmaps = writeSplits(job, submitJobDir);
private int writeSplits(org.apache.hadoop.mapreduce.JobContext job,
jobSubmitDir) throws IOException,
InterruptedException,ClassNotFoundException {
JobConf jConf = (JobConf)job.getConfiguration();
int maps;
//判断是否采用新的API,现在我们应该都是新的
if (jConf.getUseNewMapper()) {
maps = writeNewSplits(job, jobSubmitDir);
} else {
maps = writeOldSplits(jConf, jobSubmitDir);
}
return maps;
}
private <T extends InputSplit> int writeNewSplits(JobContext job, Path jobSubmitDir) throws IOException,
InterruptedException,ClassNotFoundException {
Configuration conf = job.getConfiguration();
//创建FileInputFormat
InputFormat<?, ?> input =
ReflectionUtils.newInstance(job.getInputFormatClass(), conf);
//调用FileInputFormat#getSplits
List<InputSplit> splits = input.getSplits(job);
T[] array = (T[]) splits.toArray(new InputSplit[splits.size()]);
//对split数组元素进行排序,最大的是第一个
Arrays.sort(array, new SplitComparator());
//创建Split文件,这些个文件会存在提交路径的临时目录
JobSplitWriter.createSplitFiles(jobSubmitDir, conf,
jobSubmitDir.getFileSystem(conf), array);
return array.length;
}
public List<InputSplit> getSplits(JobContext job) throws IOException {
StopWatch sw = newStopWatch().start();
//根据mapreduce.input.fileinputformat.split.minsize配置和1取最大的
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
//根据mapreduce.input.fileinputformat.split.maxsize取最大的
long maxSize = getMaxSplitSize(job);
// generate splits
List<InputSplit> splits = new ArrayList<InputSplit>();
List<FileStatus> files = listStatus(job);
for (FileStatus file: files) {
//获取文件路径
Path path = file.getPath();
//获取文件大小
long length = file.getLen();
if (length != 0) {
BlockLocation[] blkLocations;
//从本地获取文件数据块位置
if (file instanceof LocatedFileStatus) {
blkLocations = ((LocatedFileStatus) file).getBlockLocations();
} else {//非本地文件,远程调用获取文件数据块信息
FileSystem fs = path.getFileSystem(job.getConfiguration());
blkLocations = fs.getFileBlockLocations(file, 0, length);
}
if (isSplitable(job, path)) {
//获取文件数据块大小,默认128M
long blockSize = file.getBlockSize();
//计算InputSplit大小
long splitSize = computeSplitSize(blockSize, minSize, maxSize);
//将bytesRemaining(剩余未分片字节数)设置为整个文件的长度
long bytesRemaining = length;
/*
* 若剩余值bytesRemaining > 1.1 * splitSize,则继续对文件进行逻辑切分
* 若小于这个值,则作为一个InputSplit
*/
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
//计算文件的数据块的索引,只是计算InputSplit的起始位置是否位于某一块中
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
//然后将计算的索引位置作为参数计算切分的split文件,然后添加到split数组
splits.add(makeSplit(path, length-bytesRemaining, splitSize,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
/*
* 剩余字节数-splitSize,相当于下一次从这儿开始计算
* 我们也可以推断出起始位置为0,splitSize,2*splitSize,3*splitSize 等
*/
bytesRemaining -= splitSize;
}
//如果block中剩下的一小段数据量小于splitSize,还是认为它是独立的分片
if (bytesRemaining != 0) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
}
} else { // not splitable
splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts(),
blkLocations[0].getCachedHosts()));
}
} else {
//Create empty hosts array for zero lengthfiles
splits.add(makeSplit(path, 0, length, new String[0]));
}
}
// Save the number of input files for metrics/loadgen
//设置mapreduce.input.fileinputformat.numinputfile值为输入文件数量
job.getConfiguration().setLong(NUM_INPUT_FILES, files.size());
sw.stop();
if (LOG.isDebugEnabled()) {
LOG.debug("Total# of splits generated by getSplits: " + splits.size()
", TimeTaken: " + sw.now(TimeUnit.MILLISECONDS));
}
return splits;
}
public static <T extends InputSplit> void createSplitFiles(Path jobSubmitDir,
conf, FileSystem fs, T[] splits)
throws IOException, InterruptedException {
/*
* 创建切片文件,并获取FSDataOutputStream对应路径jobSubmitDir
* 届时就会生成${jobSubmitDir}/job.split文件
* jobSubmitDir:参数yarn.app.mapreduce.am.staging-dir
* 指定的路径
*/
FSDataOutputStream out = createFile(fs,
JobSubmissionFiles.getJobSplitFile(jobSubmitDir), conf);
//将切片数据写入切片文件,并得到切片元数据信息数组
SplitMetaInfo[] info = writeNewSplits(conf, splits, out);
out.close();
//将切片元数据信息写入切片元数据信息文件
writeJobSplitMetaInfo(fs,JobSubmissionFiles.getJobSplitMetaFile(jobSubmitDir),
new FsPermission(JobSubmissionFiles.JOB_FILE_PERMISSION), splitVersion,
info);
}
private static FSDataOutputStream createFile(FileSystem fs, Path splitFile,
job) throws IOException {
FSDataOutputStream out = FileSystem.create(fs, splitFile,
new FsPermission(JobSubmissionFiles.JOB_FILE_PERMISSION));
//获取副本数,默认是10
int replication = job.getInt(Job.SUBMIT_REPLICATION, 10);
fs.setReplication(splitFile, (short)replication);
//写入切片头信息
writeSplitHeader(out);
return out;
}
#遍历输入的文件
#获取文件数据块的位置以及文件数据块的大小(默认128m)
#计算分片的尺寸大小splitSize
#对文件数据块进行分片
#创建切片文件,写入头信息,文件位置位于提交job的路径
#将分片信息写入分片文件,并将得到的切片元数据信息写入切片元数据信息文件
1.2Map任务的决定因素
我们知道,map的个数是intmaps = writeSplits(job, submitJobDir);
这里产生的,也就是取决于切片数量。
那么切片数量又是由什么决定的呢?
>如果splitSize== blockSize(128M),那么只有一个切片
也就是一个Map 任务
>如果minSize超过blockSize,那么根据计算splitSize算法,会取128M和minSize中最大的,所以会减少分片数量,也就是会减少MapTask数量
>如果maxSize< blockSize,那么会选择之间比较小的然后跟minSize比较取较大者,那么这样这会增加分片数量,从而增加Map Task
总结:决定因素
# mapreduce.input.fileinputformat.split.minsize
# mapreduce.input.fileinputformat.split.maxsize
# blockSize
二 ReduceTask的决定因素
reduce在运行时往往需要从相关map端复制数据到reduce节点来处理,因此相比于map任务。reduce节点资源是相对比较缺少的,同时相对运行较慢,正确的reduce任务的个数应该是0.95或者1.75 *(节点数* mapred.tasktracker.tasks.maximum参数值)。如果任务数是节点个数的0.95倍,那么所有的reduce任务能够在 map任务的输出传输结束后同时开始运行。如果任务数是节点个数的1.75倍,那么高速的节点会在完成他们第一批reduce任务计算之后开始计算第二批 reduce任务,这样的情况更有利于负载均衡。同时需要注意增加reduce的数量虽然会增加系统的资源开销,但是可以改善负载匀衡,降低任务失败带来的负面影响。同样,Reduce任务也能够与 map任务一样,通过设定JobConf的conf.setNumReduceTasks(intnum)方法来增加任务个数。