目录
文件输入FileInputFormat & 输入分片InputSplit
文本输入TextInputFormat & 行记录阅读器LineRecordReader
Hadoop的代码中为作业的操作流程定义了几个阶段
enum Phase{
STARTING,MAP,SHUFFLE,SORT,REDUCE,CLEANUP;
}
宏观上来说,MR框架由map和reduce这两个阶段组成,但实际上这两个阶段都可以进一步划分成多个更微观的阶段,比如Mapper的输出端有个由框架提供的局部排序阶段,而Reducer输入端的收取(Fetch)和合并(Merge),以至汇合(Combine)阶段又带有排序的成分等。
Mapper类
public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
public abstract class Context
implements MapContext<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {
}
protected void setup(Context context
) throws IOException, InterruptedException {
// NOTHING
}
protected void map(KEYIN key, VALUEIN value,
Context context) throws IOException, InterruptedException {
context.write((KEYOUT) key, (VALUEOUT) value);
}
protected void cleanup(Context context
) throws IOException, InterruptedException {
// NOTHING
}
public void run(Context context) throws IOException, InterruptedException {
setup(context);
try {
while (context.nextKeyValue()) {
map(context.getCurrentKey(), context.getCurrentValue(), context);
}
} finally {
cleanup(context);
}
}
}
Mapper将输入的键值对转换成一组中间键值对;转换后的中间记录不必与输入记录的类型相同,指定的输入对可以映射为多个或零个输出对;
MR框架为作业中的每一个InputSplit(输入分片)生成一个独立的map任务,每个分片经过框架处理会变成多个键值对,这些键值对都存储在Context对象中,Context类实现了MapContext界面,具有nextKeyValue()、getCurrentKey()、getCurrentValue()、write方法;
可见Map任务真正的执行逻辑位于run()方法中,setup()和cleanup()都是空的,分别在任务执行前后进行调用;然后Mapper的核心就是一个while循环,其中对每一个键值对执行了map()方法。默认的map()方法中其实没进行说明处理,只是将输入的键值对原封不动地通过context.write()出去了,所以通常我们都会重写map()方法。
Mapper的输入
InputFormat
MR框架的数据源可以来自HDFS文件,也可以是例如查询数据库的输出等。文件的类型也没有规定特定的格式,例如也可以是网页。那么MR框架是如何读出不同类型的数据,然后形成键值对并作为Mapper(map()方法)的输入呢,这就是InputFormat的作用。不同的数据源,不同的数据格式,就需要采用不同的InputFormat类型,所以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描述了MR程序的输入规则,其中getSplits()返回一个InputSplit的列表,一个分片对应一个Mapper。如果给定Mapper的数量,那么分片的数量也就随之确认了。但如果不给定Mapper的数量,如何进行分片就是不同类型的InputFormat需要考虑的问题了。InputSplit是一个接口:
public interface InputSplit extends Writable {
long getLength() throws IOException;
String[] getLocations() throws IOException;
}
InputSplit包含了以字节为长度的长度和一组存储位置(主机名)。分片并不包含数据本身,而是指向数据的引用。存储位置供MR框架使用以便将map任务尽量放在分片数据的附近,而分片大小用来排序分片,以便处理最大的分片,从而最小化作业时间。
map任务将输入分片传给InputFormat中的createRecordReader来获得这个分片的RecordReader,RecordReader即记录阅读器,所以实际上是RecordReader来将原始数据转换成输入map()方法的键值对。同样RecordReader中也不存放数据本身,而是指向数据的引用。每种InputFormat都有配套的RecordReader,Mapper中对于context.nextKeyValue()等方法的调用实际上最终是由RecordReader来实现的。
文件输入FileInputFormat & 输入分片InputSplit
如果是基于对文件的操作,通常使用的是FileInputFormat子类。
//FileInputFormat的指定泛型类型与map()的输入键值对类型一致,即与k1与v1类型一致
public abstract class FileInputFormat<K, V> implements InputFormat<K, V>
在Driver类中通过addInputPath()可以指定一个文件或者是一个目录,若指定了一个目录则默认其内容不会被递归处理,因为若包含子目录,也会被认为是文件从而产生错误,但是也可以通过将mapreduce.input.fileinputformat.input.recursive设置为true以强制对输入目录进行递归读取。
//默认为false
public static final String INPUT_DIR_RECURSIVE =