hadoop基础----hadoop理论(四)-----hadoop分布式并行计算模型MapReduce详解

来源:互联网 发布:电视直播软件下线了? 编辑:程序博客网 时间:2024/05/17 21:45

我们在前一章已经学习了HDFS:

hadoop基础----hadoop理论(三)-----hadoop分布式文件系统HDFS详解


我们已经知道Hadoop=HDFS(文件系统,数据存储技术相关)+ MapReduce(数据处理)。

本章就来学习MapReduce数据处理。


MapReduce是什么

     MapReduce是现今一个非常流行的分布式处理数据的编程模型,它被设计用于并行计算海量数据。第一个提出该技术框架的是Google公司,而Google的灵感则来自于函数式编程语言,如LISP, Scheme, ML等。

    我们在前面讲过Hadoop是一个分布式计算的解决方案,也就是帮助我们把 一个任务分到很多台计算机来运算。

    这个把任务分到很多台计算机来运算就是MapReduce在负责。它也是学习Hadoop必须掌握的开发编程部分。





MapReduce的优点

   MapReduce的流行自然有它的原因,在我看来有以下很重要的两点:

1.简单易用(相对自己实现分布式来说):任务调度分配等对开发编程者透明,只需要做好配置,根据一定的格式编写好Map和Reduce方法,就能实现分布式运算大幅度提升运算性能。

2.扩展性强:最大的优点是易扩展,如果我们有一个已经写好的MapReduce应用程序,仅需要修改配置就能把它扩展到几百、几千甚至上万台机器上运行。





MapReduce的工作机制

    编写MapReduce的整体思路并不复杂,从它的命名也可以看出来,它的核心步骤只有两个步骤:Map和Reduce。Map可以理解为初略归类,Reduce可以理解为精简结果得到最终结果。我们只要套用格式,在格式内完成我们的业务要求对数据进行处理即可。

   但是它的工作机制就要稍微复杂一些,因为涉及到分布并行运算的调度和管理。

   对于MapReduce的工作机制,我们需要有一个大概的了解以便在后面对MapReduce更好的运用和进行优化。

   MapReduce的简易工作流程图如下:

  


角色

在MapReduce的工作流程中主要涉及4个实体角色:客户端,JobTracker,TaskTracker,HDFS

具体如下:

客户端(client)

编写MapReduce代码,配置作业,提交作业。


JobTracker

  初始化作业,分配作业,与TaskTracker通信,协调整个作业的执行;一个Hadoop集群只有一个JobTracker。

  JobTracker守护进程是应用程序和Hadoop之间的纽带。一旦提交代码到集群上,JobTracker就会确定执行计划,包括决定处理哪些文件、为不同的任务分配节点以及监控所有任务的运行。

  如果任务失败,JobTracker将自动重启任务,但所分配的节点可能会不同,同时受到预定义的重试次数限制。
  每个Hadoop集群只有一个JobTracker守护进程。它通常运行在服务器集群的主节点上。


TaskTracker

  保持与JobTracker的通信,在分配的数据片段上执行Map或Reduce任务。

   Hadoop集群中可以包含多个TaskTracker。

   与存储的守护进程一样,计算的守护进程也遵循主/从架构:JobTracker作为主节点,监测MapReduce作业的整个执行过程,同时,TaskTracker管理各个任务在每个从节点上的执行情况。
   每个TaskTracker负责执行由JobTracker分配的单项任务。虽然每个从节点上仅有一个TaskTracker,但每个TaskTrarcker可以生成多个JVM (Java虚拟机)来并行地处理许多mapreduce任务。
   TaskTracker的一个职责是持续不断地与JobTracker通信。如果JobTracker在指定的时间内没有收到来自TaskTracker的"心跳",它会假定TaskTracker已经崩溃了,进而重新提交相应的任务到集群中的其他节点中。



HDFS

保存作业的数据、配置信息等,保存作业结果。





任务流程

分别是提交作业,初始化作业,分配任务,执行任务,更新任务执行进度和状态,完成作业。

具体如下:


提交作业

一个MapReduce作业在提交到Hadoop上之后,会进入完全地自动化执行过程。

在这个过程中,用户除了监控程序的执行情况和强制中止作业之外,不能对作业的执行过程进行任何干扰。

所以在作业提交之前,用户需要将所有应该配置的参数按照自己的意愿配置完毕。


