1最先启动MRAppMaster,MRAppMaster根据job的描述信息,计算需要的maptask实例的数量,然后向集群申请机器,启动相应数量的maptask进程。
2 maptask启动之后,根据给定的数据切片范围进行数据处理。
A利用指定的inputformat来获取RecordReader对象读取数据,形成KV输入。
B将输入的kv对传递给客户定义的map方法,做逻辑运算,将map方法输出的kv对收集到缓存。
C将缓存中的KV对按照K分区排序会溢写到磁盘文件。
3 MRAppMaster监控到所有maptask进程任务完成之后(真实情况是,某些maptask进程处理完成以后,就会开始启动reducetask去已经完成maptask处去fetch数据),会根据客户指定的参数启动相应数量的reducetask进程。并告知reducetask进程要处理的数据范围(数据分区)。
4 ReduceTask进程启动以后,根据MRAppMaster告知的待处理数据的位置,抓取maptask的输出结果文件,并在本地进行重新归并排序。按照相同key的KV为一个组,调用客户定义的reduce方法进行逻辑运算,收集输出的结果KV,调用客户指定的outputformat将结果数据输出到外部的存储。
Maptask的并行度决定了map阶段的任务处理并发程度。
一个job的map阶段并行度由客户端提交的job决定。
客户端对map阶段并行度的规划逻辑为:
将待处理数据执行逻辑切片。按照一个特定切片的大小,将待处理的数据划分成逻辑上的多个split,然后每一个split分配一个maptask实例,并进行处理。
这段逻辑以及形成的切片规划描述文件,是由FileInputFormat实现类的getSplits方法来完成的。该方法返回List
FileInputFormat的getSplits方法。
public List getSplits(JobContext job) throws IOException {
Stopwatch sw = (new Stopwatch()).start();
long minSize = Math.max(this.getFormatMinSplitSize(), getMinSplitSize(job));
long maxSize = getMaxSplitSize(job);
List splits = new ArrayList();
List files = this.listStatus(job);
Iterator i$ = files.iterator();
while(true) {
while(true) {
while(i$.hasNext()) {
FileStatus file = (FileStatus)i$.next();
Path path = file.getPath();
long length = file.getLen();
if (length != 0L) {
BlockLocation[] blkLocations;
if (file instanceof LocatedFileStatus) {
blkLocations = ((LocatedFileStatus)file).getBlockLocations();
} else {
FileSystem fs = path.getFileSystem(job.getConfiguration());
blkLocations = fs.getFileBlockLocations(file, 0L, length);
}
if (this.isSplitable(job, path)) {
long blockSize = file.getBlockSize();
long splitSize = this.computeSplitSize(blockSize, minSize, maxSize);
long bytesRemaining;
int blkIndex;
for(bytesRemaining = length; (double)bytesRemaining / (double)splitSize > 1.1D; bytesRemaining -= splitSize) {
blkIndex = this.getBlockIndex(blkLocations, length - bytesRemaining);
splits.add(this.makeSplit(path, length - bytesRemaining, splitSize, blkLocations[blkIndex].getHosts(), blkLocations[blkIndex].getCachedHosts()));
}
if (bytesRemaining != 0L) {
blkIndex = this.getBlockIndex(blkLocations, length - bytesRemaining);
splits.add(this.makeSplit(path, length - bytesRemaining, bytesRemaining, blkLocations[blkIndex].getHosts(), blkLocations[blkIndex].getCachedHosts()));
}
} else {
splits.add(this.makeSplit(path, 0L, length, blkLocations[0].getHosts(), blkLocations[0].getCachedHosts()));
}
} else {
splits.add(this.makeSplit(path, 0L, length, new String[0]));
}
}
job.getConfiguration().setLong("mapreduce.input.fileinputformat.numinputfiles", (long)files.size());
sw.stop();
if (LOG.isDebugEnabled()) {
LOG.debug("Total # of splits generated by getSplits: " + splits.size() + ", TimeTaken: " + sw.elapsedMillis());
}
return splits;
}
}
}
获取切片大小的方法。
long splitSize = this.computeSplitSize(blockSize, minSize, maxSize);
protected long computeSplitSize(long blockSize, long minSize, long maxSize) {
return Math.max(minSize, Math.min(maxSize, blockSize));
}
computeSplitSize的计算逻辑是在maxSize和blockSize之间取一个最小值,然后将这个最小值和minSize比较,取结果较大的那个值。这个方法传进去了3个参数。blockSize、minSize、maxSize。
blockSize:默认值是128M,通过dfs.blocksize设置。
minSize:默认值是1,通过mapreduce.input.fileinputformat.split.minsize指定。
maxSize: 默认值是Long.MaxValue,可以通过mapreudce.input.fileinputformat.split.maxsize指定。
获得最小值的代码:
public static long getMinSplitSize(JobContext job) {
return job.getConfiguration().getLong("mapreduce.input.fileinputformat.split.minsize", 1L);
}
long minSize = Math.max(this.getFormatMinSplitSize(), getMinSplitSize(job));
作比较取值的时候,会将job中设置的最小值和getFormatMinSplitSize得到的最小值作比较,得到较大的那个值,作为minSize。但是这个最小值,不一定按设定的来。在getSplits中还有一个限制。
protected long getFormatMinSplitSize() {
return 1L;
}
public static long getMaxSplitSize(JobContext context) {
return context.getConfiguration().getLong("mapreduce.input.fileinputformat.split.maxsize", 9223372036854775807L);
}
以上是获得最大值的代码。
1、如果job的每个map或者reduce的运行时间都只有30-40s,那么就减少这个job的map或者reduce的数量。因为计算本来就没有花费多少时间,大部分时间都浪费在了task的设置以及加入调度器中进行调度了。
配置task的jvm重用。
mapred.job.reuse.jvm.num.tasks,默认值为1,表示一个jvm上面最多可以顺序执行的属于同一个job的task数目是1。即一个task开启一个jvm。该值可以在mapred-site.xml文件中更改,配置成多个,意味着多个task运行了在同一个jvm上面,但不是同时执行,而是排队顺序执行。
2、如果iniput的文件非常大,如1TB,可以考虑将blocksize调大。比如256MB或者512MB。
Maptask的并发数由切片决定,但是reducetask的数量可以直接手动设置。
比如:
job.setNumReduceTask(4);
如果数据分布不均匀,那么可能在reduce阶段产生数据倾斜。
Reducetask的数量不是任意设定的,要考虑业务的逻辑,在需要计算全局汇总结果的时候,就只能有1个reducetask。
尽量不要运行太多的reducetask。大多数job,最好的reduce个数最多和集群中的reduce持平,或者比集群的reduce slots小。这个对小集群而言,尤其重要。