从高层看,有四个独立的实体:
- 客户端,提交MapReduce任务
- jobtracker,协调任务的运行。jobtracker是一个Java应用,它的主类是JobTracker
- tasktrackers,运行被分割的任务。tasktrackers是一个Java应用,它的主类是TaskTracker
- 分布式文件系统(通常是HDFS),用来在其它实体之间共享文件
Job提交
JobClient中的runjob()方法是一个创建JobClient实例并调用它的submitJob()方法,在提交job之后,runJob()一秒钟获取一次任务执行进度的报告,并且在与上次报告不同的情况下,像控制台发送进度信息。当任务完成,如果是成功的,job计数器显示出来。否则,任务中引发错误的原因被记录到控制台上。
JobClient的submitJob()方法实现了任务提交过程,它做了如下的工作:
- 向jobtracker询问一个新的job ID(通过调用JobTracker上的getNewJobId())
- 检查job的输出明细。比如,输出目录不存在或者指定的输出目录是否已经存,这样,这个任务就不会被提交并返回一个错误给 MapReduce程序。
- 计算任务的输入分割。如果不能计算分割,比如,没有指定输入路径,然后这个任务就不会被提交并返回一个错误给MapReduce 程序
- 拷贝这个任务运行所需要的资源——包括任务的jar文件,配置文件,还有计算出的输入分割——到jobtracker的一个目录下,这 个目录以这个job ID开始。任务jar以一个高的复制因子被复制(通过mapred.submit.replication属性来设置,默认是10), 这样当它们为这个job运行tasks的时候,在集群中就有很多的拷贝。
- 告诉jobtracker,任务已经准备好运行(通过调用JobTracker的submitJob())
Job初始化
当JobTracker的submitJob()方法被调用后,它将它放在一个内部队列里,job计划会提取它并初始化它。初始化涉及创建一个代表正在运行的job的对象,这个对象封装了它的tasks,并获取相关信息来跟踪tasks的状态和进度。
创建一个要运行的tasks列表,job计划首先取出JobClient从共享文件系统中计算出来的输入分割。然后为每个分割创建一个map。reduce task的个数是由JonConf的mapred.reduce.tasks属性设定的,通过setNumReducetasks()方法来设定,然后计划就简单地创建这个数量的reduce task。tasks这个时候就被分配ID了。
Task分配
TaskTrackers运行一个循环,周期性的向jobtracker发送心跳。心跳向jobtracker证明这个tasktracker是活着的,但是它们也作为信息的一个通道。作为心跳的一部分,一个tasktracker说明是否为一个新的task的运行做好了准备,如果做好了,jobtracker将安排一个task,并且通过心跳的返回值来和tasktracker通信。
jobtracker在能够选择一个任务给一个tasktracker之前,jobtracker必须选择一个job来提取task。有很多的计划算法,默认的是简单地维护一个优先级的job列表。选择一个job之后,jobtracker就开始选择这个job的task。
Tasktrackers有固定数量的map和reduce槽:比如,一个tasktracker可能能并行运行两个map任务和2个reduce任务。默认的计划是在reduce任务槽填满之前填满map task槽,所以如果tasktracker有至少一个map任务槽,jobtracker将选择一个map任务;否则,将选择一个reduce任务。
要选择一个reduce任务,jobtracker简单的从它的准备好执行的reduce任务列表中抽取下一个,因为这里没有数据位置的考虑。然而,对于一个map任务,要考虑tasktracker的网络位置并选择一个与tasktracker最近的输入分割。乐观情况下,任务是rack-local的:和这个分割在同一个rack上,但是不是同样的节点。一些task既不是data-local也不是rack-local,而是从不同的任务在运行的rack上获取数据。你可以通过查看一个job的counters读到每种类型task的比例。
任务执行
现在这个tasktracker已经被分配了task,下一步它就要运行这个task了。首先,它通过将job jar包从共享文件系统拷贝到tasktracker
的文件系统上。它也从分布式缓存拷贝任何这个应用需要的文件到本地磁盘;第二,它为这个task创建一个本地工作目录,解压jar的内容到这个
目录下;第三,创建一个TaskRunner实例来运行这个task。
TaskRunner载入一个新的java虚拟机来运行每个task,所以用户定义的map和reduce中有任何的bug都不会影响到tasktracker。然而
,可以在task间重用JVM。