Hadoop MapReduce工作机制

1.MapReduce运行过程

如下图所示,运行过程包含下述4个独立的实体

--JobClient:提交MapReduce作业

--JobTracker:协调作业的运行

--TaskTracker:运行作业划分后的任务

--分布式文件系统(一般为HDFS):用来在其他实体之间共享作业文件

1)MapReduce作业的提交

--向JobTracker请求一个新的作业ID。

--检查作业的输出说明。例如,如果没有指定输出目录或者输出目录已存在,作业就不提交,错误抛回给MapReduce作业。

--计算作业的输入分片。如果分片无法计算,例如由于输入路径不存在,作业也不提交,错误抛回给MapReduce作业。

--将运行作业所需要的资源(Jar文件,配置文件和计算所得输入分片)复制到一个作业ID命名的目录下JobTracker的文件系统中。作业Jar的副本默认为10。

mapred-site.xml

<property>
  <name>mapred.submit.replication</name>
  <value>10</value>
  <description>The replication level for submitted job files.  This
  should be around the square root of the number of nodes.
  </description>
</property>

--告知JobTracker作业准备执行,提交MapReduce作业到JobTracker。

提交作业后,JobClient每秒轮询作业的进度,显示最新的作业进度和状态信息。

2)作业的初始化

JobTracker接收对其提交的作业后,会把此调用放入一个队列,交由作业调度器调度,初始化。初始化包括创建一个表示正在运行作业的对象---封装任务和记录信息,以便跟踪任务的状态和进程。

--从分布式文件系统中获取JobClient已计算好的输入分片信息,为每个分片创建一个map任务

--创建reduce任务,任务数量由用户通过mapred.reduce.task属性或者setNumReduceTasks接口设置

mapred-site.xml

<property>
  <name>mapred.reduce.tasks</name>
  <value>1</value>
  <description>The default number of reduce tasks per job. Typically set to 99%
  of the cluster's reduce capacity, so that if a node fails the reduces can 
  still be executed in a single wave.
  Ignored when mapred.job.tracker is "local".
  </description>
</property>

--指定任务ID

作业调度器:

mapred-site.xml

<property>
  <name>mapred.jobtracker.taskScheduler</name>
  <value>org.apache.hadoop.mapred.JobQueueTaskScheduler</value>
  <description>The class responsible for scheduling the tasks.</description>
</property>

--FIFO+优先级,不支持抢占

--Fair Scheduler,支持抢占

--Capacity Scheduler

3)任务的分配

TaskTracker运行简单的循环来对JobTracker发送心跳,告知自己的是否存活,同时交互信息,例如告知它是否已经准备运行新的任务,如果是JobTracker会为它分配一个任务。

在JobTracker为TaskTracker选择任务之前,JobTracker必须先选定任务所在的作业。

对于map任务和reduce任务,TaskTracker会分配适当的固定数量的任务槽。默认调度器在处理reduce任务槽之前,会填满空闲的map任务槽。因此,如果TaskTracker至少有一个空闲的map任务槽,JobTracker会为它选择一个map任务,否则选择一个reduce任务。

mapred-site.xml

<property>
  <name>mapred.tasktracker.map.tasks.maximum</name>
  <value>2</value>
  <description>The maximum number of map tasks that will be run
  simultaneously by a task tracker.
  </description>
</property>

<property>
  <name>mapred.tasktracker.reduce.tasks.maximum</name>
  <value>2</value>
  <description>The maximum number of reduce tasks that will be run
  simultaneously by a task tracker.
  </description>
</property>

任务节点的选择:

--Map任务:考虑TaskTracker的网络位置,并选取一个距离其输入分片文件最近的TaskTracker。

优先级别为:数据本地 > 机架本地 > 不同机架

--Reduce任务:不考虑数据的本地化。

4)任务执行

--通过分布式文件系统将作业的JAR文件复制到TaskTracker所在的文件系统,实现作业JAR文件本地化,

