![fd9ed03ea86ac945266cfe50050bc4fd.png](https://img-blog.csdnimg.cn/img_convert/fd9ed03ea86ac945266cfe50050bc4fd.png)
前言
我们上一节讲了关于 MapReduce
中的应用场景和架构分析,最后还使用了一个CountWord
的Demo
来进行演示,关于MapReduce
的具体操作。如果还不了解的朋友可以看看上篇文章:[初识MapReduce的应用场景(附JAVA和Python代码)](初识MapReduce的应用场景(附JAVA和Python代码) - 掘金)
接下来,我们会讲解关于MapReduce
的编程模型,这篇文章的主要目的就是讲清楚Mapreduce
的编程模型有多少种,它们之间是怎么协调合作的,会尽量从源码的角度来解析,最后就是讲解不同的语言是如何调用Hadoop
中的Mapreduce
的API
的。
目录
- MapReduce 编程模型的框架
- 五种编程模型的详解
- InputFormat
- OutPutFormat
- Mapper
- Reducer
- Partitioner
- 总结
MapReduce 编程模型的框架
我们先来看一张图,关于MapReduce
的编程模型
![6cf9fbed965cdb402ddc542ab0300251.png](https://img-blog.csdnimg.cn/img_convert/6cf9fbed965cdb402ddc542ab0300251.png)
- 用户程序层
用户程序层是指用户用编写好的代码来调用MapReduce
的接口层。
- 工具层
- Job control 是为了监控`Hadoop`中的`MapReduce`向集群提交复杂的作业任务,提交了任务到集群中后,形成的任务是一个有向图。每一个任务都有两个方法
submit()
和waitForCompletion(boolean)
,submit()
方法是向集群中提交作业,然后立即返回,waitForCompletion(boolean)
就是等待集群中的作业是否已经完成了,如果完成了,得到的结果可以当作下个任务的输入。
chain Mapper
和chain Reducer
的这个模块,是为了用户编写链式作业,形式类似于Map + Reduce Map *
,表达的意思就是只有一个Reduce
,在Reduce
的前后可以有多个Map
Hadoop Streaming
支持的是脚本语言,例Python
、PHP
等来调用Hadoop
的底层接口,Hadoop Pipes
支持的是C++
来调用。
- 编程接口层,这一层是全部由
Java
语言来实现的,如果是Java
来开发的话,那么可以直接使用这一层。
详解五种编程模型
InputFormat
作用
对输入进入MapReduce
的文件进行规范处理,主要包括InputSplit
和RecordReader
两个部分。TextOutputFormat
是默认的文件输入格式。
![74dc9fc6524d4441a2c97f7dbec5a1d3.png](https://img-blog.csdnimg.cn/img_convert/74dc9fc6524d4441a2c97f7dbec5a1d3.png)
InputSplit
这个是指对输入的文件进行逻辑切割,切割成一对对Key-Value
值。有两个参数可以定义InputSplit
的块大小,分别是mapred.max.split.size
(记为minSize
)和mapred.min.split.size
(记为maxSize
)。
RecordReader
是指作业在InputSplit
中切割完成后,输出Key-Value
对,再由RecordReader
进行读取到一个个Mapper
文件中。如果没有特殊定义,一个Mapper
文件的大小就是由Hadoop
的block_size
决定的,Hadoop 1.x
中的block_size
是64M
,在Hadoop 2.x
中的
block_size
的大小就是128M
。
切割块的大小
在Hadoop2.x
以上的版本中,一个splitSize
的计算公式为
OutputFormat
作用
对输出的文件进行规范处理,主要的工作有两个部分,一个是检查输出的目录是否已经存在,如果存在的话就会报错,另一个是输出最终结果的文件到文件系统中,`TextOutputFormat`是默认的输出格式。
![c3656c016e0687b7d027b6f305403afe.png](https://img-blog.csdnimg.cn/img_convert/c3656c016e0687b7d027b6f305403afe.png)
OutputCommiter
OutputCommiter
的作用有六点:
- 作业(
job
)的初始化
//进行作业的初始化,建立临时目录。
//如果初始化成功,那么作业就会进入到 Running 的状态
public abstract void setupJob(JobContext var1) throws IOException;
```
- 作业运行结束后就删除作业
```
//如果这个job完成之后,就会删除掉这个job。
//例如删除掉临时的目录,然后会宣布这个job处于以下的三种状态之一,SUCCEDED/FAILED/KILLED
@Deprecated
public void cleanupJob(JobContext jobContext) throws IOException {
}
- 初始化
Task
//初始化Task的操作有建立Task的临时目录
public abstract void setupTask(TaskAttemptContext var1) throws IOException;
- 检查是否提交`Task`的结果
//检查是否需要提交Task,为的是Task不需要提交的时候提交出去
public abstract boolean needsTaskCommit(TaskAttemptContext var1) throws IOException;
- 提交
Task
//任务结束的时候,需要提交任务
public abstract void commitTask(TaskAttemptContext var1) throws IOException;
- 回退
Task
//如果Task处于KILLED或者FAILED的状态,这Task就会进行删除掉临时的目录
//如果这个目录删除不了(例如出现了异常后,处于被锁定的状态),另一个同样的Task会被执行
//然后使用同样的attempt-id去把这个临时目录给删除掉,也就说,一定会把临时目录给删除干净
public abstract void abortTask(TaskAttemptContext var1) throws IOException;
处理Task Side-Effect File
在Hadoop
中有一种特殊的文件和特殊的操作,那就是Side-Eddect File
,这个文件的存在是为了解决某一个Task
因为网络或者是机器性能的原因导致的运行时间过长,从而导致拖慢了整体作业的进度,所以会为每一个任务在另一个节点上再运行一个子任务,然后选择两者中处理得到的结果最快的那个任务为最终结果,这个时候为了避免文件都输入在同一个文件中,所以就把备胎任务输出的文件取作为 Side-Effect File
RecordWriter
这个是指输出KEY-VALUE
对到文件中。
Mapper和Reducer
详解Mapper
InputFormat
为每一个 InputSplit
生成一个 map
任务,mapper
的实现是通过job
中的setMapperClass(Class)
方法来配置写好的map
类,如这样
//设置要执行的mapper类
job.setMapperClass(WordMapper.class);
其内部是调用了map(WritableComparable, Writable, Context)
这个方法来为每一个键值对写入到InputSplit
,程序会调用cleanup(Context)
方法来执行清理任务,清理掉不需要使用到的中间值。
关于输入的键值对类型不需要和输出的键值对类型一样,而且输入的键值对可以映射到0
个或者多个键值对。通过调用context.write(WritableComparable, Writable)
来收集输出的键值对。程序使用Counter
来统计键值对的数量,
在Mapper
中的输出被排序后,就会被划分到每个Reducer
中,分块的总数目和一个作业的reduce
任务的数目是一样的。
需要多少个Mapper任务
关于一个机器节点适合多少个map
任务,官方的文档的建议是,一个节点有10
个到100
个任务是最好的,如果是cpu
低消耗的话,300
个也是可以的,最合理的一个map
任务是需要运行超过1
分钟。
详解Reducer
Reducer
任务的话就是将Mapper
中输出的结果进行统计合并后,输出到文件系统中。
用户可以自定义Reducer
的数量,使用Job.setNumReduceTasks(int)
这个方法。
在调用Reducer
的话,使用的是Job.setReducerClass(Class)
方法,内部调用的是 reduce(WritableComparable, Iterable<Writable>, Context)
这个方法,最后,程序会调用cleanup(Context)
来进行清理工作。如这样:
//设置要执行的reduce类
job.setReducerClass(WordReduce.class);
Reducer
实际上是分三个阶段,分别是Shuffle
、Sort
和Secondary Sort
。
shuffle
这个阶段是指Reducer
的输入阶段,系统会为每一个Reduce
任务去获取所有的分块,通过的是HTTP
的方式
sort
这个阶段是指在输入Reducer
阶段的值进行分组,sort
和shuffle
是同时进行的,可以这么理解,一边在输入的时候,同时在一边排序。
Secondary Sort
这个阶段不是必需的,只有在中间过程中对key
的排序和在reduce
的输入之前对key
的排序规则不同的时候,才会启动这个过程,可以通过的是Job.setSortComparatorClass(Class)
来指定一个Comparator
进行排序,然后再结合 Job.setGroupingComparatorClass(Class)
来进行分组,最后可以实现二次排序。
在整个`reduce`中的输出是没有排序
需要多少个 Reducer 任务
建议是0.95
或者是1.75
*mapred.tasktracker.reduce.tasks.maximum
。如果是0.9
的话,那么就可以在mapper
任务结束时,立马就可以启动`Reducer`任务。如果是1.75
的话,那么运行的快的节点就可以在map
任务完成的时候先计算一轮,然后等到其他的节点完成的时候就可以计算第二轮了。当然,Reduce
任务的个数不是越多就越好的,个数多会增加系统的开销,但是可以在提升负载均衡,从而降低由于失败而带来的负面影响。
Partitioner
这个模块用来划分键值空间,控制的是map
任务中的key
值分割的分区,默认使用的算法是哈希函数,HashPartitioner
是默认的Partitioner
。
总结
这篇文章主要就是讲了MapReduce
的框架模型,分别是分为用户程序层、工具层、编程接口层这三层,在编程接口层主要有五种编程模型,分别是InputFomat
、MapperReduce
、Partitioner
、OnputFomat
和Reducer
。主要是偏理论,代码的参考例子可以参考官方的例子:[WordCount_v2.0](MapReduce Tutorial)
这是MapReduce
系列的第二篇,接下来的一篇会详细写关于MapReduce
的作业配置和环境,结合一些面试题的汇总,所以接下来的这篇还是干货满满的,期待着就好了。