需要配置的主要内容有:

1.程序代码
这里主要是指map函数和reduce函数的具体代码,这是一个MapReduce作业对应的程序必不可少的部分,并且这部分代码的逻辑正确与否与运行结果直接相关。


2.Map接口和Reduce接口的配置
在MapRcduce中,Map接口需要派生自Mapper <k1,v1,k2,v2>接口,Reduce接口则要派生自educer<k2,v2,k3,v3>。它们都对应唯一一个方法,分别是map函数和reduce函数。

在调用这两个方法时需要配置它们的四个参数,分别是输入key的数据类型、输入value的数据类型、输出key-value对的数据类型和Reporter实例,其中输入输出的数据类型要与继承时所设置的数据类型相同,还有一个要求是Map接口的输出key-value类型和Reduce接口的输入key-value类型要对应,因为map输出组合value之后,它们会成为reduce的输入内容(入门者请特别注意,很多入门者编写的MapReduce程序中会忽视这个问题)。


3.输入输出路径
作业提交之前还需要在主函数中配置MapReduce作业在Haduop集群的输入路径和输出路径(必须保证输出路径不存在,如果存在程序会报错,这也是初学者经常犯的错误)。



4.其他类型设置
比如调用runJob方法:先要在主函数中配置如Output的key和Value类型、作业名称、InputFormat和OutputFormat等,最后再调用JobClient的runJob方法。



配置完作业的所有内容并确认无误之后提交就可以运行作业了。




用户程序调用JobClient的runJob方法,在提交JobConf对象之后,runJob方法会先行调用JobSubmissionProtocol接口所定义的submitJob方法,并将作业提交给JobTracker。
提交过程中最关键的是JobClient对象中submitJob()方法。
我们来看看submitJob的详细过程:
1.通过调用JobTracker对象的getNewJobId()方法从JobTracker处获取当前作业的ID号。

2.检查作业相关路径。在代码中获取各个路径信息的时候会对作业的对应路径进行检查。
比如,如果没有指定输出目录或文件已经存在,作业就不会被提交,并且会给MapReduce程序返回错误信息,再比如输入目录不存在也会返回错误等。

3.计算作业的输入划分,并将划分信息写入job.split文件,如果写入失败就会返回错误。
split文件的信息主要包括:split文件头、split文件版本号、split的个数。这些信息中
每一条都会包括以下内容:split类型名(默认FileSplit), split的大小、split的内容(对于
FileSplit来说是写入的文件名,此split在文件中的起始位置上)、split的location信息(即在哪个DataNode上)。

4.将运行作业所需要的资源------包括作业JAR文件、配置文件和计算所得的输入划分等复制到作业对应的HDFS上。

5.调用JobTracker对象的submitJob()方法来真正提交作业,告诉JobTracker作业准备执行。




初始化任务

在客户端用户作业调用JobTracker对象的submitJob()方法后,JobTracker会把此调用放入内部的TaskScheduler变量中,然后进行调度,默认的调度方法是JobQueueTaskScheduler,也就是FIFO调度方式。

当客户作业被调度执行时,JobTracker会创建一个代表这个作业的JobInProgress对象,并将任务和记录信息封装到这个对象中,以便跟踪任务的状态和进程。
接下来JobInProgress对象的initTasks函数会对任务进行初始化操作。
initTasks函数的操作如下:
1.从HDFS中读取作业对应的job.split。

JobTracker从HDFS中作业对应的路径获取JobClient写入的job.split文件,得到输入数据的划分信息。为后面初始化过程中map任务的分配做好淮备。


2.创建并初始化map任务和reduce任务。

initTasks先根据输入数据划分信息中的个数设定map task的个数,然后为每个map tasks生成一个TaskInProgress来处理input split,并将map task放入nonRunningMapCache中,以便在JabTracker向TaskTracker分配map task的时候使用。接下来根据JobConf中的mapred.reduce.tasks属性利用setNumReduceTasks()方法来设置reduce task的个数,然后采用类似map task的方式将reduce task放入nonRunningReduces中,以便在向TaskTracker分配reduce task的时候使用。


3.最后就是创建两个初始化task,根据个数和输入划分已经配置的信息,并分别初始化map和reduce。




