Hadoop:MapReduce之Mapper类的输入

目录

Mapper类

Mapper的输入

InputFormat

文件输入FileInputFormat & 输入分片InputSplit

文本输入TextInputFormat & 行记录阅读器LineRecordReader

Mapper的输出

收集器Collector

分区器Partitioner

案例:分别计算奇数行和偶数行之和


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 = 
  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值