同时将应用程序所需要的全部文件从分布式缓存复制到本地磁盘。

--TaskTracker为任务新建一个本地工作目录,并把JAR文件中的内容解压到这个文件夹下。

--TaskTracker新建一个TaskRunner实例来运行该任务。

TaskRunner启动一个新的JVM来运行每个任务,以便用户定义的map和reduce函数不会影响到TaskTracker实例。不同的任务之间可以重用JVM。子进程通过umbilical接口与父进程进行通信。任务的子进程每隔几秒便告知父进程它的进度,直到任务完成。

Streaming和Pipes

运行特殊的map和reduce任务,从而运行用户提供的可执行程序,并与之通信。

--Streaming:使用标准输入和输出进行通信

--Pipes:使用套接字(Socket)进行通信

5)进度和状态的更新

一个作业和他的每个任务都有一个状态,包括:作业或任务的状态,map和reduce运行的进度、作业计数器的值、状态消息描述。

进度:

--Map任务:已处理输入所占的比例

--Reduce任务:整个过程分为3部分,与shuffle的三个阶段对应。比如,如果任务已执行reducer一般的输入,那么任务的进度便是5/6。复制1/3+排序1/3+reduce1/3*1/2=5/6。

构成进度的所有操作如下:

--读入一条输入记录

--写入一条输出记录

--状态更改

--计数器变更

--调用Reporter的progress方法

如果任务报告了进度,便会设置一个标志以表明状态变化将被发送到TaskTracker。有一个独立的线程每隔3秒检查一次此标志,如果已设置,则告知TaskTracker当前任务状态。同时,TaskTracker每隔5秒(最小值,由集群大小决定,对于更大的集群,间隔会更长)发送心跳到JobTracker。

JobTracker将这些更新合并起来,产生一个表明所有运行作业及其所含任务状态的全局视图。

6)作业的完成

--当JobTracker收到Job最后一个Task完成的消息时候便把Job的状态设置为”成功“,JobClient也成功结束

--发送HTTP作业通知

mapred-site.xml

<property>
 <name>job.end.notification.url</name>
 <value>http://localhost:8080/jobstatus.php?jobId=$jobId&amp;jobStatus=$jobStatus</value>
 <description>Indicates url which will be called on completion of job to inform
              end status of job.
              User can give at most 2 variables with URI : $jobId and $jobStatus.
              If they are present in URI, then they will be replaced by their
              respective values.
</description>
</property>

--JobTracker清空作业的工作状态,指示TaskTracker也清空作业的工作状态(如删除中间输出)

2.失败

1)任务失败

--map或reduce任务代码跑出异常

子任务JVM在退出之前向TaskTracker发送错误报告。错误报告记入用户日志。TaskTracker会将此次task attempt标记为failed,释放一个任务槽运行另一个任务。

--子进程JVM突然退出

TaskTracker会注意到进程已经退出,并将此次task attempt标记为failed。

--任务超时

TaskTracker注意到已有一段时间没有收到进度的更新,便会将任务标记为failed。在此之后,JVM子进程将被自动杀死。默认超时为10分钟,设置为0则将关闭超时判定,永不超时。

mapred-site.xml

<property>
  <name>mapred.task.timeout</name>
  <value>600000</value>
  <description>The number of milliseconds before a task will be
  terminated if it neither reads an input, writes an output, nor
  updates its status string.
  </description>
</property>

JobTracker得知一个task attempt失败后(通过TaskTracker的心跳),它将重新调度该任务的执行。

JobTracker会尝试避免重新调度失败过的TaskTracker上的任务。

任何任务失败次数大于4次,整个作业都会失败。

mapred-site.xml

<property>
  <name>mapred.map.max.attempts</name>
  <value>4</value>
  <description>Expert: The maximum number of attempts per map task.
  In other words, framework will try to execute a map task these many number
  of times before giving up on it.
  </description>