分配任务

     TaskTracker和JobTracker之间的通信与任务的分配是通过心跳机制完成的。
     TaskTracker作为一个单独的JVM执行一个简单的循环,主要实现每隔一段时间向JobTracker发送心跳(heartbeat):告诉JobTracker此TaskTracker是否存活,是否准备执行新的任务。
在JobTracker接收到心跳信息后,如果有待分配的任务,它就会为TaskTracker分配一个任务,并将分配信息封装在心跳通信的返回值中返回给TaskTracker。
TaskTracker从心跳方法的Response中得知此TaskTracker需要做的事情,如果是一个新的task则将task加入本机的任务队列中。

   下面从TaskTracker中的transmitHeartBeat()方法和JobTracker中的heartbeat()方法的思路出发,介绍任务分配的详细过程,以及在此过程中TaskTracker和JobTracker的通信。

    TaskTracker首先发送自己的状态(主要是map任务和reduce任务的个数是否小于上限),并根据自身条件选择是否向JobTracker请求新的Task,最后发送心跳。
   JobTracker接收到TaskTracker的心跳之后首先分析心跳信息,如果发现TaskTracker在请求一个task,那么任务调度器就会将任务和任务信息封装起来返回给TaskTracker。
    针对map任务和reduce任务,TaskTracker有固定数量的任务槽(map任务和reduce任务的个数都有上限)。

    当TaskTracker从JobTracker返回的心跳信息中获取新的任务信息时,它会将map任务或者reduce任务加入到对应的任务槽中。需要注意的是,在JobTracker为TaskTracker分配map任务的时候,为了减小网络带宽会考虑将map任务数据本地化。它会根据TaskTracker的网络位置,选取一个距离此TaskTraoker map任务最近的输入划分文件分配给此TaskTracker。最好情况是,划分文件就在TaskTracker的本地(TaskTracker往往是FIDFS的DataNode中,所以这种情况是存在的)。








执行任务

 在TaskTracker申请到新的任务之后,就要在本地运行任务了。运行任务的第一步是将
任务本地化(将任务运行所必需的数据、配置信息、程序代码从HDFS复制到TaskTracker
本地)。这主要是通过调用localizeJob()方法来完成的。这个方法主要通过下面几个步骤来完成任务的本地化:
    1.将job.split拷贝到本地:
    2.将job.jar拷贝到本地;
    3.将job的配置信息写人jab.xml:
    4.创建本地任务目录,解压job.jar;
    5.调用launchTaskForJob()方法发布任务。
    任务本地化之后,就可通过调用launchTaskForJob()真正启动起来。接下来
launchTaskForJob()又会调用launchTask()方法启动任务。launchTask()方法的代码主要流程如下:
从代码中可以看出launchTask()方法先会为任务创建本地目录,然后启动TaskRunner。
在启动TaskRunner后,对于map任务,会启动MapTaskRunner;对于reduce任务则启动
ReduceTaskRunner。
    这之后,TaskRunner又会启动新的java虚拟机来运行每个任务。
以map任务为例,任务执行的简单流程是:
    1.配置任务的执行参数(获取Java程序的执行环境和配置参数等);
    2.在Child临时文件表中添加map任务信息〔运行map和reduce任务的主进程是Child类);
    3.配置log文件夹,然后配置map任务的通信和输出参数;
    4.读取input split,生成RecordReader读取数据;
    5.为map任务生成MapRunnable,依次从RecordReader中接收数据,并调用Mapper
的map函数进行处理;
    6.最后将map函数的输出调用collect收集到MapOutputBuffer中。





