MapReduce框架在Yarn上的详解

来源:互联网 发布:unity3d加速器 编辑:程序博客网 时间:2024/05/02 03:00

MapReduce任务解析

Timeline MapReduce Job

YARN上一个MapReduce任务叫做一个Job。一个Job的主程序在MapReduce框架上实现的应用名称叫MRAppMaster.

MapReduce任务的Timeline

这是一个MapReduce作业执行时间:

  • Map 阶段:根据数据块会执行多个Map Task
  • Reduce 阶段:根据配置项会执行多个Reduce Task

为提高Shuffle效率Reduce阶段会在Map阶段结束之前就开始。(直到所有MapTask完成之后ReduceTask才能完成,因为每个ReduceTask依赖所有的MapTask的结果)

Map阶段

首先看看Map阶段,一个Job需要多少Map Task

用户会提交什么?

当一个客户端提交的应用时会提供以下多种类型的信息到YARN上。

  • 一个configuration(配置项):Hadoop有默认的配置项,所以即使什么都不写它也有默认的配置项加载。优先级高到低顺序是用户指定的配置项>etc/conf下的XML>默认配置项
  • 一个JAR
    • 一个map()实现(Map抽象类的实现)
    • 一个combiner 实现(combiner抽象类的实现,默认是跟Reduce实现一样)
    • 一个reduce()实现(Reduce抽象类的实现)
  • 输出输入信息:
    • 输入目录:输入目录的指定,如输入HDFS上的目录、S3或是多少个文件。
    • 输出目录:输出目录的指定,在HDFS还是在S3

输入目录中的文件数用于决定一个JobMapTask的数量。

那么到底会有多少个MapTask?

Application Master会为每一个split(分片)创建一个MapTask。通常情况下,每个文件都会是一个split。如果文件太大(大于128MHDFS默认块大小)就会分为多个split并关联到这个文件,也就是一个文件会产生多个Map Task。获取split数量方法代码如下 getSplits() of the FileInputFormat class:

num_splits = 0for each input file f:   remaining = f.length   while remaining / split_size > split_slope:      num_splits += 1      remaining -= split_size
split_slope = 1.1split_size =~ dfs.blocksize

MapTask执行过程

Application Master会向Resource Maneger资源管理器提交job所需要的资源:为每一个split文件申请一个container来运行Map Task

为了提高文件读取效率containermap split所在的机器上运行是最为理想的。因此AM会根据数据本地性>CPU>内存匹配的方式分配container

  • 如果发现一个Node Manager上有所需的map split那么相关的container就会分配到这个NM(因为根据HDFS备份机制有3台机器上同时拥有相同的块);
  • 否则, 会分配到机柜内的其他机器上;
  • 否则, 会分配到集群上的任何一个机器上

当容器被分配给AMMap Task任务就会启动。

Map 阶段:示例

Map Phase execution

这是一个典型的Map执行场景:

  • 2Node Manager:每个Node Manager拥有2GB内存,而每个MapTask需要1GB内存,因此每个NM可以同时运行2container
  • 没有其他的应用程序在集群中运行
  • 我们的job9split (例如,在输入目录里有8个文件,但其中只有一个是大于HDFS块大小的文件,所以我们把它分为2map split);因此需要9map

MapTask执行的Timeline

Map Task Execution Timeline

现在让我们专注于一个Map Task任务。这是Map Task任务执行时间线:

  • 初始化(INIT)阶段:初始化Map Task(默认是什么都没有。。)
  • 执行(EXECUTION)阶段: 对于每个 (key, value)执行map()函数
  • 排序(SPILLING)阶段:map输出会暂存到内存当中排序,当缓存达到一定程度时会写到磁盘上,并删除内存里的数据
  • SHUFFLE 阶段:排序结束后,会合并所有map输出,并分区传输给reduce

MapTask:初始化(INIT

1. 创建一个Task上下文,Reduce也继承自它(TaskAttemptContext.class)

2. 创建MAP实例 Mapper.class

3. 设置input (e.g., InputFormat.class, InputSplit.class, RecordReader.class)

4. 设置output (NewOutputCollector.class)

5. 创建mapper的上下文(MapContext.class, Mapper.Context.class)

6. 初始化输入,例如

7. 创建一个SplitLineReader.class object

8. 创建一个HdfsDataInputStream.class object

MapTask:执行(EXECUTION

MapTask execution

Map的执行阶段从 Mapper classrun 方法开始,我们通常要写的也就是它了。默认情况下run之前会调用setup方法:这个函数没有做任何事情,但是我们可以重写它来配置相关的类变量等信息。执行setup方法之后会对每一个<key, value>执行map()函数。之后map context会存储这些数据到一个缓存区,为后续排序做准备。

map执行完处理时,还会调用一个clean方法:默认情况下,也不执行任何操作,但用户也可以重写它。

MapTask:排序(SPILLING

Spilling phase

执行阶段期间map会把数据写进一个缓存区(MapTask.MapOutputBuffer)。这个缓存大小由配置项设定mapreduce.task.io.sort.mb  (默认:100MB)。为了提高硬盘刷写速度缓存区达到80%会写数据到磁盘,会有一个单独的线程并行执行。当缓存区容量达到100%那么就要等到这个单独的线程把数据写完才能继续执行map方法。

排序线程会执行以下动作:

1. 创建一个SpillRecord和一个FSOutputStream (在本地文件系统)

2. 在内存中对键值对进行快速排序

3. 分区

4. 按顺序写入本地分区文件。

Shuffle阶段


shuffle阶段主要是做数据的排序和合并操作,然后把数据存到本地文件系统上,等待Reduce来获取数据。等到所有的MapTask产出的数据传输都Reduce机器上,并对数据进行排序以后才能算是Shuffle过程的结束。也就说从Map函数出来之后到Reduce函数之前的所有数据操作都叫Shuffle操作,包括排序、合并、分区、传输等。

Reduce阶段

Reduce阶段的run与Map阶段的run执行是类似的。

refhttp://ercoppa.github.io/HadoopInternals/AnatomyMapReduceJob.html


0 0