1.一个文本如何变成map或者reduce处理的键值对,它们处理完之后的键值对又是如何变成了一个文本输出的?
答:首先文件会按照大小均分成几个split,每个split被最终变成一些(k,v),每个(k,v)代表一行,k表示开始在文件中出现的字节,即偏移量。v表示该行的内容。每个split交给一个maptask区处理。
2.什么是split?
答:当把文件上传到HDFS中,第一步就是数据划分,即真实物理上的划分,数据文件上传到HDFS后,要把文件划分成一块一块block,每块的大小可在hadoop-default.xml里配置,默认每块64MB,一个文件被分成多个64MB大小的小文件。另外,为了保证数据的安全,上传的文件被默认复制成3份,当一份数据宕掉,其余的可以即刻补上。
HDFS上的spilt块会满足以下几个条件:一个split大于等于1的整数个Block;一个split不会包含两个File的Block,即不会跨越File边界;split和Block的关系是一对多的关系,默认一对一;maptasks的个数最终决定于splits的长度。
在map task执行时,它的输入数据来源于HDFS的block,当然在MapReduce中map task只读取split数据。
3.split的结构是怎样的?
答:split的构造函数记录了每个split对应的开始和范围。
public FileSplit(Path file, long start, long length, String[] hosts) {
this.file = file;
this.start = start;
this.length = length;
this.hosts = hosts;
}
4.LineRecordReader类是如何实现将文本转换为(k,v)的?
答:以TextInputFormat为例,通过函数creatRecordReader来创建一个新的LineRecordReader类,该类有两个重要的功能,一个是将数据封装成(k, v),其次是处理split的边界和行的边界不一致的问题。具体的实现如下:
//来自org.apache.hadoop.mapreduce.lib.input.LineRecordReader
nextKeyValue()部分代码{
key.set(pos);
newSize = in.readLine(value, maxLineLength,Math.max((int)Math.min(Integer.MAX_VALUE, end-pos),maxLineLength));
}
public LongWritable getCurrentKey() {
return key;
}
public Text getCurrentValue() {
return value;
}
上面显示的代码是不完整的,只包含了赋值的语句。这几个方法完成了把数据封装成(k,v)的形式。nextKeyValue()通过LineReader获取一行的数据,把字节的偏移量作为key,读入的数据作为value。同时提供了
getCurrent()来获取当前的Key,getCurrentValue()获取当前的value值。
5.如何实现的并行处理文件(如何比较均匀的分配一个文件给不同的map来处理)?
答:首先在文件读入的过程就会按照文件大小均为为多个Split,每个Split都是交由一个特定的maptask去处理的,所以可以实现并行处理文件。mapper在运行的时候不断获得(k,v)对,不断交给map去处理。
6.Split是直接按照大小分的,而每行则是按照’\n’来划分的,那么如何处理行的边界和split的边界不一致的问题?
答:通过initlialize()和nextKeyValue()函数的组合来处理行的边界和Split的边界不一致的问题。
//initialize()部分代码
if (skipFirstLine) { // skip first line and re-establish "start".
istart += in.readLine(new Text(), 0,(int)Math.min((long)Integer.MAX_VALUE, end - start));
}
//nextKeyValue()部分代码
while (pos < end) {
newSize = in.readLine(value, maxLineLength,Math.max((int)Math.min(Integer.MAX_VALUE, end-pos),maxLineLength));
if (newSize == 0) {
break;
}
pos += newSize;
if (newSize < maxLineLength) {
break;
}
}
从nextKeyValue()的读取停止条件可以看出,每次都是读取完整的一行的,即使是超过了Split的边界(其实这个边界就是相当于是一个约定而已,可以做稍微的调整)。假设有邻近的两块,那么第二块的开头已经被读过了,那么怎么保证不重复读取呢?读取第二块时,默认开头已经读取过了,跳过第一个’\n’之前的内容。但是如果是第一块呢,没有读取过那么就不跳。结果就是initialize()中的判断是否跳过第一个’\n’之前的内容。
7.对于外部调用而言,内部是如何实现将文本转换为(k,v)的?
答:首先intialize()来实现初始化,nextKeyValue()来判断是否还有可以使用的(k,v),getCurrent()获取当前的Key值,getCurrentValue()获取当前的value值。