更新任务执行进度和状态

   一个MapReduce作业在提交到Hadoop上之后,会进入完全地自动化执行过程,用户只能监控程序的执行状态和强制中止作业。但是MapReduce作业是一个长时间运行的批量作业,有时候可能需要运行数小时。

   所以对于用户而言,能够得知作业的运行状态是非常重要的。在Linux终端运行MapReduce作业时,可以看到在作业执行过程中有一些简单的作业执行状态报告,这能让用户大致了解作业的运行情况,井通过与预期运行情况进行对比来确定作业是否按照预定方式运行。


    在MapReduce作业中,作业的进度主要由一些可衡量可计数的小操作组成。

    比如在map任务中,其任务进度就是已处理输入的百分比,比如完成100条记录中的50条,那么map任务的进度就是50%(这里只是针对一个map任务举例,并不是指在Linux终端中执行MapReduce任务时出现的map50%,在终端中出现的50%是总体map任务的进度,是将所有map任务的进度组合起来的结果)。

    总体来讲,MapReduce作业的进度由下面几项组成:

    mapper(或reducer)读入或写出一条记录,在报告中设置状态描述,增加计数器,调用Reporter对象的progess()方法。 
   由MapReduce作业分割的每个任务中都有一组计数器,它们对任务执行过程中的进度组成事件进行计数。如果任务要报告进度,它便会设置一个标志以表明状态变化将会发送到TaskTracker上。另一个监听线程检查到这标志后,会告知TaskTracker当前的任务状态。

    同时,TaskTracker每隔5秒在发送给JobTracker的心跳中封装任务状态,报告自己的任务执行状态。
    通过心跳通信机制,所有TaskTracker的统计信息都会汇总到JobTracker处。JobTracker将这些统计信息合并起来,产生一个全局作业进度统计信息,用来表明正在运行的所有作业,以及其中所含任务的状态。

    最后,JobClient通过每秒查看JobTracker来接收作业进度的最新状态。










完成作业

    所有TaskTracker任务的执行进度信息都会汇总到JobTracker处,当JobTracker接收到最后一个任务的已完成通知后,便把作业的状态设置为“成功”。

    然后,JobClient也将及时得知任务已成功完成,它便会显示一条信息告知用户作业已完成,最后从runJob()方法处返回(在返回后JobTracker会清空作业的工作状态,并指示TaskTracker也清空作业的工作状态,比如删除中间输出等)。







数据流程

整个数据流程发生在任务流程中的 执行任务,更新任务执行进度和状态,完成作业  这三个阶段。

整个数据流程可以用五个方法来概括:

分别是InputFormat,map,Shuffle和Sort, reduce,  OutputFormat。

具体如下:

InputFormat方法---负责数据输入

InputFormat会需执行的两个功能:
1.确定所有用于输入数据的文件,并将之分割为输入分片。每个map任务分配一个分片。

输入数据通常写在较大的文件中,通常几十或数百GB,甚至更大。MapReduce的基本原则之一是将输人数据分割成块。
这些块可以在多台计算机上并行处理。在Hadoop术语中这些块被称为输入分片(InputSplit)。
InputSplit是Hadoop定义的用来传送给每个单独的map的数据,InputSplit存储的并非数据本身,而是一个分片长度和一个记录数据位置的数组。
每个分片应该足够小以实现更细粒度的并行。
输入数据都在一个分片中,那就没有并行了)。另一方面,每个分片也不能太小,否则启动和停止分片的处理会花很大一部分执行时间。



2.提供一个对象(RecordReader),循环提取给定分片中的记录,并解析每个记录为预定义类型的键与值。


InputFormat会调用getRecordReader()方法生成RecordReader, RecordReader再通过creatKey()、creatValue()方法创建可供map处理的<key, value>对,即<k1, vl>。简而言之,InputFormat()方法是用来生成可供map处理的<key, value>对的。




InputFormat实现了多种方法将不同类型的输入数据转化为map能够<key, value>对,它们都继承自InputFormat,分别是:(具体在下面的《MapReduce的类型和格式》小结讲解)
BaileyBorweinPlouffe.BbpInputFormat
ComposableInputFormat
CompositeInputFormat
DBInputFormat
DistSum.Machine.AbstractInputFormat
FileInputFormat
其中,FileInputFormat又有多个子类,分别为:
CombineFileInputFormat
KeyValueTextInputFormat
NLineInputFormat
SequenceFileInputFormat
TeraInputFormat
TextInputFormat
其中,TextInputFormat是Hadoop默认的输入方法,在TextInputFormat中,每个文件(或其一部分)都会单独地作为map的输入,而这是继承自FileInputFormat的。之后,每行
数据都会生成一条记录,每条记录则表示成<key, value>形式:
key值是每个数据的记录在数据分片中的字节偏移量,数据类型是LongWritable;
value值是每行的内容,数据类型是Text。






map

  map函数接收经过InputFormat处理所产生的<k1,v1>,然后输出<k2, v2>。 
  map函数继承自MapReduceBase,并且它实现了Mapper接口,此接口是一个范型类