</property>

<property>
  <name>mapred.reduce.max.attempts</name>
  <value>4</value>
  <description>Expert: The maximum number of attempts per reduce task.
  In other words, framework will try to execute a reduce task these many number
  of times before giving up on it.
  </description>
</property>

可以为作业设置在不触发作业失败的情况下允许任务失败的最大百分比。

任务尝试也是可以中止的,这与失败不同,被中止的task attempt不会被计入任务运行尝试次数。

2)TaskTracker失败

如果一个TaskTracker由于崩溃或运行过于缓慢而失败,它将停止向JobTracker发送心跳。

JobTracker会注意到已经停止发送心跳的TaskTracker,并将它从等待任务调度的TaskTracker池中移除。如果是未完成的作业,JobTracker会安排此TaskTracker上已经运行并完成的map任务重新运行,因为reduce任务无法访问。任何进行中的任务也都会被重新调度。

mapred-site.xml

<property>
  <name>mapred.tasktracker.expiry.interval</name>
  <value>600000</value>
  <description>Expert: The time-interval, in miliseconds, after which
  a tasktracker is declared 'lost' if it doesn't send heartbeats.
  </description>
</property>

即使TaskTracker没有失败,也可能被JobTracker列入黑名单。例如TaskTracker上面的失败任务数远高于集群的平均失败任务数。被列入黑名单的TaskTracker可以通过重启从JobTracker的黑名单中移出。

3)JobTracker失败

Hadoop没有处理JobTracker失败的机制,它是一个单点故障,因此在这种情况下,任务注定失败。

可以使用ZooKeeper的协调机制来改变这种情况。

3.Shuffle和排序

MapReduce会确保每个reducer的输入都按键排序。系统执行排序的过程---将map的输出作为reducer的输入---称为shuffle。

1)Map端

每个Map任务都一个环形的内存缓冲区,用于存储任务的输出,并出于效率的考虑进行预排序。当缓冲区达到阀值时,一个后台线程便开始把内容写到磁盘中。在写磁盘过程中,map输出数据继续被写到缓冲区,但如果在此期间缓冲区被填满,map会阻塞直到写磁盘过程完成。

mapred-site.xml

<property>
  <name>io.sort.mb</name>
  <value>100</value>
  <description>The total amount of buffer memory to use while sorting 
  files, in megabytes.  By default, gives each merge stream 1MB, which
  should minimize seeks.</description>
</property>

<property>
  <name>io.sort.spill.percent</name>
  <value>0.80</value>
  <description>The soft limit in either the buffer or record collection
  buffers. Once reached, a thread will begin to spill the contents to disk
  in the background. Note that this does not imply any chunking of data to
  the spill. A value less than 0.5 is not recommended.</description>
</property>

<property>
  <name>mapred.local.dir</name>
  <value>${hadoop.tmp.dir}/mapred/local</value>
  <description>The local directory where MapReduce stores intermediate
  data files.  May be a comma-separated list of
  directories on different devices in order to spread disk i/o.
  Directories that do not exist are ignored.
  </description>
</property>

--在写磁盘之前,线程首先根据数据最终要传送到的reducer把数据划分成相应的分区。在每个分区中,后台线程按键进行内排序,如果有一个combiner,它会在排序后的输出上运行

--一旦内存达到溢出写的阀值,就会新建一个溢出写文件,因此在map任务写完其最后一个输出记录之后,会有几个溢出文件。在任务完成之前,溢出写文件被合并成一个已分区且已排序的输出文件。io.sort.factor控制一次最多能合并多少流

mapred-site.xml

<property>
  <name>io.sort.factor</name>
  <value>10</value>
  <description>The number of streams to merge at once while sorting
  files.  This determines the number of open file handles.</description>
</property>

2)Reduce端

Reducer通过HTTP方式得到输出文件的分区。

mapred-site.xml

