MapReducer编程之数据分片Split源码解读
数据切片核心源码
// Create the splits for the job
LOG.debug("Creating splits at " + jtFs.makeQualified(submitJobDir));
// TODO 数据是如何分片的 核心方法入口
int maps = writeSplits(job, submitJobDir);
conf.setInt(MRJobConfig.NUM_MAPS, maps);
LOG.info("number of splits:" + maps);
private int writeSplits(org.apache.hadoop.mapreduce.JobContext job,
Path jobSubmitDir) throws IOException,
InterruptedException, ClassNotFoundException {
JobConf jConf = (JobConf)job.getConfiguration();
int maps;
if (jConf.getUseNewMapper()) {
// TODO 数据分片新API 入口
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();
// TODO 通过反射拿到InputFormat
// TODO InputFormat是什么?已经明确知道现在使用的InputFormat,在MapReduce里面如果你想读数据一定要用到InputFormat,只不过是InputFormat的某个子类
InputFormat<?, ?> input =
ReflectionUtils.newInstance(job.getInputFormatClass(), conf);
// TODO 拿到数据分片数
// TODO 到这里,我们可以初步理解 假设文件是200M,默认SplitSize是128M,则这里得到的splits就是2
// 200M --> 被分成 128M 和 72M
// InputSplit 被一个Mapper处理 等同于Block
List<InputSplit> splits = input.getSplits(job);
T[] array = (T[]) splits.toArray(new InputSplit[splits.size()]);
// sort the splits into order based on size, so that the biggest
// go first
Arrays.sort(array, new SplitComparator());
JobSplitWriter.createSplitFiles(jobSubmitDir, conf,
jobSubmitDir.getFileSystem(conf), array);
return array.length;
}
/**
* 得到一个files集合,把它放到FileSplits(InputSplit的一个实现)
* 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 {
StopWatch sw = new StopWatch().start();
// 从这里开始计算分片
// getFormatMinSplitSize() 源码中写死了,值为 1
// getMinSplitSize(job) 从参数:mapreduce.input.fileinputformat.split.minsize 中得到
// 如果没有拿到,则取默认值:1L
// 获取最大值 max(1,1L) 最终minSize=1
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
// getMaxSplitSize(job) 的值从参数 mapreduce.input.fileinputformat.split.maxsize 获取,如 // 果没有设置,为默认值:Long.MAX_VALUE 即为Long类型的最大值:2的63次方减1。
// 最终 maxSize = 2的63次方减1
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)) {
// 获取BlockSize的大小,默认128M(注意:在服务器上,Windows本地是32M)
long blockSize = file.getBlockSize();
// splitSize:每个文件被切成多大
// computeSplitSize 方法中是获取最大值:
// Math.max(minSize, Math.min(maxSize,blockSize));
// 这里minSize是1;maxSize是2的63次方减1;blockSize是128M
// 则 splitSize = 128M
long splitSize = computeSplitSize(blockSize, minSize, maxSize);
// bytesRemaining : 剩余的字节(就是文件还剩多少字节,初始值length是文件的大小)
long bytesRemaining = length;
// ***** (五颗星) SPLIT_SLOP: 源码中写死了,是1.1
// 接下来开始切分文件 while条件(文件大小/128M > 1.1)
// 这里当while条件为true时才会对文件进行切分。
// 假如文件大小是 129M ==> 则:129M/128M < 1.1
// 此时该文件会被当作一个Split去处理
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, splitSize,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
bytesRemaining -= 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 length files
splits.add(makeSplit(path, 0, length, new String[0]));
}
}
// Save the number of input files for metrics/loadgen
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;
}
Debug流程
总结
1、当文件 < BlockSize(默认128M)时:文件个数 = 分片的个数 = maptask个数
2、当文件 > BlockSize(默认128M)时 并且 (文件大小/BlockSize > 1.1) 时,文件会被切分