型,它有4种形式的参数,分别用来指定map的输入key值类型、输入value值类型、输
出key值类型和输出value值类型。
    实现此接口类还需要实现map方法,map方法会具体负责对输入进行操作,我们进行MapReduce主要就是编写这部分业务操作的代码,比如map方法对输入的行以空格为单位进行切分,然后使用OutputCollect收集输出的<word,1>,即<k2,v2>。







Shuffle和Sort

      map的输出会经过一个名为shuffle的过程交给reduce处理(在“MapReduce数据流”图中也可以看出),当然也有map的结果经过sort-merge交给reduce处理的。其实在MapReduce流程中,为了让reduce可以并行处理map结果,必
须对map的输出进行一定的排序和分割,然后再交给对应的reduce,而这个将map输出进行进一步整理并交给reduce的过程就成为了shuffle。

   从shuffle的过程中可以看出,它是MapRcducc的核心所在,shuffle过程的性能与整个MapReduce的性能直接相关。
   总体来说,shuffle过程包含在map和reduce两端中。在map端的shuffle过程是对map的结果进行划分(partition )、排序(sort)和分割(spill),然后将属于同个划分的输出合并在一起(merge ),并写在磁盘上,同时按照不同的划分将结果发送给对应的reduce (map输出的划分与reduce的对应关系由JobTracker确定)。 reduce端又会将各个map送来的属于
同一个划分的输出进行合并(merge) ,然后对merge的结果进行排序,最后交给reduce处理。

   下面将从map和reduce两端详细介绍shuffle过程。  
    map端
    从MapReduce的程序中可以看出,map的输出结果是由collector处理的,所以map端的shuffle过程包含在collect函数对map输出结果的处理过程中。下面从具体的代码来分析map端的shuffle过程。
    首先从collect函数的代码入手。从代码可以看出map函数的输出内存缓冲区是一个环形结构。
    当输出内存缓冲区内容达到设定的阔值时,就需要把缓冲区内容分割(spill)到磁盘中了。但是在分割的时候map并不会阻止继续向缓冲区中写入结果,如果map结果生成的速度快于写出速度,那么缓冲区会写满,这时map任务必须等待,直到分割写出过程结束。
    在collect函数中将缓冲区中的内容写出时会调用sortAndSpill函数。sortAndSpill函数每被调用一次就会创建一个spill文件,然后按照key值对需要写出的数据进行排序,最后按照划分的顺序将所有需要写出的结果写入这个spill文件中。如果用户作业配置了combiner类,那么在写出过程中会先调用combineAndSpill()再写出,对结果进行进一步的合并
(combine)是为了让map的输出数据更加紧凑。
    显然,直接将每个map生成的众多spill文件(因为map过程中,每一次缓冲区写出都会产生一个spill文件)交给reduce处理不现实。
    所以在每个map任务结束之后在map的TaskTracker上还会执行合并操作(merge ) ,这个操作的主要目的是将map生成的众多spill文件中的数据按照划分重新组织,以便于reduce处理。
   主要做法是针对指定的分区,从各个spill文件中拿出属于同一个分区的所有数据,然后将它们合并在一起,井写入一个已分区且已排序的map输出文件中。这个过程的详细情况请参考mergeParts[]函数的代码。
    待唯一的已分区且已排序的map输出文件写入最后一条记录后,map端的shuffle阶段就结束了。

   下面就进入reduce端的shuffle阶段。




    reduce端
    在reduce端,shuffle阶段可以分成三个阶段:复制map输出、排序合并、reduce处理。
    下面按照这三个阶段进行详细介绍。
    如前文所述,map任务成功完成后,会通知父TaskTracker状态已更新,进而TaskTraeker通知JobTracker(这些通知在心跳机制中进行)。所以,对于指定作业来说,JobTracker能够记录map输出和TaskTracker的映射关系。reduce会定期向JabTracker获取map的输出位置。一旦拿到输出位置,reduce任务就会从这个输出对应的TaskTracker上复制输出到本地〔如果map的输出很小,则会被复制到执行reduce任务的TaskTracker节点的内存中,便于进一步的处理,否则会放入磁盘),而不会等到所有的map任务结束。这就是reduce任务的复制阶段。
    在reduce复制map的输出结果的同时,reducc任务就进入了合并(merge)阶段。这一阶段主要的任务是将从各个map TaskTracker上复制的map输出文件(无论在内存还是在磁盘)进行整合,并维持数据原来的顺序。
    reduce端的最后阶段就是对合并的文件进行reduce处理。reduce TaskTracker从合并的文件中按照顺序先拿出一条数据,交给reduce函数处理,然后直接将结果输出到本地的HDFS上(因为在Hadoop集群上,TaskTracker节点一般也是DataNode节点),接着继续拿出下一条数据,再进行处理。