<property>
  <name>tasktracker.http.threads</name>
  <value>40</value>
  <description>The number of worker threads that for the http server. This is
               used for map output fetching
  </description>
</property>

--复制

reduce任务需要集群上若干个map任务的map输出作为其特殊的分区文件。每个map任务的完成时间可能不同,因此只要有一个map任务完成,reduce任务就开始复制其输出。默认情况下有5个复制线程。

mapred-site.xml

<property>
  <name>mapred.reduce.parallel.copies</name>
  <value>5</value>
  <description>The default number of parallel transfers run by reduce
  during the copy(shuffle) phase.
  </description>
</property>

如果map输出相当小,则会被复制到reduce tasktracker的内存,否则当内存缓冲区达到阀值或达到map输出阀值,则合并后溢出写到磁盘中。

mapred-site.xml

<property>
  <name>mapred.job.shuffle.input.buffer.percent</name>
  <value>0.70</value>
  <description>The percentage of memory to be allocated from the maximum heap
  size to storing map outputs during the shuffle.
  </description>
</property>

<property>
  <name>mapred.job.shuffle.merge.percent</name>
  <value>0.66</value>
  <description>The usage threshold at which an in-memory merge will be
  initiated, expressed as a percentage of the total memory allocated to
  storing in-memory map outputs, as defined by
  mapred.job.shuffle.input.buffer.percent.
  </description>
</property>

<property>
  <name>mapred.inmem.merge.threshold</name>
  <value>1000</value>
  <description>The threshold, in terms of the number of files 
  for the in-memory merge process. When we accumulate threshold number of files
  we initiate the in-memory merge and spill to disk. A value of 0 or less than
  0 indicates we want to DON'T have any threshold and instead depend only on
  the ramfs's memory consumption to trigger the merge.
  </description>
</property>

为了合并,压缩的map输出都必须在内存中解压。  

--排序&合并

合并map输出,维持其顺序排序。

这是循环进行的。比如有50个map输出,而合并因子是10,合并将进行5趟,最后有5个中间文件。最后直接把5个中间文件合并成一个已排序的文件输入到reduce函数,从而省略了一次磁盘往返行程。

mapred-site.xml

<property>
  <name>io.sort.factor</name>
  <value>10</value>
  <description>The number of streams to merge at once while sorting
  files.  This determines the number of open file handles.</description>
</property>

这里需要注意的是:每趟合并目标是合并最小数量的文件以便满足最后一趟的合并系数,eg:有40个文件,我们不会再4趟中,每趟合并10个文件然后得到4个文件,相反第一堂只合并4个文件,最后的三趟每次合并10个文件,在最后的一趟中4个已经合并的文件和余下的6个文件(未合并)进行10个文件的合并(见下图),其实这里并没有改变合并次数,它只是一个优化措施,尽量减少写到磁盘的数据量。

4.配置调优

--shuffle过程尽量多提供内存空间,并且需要确保map和reduce函数能得到足够的内存来运行

--运行map任务和reduce任务的JVM,其内存要足够大 mapred.child.java.opts

1)map端

io.sort.mb    类型int默认100=》map的内存缓冲区

io.sort.record.precent 类型:float默认0.05=》io.sort.mb的缓存区记录索引kvindices和缓存区记录索引排序工作数组kvoffsets占用空间比例

io.sort.spill.percent  类型:float默认0.8=》io.sort.mb的缓冲数据边界阙值

io.sort.factor 类型int默认10=》每次合并文件数

min.mum.spills.for.combine类型int默认3=》运行combiner需要的最少溢出文件数

mapred.compress.map.output类型boolean默认false=》是否压缩map输出

mapred.map.output.compression.coderc类型classname默认DefaultCodec=》map输出的压缩编码器

tasktracker.http.threads类型int默认40=》每个tasktracker的工作线程数,将map输出到reduce#只能全局设定

2)reduce端

