Hadoop InputFormat

Hadoop可以处理不同数据格式(数据源)的数据,从文本文件到(非)关系型数据库,这很大程度上得益于Hadoop InputFormat的可扩展性设计,InputFormat层次结构图如下:
 
 
InputFormat(org.apache.hadoop.mapreduce.InputFormat)被设计为一个抽象类,代码如下:
 
public abstract class InputFormat<K, V> {
     public abstract List<InputSplit> getSplits(JobContext context)
          throws IOException, InterruptedException;
 
     public abstract RecordReader<K, V> createRecordReader(InputSplit split, TaskAttemptContext context)
          throws IOException, InterruptedException;
}
 
InputFormat为我们提供两个很重要的功能:
 
(1)如何将数据源中的数据(不一定是文件)形成切片(InputSplit,可能有多个),数据只有被合理切分,我们才可以分布式处理;
(2)如何读取并解析切片中的数据(RecordReader),数据源中的数据形式各异,只有转换为通用的Key-Value Pair,我们才可以利用MapReduce框架完成计算过程。
 
InputSplit
 
InputSplit表示数据源中的部分数据,以MapReduce为例,每一个InputSplit中的数据会被一个Map Task负责处理,亦即有多少个InputSplit便会形成多少个Map Task。InputSplit并不实际存储数据,仅仅关联数据,类似于索引的作用。对于文件而言,InputSplit中保存着文件名称、文件起始偏移量、数据长度;对于数据库而言,InputSplit中保存着数据表名称、数据行范围。
 
InputSplit也被设计为一个抽象类,可以看出不同的InputFormat通常会对应着不同的InputSplit,代码如下:
 
public abstract class InputSplit {
     public abstract long getLength()
          throws IOException, InterruptedException;
 
     public abstract String[] getLocations()
          throws IOException, InterruptedException;
}
 
InputSplit中保存着两个信息:
 
(1)数据大小:InputSplit所关联数据的大小,MapReduce根据此值对所有InputSplit进行排序,使“大”的InputSplit得以优先运行,进而缩短整个Job的运行时间;
(2)数据存储位置:MapReduce根据此值完成Map Task的调度,即对于某个InputSplit来说,选取哪个计算节点运行Map Task完成这个InputSplit的数据处理,旨在拉近计算与数据之间的“距离”,移动计算而非移动数据,减少集群网络带宽资源。
 
大多数时候我们并不需要关心InputSplit的生成过程,它由MapReduce框架通过InputFormat getSplits()生成。
 
RecordReader
 
Map Task实际处理的是InputSplit的一条条“记录”(Record),我们需要一种方式能够将InputSplit关联的数据转换为一条条的“记录”,即Key-Value Pairs。对于文本文件的一条Record而言,key为数据在文本文件的起始偏移量,value为对应的一行字符串数据。
 
RecordReader就是被设计用来完成这项工作的。
 
RecordReader实际就是一个迭代器,不断地将InputSplit关联的数据传送给我们自定义的Map函数,代码如下:
 
public void run(Context context) throws IOException, InterruptedException {
     setup(context);
 
     while (context.nextKeyValue()) {
          map(context.getCurrentKey(), context.getCurrentValue(), context);
     }
 
     cleanup(context);
}
 
RecordReader被封装于Context内部,相关方法调用会由Context转交至RecordReader完成。
 
注:RecordReader为提高实现效率,每次迭代返回(getCurrentKey、getCurrentValue)的key、value实例都是相同的(重复使用实例),仅仅两者实例的内容不同而已。某些情况下我们可能需要缓存这些key、value以便后续处理,此时我们需要“复制”这些值,比如new Text(key),new Text(value)。Mapper和Reducer的数据处理过程均需要注意这个问题。
 
综上所述,我们可以根据实际情况,自定义扩展InputFormat完成特殊数据格式(数据源)的分析,主要涉及到切片(InputSplit)的形成以及数据的解析(RecordReader)。
 
posted on 2015-05-14 11:40  非著名野生程序员  阅读(529)  评论(0编辑  收藏  举报