reduce

reduce函数以map的输出作为输入。reduce函数也继承自MapReduceBase,并且它实现了Reducer接口,它也要实现reduce方法,在此方法中,reduce函数将输入的key值作为输出的key值,然后将
获得的多个value值处理,我们进行MapReduce主要就是编写这部分业务操作的代码,作为输出的value值。






OutputFormat-----负责输出

当MapReduce输出数据到文件时,使用的是OutputFormat类,因为每个reducer仅需将它的输出写入自己的文件中,输出无需分片。
输出文件放在一个公用目录中,通常命名为part-nnnnn,这里nnnnn是reducer的分区ID。

RecordWriter对象将输出结果进行格式化,而RecordReader对输入格式进行解析。

OutputFormat也有多种实现以便输出不同格式的结果,实现的接口如下:

(具体在下面的《MapReduce的类型和格式》小结讲解)

TextOutputFormat
NullOutputFormat
SequenceFileOutputFormat
MultipleSequenceFileOutputFormat
MultipleTextOutputFormat
DBOutputFormat

默认的OutputFormat是TextOutputFormat。将每个记录写为一行文本。每个记录的键和值通过toString()被转换为字符串( string ),并以制作符(\t)分隔。分隔符可以在mapred.textoutputformat.separator属性中修改。





作业调度机制

    在0.19.0版本之前,Hadoop集群上的用户作业采用先进先出(FTFO, First Input FirstOutput )的调度算法,即按照作业提交的顺序来运行。同时每个作业都会使用整个集群,因此它们只有轮到自己运行时才能享受整个集群的服务。、    虽然FIFO调度器最后又支持设置了优先级的功能,但是由于不支持优先级抢占,所以这种单用户的调度算法仍然不符合云计算中采用并行计算来提供服务的宗旨。

    从0.19.0版本开始,hadoop除了默认的FIFO调度器外,还提供了支持多用户同时服务和集群资源公平共享的调度器,即公平调度器( Fair SchedulerGuide)和容量调度器(Capacity Scheduler Guide )。
    下面主要介绍公平调度器。
    公平调度是为作业分配资源的方法,其目的是随着时间的推移,让提交的作业获取等量的集群共享资源,让用户公平地共享集群。具体做法是:当集群上只有一个作业在运行时,它将使用整个集群,当有其他作业提交时,系统会将TaskTracker节点空闲的时间片分配给这些新的作业,并保证每一个作业都得到大概等量的CPU时间。
    公平调度器按作业池来组织作业,它会按照提交作业的用户数目将资源公平地分到这些作业池里。

    默认情况下,每一个用户拥有一个独立的作业池,以使每个用户都能获得一份等同的集群资源而不会管它们提交了多少作业。在每一个资源池内,会使用公平共享的方法在运行作业之间共享容量。

    除了提供公平共享方法外,公平调度器还允许为作业池设置最小的共享资源,以确保特定用户、群组或生产应用程序总能获取到足够的资源。对于设置了最小共享资源的作业池来说,如果它包含了作业,它至少能获取到最小的共享资源。但是如果最小共享资源超过作业需要的资源时,额外的资源会在其他作业池间进行切分。
    在常规操作中,当提交了一个新作业时,公平调度器会等待已运行作业中的任务完成,以释放时间片给新的作业。但公平调度器也支持作业抢占。如果新的作业在一定时间(即超时时间,可以配置)内还未获取公平的资源分配,公平调度器就会允许这个作业抢占已运行作业中的任务,以获取运行所需要的资源。另外,如果作业在超时时间内获取的资源不到公平共亨资源的一半时也允许对任务进行抢占。

    而在选择时,公平调度器会在所有运行任务中选择最近运行起来的任务,这样浪费的计算相对较少。由于hadoop作业能容忍丢失任务,抢占不会导致被抢占的作业失败,只是让被抢占作业的运行时间更长。
    最后,公平调度器还可以限制每个用户和每个作业池并发运行的作业数量。这个限制可以在一个用户一次性提交数百个作业或当大量作业并发执行时来确保中间数据不会塞满集群上的磁盘空间。超出限制的作业会被列入调度器的队列中进行等待,直到早期作业运行完毕。公平调度器再根据作业优先权和提交时间的排列情况从等待作业中调度即将运行的作业。