mapred.reduce.parallel.copies类型int默认5=》复制map输出数据的线程数

mapred.reduce.copy.backoff类型int默认300=》获取一个map数据的最大时间

io.sort.factor

mapred.job.shuffer.input.buffer.percent类型float默认0.70=>shuffer的复制阶段,分配给map输出的缓冲区的比例

mapred.job.shuffer.merge.percent类型float默认0.66=》mapred.job.shuffer.input.buffer.percent的阙值

mapred.inmem.merge.threshold类型int默认1000=》mapred.job.shuffer.input.buffer.percent的文件数阙值

mapred.job.reduce.input.buffer.percent类型float默认0.0=》reduce过程中在内存中保存map输出的比例

5.任务的执行

1)推测执行

Hadoop不会尝试诊断或修复执行慢的任务,相反,在一个任务运行比预期慢的时候,它会尽量检测,并启动另一个相同的任务作为备份,这就是所谓的任务的推测执行。

只有在一个作业的所有任务都启动之后才启动推测执行的任务,并且只针对那些已运行一段时间且比作业中其它任务平均进度慢的任务。一个任务完成后,任务正在运行的重复任务都将被中止。

默认情况下,推测执行为开启状态。通常在集群上关闭此选项,而让用户根据个别作业需要开启该功能。

mapred-site.xml

<property>
  <name>mapred.map.tasks.speculative.execution</name>
  <value>true</value>
  <description>If true, then multiple instances of some map tasks 
               may be executed in parallel.</description>
</property>

<property>
  <name>mapred.reduce.tasks.speculative.execution</name>
  <value>true</value>
  <description>If true, then multiple instances of some reduce tasks 
               may be executed in parallel.</description>
</property>

2)任务JVM重用

mapred-site.xml

<property>
  <name>mapred.job.reuse.jvm.num.tasks</name>
  <value>1</value>
  <description>How many tasks to run per jvm. If set to -1, there is
  no limit. 
  </description>
</property>

3)跳过坏记录

处理坏记录的最佳位置在于mapper和reducer代码。

少数情况下,我们无法再mapper或reducer中处理它,例如软件BUG存在于第三方的库中。这种情况下,我们可以启用skipping mode。

当任务失败两次后才会启用skipping mode,对于一个一直在某条坏记录上失败的任务,TaskTracker将会运行以下task attempt得到相应的结果。

--任务失败

--任务失败

--开启skipping mode。任务失败,失败记录由TaskTracker或者NM保存

--仍然启用skipping mode,任务继续运行,但是跳过上一次尝试失败的坏记录

在默认情况下,skipping mode是关闭的 可以用SkipBadRecords类来启用该功能。

并且默认情况下,skipping mode仅能跳过一次坏记录,为了跳过足够多的坏记录,需要增加task attempt次数。

Hadoop检测出来的坏记录以序列文件的形式保存在_logs/skip子目录下的作业输出目录中。

4)任务执行环境

Hadoop为map任务和reduce任务提供运行环境相关信息。

5)任务附属文件

要确保同一个任务的多个实例不会向同一个文件进行写操作,要避免以下问题:

--如果任务失败并被重试,那么第二个任务运行时原来的部分输出依旧是存在的,所以应先删除原来的文件

--在启用推测执行的情况下,同一任务的两个实例会同时写一个文件

解决方式

--将输出写到这一任务尝试特定的临时文件夹,一旦任务成功完成,该目录的内容就复制到作业的输出目录。如果一个任务失败并被重试,第一个任务尝试的部分输出就会被清除。

--任务和该任务的推测实例位于不同的工作目录,并且只有第一个完成的任务才会把其工作目录中的内容传到输出目录,其它的都会被丢弃。

6.参考资料

http://www.cnblogs.com/biyeymyhjob/archive/2012/08/11/2631750.html

Hadoop权威指南

猜你喜欢

转载自siyuan-zhu.iteye.com/blog/2038610