MapReduce 的工作机制

解剖MapReduce job的运行机制

你可以在一个Job对象上调用submit()方法来运行一个MapReduce Job(或者也可以调用waitForCompletion()方法,如果job还没有提交,它可以在提交job后等待job的完成),但是这些方法的调用在后台隐藏了大量的处理细节。

下图展示了MapReduce Job运行的整个过程:


从较高层次来看,可以分为5个独立的实体:

  • 客户端,提交MapReduce Job。
  • YARN资源管理器,协调分配集群计算资源。
  • YARN node manager,启动和监控集群节点上的计算容器(container)。
  • MapReduce application master,主要负责协调MapReduce Job的task的运行。application master和MapReduce task运行在container中,并且受resource manager的调度和node manager的管理。
  • 分布式文件系统(通常是HDFS),主要在各实体间共享Job文件。
Job Submission

在Job对象上调用submit()方法会创建一个内部的JobSubmitter的实例,并调用它的submitJobInternal()方法(图中第1步),提交Job后,waitForCompletion()方法会每秒一次检查job的处理进度,并且如果自上次的报告有变更,它会报告进度给控制台。此外,导致Job失败的错误也会被记录到控制台。

Job的提交过程是由JobSubmitter实现的,主要执行了以下操作:

  • 请求resource manager提供一个应用ID(application ID),作为MapReduce Job的ID(第2步)。
  • 检查Job的输出规范。例如,如果输出目录没有指定或者已经存在,则该Job不能被提交,并且会向MapReduce程序抛出一个错误。
  • 计算Job的输入切片。如果切片不能被计算(比如,因为输入路径不存在),则该Job不能被提交,并且会向MapReduce程序抛出一个错误。
  • 复制运行Job需要的资源,包括jar文件、配置文件和计算好的输入切片,到共享文件系统的一个以Job ID命名的目录里(第3步)。Job JAR文件的复制具有较高的复制因子(它由the mapreduce.client.submit.file.replication属性控制,默认为10),所以,运行Job的task时,在集群的node manager节点中中会有很多副本可以被访问。
  • 在resource manager上通过调用submitApplication()方法来提交job。
Job Initialization

当resource manager接收到submitApplication()调用时,它会将请求转给YARN调度器来处理,调度器分配一个container,然后resource manager在container上启动application master进程,并被node manager节点管理(第5a和5b步)。

MapReduce Job 的application master是一个Java 应用程序,它的main class 是MRAPPMaster。它初始化job是通过创建若干簿记(bookkeeping)对象来跟踪job的进度,因为它会接收到来自task的进度和完成情况的报告(第6步)。下一步,它接收来自共享文件系统的输入切片(第7步),然后它为每个输入切片创建一个map task,以及由mapreduce.job.reduces属性(或设置job的setNumReduceTasks()方法)决定的若干reduce task,同时为每个task指定一个ID。

application master 决定如何运行这些task。如果job比较小,application master也许会选择在同一个JVM中运行这些task。这发生在它判断在新的container中分配和运行这些task的开销,相比在一个节点上依次运行它们低于并行运行它们。这样的job被称作“超级任务”。

那么,怎么判断是一个小的Job?默认情况下,一个小的Job是低于10个mapper,只有一个reducer,和输入大小小于HDFS块的大小(注意:对于job来说,这些值是可以通过设置mapreduce.job.ubertask.maxmaps,apreduce.job.ubertask.maxreduces和mapreduce.job.ubertask.maxbytes这些属性值改变的),“超级任务”必须通过设置mapreduce.job.ubertask.enable属性值为true来显示启用的。

最后,在运行任何task之前,application master会调用OutputCommitter.的setupJob()方法。默认为FileOutputCommitter,它会为Job创建最终的输出目录和task输出的临时工作空间。

Task Assignment(任务分配)

如果一个Job没有以超级任务运行,那么application master将会为job的map task和reduce task从resource manager中请求更多的container(第8步)。对map task的请求的优先级要高于对reduce task的请求优先级,因为所有的map task必须在reduce task的sort 阶段(shuffle和sort)开始之前完成。对reduce task的请求需要直到完成5%的map task,才能开始(Reduce low start)。

reduce task可以运行在集群中的任何地方,但是对于map task有数据本地化的约束,这是调度器努力追求的。在优化的情况下,map task是数据本地化的--也就是说,和运行任务所需的输入切片是在同一个几点上的。或者任务是机架本地化:和切片在同一个机架,但不是相同的节点。有些task既不是数据本地化也不是机架本地化,而是从不同的机架上接收数据。对于特定的job,你可以通过查看job的计数器来决定在每一个本地化层级上运行 task的数量。

