-
FileInputFormat
-
计算 splitSize
-
goalSize 文件总大小
-
minSize 配置 mapreduce.input.fileinputformat.split.minsize,默认值 1
-
blockSize 配置 dfs.block.size 默认值 128m
computeSplitSize
protected long computeSplitSize(long goalSize, long minSize,
long blockSize) {
return Math.max(minSize, Math.min(goalSize, blockSize));
}
-
计算 MapTask数量
job.setLong(NUM_INPUT_FILES, files.length);
long totalSize = 0; // compute total size
for (FileStatus file: files) { // check we have valid files
if (file.isDirectory()) {
throw new IOException("Not a file: "+ file.getPath());
}
totalSize += file.getLen();
}
long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
long minSize = Math.max(job.getLong("mapred.min.split.size", 1), minSplitSize);
for (FileStatus file: files) {
Path path = file.getPath();
long length = file.getLen();
if (length != 0) { // 文件不为空
FileSystem fs = path.getFileSystem(job);
BlockLocation[] blkLocations;
if (file instanceof LocatedFileStatus) {
blkLocations = ((LocatedFileStatus) file).getBlockLocations();
} else {
blkLocations = fs.getFileBlockLocations(file, 0, length);
}
if (isSplitable(fs, path)) { // 可以进行分块
long blockSize = file.getBlockSize();
long splitSize = computeSplitSize(goalSize, minSize, blockSize);
long bytesRemaining = length;
// bytesRemaining/splitSize > 1.1 那么就进行切分 分块
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
String[][] splitHosts = getSplitHostsAndCachedHosts(blkLocations,
length-bytesRemaining, splitSize, clusterMap);
splits.add(makeSplit(path, length-bytesRemaining, splitSize,
splitHosts[0], splitHosts[1]));
bytesRemaining -= splitSize; // 切分块后 剩下的文件大小
}
if (bytesRemaining != 0) {
String[][] splitHosts = getSplitHostsAndCachedHosts(blkLocations, length
- bytesRemaining, bytesRemaining, clusterMap);
splits.add(makeSplit(path, length - bytesRemaining, bytesRemaining,
splitHosts[0], splitHosts[1]));
}
} else { // 不可分块 map个数为1个
String[][] splitHosts = getSplitHostsAndCachedHosts(blkLocations,0,length,clusterMap);
splits.add(makeSplit(path, 0, length, splitHosts[0], splitHosts[1]));
}
} else {
//Create empty hosts array for zero length files
splits.add(makeSplit(path, 0, length, new String[0]));
}
}
-
循环遍历所有文件
-
获取文件格式
-
如果文件格式不支持切分,那么就分为1个
-
如果文件格式支持切分,将文件大小/splitSize的大小>1.1,那么就切分为1个块,否则直接为1个块
-
-
-
调整参数
-
如果想增加map个数,则设置mapred.map.tasks 为一个较大的值。
-
如果想减小map个数,则设置mapred.min.split.size 为一个较大的值。
-
拓展:
压缩与切分
是否可切分:对应的压缩算法是否可以搜索数据流的任意位置并进一步往下读取数据。
以下做三点说明:
-
以一个存储在HDFS文件系统中且不进行压缩的大小为 1 GB 的文件为例。如果HDFS的块大小设置为128,那么该文件将被存储在8个块中,把这个文件作为输入数据的MapReduc/Spark作业,将创建8个map/task任务,其中每个数据块对应一个任务作为输入数据。
-
现在,假如经过gzip压缩后,文件大小为1GB。与之前一样,HDFS也是将这个文件存储成8个数据块。但是每个单独的map/task任务将无法独立于其他任务进行数据处理,官方一点的说法,原因就是因为数据存储在HDFS时是被切成块的,且该压缩算法无法从任意进行读取。
-
通俗的讲解,就是因为存储在HDFS的每个块都不是完整的文件,我们可以把一个完整的文件认为是具有首尾标识的,因为被切分了,所以每个数据块有些有头标示,有些有尾标示,有些头尾标示都没有,所以就不能多任务来并行对这个文件进行处理。 对于这种不可切分的,只有将该文件的所有HDFS的数据块都传输到一个map/task任务来进行处理,但是大多数数据块都没有存储在这个任务的节点上,所以需要跨节点传输,且不能并行处理,因此运行的时间可能很长。
-
CombineFileInputFormat
在MR实践中,会有很多小文件,单个文件产生一个mapper,资源比较浪费,后续没有reduce逻辑的话,会产生很多小文件,文件数量暴涨,对后续的Hive job产生影响。
所以需要在mapper中将多个文件合成一个split作为输入,CombineFileInputFormat满足我们的需求。
-
切分方式
第一次:将同DN上的所有block生成Split,生成方式:
-
循环nodeToBlocks,获得每个DN上有哪些block
-
循环这些block列表
-
将block从blockToNodes中移除,避免同一个block被包含在多个split中
-
将该block添加到一个有效block的列表中,这个列表主要是保留哪些block已经从blockToNodes中被移除了,方便后面恢复到blockToNodes中
-
向临时变量curSplitSize增加block的大小
-
判断curSplitSize是否已经超过了设置的maxSize
-
如果超过,执行并添加split信息,并重置curSplitSize和validBlocks
-
没有超过,继续循环block列表,跳到第2步
-
-
当前DN上的block列表循环完成,判断剩余的block是否允许被split(剩下的block大小之和是否大于每个DN的最小split大小)
-
如果允许,执行并添加split信息
-
如果不被允许,将这些剩余的block归还blockToNodes
-
-
重置
-
跳到步骤1
第二次:对不再同一个DN上但是在同一个Rack上的block进行合并(只是之前还剩下的block);
最后,对于既不在同DN也不在同rack的block进行合并(经过前两步还剩下的block)
-
调整参数
可调整参数,以影响map的个数
-
mapreduce.input.fileinputformat.split.minsize.per.node (同mapred.min.split.size.per.node) 默认值 1
-
mapreduce.input.fileinputformat.split.minsize.per.rack(同mapred.min.split.size.per.rack) 默认值 1
-
mapreduce.input.fileinputformat.split.maxsize(同mapred.max.split.size) 默认值 25600000(256MB)
最适合调整的参数为 mapreduce.input.fileinputformat.split.maxsize,即合并文件的大小
-
DBInputFormat
statement = connection.createStatement();
// Returns the query for getting the total number of rows
results = statement.executeQuery(getCountQuery());
results.next();
long count = results.getLong(1);
int chunks = job.getConfiguration().getInt(MRJobConfig.NUM_MAPS, 1);
// 每一个块大小
long chunkSize = (count / chunks);
results.close();
statement.close();
List<InputSplit> splits = new ArrayList<InputSplit>();
// Split the rows into n-number of chunks and adjust the last chunk
// accordingly
for (int i = 0; i < chunks; i++) {
DBInputSplit split;
if ((i + 1) == chunks)
split = new DBInputSplit(i * chunkSize, count);
else
split = new DBInputSplit(i * chunkSize, (i * chunkSize)
+ chunkSize);
splits.add(split);
}
connection.commit();
return splits;
-
切分方式
-
获取SQL查询后的总行数,获取设置MAP的个数
-
计算每个块大小 total_number_of_rows / map_num
-
将获取到的结果按照,每个块大小进行切分
-
调整参数
-
mapreduce.job.maps
根据需要,调整mapreduce.job.maps,即可达到想要的map数目
参考链接
hadoop 文件块合并 hadoop文件切分_mob6454cc76bc4a的技术博客_51CTO博客
Hadoop中CombineFileInputFormat详解_job.setinputformatclass(combinesequencefileinputfo-CSDN博客