Hadoop mapred 与 mapreduce 包中 InputFormat 的区别


1. 背景

最近看到一个问题:

org.apache.hadoop.mapreduce.InputFormatorg.apache.hadoop.mapred.InputFormat 看起来方法差不多,有什么区别?为什么 Hive 里面很多地方用的是 mapred 包,而一般自己写 MapReduce 程序时用的是 mapreduce 包?

这个问题比较容易让人困惑,因为这两个类名字一样,作用也很像,都是 MapReduce 中负责定义输入数据如何切分、如何读取的组件。

但是它们不是同一个 API。

简单来说:

org.apache.hadoop.mapred.InputFormat      是旧版 MapReduce API
org.apache.hadoop.mapreduce.InputFormat   是新版 MapReduce API

它们解决的是同一类问题,但属于 Hadoop 不同时期的接口设计。

2. InputFormat 是做什么的

不管是 mapred.InputFormat 还是 mapreduce.InputFormat,核心职责都差不多。

Apache Hadoop 官方 Javadoc 中对 InputFormat 的说明是:MapReduce 框架依赖 InputFormat 做三件事:

  1. 校验作业的输入配置。
  2. 将输入文件切分成逻辑上的 InputSplit
  3. 提供 RecordReader,把 InputSplit 中的数据读取成一条条 key/value 记录。

也就是说,InputFormat 决定了一个 MapReduce 任务从哪里读数据,以及如何把数据拆给多个 Mapper 执行。

以最常见的 TextInputFormat 为例,它会把文本文件按行读取:

key   = 当前行在文件中的偏移量
value = 当前行的文本内容

所以平时写 MapReduce 程序时,设置不同的 InputFormat,本质上就是换一种输入解析方式。

3. 两个 InputFormat 的方法对比

3.1 旧 API:org.apache.hadoop.mapred.InputFormat

mapred 包中的 InputFormat 是一个接口,方法如下:

public interface InputFormat<K, V> {

    InputSplit[] getSplits(JobConf job, int numSplits) throws IOException;

    RecordReader<K, V> getRecordReader(
            InputSplit split,
            JobConf job,
            Reporter reporter) throws IOException;
}

这个版本使用的是旧 API 中的对象:

对象 说明
JobConf 旧版作业配置对象
InputSplit[] 返回数组形式的 split
Reporter 用于上报进度、计数器等信息
RecordReader 旧版 RecordReader 接口

3.2 新 API:org.apache.hadoop.mapreduce.InputFormat

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;
}

这个版本使用的是新 API 中的对象:

对象 说明
JobContext 作业上下文
TaskAttemptContext task attempt 上下文
List<InputSplit> 返回集合形式的 split
RecordReader 新版 RecordReader 抽象类

对比下来可以看到,两个 InputFormat 的职责一样,但是方法签名和配套对象不同。

4. 为什么会有两个包

Hadoop 早期 MapReduce API 主要在 org.apache.hadoop.mapred 包中。

后来 Hadoop 对 MapReduce API 做了一次新的封装,新的 API 放在 org.apache.hadoop.mapreduce 包中。新版 API 更强调上下文对象,例如:

JobContext
TaskAttemptContext
Mapper.Context
Reducer.Context

旧版 API 中经常能看到:

JobConf
OutputCollector
Reporter

新版 API 中则更多使用:

Configuration
Job
Context

所以这两个包可以理解为:

包名 API 代际 常见对象
org.apache.hadoop.mapred 旧 API JobConfReporterOutputCollector
org.apache.hadoop.mapreduce 新 API JobJobContextTaskAttemptContextContext

从用户编写普通 MapReduce 程序的角度看,如果没有历史包袱,一般优先使用 org.apache.hadoop.mapreduce 这一套新 API。

5. 为什么 Hive 里经常使用 mapred 包

Hive 里面大量使用 org.apache.hadoop.mapred.InputFormat,这不是因为新 API 不能用,而是 Hive 的历史和生态兼容性决定的。

Hive 很早就开始基于 Hadoop MapReduce 做 SQL 执行引擎,当时 Hadoop 生态中大量存储格式、SerDe、InputFormat、OutputFormat 都是基于旧 API 开发的。

例如 Hive 中常见的文件格式类会涉及:

