输入流程解析
文件从上传到HDFS到输入到map函数中,大致可以分为4步。
文件上传到hdfs中,被划分为若干份block,输入时,将所有block读取,划分为若干个split,每个split对应与一个map task,然后每个split划分为多个record
void map(
K1 key, # record的key
V1 value, # record的value
OutputCollector<K2, V2> output, Reporter reporter)throws IOException;
1. 上传到hdfs
一个文件上传到hdfs中时,以配置文件的dfs.block.size规定的大小,划分为若干个block,然后储存在datanode中。
2. 从hdfs读取块,再划分为split
spit大小的计算:
属性名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
mapred.min.split.size | int | 1 | 一个split的最小值 |
mapred.max.split.size | long | Long.MAX_VALUE即9223372036854775807 | 一个split的最大值 |
dfs.block.size | long | 128M | hdfs中的块大小 |
goalSize | long | totalSize/numSplit | totalSize为文件总大小 numSplit为用户规定的map task个数 |
inputSplit大小的计算公式:
**hadoop1.x:**splitSize = max{ minSize , min{ goalSize , blockSize}}
**hadoop2.x:**splitSize = max{ minSize , min{ maxSize , blockSize}}
一般来说,为了节省网络带宽和磁盘IO,splitSize最后与blockSize相等,实现数据本地化。默认配置也是这样做的。
注意:一般来说,block的划分是不会顾虑record的完整性,所以,RecordReader规定,每个inputSplit的第一条不完整的记录划分给前一个inputSplit处理,也就是说,虽然公式计算出splitSize=blockSize。但splitSize不是正好的128M,而是把最前面那条不完整的record数据传输给前一个split,也接受后一个split传输过来的record数据。
inputsplit
划分完split,需要储存到一个对象中,这个对象就是inputsplit,但是,inputsplit不存从实际的数据,只是储存的是这个split的引用,即这个split在datanode储存的地址。即
public abstract class InputSplit {
public abstract long getLength();
public abstract String[] getLocations();
}
3. 划分成record
public abstract class RecordReader<KEYIN, VALUEIN> implements Closeable {
public abstract void initialize(
InputSplit split,
TaskAttemptContext context) ;
public abstract boolean nextKeyValue() ;
public abstract KEYIN getCurrentKey();
public abstract VALUEIN getCurrentValue() ;
# 返回一个百分数,表示读取了split的百分之多少。
public abstract float getProgress() ;
public abstract void close() ;
}
RecordReader类的各个方法的含义很明确,就不多做解释了。