错误处理机制

   Hadoop有很强的容错性。这主要是针对由成千上万台普通机器组成的集群中常态化的硬件故障的,Hadoop能够利用冗余数据方式来解决硬件故障,以保证数据安全和任务执行。

   那么MapReduce在具体执行作业过程中遇到硬件故障会如何处理呢?对于用户代码的缺陷或进程崩溃引起的错误又会如何处理呢?我们从硬件故障和任务失败两个方面说明MapReduce的错误处理机制。


   硬件故障

  从MapReduce任务的执行角度出发,所涉及的硬件主要是JobTracker和TaskTracker(对应从HDFS出发就是NameNode和DataNode)。显然硬件故障就是JobTracker机器故障和TaskTracker机器故障。
    在Hadoop集群中,任何时候都只有唯一一个JobTracker。所以JobTracker故障就是单点故障,这是所有错误中最严重的错误。到目前为止,在Hadoop中还没有相应的解决办法。
    能够想到的是通过创建多个备用JobTracker节点,在主JobTracker失败之后采用领导选举算法(Hadoop中常用的一种确定master的算法)来重新确定JobTracker节点。在一些企业使用Hadoop提供服务时,就采用了这样的方法来避免JobTracker错误。
    机器故障除了JobTracker错误外就是TaskTracker错误。TaskTracker故障相对较为常见,并且MapReduce也有相应的解决办法,主要是重新执行任务。下面将详细介绍当作业遇到TaskTracker错误时,MapReduce所采取的解决步骤。
    在Hadoop中,正常情况下,TaskTracker会不断地与系统JobTracker通过心跳机制进行通信。如果某TaskTracker出现故障或运行缓慢,它会停止或很少向JobTracker发送心跳。如果一个TaskTracker在一定时间内(默认是1分钟)没有与JobTracker通信,那么JobTracker会将此TaskTracker从等待任务调度的TaskTracker集合中移除。同时JobTracker会要求此TaskTracker上的任务立刻返回,如果此TaskTracker任务是仍然在mapping阶段的map任务,那么JobTracker会要求其他的TaskTracker重新执行所有原本由故障TaskTracker执行的map任务。如果任务是在reduce阶段的reduce任务,那么JobTracker会要求其他TaskTracker重新执行故障TaskTracker未完成的reduce任务。

    比如,一个TaskTracker已经完成被分配的3个reduce任务中的2个,由于reduce任务一旦完成会将数据写到HDFS上,所以只有第三个未完成的reduce需要重新执行。但是对于map任务来说,即使TaskTracker完成了部分map ,但是reduce仍可能无法获取此节点上所有map的所有输出。所以无论map任务完成与否,故障TaskTracker上的map任务都必须重新执行。

  

  任务失败

    在实际任务中,MapReduce作业还会遇到用户代码缺陷或进程崩溃引起的任务失败。
    用户代码缺陷会导致它在执行过程中抛出异常。此时,任务JVM进程会自动退出,并向TaskTracker父进程发送错误消息,同时错误消息也会写入log文件,最后TaskTracker将此次任务尝试标记失败。
   对于进程崩溃引起的任务失败,TaskTracker的监听程序会发现进程退出,此时TaskTracker也会将此次任务尝试标记为失败。对于死循环程序或执行时间太长的程序,由于TaskTracker没有接收到进度更新,它也会将此次任务尝试标记为失败,并杀死程序对应的进程。
    在以上情况中,TaskTracker将任务尝试标记为失败之后会将TaskTracker自身的任务计数器减1,以便向JobTracker申请新的任务。TaskTracker也会通过心跳机制告诉JobTracker本地的一个任务尝试失败。JobTracker接到任务失败的通知后,通过重置任务状态,将其加入调度队列来重新分配该任务执行(JobTracker会尝试避免将失败的任务再次分配给运行失败的TaskTracker)。如果此任务尝试了4次(次数可以进行设置)仍没有完成,就不会再被重试,此时整个作业也就失败了。




