Hadoop MapReduce 的工作机制


Hadoop MapReduce 的工作机制
-----------------------------------------------------------------------------------------------------------------------------------------------




1 剖析 MapReduce 作业运行机制
-----------------------------------------------------------------------------------------------------------------------------------------------
可以通过一个简单的方法调用来运行 MapReduce 作业: Job 对象上的 submit() 。也可以调用 waitForCompletion() ,
它用于提交以前没有提交过的作业,并等待它的完成。
submit() 方法调用封装了大量的处理细节。

用于执行的框架通过 mapreduce.framework.name 属性进行设置, 值 local 表示本地的作业运行器, classic 表示经典的 MapReduce 框架,也称为 MapReduce 1 ,
yarn 表示新的框架。


YARN ( MapReduce 2)
--------------------------------------------------------------------------------------------------------------------
对于节点数超出 4000 的大型集群, 经典 MapReduce 系统开始面临扩展性问题。
2010 年雅虎的一个团队开始设计下一代的 MapReduce , 由此 YARN ( Yet Another Resource Negotiator 的缩写) 应运而生。

YARN 将 Jobtracker 的职能划分为多个独立的实体,从而改善了 “经典的” MapReduce 面临的扩展瓶颈问题 。Jobtracker 负责作业调度和任务进度监视。

YARN 将这两种角色划分为两个独立的守护进程:管理集群上资源使用的资源管理器 和 管理集群上运行任务生命周期的应用管理器。
基本思路是:应用服务器与资源管理器协商集群的计算资源 —— 容器(每个容器都有特定的内存上限)容器由集群节点上运行的节点管理器监视,以确保应用程序使用的资源不会超过分配给它的资源。

应用的每个实例(指一个 MapReduce 作业)由一个专用的应用 master , 它运行在应用的运行期间。
YARN 比 MapReduce 更具一般性,实际上 MapReduce 只是 YARN 应用的一种形式,有很多其他的 YARN 应用以及其他正在开发的程序可以在 YARN 上运行。
YARN 的精妙之处在于不同的 YARN 应用可以在同一个集群上共存。

名词解释:
-------------------------------------------------------------------------------------------------------------------
Resource Manager     : RM , 资源管理器, 负责管理所有应用程序计算资源的分配
Application Master     : AM , 应用管理器,每一个应用程序的 AM 负责相应的调度和协调。
Container             : 容器, YARN 为将来的资源隔离而提出的框架,每一个任务对应一个 Container , 且只能在该 Container 中运行
Node Manager        : 节点管理器,管理每个节点上的资源和任务,主要由两个作用:定期向 RM 汇报该节点的资源使用情况和各个 Container 运行状态;
                    接收并处理 AM 的任务启动、停止等请求。


在最高层次上, YARN 包括5个独立的实体:
--------------------------------------------------------------------------------------------------------------------
    1. 提交 MapReduce 作业的客户端
    2. YARN 资源管理器,负责协调集群上计算资源的分配
    3. YARN 节点管理器,负责启动和监视集群中机器上的计算容器 Container
    4. MapReduce 应用程序 master , 负责协调运行 MapReduce 作业( Job)上的任务( Task )。 Application Master 和 MapReduce 作业任务都在容器内( Containers)运行 。这些容器由资源管理器分配并由节点管理器管理。
    5. 分布式文件系统(一般为 HDFS),用来与其他实体间共享作业文件。                


