尽管MR由于计算效率问题,已经不适用于大多数业务场景,Hive3开始计算引擎改为TEZ,但MR的经典思路在Hadoop生态各组件都有体现,重温后对各组件原理的理解还有使用都有帮助,如Spark的RDD分区里面的源码就能看到MR分片思想的影子。这里仅拿输入Map前分片(Split)这个动作的源码做窥探,深入挖掘下分片(split)的思想。
一、简单总结
进行MR前,对要计算的各文件按分片逻辑切分,多少个分片则对应产生多少个Map计算。
分片逻辑以某个值(splitSize)为基准,超过其1.1倍时,则再做分片处理,直到文件小于基准值的1.1倍为止。
在没有另外设置的情况下,splitSize的大小对应blockSize的默认大小,即128M。
如文件使用Gzip或Snappy等不支持切分的算法压缩,则不管文件多大都一个分片。
ps:上面建立在没有手动调整参数设置的情况下
二、数据分片(源码解读)
上图为MR的大致流程,分片(spilt)这动作其实就是MR这个过程的输入,指的是对需要计算的目录文件进行分片。
MR核心思想:分而治之,这思想也是分布式计算的思想,把大文件分成小文件,然后一个作业处理一个小文件,最后再做合并,实现对大数据文件的批处理。
//摘至Hadoop2.7.1源码,文件:FileInputFormat.java
private static final double SPLIT_SLOP = 1.1; // 10% slop //切片溢出系数
private long minSplitSize = 1;
public InputSplit[] getSplits(JobConf job, int numSplits)
throws IOException {
StopWatch sw = new StopWatch().start(); //开启线程
FileStatus[] files = listStatus(job); //获取文件状态
// Save the number of input files for metrics/loadgen
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();
}
//无定义numSplits大小则goalSize默认为文件大小
long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
//最小切片不设置默认为1
long minSize = Math.max(job.getLong(org.apache.hadoop.mapreduce.lib.input.
FileInputFormat.SPLIT_MINSIZE, 1), minSplitSize);
// generate splits
ArrayList<FileSplit> splits = new ArrayList<FileSplit>(numSplits);
NetworkTopology clusterMap = new NetworkTopology();
for (FileStatus file: files) {
Path path = file.getPath(); //文件路径及长度,存储在namenode
long length = file.getLen();
if (length != 0) {
FileSystem fs = path.getFileSystem(job);
BlockLocation[] blkLocations; //获取文件block块
if (file instanceof LocatedFileStatus) {
blkLocations = ((LocatedFileStatus) file).getBlockLocations();
} else {
blkLocations = fs.getFileBlockLocations(file, 0, length);
}
if (isSplitable(fs, path)) {
long blockSize = file.getBlockSize(); //获取block大小,默认128M
//计算切片大小,函数在代码下方
long splitSize = computeSplitSize(goalSize, minSize, blockSize);
long bytesRemaining = length;
//变量设定了溢出系数SPLIT_SLOP为1.1
//每次切完判断是否大于块的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 {
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]));
}
}
sw.stop();
if (LOG.isDebugEnabled()) {
LOG.debug("Total # of splits generated by getSplits: " + splits.size()
+ ", TimeTaken: " + sw.now(TimeUnit.MILLISECONDS));
}
return splits.toArray(new FileSplit[splits.size()]); //集合转为数组
}
protected long computeSplitSize(long goalSize, long minSize,
long blockSize) {
return Math.max(minSize, Math.min(goalSize, blockSize));
}
这里涉及到几个关键的计算变量,由公式Math.max(minSize,Math.min(goalSize, blockSize))体现:
- goalSize:输入总大小与如此numSplits比值,如没有设置则按默认为文件总大小,numSplits通过参数mapreduce.job.maps可以设定(故想增加map个数,可以通过设置该参数为一个较大的值)。
- blockSize:HDFS的块大小,默认128M。
- minSize:1与SPLIT_MINSIZE取最大的一个,通过参数mapreduce.input.fileinutformat.split.minsize可以设定(故想减少map个数,可以同个设置该参数为一个较大的值)
1.上面参数hadoop版本1.x与2.x有区别。
2.通过公式可知只能通过参数设置控制增加或减少map数量,无法具体指定个数。
3.reduce个数默认为1个,也可以通过参数mapreduce.job.reduces设置。
4.上面源码getSplits调用后并不实际做文件切分,而是返回一个列表记录要切分的信息,后续提交到Yarn上处理。
三、实验验证
提取hadoop目录hadoop/etc/hadoop下xml文件共计9份上传到HDFS,做字母计数计算,测试是否启动对应9个map任务。
#在HDFS上新建input文档,把hadoop/etc/hadoop目录下xml文件导入
hdfs dfs -mkdir input
hdfs dfs -put /usr/local/hadoop/etc/hadoop/*.xml input
hadoop jar /usr/local/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar grep input output 'dfs[a-z.]+'
由于每个文件不超过block块128M,可以看到启动了9个Map任务,1个Reduce任务。
Referance
blog.csdn.net/sun_0128/article/details/107115914
学习交流,有任何问题还请随时评论指出交流。