MapReduce的类型和格式

我们在数据流程的输入输出中已经介绍了有哪些类型的输入输出,随着版本的更新,MapReduce支持的数据类型也会越来越多的。我们就来了解现有常用的一些类型和格式。如下:

输入

ComposableInputFormat

抽象类,该类的子类需要提供ComposableRecordReader而不再是RecordReader。


CompositeInputFormat

在具有相同的排序和分区的一组数据源上执行join操作的InputFormat。


DBInputFormat

从SQL表中读取数据的InputFormat。DBInputFormat使用包含记录号的LongWritable做为键,DBWritable做为值。该类的子类为DataDrivenDBInputFormat,该类与父类使用不同的机制划分InputSplit。



FileInputFormat
其中,FileInputFormat又有多个子类,分别为:
CombineFileInputFormat

抽象类,该类的getSplits(JobContext)返回的不是List<FileSplit>而是List<CombineFileSplit>。CombineFileSplit根据输入路径中的文件构造,该对象不能够有不同池中的文件,每个CombineFileSplit可能包含不同文件的块。该类有两个子类用于sequence文件和纯文本文件,分别为CombineSequenceFileInputFormat和CombineTextInputFormat,其RecordReader分别为SequenceFileRecordReaderWrapper和 TextRecordReaderWrapper。



KeyValueTextInputFormat

用于纯文本文件的InputFormat,每行使用分隔字节划分为键和值,该分隔符由参数mapreduce.input.keyvaluelinerecordreader.key.value.separator指定,默认使用\t。如果该分隔符不存在则整行将做为键,值为空。RecordReader为KeyValueLineRecordReader。



NLineInputFormat

将输入中的N行做为一个InputSplit,其中N可以由参数mapreduce.input.lineinputformat.linespermap指定,默认为1。RecordReader也是用LineRecordReader。



SequenceFileInputFormat

用于sequence文件的InputFormat,获取InputSplit的方法继承自FileInputFormat,并未重写,其RecordReader为SequenceFileRecordReader。该类有三个子类:SequenceFileAsBinaryInputFormat、SequenceFileAsTextInputFormat和 SequenceFileInputFilter,分别用于从sequence文件的二进制格式中读取键值、将sequence文件中的键值转换为字符串形式、从sequence文件中抽样然后交由MapReduce作业处理,抽样由过滤器类确定。三者的RecordReader分别为:SequenceFileAsBinaryRecordReader、SequenceFileAsTextRecordReader和FilterRecordReader。



TeraInputFormat

读取前10个字符作为key,剩余的为value。类型都为Text。



TextInputFormat

用于纯文本文件的InputFormat,也是默认的InputFormat。输入文件被分解为行,回车或者换行做为行结束的标记,键为行在文件中的位置,值为行内容。该InputFormat使用LineRecordReader读取InputSplit的内容。



输出

TextOutputFormat

输出到纯文本文件,格式为 key + ” ” + value。


NullOutputFormat

hadoop中的/dev/null,将输出送进黑洞。


SequenceFileOutputFormat

输出到sequence file格式文件。


MultipleSequenceFileOutputFormat,MultipleTextOutputFormat

根据key将记录输出到不同的文件。


DBOutputFormat

输出到数据库中




MapReduce与HDFS的关系以及Hadoop常用架构

我们在上一篇了解了HDFS的常用架构,本篇又了解MapReduce的常用架构。那么HDFS跟MapRedece融合使用的常用架构是什么?

根据每个节点的作用我们一般使用如下架构:



也就是NameNode和JobTracker用同一台机子,其它的机子分别同时作为DataNode和TaskTracker节点。

这样NameNode和JobTraceker作为调度节点,其它的机子就负责存储和运算。





开发MapReduce程序

本节记录具体开发MapReduce程序的过程,包括系统环境参数的配置以及MapReduce程序的开发编写运行出结果。将在实战部分学习。



MapReduce实例

本节记录MapReduce应用案例包括:单词计数,数据去重,排序,单表关联,多表关联。将在实战部分学习。



MapReduce优化

我们学习了怎样使用MapReduce之后就应该会想到如果让它更好的运作,本节记录MapReduce的优化

例如:选择reducer的个数,Shuffle的优化等。将在实战部分学习。





1 1