作业的运行过程:
---------------------------------------------------------------------------------------------------------------------
    1. 作业提交(Job Submission):
    -----------------------------------------------------------------------------------------------------------------
        The submit() method on Job creates an internal JobSubmitter instance and calls submitJobInternal() on it.
        Having submitted the job, waitForCompletion() polls the job’s progress once per second and reports the progress to the console if it has changed since the last report.
        When the job completes successfully, the job counters are displayed. Otherwise, the error that caused the job to fail is logged to the console.
        
        The job submission process implemented by JobSubmitter does the following:
        -------------------------------------------------------------------------------------------------------------
        ① Asks the resource manager for a new application ID, used for the MapReduce job ID
        ② Checks the output specification of the job. 例如,如果输出目录没有指定或者输出目录已存在,作业不会提交并且 MapReduce 程序抛出错误
        ③ 计算作业的输入分片(Computes the input splits for the job)。如果分片不能计算(例如输入路径不存在),作业不会提交并且 MapReduce 程序抛出错误
        ④ 复制运行作业所需的资源到共享文件系统目录(a directory named after the job ID ),including the job JAR file, the configuration file, and the computed input splits.
            The job JAR is copied with a high replication factor (controlled by the mapreduce.client.submit.file.replication property, which defaults to 10) ,
            so that there are lots of copies across the cluster for the node managers to access when they run tasks for the job.
        ⑤ Submits the job by calling submitApplication() on the resource manager

        
        
    2. 作业初始化(Job Initialization)
    ------------------------------------------------------------------------------------------------------------------
        资源管理器收到调用它的 submitApplication() method 后,便将请求传递个调度器( YARN scheduler ),调度器分配一个容器( Container ),
        然后资源管理器在节点管理器的管理下启动 Application Master 进程。
        
        Application Master 是一个 Java 程序,它的主类是 MRAppMaster 。 它对作业进行初始化:创建多个簿记对象(bookkeeping objects)以保持对作业进度的跟踪,它将接收来自任务的进度和完成报告;
        接下来,接收来自共享文件系统的在客户端计算的输入分片。然后对每一个分片创建一个 map 任务对象和一定数量的 reduce 对象,reduce 任务数量由配置属性 ${mapreduce.job.reduces} 确定。(set by the setNumReduceTasks() method on Job)

        Application Master 决定如何运行构成 MapReduce Job 的各个任务,如果作业很小, Application Master 可以选择在与它同一个 JVM 上运行任务。
        判断在新的容器中分配和运行任务从而获得并行运行它们的开销比在一个节点上顺序运行它们更重,就会发生这一情况。这种作业称为 uberized, 或者运行于 uber task ( run as an uber task)。

        如何界定一个小作业?默认情况下,一个小作业就是有小于10个 mapper , 只有一个 reducer , 且输入大小小于1个 HDFS 块的作业。
        注意,这些值可以通过设置改变:
            mapreduce.job.ubertask.maxmaps
            mapreduce.job.ubertask.maxreduces
            mapreduce.job.ubertask.maxbytes
            
        Uber task 必须显示设置才可以启用,通过设置 :mapreduce.job.ubertask.enable 值为 true.
        
        最后,在任何任务运行之前, Application Master 在 OutputCommitter 上调用 setJob() 方法 ,默认值是 FileOutputCommitter , 它为作业创建最终输出目录和任务输出的临时工作空间 。
        
        
    3. 任务分配( Task Assignment )    
    ------------------------------------------------------------------------------------------------------------------
        如果作业不适合作为 Uber 任务运行,那么 Application Master 就会为该作业中的所有 map 任务和 reduce 任务向资源管理器请求容器。
        map 任务的请求先于 reduce 任务并且具有更高的优先级,因为排序阶段之前所有的 map 任务必须完成, reduce 才能开始。 Requests for reduce tasks are not made until 5% of map tasks have completed.
        reduce 任务可以运行在集群的任何位置上,但对 map 任务的请求有数据本地化的约束,调度器应设法给予考虑。在优化场景下,任务是数据本地化的( data local ) —— 也就是说,任务运行在和数据分片在同一节点上。
        另一方面,任务也可能是机架本地的( rack local ):和数据分片在同一机架上,但不在同一节点上。
        另有一些任务既不是数据本地的也不是机架本地的,它们从其它机架的节点获取数据(相对于运行任务的节点)。
        对于一个特定作业的运行,可以查看作业计数器( job's counters )确定任务运行在每一种本地化级别。
        
        请求也为任务指定了内存需求和 CPU 。默认情况下, map 任务和 reduce 任务都分配到 1024MB 的内存和1个CPU虚拟内核。 这些值是基于每作业基础上( on a per-job basis ),可通过以下属性配置的:
        ---------------------------------------------------------------------------------
        mapreduce.map.memory.mb
        mapreduce.reduce.memory.mb
        mapreduce.map.cpu.vcores
        mapreduce.reduce.cpu.vcores
        
    
    4. 任务执行( Task Execution )
    ------------------------------------------------------------------------------------------------------------------
    一旦资源管理器的调度器为任务分配了容器, Application Master 就通过与节点管理器通信来启动容器。该任务由主类为 YarnChild 的 Java 应用程序执行。
    在它运行任务之前,首先将任务需要的资源本地化,包括作业的配置、JAR 文件和所有来自分布式缓存的文件。最后,运行 map 任务或 reduce 任务。
    
    YarnChild 运行在指定的 JVM 上,所以任何在用户定义的 map 和 reduce 函数(甚至在 YarnChild )上的 bug 都不会影响节点管理器( Node Manager )—— 例如导致它崩溃或挂起。
        
    每个任务可能执行设置或提交动作,这些操作运行在和任务本身同一个 JVM 上,由作业的 OutputCommitter 确定。    
    对于基于文件的作业 ( For file-based joh ),提交操作把任务输出从临时位置移动到最终位置。
    
    Streaming runs special map and reduce tasks for the purpose of launching the usersupplied executable and communicating with it.
    流任务通过标准输入输出与进程通信。
    During execution of the task, the Java process passes input key-value pairs to the external process, which runs it through the
    user-defined map or reduce function and passes the output key-value pairs back to the Java process.
    From the node manager’s point of view, it is as if the child process ran the map or reduce code itself.
        
        
    5. 进度和状态更新(Progress and Status Updates)    
    ----------------------------------------------------------------------------------------------------------------------
    MapReduce jobs are long-running batch jobs, taking anything from tens of seconds to hours to run. Because this can be a significant length of time, it’s important for the user to get feedback on how the job is progressing.
    A job and each of its tasks have a status, which includes such things as the state of the job or task (e.g., running, successfully completed, failed), the progress of maps and reduces, the values of the job’s counters, and
    a status message or description (which may be set by user code). These statuses change over the course of the job, so how do they get communicated back to the client?
        
    在 YARN 下运行时,任务每三秒钟通过 umbilical 接口向 Application Master 汇报进度和状态(包括计数器),作为作业的汇聚视图(aggregate view)。    
        
    资源管理器的 web UI 展示了正在运行的应用以及连接到的对应的 Application Master , 每个 Application Master 展示 MapReduce 作业的进度等进一步的细节,包括进度。
        
    在作业的进程中,客户端每隔1秒钟查询一次 Application Master 以接收进度最新状态,接收间隔可以通过如下属性设置:
            
            mapreduce.client.progressmonitor.pollinterval
            
    Clients can also use Job’s getStatus() method to obtain a JobStatus instance, which contains all of the status information for the job.

    
    
    6. 作业完成(Job Completion)
    ----------------------------------------------------------------------------------------------------------------------
    当 Application Master 收到作业的最后一个任务完成的通知,它改变作业的状态为 " successful " 。然后,当作业获取状态,它得知作业已成功完成,
    因此打印消息告知用户并从 waitForCompletion() 方法返回,就此将作业的统计和计数也打印到控制台输出。
    
    The application master also sends an HTTP job notification if it is configured to do so. This can be configured by clients wishing to receive callbacks, via the
            
            mapreduce.job.end-notification.url
            
    最后,作业完成, Application Master 和 task containers 清理其工作状态(删除中间输出),调用 OutputCommitter 的 commitJob() method ,作业历史服务器(job history server)存档作业的信息供用户需要时查询。
        
        
*
*
*


2 失败(Failures)        
-----------------------------------------------------------------------------------------------------------------------------------------------        
实际情况是,用户代码错误不断,进程崩溃,机器故障,如此种种。使用 Hadoop 的最主要的好处之一是它能处理此类故障并能让你的作业成功完成。
我们需要考虑失败的如下实体: the task , the Application Master , the Node Manager , and the Resource Manager .    
        
        
1. 任务失败( Task Failure )
---------------------------------------------------------------------------------------------------------------------------------------
    首先考虑任务失败的情况,最常见的情况是 map 任务或 reduce 任务中的用户代码抛出异常。如果这种情况发生, 任务 JVM 在退出之前会向其父 Application Master 发送错误报告,错误最终会写入用户日志。
    Application Master 将此次任务尝试标记为失败( failed ),并且释放容器以使它的资源可以被另一个任务使用。
    
    对于 Streaming 任务,如果 Streaming 进程以非零退出码退出,则标记为 failed 。这种行为由 stream.non.zero.exit.is.failure 属性控制,默认值为 true
    
    另一种错误情况是子进程 JVM 突然退出,可能由于 JVM 软件缺陷而导致 MapReduce 用户代码由于某些特殊原因造成 JVM 退出。在这种情况下,节点管理器会注意到进程已退出,并通知 Application Master 将此次任务尝试标记为失败( failed ).
        
    任务挂起的处理方式则有不同。一旦 Application Master 注意到已经有一段时间没有收到进度的更新,便会将任务标记为 failed 。之后,任务 JVM 进程将自动被杀死。
    任务被认为失败的超时间隔通常为10分钟,可以以作业为基础(或以集群为基础)通过设置 mapreduce.task.timeout 以毫秒为单位的属性值进行配置。    
            
            注意, 如果是 Streaming 进程挂起, Node Manager 只有在以下情况下会杀死它(连同启动它的 JVM):
            ---------------------------------------------------------------------------------------------
            ① yarn.nodemanager.container-executor.class 设置为 org.apache.hadoop.yarn.server.nodemanager.LinuxContainerExecutor
            ② 或者使用默认的容器执行器 并且 setsid 在系统上可用(这样,任务 JVM 和任何它启动的进程都在同一个进程组内)。
            其它任何情况,孤立的 Streaming 进程堆积在系统上,随着时间的推移,会影响到利用率。
        
    超时( timeout )设置为 0 将关闭超时判定,那样,长时间运行的任务将永远不会被标记为 failed 。这种情况下,挂起的任务永远不会释放它的容器,随着时间的推移最终降低整个集群的效率。
    应该尽量避免这种设置,同时充分确保任务能够定期报告其进度。
        
    当 Application Master 被通知一个任务尝试失败后,将重新调度该任务的执行, Application Master 避免在之前失败的 Node Manager 上重新调度该任务,
    此外,如果一个任务失败次数超过4次,将不会再重试。这个值是可以设置的,对于 map 任务,由 mapreduce.map.maxattempts 属性控制;对于 reduce 任务,
    由 mapreduce.reduce.maxattempts 控制。默认情况下,如果任何任务失败超过4次(或任何配置的值),则整个作业失败。
    
    对于有些应用,我们不希望一旦由少数几个任务失败就终止整个作业,因为尽管有些失败,作业的一些结果可能还是可用的。在这种情况下,可以为作业设置在不触发作业失败的情况下允许任务失败的最大百分数。
        
        mapreduce.map.failures.maxpercent
        mapreduce.reduce.failures.maxpercent
        
    任务尝试( task attempt )也可以被杀死的( killed ),这与失败不同。任务尝试可以被杀死是因为它是一个推测副本( killed because it is a speculative duplicate )
    或者因为运行它的 Node Manager 失败,并且 Application Master 将所有运行在其上的任务都标记为 killed ,被杀死的任务尝试不计入任务运行尝试次数,因为被杀死的尝试不是任务的错误。
    
    用户也可以通过 web UI 或命令行来杀死任务尝试或使之失败。也可以通过同样的机制杀死作业。
    
        命令行: mapred job
        
        
2. Application Master Failure
---------------------------------------------------------------------------------------------------------------------------------------
    YARN 中的应用程序在运行失败的时候有几次尝试的机会,就像 MapReduce 中的任务一样在遇到硬件或网络故障时要进行几次尝试一样。
    运行 MapReduce Application Master 的最多尝试次数由 mapreduce.am.max-attempts 属性控制,默认值是 2 ,因此 MapReduce 的 Application Master 失败 2 次之后就不再继续尝试,作业也就失败了。

    YARN imposes a limit for the maximum number of attempts for any YARN application master running on the cluster, and individual applications may not exceed this limit.
    The limit is set by yarn.resourcemanager.am.max-attempts and defaults to 2, so if you want to increase the number of MapReduce application master attempts,
    you will have to increase the YARN setting on the cluster, too.

    恢复工作是这样的: Application Master 向资源管理器发送周期性的心跳,当 Application Master 发生故障时,资源管理器将检测到该故障并在一个
    新的容器(由节点管理器管理)中启动一个新的 master 实例。 In the case of MapReduce Application Master , 它利用作业历史来恢复已经失败的应用的任务状态,因此它们不必重新运行。
    恢复能力在默认情况下是启用的,也可以通过设置 yarn.app.mapreduce.am.job.recovery.enable 值为 false 来禁用。

    MapReduce 客户端向 Application Master 轮询进度报告,如果它的 Application Master 运行失败,客户端就需要定位新的实例。在作业初始化期间,客户端向资源管理器询问并缓存 Application Master 地址,
    使其每次需要向 Application Master 查询时不必使资源管理器过载( overload the Resouc Manager ),如果 Application Master 失败,客户端在发出状态更新时会经历一次请求超时,
    这时客户端会返回资源管理器请求新的 Application Master 的地址。这个过程对用户是透明的。
    
    

3. 节点管理器失败(Node Manager Failure)
---------------------------------------------------------------------------------------------------------------------------------------
    如果节点管理器失败,测底崩溃或运行非常慢,它会停止向资源管理器发送心跳( 或者发送得非常稀少 )。资源管理器会注意到一个节点管理器已停止发送心跳,如果它没有收到心跳超过10分钟,就从调度容器的
    节点管理池移除它。(这个值是可配置的,毫秒单位:yarn.resourcemanager.nm.liveness-monitor.expiry-interval-ms )
    
    在故障的节点管理器上运行的所有 task 或 Application Master 都用前面两节描述的机制进行恢复。
    另外, Application Master 安排那些在故障节点上成功完成的属于一个未完成的作业的 map 任务重新运行,因为它们的中间输出存在于失败的节点管理器的本地文件系统上,对于 reduce 任务来说是不可访问的。

    如果应用程序运行的失败次数过高,即使节点管理器自己没有失败,这些节点管理器可能会被拉黑( be blacklisted )。
    黑名单由 Application Master 管理,对于 MapReduce , 如果一个节点管理器上有超过 3 个任务失败, Application Master 就会尽量将任务调度到不同的节点上。用户可以通过如下属性设置:
    
            mapreduce.job.maxtaskfailures.per.tracker
            
    注意:
        资源管理器不跨越应用执行黑名单操作,因此新作业的任务可能被调度到坏的节点上( bad nodes)即便它们已经被一个之前作业的 Application Master 列入黑名单。


4. 资源管理器失败( Resource Manager Failure )
---------------------------------------------------------------------------------------------------------------------------------------
    资源管理器失败是非常严重的问题,没有资源管理器,作业和任务容器将无法启动。
    默认配置下,资源管理器是单点故障的,因为机器故障时,所有运行中的作业都将失败,并且是不可恢复的。
    
    为实现高可用性(HA),运行一对 活动-备用(active-standby)配置的资源管理器是有必要的。如果活动的资源管理器失败,备用资源管理器能够在不明显中断客户端情况下恢复。    
    所有运行中的应用程序信息存储到高可用性状态存储(由 ZooKeeper 或 HDFS 支持),因此备用资源管理器能恢复失败的活动资源管理器核心状态。
    
    节点管理器信息没有存储到状态存储( state store )当中,因为它可以在向新资源管理器发送第一个心跳包时由新资源管理器相对迅速地重建(注意,任务也不是资源管理器的一部分,因为它们由 Application Master 管理)。
    
    当新资源管理器启动,它从 state store 中读取应用程序信息,然后重新启动集群上的所有应用的 Application Master ,这不计入失败的应用程序尝试次数,因为应用程序失败不是由于应用代码错误,而是被系统强制杀死的。
    实践中,对于 MapReduce , Application Master 重启不会有问题,因为它们从完成的任务恢复。
    
    从备用( standby)到活动( active)的转换由救援控制器处理( handled by a failover controller)。默认的救援控制器是自动的( automatic one), 利用 ZooKeeper 的主管选择( leader election )确保在某一时刻只有一个单独的活动资源管理器。
        
    不像 HDFS HA , 救援控制器不必是一个独立的进程( standalone process), 为了容易配置,默认它嵌入到资源管理器中。手动救援也是可以配置的,但不建议那么做。    
        
    客户端和节点管理器必须配置以处理资源管理器故障恢复,因为现在由两个可能的资源管理器和它们通信。它们以循环的方式( round-robin fashion)尝试连接每一个资源管理器,直到它们发现活动的那一个( active one )。
    如果活动的失败了,它们会重新尝试( retry)直到备用的变成活动的。
        
    
*
*
*

3 洗牌和排序(Shuffle and Sort)        
-----------------------------------------------------------------------------------------------------------------------------------------------                
MapReduce 确保每个 reducer 的输入都是按键排序的。系统执行排序,并将 map 输出作为输入传给 reducer 的过程——被称为洗牌( shuffle )。从许多方面来看,
shuffle 是 MapReduce 的心脏,是“奇迹”发生的地方。

注解:事实上, shuffle 这个说法是不准确的,因为在某些环境下,它只代表 reducer 任务从 map 任务获取输出的这部分过程。


    map 端(The Map Side)
    --------------------------------------------------------------------------------------------------------------------------------
    map 函数开始产生输出时,并不是简单地将它写入磁盘。这个过程更复杂,它利用缓冲区的方式写到内存并出于效率的考虑进行预排序。
    
    每个 map 任务都有一个环形内存缓冲区( circular memory buffer )用于存储任务输出。默认情况下,缓冲区为100MB,此值可以通过 mapreduce.task.io.sort.mb 属性改变。
    一旦缓冲区内容达到阈值( mapreduce.map.sort.spill.percent , 默认值为 0.8 ,或80%),一个后台线程便开始把内容溢出( spill the contents to disk )到磁盘。
    在溢出写到磁盘过程中, map 输出继续写到缓冲区。但如果在此期间缓冲区被填满, map 会阻塞直到写磁盘过程完成。溢出写入以轮巡的方式写入到由 mapreduce.cluster.local.dir
    属性指定的作业特定的目录的子目录中。

    在写磁盘之前,线程首先根据数据最终要发送的 reducer 把数据划分成对应的分区( partition)。在每个分区内部,后台线程在内存中进行按键排序,并且如果有一个 combiner 函数,
    它就在排序后的输出上运行,运行 combiner 使得 map 的输出结果更紧凑,因此更少的数据写到磁盘和传输给 reducer 。

    每次内存缓冲区达到溢出阈值,就会新建一个溢出文件( spiil file),因此在 map 任务写完其最后一个输出记录之后,会有几个溢出文件。
    在任务完成之前,溢出文件被合并成一个分区的且已排序的输出文件。配置属性 mapreduce.task.io.sort.factor 控制一次最多能合并多少个流,默认值为10。
    
    如果至少存在3个溢出文件( 通过 mapreduce.map.combine.minspills 属性设置 ), combiner 就会在输出文件写到磁盘之前再次运行。回顾 combiner 可以在输入上反复运行而不影响最终结果。
    如果只有一个或两溢出,那么调用 combiner 的开销对 map 输出的减少就没有什么价值,因此就不会为该 map 输出再次运行 combiner 。
    
    在将 map 输出写到磁盘过程中对它进行压缩往往是个好主意,因为那样做会是写入磁盘更快,节省磁盘空间,降低传输给 reducer 的数据量。
    默认情况下,输出是不压缩的,但是通过设置 mapreduce.map.output.compress 为 true 很容易启用。使用的压缩库由
        
        mapreduce.map.output.compress.codec
    
    指定。

    输出文件的分区通过 HTTP 对 reducer 可用,用于服务文件分区的最大工作线程数量由 mapreduce.shuffle.max.threads 属性控制。此设置是针对每个节点管理器,不是针对每个 map 任务。
    默认值 0 ,设置最大线程数为 2 倍于机器的 CPU 数量。


    reduce 端( The Reduce Side )
    --------------------------------------------------------------------------------------------------------------------------------
    map 输出文件位于运行 map 任务的本地磁盘(注意,尽管 map 输出经常写到 map 的本地磁盘,但 reduce 输出不是这样),但是现在需要在机器上为分区运行 reduce 任务。
    而且, reduce  任务需要集群上若干个 map 任务的输出作为其特殊的分区文件。 map 任务可能在不同的时间完成, 因此只要有一个 map 任务完成 reduce 任务就开始复制它的输出,这就是 reduce 任务的复制阶段。
    reduce 任务有少量复制线程,因此能并行取得 map 输出,默认 5 个线程,可通过 mapreduce.reduce.shuffle.parallelcopies 属性改变。

    提示: reducer 如何知道要从哪台机器上获取 map 输出呢 ?
    -------------------------------------------------------------------
        一个 map 任务成功完成,它利用心跳机制通知它的 Application Master . 因此,对于一个给定的作业, Application Master 知道 map 输出和机器的映射关系。
        reduce 中有一个线程定期询问 master 以获取 map 输出主机,直到它全部获取到它们。
        主机不会在第一个 reducer 一获取到 map 输出就从磁盘删除它们,因为 reducer 在此之后可能会失败,相反,它们会一直等到 Application Master 告诉它们时才删除,此时到作业已完成。

    如果 map 输出足够小(由 mapreduce.reduce.shuffle.input.buffer.percent 属性控制,指定用于此用途的堆空间的百分数),它会被复制到 reduce 任务的 JVM 内存,否则被复制到磁盘。
    一旦内存缓冲区达到阈值(由 mapreduce.reduce.shuffle.merge.percent 控制)或者到达 map 输出输出数量阈值(mapreduce.reduce.merge.inmem.threshold)则合并后溢出写到磁盘中。
    如果指定了 combiner ,它会在合并期间运行以降低写入磁盘的数据量。
    
    随着磁盘上副本的累积,一个后台线程会将它们合并为更大的、排好序的文件。这会为后面的合并节省一些时间。注意,为了执行合并,被压缩的 map 输出(通过 map 任务)必须在内存中解压。


    当所有的 map 输出都复制完, reduce 进入排序阶段(更恰当的说法是合并阶段,因为排序是在 map 端进行的),这个阶段将合并 map 输出,维持其顺序排序,这是循环进行的( This is done in rounds ) 。
    例如有 50 个 map 输出,而合并因子是 10 ( 默认值, 由 mapreduce.task.io.sort.factor 属性控制,就像 map 的合并),将有 5 个回合( rounds )。每个回合合并 10 个文件到 1 个文件,
    因此最后会有 5 个中间文件。
    没有一个最后回合把这 5 个文件合并到一个单独的排序的文件,合并节省了一次磁盘往返行程,直接将数据提供给 reduce function ,这就是最后阶段 reduce 阶段。这最后的合并可以混合来自内存和磁盘的片段。

    在 reduce 阶段, 为已排序的每个键( each key )调用 reduce function 。此阶段的输出直接写到输出文件系统,典型的为 HDFS 。对于 HDFS ,因为节点管理器也运行数据节点,第一个块复本会被写入本地磁盘。



    配置调优 (Configuration Tuning)
    --------------------------------------------------------------------------------------------------------------------------------
    现在我们已经有比较好的基础来理解如何调优 Shuffle 过程来提高 MapReduce 性能,相关的设置以作业为单位(除非特别说明),默认值适用于一般性作业。 ( which are good for general-purpose jobs. )

    总的原则是给 shuffle 尽可能多的内存空间。但也有个平衡问题,就是需要确保 map 和 reduce 函数有足够的内存来运行。这就是为什么编写 map 和 reduce 函数时尽量少用内存的原因 —— 它们不能无限制地使用内存。
    例如,要尽量避免在 map 中累积数据 。
    
    运行 map 任务和 reduce 任务的 JVM , 其内存大小由 mapred.child.java.opts 属性设置,任务节点上应该把这个值设置得尽可能大。

    在 map 端, 避免多次的溢出到磁盘( spill to disk )会获得最好的性能,一次是最佳优化。如果能估算 map 输出大小,就可以合理地设置 mapreduce.task.io.sort.* 属性来最小化溢出写磁盘次数。特别是,应该
    尽可能提升 increase mapreduce.task.io.sort.mb 的值。
    有一个 MapReduce 计数器( SPELLED_RECORDS )记录在整个作业过程中记录溢出到磁盘的数量,对调优很有用。注意计数器包括 map 和 reduce 两端溢出。

    在 reduce 端,中间数据整个驻留在内存中能获得最佳性能,默认情况这是不可能发生的,因为通常将内存预留给 reduce 函数,但如果 reduce 函数内存需求不大,设置 mapreduce.reduce.merge.inmem.threshold 为 0 ,
    并且设置 mapreduce.reduce.input.buffer.percent 为 1.0 (或低一点的值)就可以提升性能。
        
    更常见的情况是, Hadoop 使用默认为 4KB 的缓冲区,这是很低的,因此应该在集群上增加这个值(通过设置 io.file.buffer.size )。
    
    
*
*
*

    
3 任务执行(Task Execution)        
-----------------------------------------------------------------------------------------------------------------------------------------------    


    任务执行环境(The Task Execution Environment)
    --------------------------------------------------------------------------------------------------------------------------------
    Hadoop 为 map 任务或 reduce 任务提供运行环境相关信息,例如 map 任务可以知道它处理的文件名称, map 任务或 reduce 任务可以得知任务的尝试次数。
    下表的属性, 在新版 API 中可以从传递给 mapper 或 reduer 的所有方法的 context 对象获得。
    
                                    
                                        Task environment properties
    
    +-------------------------------+-----------+-----------------------------------+---------------------------------------+
    |            属性名                |    类型    |            描述                    |        例子                            |
    +-------------------------------+-----------+-----------------------------------+---------------------------------------+
    | mapreduce.job.id                | String    | The job ID                        | job_200811201130_0004                    |
    +-------------------------------+-----------+-----------------------------------+---------------------------------------+
    | mapreduce.task.id                | String    | The task ID                        | task_200811201130_0004_m_000003        |
    +-------------------------------+-----------+---------------------------------------------------------------------------+
    | mapreduce.task.attempt.id        | String    | The task attempt ID                | attempt_200811201130_0004_m_000003_0    |
    +-------------------------------+-----------+-----------------------------------+---------------------------------------+
    | mapreduce.task.partition        | int        | The index of task within the job    | 3                                        |
    +-------------------------------+-----------+-----------------------------------+---------------------------------------+
    | mapreduce.task.ismap            | boolean    | Whether this task is a map task    | true                                    |
    +-------------------------------+-----------+-----------------------------------+---------------------------------------+


    
    推测执行 ( Speculative Execution )
    --------------------------------------------------------------------------------------------------------------------------------
    MapReduce 模型是将作业分解成任务,然后并行地运行任务以使作业的整体执行时间少于各个任务顺序执行的时间。这使作业执行时间对运行缓慢的任务很敏感,
    因为仅仅一个缓慢的任务会使整个作业所用的时间明显变长。当一个作业由几百或几千个任务组成时,可能出现少数“拖后腿”的任务,这是很常见的。
    
    任务执行缓慢可能有多种原因,包括硬件老化或软件配置错误,但检测具体原因可能比较困难,因为尽管经历了比预期更长的时间,任务仍然成功完成。
    Hadoop 不会尝试诊断和修复运行缓慢的任务,相反,当一个任务比预期的运行缓慢的时候,它会尽量检测并启动一个相等的任务作为备份,
    这个术语被称之为任务的 “推测执行”( speculative execution of tasks )。

    理解这一点是很重要的:推测执行( speculative execution )不是同时启动两个重复的任务然后彼此赛跑这样工作的,这是对集群资源的浪费。
    相反,调度器( scheduler ) 跟踪一个作业内所有相同类型任务( map 或 reduce )进程,仅仅对那些运行明显比平均值慢的很小比例的任务启动推测执行。
    当一个任务成功完成,任何正在运行的重复的任务都会被杀死 ( killed), 因为不再需要它们了。因此,如果原任务在推测任务之前完成,推测任务就会被杀死,
    另一方面,如果推测任务先完成,原任务被杀死。

    推测执行是一种优化措施,不是使作业运行可靠的特性。如果程序中存在 bug 有时会导致任务挂起或速度减慢,依赖推测执行避免这些问题是不明智的,也不能可靠运行,
    因为相同的 bug 也可能影响推测执行的任务。应该修复 bug 使任务不再挂起或运行减慢。
    
    推测执行默认是启用的。可以独立地为 map 任务、 reduce 任务、集群范围、作业范围启用或禁用,相关属性如下:
    
    
                            Speculative execution properties
    +---------------------------------------------------+-----------+-----------------------------------------------+
    |                属性名                                |    类型    |    默认值                                        |
    +---------------------------------------------------+-----------+-----------------------------------------------+
    | mapreduce.map.speculative                            | boolean    | true                                            |
    +---------------------------------------------------+-----------+-----------------------------------------------+
    | mapreduce.reduce.speculative                        | boolean    | true                                            |
    +---------------------------------------------------+-----------+-----------------------------------------------+
    | yarn.app.mapreduce.am.job.speculator.class        | Class        | org.apache.hadoop.mapreduce.v2.app.speculate    |
    +---------------------------------------------------+-----------+-----------------------------------------------+
    | yarn.app.mapreduce.am.job.task.estimator.class    | Class        | org.apache.hadoop.mapreduce.v2.app.speculate    |
    +---------------------------------------------------+-----------+-----------------------------------------------+


    为什么会想关闭推测执行?推测执行的目的是减少作业执行时间,但这是以牺牲集群效率为代价的。在一个繁忙的集群中,推测执行会减少整体的吞吐量。因为冗余的任务执行尝试会降低一个作业的执行时间。

    对于 reduce 任务,关闭推测执行是有益的,因为任何重复的 reduce 任务都必须获取同一 map 输出作为最先任务,这将大幅度增加集群上的网络传输。
    关闭推测执行的另一种情况是考虑到非幂等任务( nonidempotent tasks )。然而,在很多情况下把任务写成幂等的(idempotent),然后利用 OutputCommitter 在任务成功的时候提升输出写入到它最终的位置。

    

    Output Committers
    --------------------------------------------------------------------------------------------------------------------------------
    Hadoop MapReduce 使用一个提交协议来确保作业和任务或者成功或者测底失败。这种行为通过为作业使用 OutputCommitter 来实现。新版 MapReduce API ,OutputCommitter 由 OutputFormat 确定,通过它的 getOutputCommitter() method 。
    默认是 FileOutputCommitter ,适用于基于文件的( file-based)  MapReduce 。可以自定义已有的 OutputCommitter 或者如果需要为作业或任务做特殊的设置或清理,可以写一个新的实现。

    OutputCommitter API :

    public abstract class OutputCommitter {
        public abstract void setupJob(JobContext jobContext) throws IOException;
        public void commitJob(JobContext jobContext) throws IOException { }
        public void abortJob(JobContext jobContext, JobStatus.State state)
        throws IOException { }
        public abstract void setupTask(TaskAttemptContext taskContext)
        throws IOException;
        public abstract boolean needsTaskCommit(TaskAttemptContext taskContext)
        throws IOException;
        public abstract void commitTask(TaskAttemptContext taskContext)
        throws IOException;
        public abstract void abortTask(TaskAttemptContext taskContext)
        throws IOException;
    }

    setupJob() method 在作业运行前调用,通常用于执行初始化。对于 FileOutputCommitter ,这个方法创建最终的输出目录 ${mapreduce.output.fileoutputformat.outputdir} ,
    以及任务输出的临时工作空间 _temporary ,作为其下面的子目录。
    
    如果作业成功,commitJob() method 被调用,在默认的基于文件的实现中,删除临时工作空间,并且在输出目录内创建一个名为 _SUCCESS 的隐藏的空标志文件  ,
    以此告知文件系统的客户端该作业成功完成了。如果作业不成功, abortJob() 被调用,带一个 state object 指出是作业失败(job failed)还是被杀死(killed)。在默认实现中,删除作业的临时工作空间。

    任务级别的操作与此类似。任务运行之前 setupTask() method 被调用,默认实现什么也不做,因为任务输出的临时目录命名在任务输出写入的时候创建。
    任务的提交阶段是可选的,并且可以通过 needsTaskCommit() 返回 false 被禁用。这使框架不必为任务运行分布式提交协议,commitTask() 不会被调用,abortTask() 也不会被调用。在任务没有输出写入时候, FileOutputCommitter 会跳过提交阶段。
    
    如果任务成功, commitTask() 被调用,默认实现是移动临时任务输出目录( which has the task attempt ID in its name to avoid conflicts between task attempts )到最终输出路径,${mapreduce.output.fileoutputformat.outputdir}
    否则,框架调用 abortTask() ,其中删除临时任务输出目录。
    
    执行框架保证特定任务在有多任务尝试情况下只有一个任务会被提交,其他则被取消。这种情景是可能出现的,因为第一次尝试( attempt )可能某些原因失败 —— 这种情况下,它会被取消( aborted),之后一个成功的尝试被提交。
    如果两个任务尝试作为推测复本同时运行也可能发生这种状况,这种情景下,第一个完成的会被提交,另外一个被取消。

        任务端结果文件( Task side-effect files )
        -----------------------------------------------------------------------
        对 map 和 reduce 任务输出,常用的写方法是利用 OutputCollector 收集键-值对。有些应用需要比单一的键值对更灵活的方式,那样应用从 map 或 reduce 任务输出直接写到分布式文件系统,例如 HDFS 。
        高度注意,确保同一个任务的多个实例不要写入同一个文件。OutputCommitter 协议解决了这个问题。如果应用程序在它们任务的工作空间写 side files ,任务成功完成的 side files 会自动提升到输出目录,而失败的任务会将它们的 side files 删除。
        
        任务通过从其作业的配置查询 mapreduce.task.output.dir 属性值获取工作目录,另一种方法, MapReduce 程序使用 Java API 可以调用 FileOutputFormat 的 getWorkOutputPath() 静态方法得到表示工作目录的 Path 对象。
        框架会在执行任务之前创建工作目录( working directory ),因此你不需要创建它。

        举一个简单的例子,假设有一个程序用来转换图像文件的格式。一种实现方法是用一个只有 map 任务作业,其中每个 map 给定一组要转换的图像文件,如果一个 map 任务写入转换的图像文到工作目录(working directory),任务成功完成后它们会被提升到输出目录(output directory)。


    










猜你喜欢

转载自blog.csdn.net/devalone/article/details/80692059