目录
- getSplits()分析
- 切片流程总结
- InputSplit vs Block
任务提交流程:WordCount.main() -> Job.waitForCompletion() -> Job.submit() -> Job.connect() -> Cluster.Cluster() -> Cluster.initialize() -> YarnClientProtocolProvider.create() -> JobSubmitter.sbumitJobInternal() -> YARNRunner.submitJob()
其中,JobSubmitter.submitJobInternal 方法中调用了 int maps = writeSplits(job, submitJobDir);
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;
}
InputFormat是个抽象类,FileInputFormat继承该类
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); //输入对象,InputFormat是个抽象类
List<InputSplit> splits = input.getSplits(job); //调用InputFormat的实现类FileInputFormat的getSplits方法
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); //创建Split文件
return array.length;
}
getSplits()分析
InputSplit值记录了切片的元数据信息,比如起始位置,长度及所在的节点列表。
public List<InputSplit> getSplits(JobContext job) throws IOException { //文件切片
StopWatch sw = new StopWatch().start(); //纳秒级计时
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job)); //最小切割块大小为1
long maxSize = getMaxSplitSize(job); //最大切割快大小为9223372036854775807
// generate splits
List<InputSplit> splits = new ArrayList<InputSplit>(); //存储切片的list
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(); //数据块大小,本地是32M;集群Hadoop2.0是128M,1.0是64M
long splitSize = computeSplitSize(blockSize, minSize, maxSize);
//切片大小,一般等于块大小,减少数据传输开销
//computeSplitSize:Math.max(minSize, Math.min(maxSize, blockSize));
//通过调整minSize或maxSize调整切片大小
long bytesRemaining = length; //记录切片后剩余的文件长度
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
//剩余的文件长度 ÷ 切片长度 > 1.1才能执行while循环
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, splitSize,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
bytesRemaining -= splitSize;
}
if (bytesRemaining != 0) { //剩余的长度不为0,没切完
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
}
} else { // not splitable
if (LOG.isDebugEnabled()) {
// Log only if the file is big enough to be splitted
if (length > Math.min(file.getBlockSize(), minSize)) {
LOG.debug("File is not splittable so no parallelization "
+ "is possible: " + file.getPath());
}
}
splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts(),
blkLocations[0].getCachedHosts())); //直接整个文件作为一个切片
}
} else { //长度为0的文件
//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;
}
protected boolean isSplitable(JobContext context, Path file) {
final CompressionCodec codec =
new CompressionCodecFactory(context.getConfiguration()).getCodec(file);
if (null == codec) {
return true;
}
return codec instanceof SplittableCompressionCodec;
}
protected long computeSplitSize(long blockSize, long minSize, long maxSize) {
return Math.max(minSize, Math.min(maxSize, blockSize));
}
提交完切片到Yarn上,Yarn的MrAPPMaster会根据切片的信息计算开启的MapTask个数。
切片流程总结
- 获取此次任务(Job)需要操作的所有文件信息(listStatus)
- 遍历处理(切片)每一个文件(每个文件单独处理)
- 获取当前文件的路径和长度
- 如果长度不为0,判断是否可以切片
- 如果文件不是压缩文件或者是可切割的压缩文件,则可以切片
- 计算切片大小computeSplitSize(blockSize, minSize, maxSize)
Math.max(minSize, Math.min(maxSize, blockSize))
blockSize本地是32M;集群Hadoop1.0是64M;2.0是128M
默认情况下,切片大小等于blockSize。如果想切片大小大于blockSize,则调整minSize;如果想切片大小小于blockSize,则调整maxSize; - 切片前判断文件剩余大小(bytesRemaining)÷ 切片大小(splitSize)是否 > SPLIT_SLOP(1.1),如果满足则切片。循环该步直到不满足条件
- 如果切完后剩余的文件大小不等于0,则将剩下的作为一个切片
- 如果文件不可以切片,则将整个文件作为一个分片
- 如果长度为0,创建一个空的切片
- 后续工作,返回切片
获取切片信息API
- FileSplit inputSplit = (FileSplit) context.getInputSplit(); //根据文件类型获取切片信息
- String name = inputSplit.getPath().getName(); //获取切片的文件名称
Q:修改切片大小为100M
A:将maxSize修改为100M
Q:修改切片大小为256M
A:将minSize修改为256M
Q:在集群上,一个大小为666M的文件,切片的时候切几块?
A:切6块,大小分别为:128M,128M,128M,128M,128M,26M
InputSplit vs Block
- 大小:
block:blockSize本地是32M;集群Hadoop1.0是64M;2.0是128M。块的大小可以通过配置文件修改。文件的所有块(除了最后一块)大小相等,最后一块小于等于blocksize。
InputSplit:默认情况下,切片大小等于blockSize。用户可以根据数据大小修改切片大小。 - 数据形式
block:块是真实的数据,存放在HDFS上。
InputSplit:切片是逻辑上的数据,用于在Map和Reduce处理。切片只包含数据的元数据。
Q:在集群上,一个大小为129M的文件,存储的时候存几块,切片的时候切几块?
A:存储时,以128M为单位,所以存储两块,一块为128M,一块为1M
切片时,129 ÷ 128 < 1.1,所以存一块