我们在前一章已经学习了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个实体角色:client,JobTracker。TaskTracker,HDFS

详细例如以下:

client(client)

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


JobTracker

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

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

  假设任务失败,JobTracker将自己主动重新启动任务,但所分配的节点可能会不同。同一时候受到提前定义的重试次数限制。


  每一个Hadoop集群仅仅有一个JobTracker守护进程。它通常运行在server集群的主节点上。



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作业准备运行。




初始化任务

在client用户作业调用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的优化等。将在实战部分学习。





posted on 2017-07-21 10:27  lxjshuju  阅读(1852)  评论(0编辑  收藏  举报