对resource manager的请求也指定了运行task所需的内存和CPU。默认情况下,每个map和task 任务会被分配1024MB的内存和一个虚拟内核,这些值对于每个job都是可配置的,通过指定mapreduce.map.memory.mb, mapreduce.reduce.memory.mb, mapreduce.map.cpu.vcores 和 mapreduce.reduce.cpu.vcores.属性值。

Task Execution

一旦一个任务由resource manager的调度器在一个节点的container中为其分配了资源,那么application master就可以联系node manager来启动container(第9a和9b步)。task是由一个Java 应用程序YarnChild来执行的,在运行task之前,YarnChild会本地化task所需的资源,包括job的配置文件、jar文件和来自分布式缓存的任何文件(第10步)。最后,YarnChild运行map或reduce task(第11步)。

YarnChild是运行在一个专用的JVM中,所以在用户定义的map 函数或 reduce函数(甚至YarnChild)中有任何bug,都不会影响到node manager。

Streaming

Steaming是指使用用户提供的可执行文件或脚本来运行特定的map和reduce任务的一种技术。

如下图:


Streaming task使用标准的输入/输出和外部的Streaming process(其可以用任何语言编写)交互。在task运行期间,java 进程传递key-value键值对作为外部Streaming proces进程的输入,然后Streaming process进程运行用户自定义的map或reduce函数处理输入,并将处理结果以key-values键值对作为输出传递给java 进程。从node manager的角度来看,Streaming process就像是运行map或reduce代码的子进程。

Progress and Status Updates(进度和状态更新)

MapReduce Job是长时间运行的批处理作业,从数十秒到数小时不等。因为这可能是相当长的一段时间,所以对用户来说,能够获得job的执行进度的反馈是比较重要的。一个Job和其每个task都是有状态的,包括Job或task的状态(例如:正在运行、成功完成、失败等),map和reduce的进度,job计数器的值,和状态消息或描述(这可以有用户代码设置)等。这些状态的改变发生job的运行过程中,那么怎么把状态的变化信息反馈给客户端?

当一个task运行时,它会跟踪其进展情况(即task的完成比例)。对于map task来说,就是已被处理的输入的比例。对reduce task来说,有点复杂,但是系统扔可以估算出reduce已处理的输入的比例。它是这样做的,通过将总的进展分为三个部分,对应Shuffle的三个阶段。例如,如果task已经运行了reducer一半的输入,那么task的进度是5/6,因为它已经完成了copy和sort阶段(各1/3)和一半是通过reduce阶段(1/6)。

MapReduce Job进度由什么组成?
Progress(进度)并不总是可以测量的,但尽管如此,它告诉了Hadoop,一个task正在做某事。
进度由以下操作组成:
  • 读取一个输入记录(在map和reduce函数中)
  • 写一个输出记录(在map和reduce函数中)
  • 设置状态描述(通过Reporter 或者 TaskAttemptContext的setStatus()方法)
  • 增加一个计数器(使用Reporter的 incrCounter() 方法或者 Counter的 increment()方法)
  • 调用Reporter或TaskAttemptContext的progress()方法
task也有一组计数器,用于统计在task运行时发生的各种事件,它可能被内置到框架内,例如统计map输出的记录的数量,或者用户定义。

在map或reducetask运行时,子进程通过一个umbilical interface(脐带接口)和它的父application master交互。task 报告它的进度和状态(包括计数器)给application master,它有一个job的聚合视图,每隔3秒调用一次脐带接口。

resource manager的web UI 显示了每个正在运行的应用,每个应用都有一个链接到其application master的URI,进一步详细的展示了MapReduce job的进度。

在Job运行过程中,客户端会每秒(间隔时间可以通过apreduce.client.progressmonitor.pollinterva属性值设置l)通过轮询application master来接收最新的状态。客户端也可以调用getStatus()方法获得JobStatus的实例,它包含了job的状态信息。

整个过程如下图所示:


Job Completion

当application master接收到job最后一个task完成的通知,它会改变job的状态为“successful”。然后,当Job处于轮询状态时,它得知job已经成功完成,它会打印一条消息给用户,然后从waitForCompletion()方法中返回。这时,Job的统计信息和计数器也会打印到控制台。

最后,Job完成,application master和task container清除它们的工作状态(如:删除中间的输出),OutputCommitter 调用commitJob()方法提交完成job。该Job的信息会被Job历史服务器存档,供以后用户想查看是使用。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章