MapReduce中切片、Map、Reduce的关系

转载于:https://blog.csdn.net/ancony_/article/details/81274624
感谢楼主的文章!

MapReduce运行流程

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并行度决定机制

Maptask的并行度决定了map阶段的任务处理并发程度。

一个job的map阶段并行度由客户端提交的job决定。

客户端对map阶段并行度的规划逻辑为:

将待处理数据执行逻辑切片。按照一个特定切片的大小,将待处理的数据划分成逻辑上的多个split,然后每一个split分配一个maptask实例,并进行处理。

这段逻辑以及形成的切片规划描述文件,是由FileInputFormat实现类的getSplits方法来完成的。该方法返回List,每个InputSplit封装了一个逻辑切片的信息,包括长度和位置等信息。

切片机制

FileInputFormat的getSplits方法。

public List<InputSplit> getSplits(JobContext job) throws IOException {
    
    
    Stopwatch sw = (new Stopwatch()).start();
    long minSize = Math.max(this.getFormatMinSplitSize(), getMinSplitSize(job));
    long maxSize = getMaxSplitSize(job);
    List<InputSplit> splits = new ArrayList();
    List<FileStatus> 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);
}

以上是获得最大值的代码。

Maptask并行度经验

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。

reduceTask并行度决定机制

Maptask的并发数由切片决定,但是reducetask的数量可以直接手动设置。

比如:

job.setNumReduceTask(4);

如果数据分布不均匀,那么可能在reduce阶段产生数据倾斜。

Reducetask的数量不是任意设定的,要考虑业务的逻辑,在需要计算全局汇总结果的时候,就只能有1个reducetask。

尽量不要运行太多的reducetask。大多数job,最好的reduce个数最多和集群中的reduce持平,或者比集群的reduce slots小。这个对小集群而言,尤其重要。

猜你喜欢

转载自blog.csdn.net/CharlesCFA/article/details/113742812
今日推荐