org.apache.hadoop.mapred.InputFormat
org.apache.hadoop.mapred.OutputFormat
org.apache.hadoop.mapred.JobConf
org.apache.hadoop.mapred.RecordReader

在 Hive 源码中,CombineHiveInputFormat 也可以看到它使用的是 org.apache.hadoop.mapred.InputFormatFileInputFormatFileSplitJobConfReporter 等旧 API 对象。

这和 Hive 的表格式兼容有关。Hive 表的元数据中会保存输入格式和输出格式,例如:

STORED AS TEXTFILE
STORED AS ORC
STORED AS PARQUET

底层会映射到对应的 InputFormat、OutputFormat、SerDe。为了兼容历史表、历史存储格式、历史 Hadoop 版本,Hive 长期保留了 mapred 这套 API。

所以看到 Hive 使用 mapred.InputFormat 不奇怪,它更多是为了兼容已有生态。

6. 实际开发应该用哪个

6.1 自己写普通 MapReduce 程序

如果是自己写一个普通 MapReduce 作业,建议使用新版 API:

import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;

设置输入格式:

Job job = Job.getInstance(conf);
job.setInputFormatClass(TextInputFormat.class);

新版 API 的代码结构更清晰,和现在 Hadoop 文档、示例代码也更接近。

6.2 给 Hive 表实现 InputFormat

如果是给 Hive 表、Hive StorageHandler、Hive 自定义存储格式实现 InputFormat,通常要看 Hive 侧要求。

很多情况下仍然需要实现旧 API:

import org.apache.hadoop.mapred.InputFormat;
import org.apache.hadoop.mapred.InputSplit;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.RecordReader;
import org.apache.hadoop.mapred.Reporter;

原因是 Hive 的表元数据、执行链路和很多文件格式接口仍然围绕旧 API 设计。

6.3 读 Hive 表再写 MapReduce 程序

如果 MapReduce 程序要直接读取 Hive 表,尤其是使用 Hive 的 InputFormat 或老的存储格式,也可能需要使用 mapred 这一套。

这时不要只看类名,要看上下文对象。

如果方法里出现:

JobConf
Reporter
OutputCollector

基本就是旧 API。

如果方法里出现:

Job
JobContext
TaskAttemptContext
Mapper.Context
Reducer.Context

基本就是新 API。

7. 能不能混用

不建议混用。

两个包里的类名字虽然一样,但类型不一样。例如:

org.apache.hadoop.mapred.InputSplit
org.apache.hadoop.mapreduce.InputSplit

这两个 InputSplit 不是同一个类。

同样,下面两个 RecordReader 也不是同一个类:

org.apache.hadoop.mapred.RecordReader
org.apache.hadoop.mapreduce.RecordReader

如果把旧 API 的 InputFormat 设置到新 API 的 Job 中,或者反过来,很容易出现泛型不匹配、类型转换失败、方法签名对不上的问题。

实际开发时,应该先确定当前作业使用的是哪套 API,然后整条链路保持一致:

InputFormat
InputSplit
RecordReader
Mapper
Reducer
OutputFormat

这些类最好都来自同一个包体系。

8. 总结

org.apache.hadoop.mapreduce.InputFormatorg.apache.hadoop.mapred.InputFormat 的区别可以总结成一句话:

职责相同,API 代际不同,配套上下文对象不同。

具体来说:

对比项 mapred.InputFormat mapreduce.InputFormat
API 代际 旧 API 新 API
类型 interface abstract class
配置对象 JobConf JobContext
split 返回值 InputSplit[] List<InputSplit>
创建 reader 方法 getRecordReader createRecordReader
进度上报 Reporter TaskAttemptContext
常见使用场景 Hive、老 Hadoop 生态、自定义 Hive 存储格式 普通 MapReduce 程序、新代码

所以如果只是写普通 MapReduce 程序,优先选择:

org.apache.hadoop.mapreduce.InputFormat

如果是在 Hive 体系里实现表的读取逻辑,或者对接 Hive 现有存储格式,则通常需要看 Hive 要求,很多场景仍然会用:

org.apache.hadoop.mapred.InputFormat

不要只看两个类的方法很像,就认为它们可以直接替换。它们背后的 InputSplitRecordReaderJobConfContext 都不是同一套类型。


文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录