Hadoop源码解读(切片原理)
在上一篇博客中。主要聊了聊一个hadoop的job在提交后,运行前所做的一些准备工作。主要是:
- 获取Job运行的环境是LocalJobRunner还是YarnRunner。
- 校验输出路径
- 获取Job的工作目录
- 获取当前将要运行的Job的Id
- 拼接上面两个,生成当前Job的工作目录,并创建
- 生成切片信息,返回切片的数量,并将切片信息放入Job工作目录中
- 将当前Job配置信息放入Job工作目录中
这篇博客中主要聊一聊第6步,也就是生成切片信息的过程。上一个博客讲到int maps = writeSplits(job, submitJobDir)这个方法时,就没有细讲,这次我们详细聊一聊切片的过程。入点从int maps = writeSplits(job, submitJobDir);方法开始。
int maps = writeSplits(job, submitJobDir);hadoop切片的主要逻辑都在这个方法中,所以我们进到这个方法里来研究下。代码如下:
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()) {
maps = writeNewSplits(job, jobSubmitDir);
} else {
maps = writeOldSplits(jConf, jobSubmitDir);
}
return maps;
}
-
map表示切片的个数
-
maps = writeNewSplits(job, jobSubmitDir);因为是用的新的API,切片个数是调用
maps = writeNewSplits(job, jobSubmitDir);方法得到
具体方法如下:
private <T extends InputSplit> int writeNewSplits(JobContext job, Path jobSubmitDir) throws IOException, InterruptedException, ClassNotFoundException { Configuration conf = job.getConfiguration(); InputFormat<?, ?> input = ReflectionUtils.newInstance(job.getInputFormatClass(), conf); 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; }
- splits是一个List集合,里面存放的是每个切片信息,通过将splits变成一个array数组,返回这个array数组的length,来返回切片的个数
- 在这个方法里面最核心的是List splits = input.getSplits(job);这个方法获取切片信息,并将每个切片的信息放入到splits这个List集合中,所以我们现在来专门研究下这个方法,以下是这个方法的实现:
public List<InputSplit> getSplits(JobContext job) throws IOException { StopWatch sw = new StopWatch().start(); long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job)); 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)) { long blockSize = file.getBlockSize(); long splitSize = computeSplitSize(blockSize, minSize, maxSize); long bytesRemaining = length; 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; }
①在这里我挑几个重点的来聊一聊
②long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
这个minSize默认为1
③long maxSize = getMaxSplitSize(job);这个是获取maxSize,具体我们可以看看他是如何实现的
public static long getMaxSplitSize(JobContext context) { return context.getConfiguration().getLong(SPLIT_MAXSIZE, Long.MAX_VALUE); }
可以看到传入的SPLIT_MAXSIZE和Long类型的最大值。SPLIT_MAXSIZE默认为null。
public long getLong(String name, long defaultValue) { String valueString = getTrimmed(name); if (valueString == null) return defaultValue; String hexString = getHexDigits(valueString); if (hexString != null) { return Long.parseLong(hexString, 16); } return Long.parseLong(valueString); }
可以看到调用这个方法的两个参数,一个为null,另一个为Long类型的最大值。最终的结果为Long类型的最大值
④List splits = new ArrayList();表示存放切片信息的List集合
⑤List files = listStatus(job);表示获得job提交时所有输入的文件
⑥对每一个文件进行遍历,这样就默认一个文件就是一个切片。现在后续的代码就是看你每一个文件是否 可以再进行切片。
⑦Path path = file.getPath();获取文件的具体路径和文件名
long length = file.getLen();获取当前文件的大小
⑧
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)) { long blockSize = file.getBlockSize(); long splitSize = computeSplitSize(blockSize, minSize, maxSize); long bytesRemaining = length; 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; }
查看当前文件是否可以切分。因为有的文件无法切分。如果无法切分的文件,就只能当做一个切片处理。这里面有一个方法比较重要。就是计算切片大小的方法。因为你一个文件想要切片,你就需要知道,是按照什么切片大小进行切分。所以切片大小很重要。这里我们详细解读下计算切片大小这个方法。以下为这个方法的具体实现:
protected long computeSplitSize(long blockSize, long minSize, long maxSize) { return Math.max(minSize, Math.min(maxSize, blockSize)); }
-
blockSize是调用这个方法时,传过来的参数,long blockSize = file.getBlockSize();我这边具体大小为32M。在这里我大概说下这个blockSize的值。如果是在本地的话,大小为32M,如果是在hadoop1.x版本,大小为64M。hadoop2.x版本,大小为128M。
-
minSize就是之前在第2点我们得到的1
-
maxSize也是之前在第3点得到的Long的最大值
-
所以最终计算出来的切片大小为32M。是块的大小
⑩计算完切片大小后,我们来对文件进行切分。具体步骤就是
((double) bytesRemaining)/splitSize > SPLIT_SLOP
如果当前文件大小与切片大小进行相除,结果大于SPLIT_SLOP,也就是1.1,就将文件切一片,并把这个切片的具体信息加入到之前的splits集合中。然后将文件大小减去这个切片大小后,再进行判断是否还是((double) bytesRemaining)/splitSize > SPLIT_SLOP成立,一直循环,知道这个条件不成立。并最终将剩余的切片信息也加入到splits集合中。
3.JobSplitWriter.createSplitFiles(jobSubmitDir, conf, jobSubmitDir.getFileSystem(conf), array);
将切片信息写入到Job工作目录下