前言 :本文旨在理清在Hadoop中一个MapReduce作业(Job)在提交到框架后的整个生命周期过程,权作总结和日后参考,如有问题,请不吝赐教。本文不涉及Hadoop的架构设计,如有兴趣请参考相关书籍和文献。在梳 理过程中,我对一些感兴趣的源码也会逐行研究学习,以期强化基础。

作者
:Jaytalent

开始日期
:2013年9月9日

参考资料:【1】《Hadoop技术内幕--深入解析MapReduce架构设计与实现原理》董西成
                  【2】   Hadoop 1.0.0 源码
                            【3】《Hadoop技术内幕--深入解析Hadoop Common和HDFS架构设计与实现原理》蔡斌 陈湘萍
上一篇文章谈到作业的初始化,经过初始化的作业就可以被调度器调度并开始执行了。这篇文章关注调度器的工作原理。
一个MapReduce作业的生命周期大体分为5个阶段
【1】:
1. 作业提交与初始化
2.
任务调度与监控
3. 任务运行环境准备
4. 任务执行
5. 作业完成

我们假设JobTracker已经启动,那么调度器是怎么启动的?JobTracker在启动时有以下代码:

JobTracker tracker = startTracker(new JobConf());
tracker.offerService();

其中offerService方法负责启动JobTracker提供的各个服务,有这样一行代码:

taskScheduler.start();

taskScheduler即为任务调度器。start方法是抽象类TaskScheduler提供的接口,用于启动调度器。每个调度器类都要继承TaskScheduler类。回忆一下,调度器启动时会将各个监听器对象注册到JobTracker,以FIFO调度器JobQueueTaskScheduler为例:

  @Override
public synchronized void start() throws IOException {
super.start();
taskTrackerManager.addJobInProgressListener(jobQueueJobInProgressListener);
eagerTaskInitializationListener.setTaskTrackerManager(taskTrackerManager);
eagerTaskInitializationListener.start();
taskTrackerManager.addJobInProgressListener(
eagerTaskInitializationListener);
}

这里注册了两个监听器,其中eagerTaskInitializationListener负责作业初始化,而jobQueueJobInProgressListener则负责作业的执行和监控。当有作业提交到JobTracker时,JobTracker会执行所有订阅它消息的监听器的jobAdded方法。对于 eagerTaskInitializationListener来说:

  @Override
public void jobAdded(JobInProgress job) {
synchronized (jobInitQueue) {
jobInitQueue.add(job);
resortInitQueue();
jobInitQueue.notifyAll();
}
}

提交的作业的JobInProgress对象被添加到作业初始化队列jobInitQueue中,并唤醒初始化线程(若原来没有作业可以初始化):

class JobInitManager implements Runnable {
public void run() {
JobInProgress job = null;
while (true) {
try {
synchronized (jobInitQueue) {
while (jobInitQueue.isEmpty()) {
jobInitQueue.wait();
}
job = jobInitQueue.remove(0);
}
threadPool.execute(new InitJob(job));
} catch (InterruptedException t) {
LOG.info("JobInitManagerThread interrupted.");
break;
}
}
threadPool.shutdownNow();
}
}

这种工作方式是一种“生产者-消费者”模式:作业初始化线程是消费者,而监听器 eagerTaskInitializationListener是生产者。这里可以有多个消费者线程,放到一个固定资源的线程池中,线程个数通过mapred.jobinit.threads参数配置,默认为4个。

下面我们重点来看调度器中的另一个监听器。 jobQueueJobInProgressListener对象在调度器中初始化时连续执行了两个构造器完成初始化:
 public JobQueueJobInProgressListener() {
this(new TreeMap<JobSchedulingInfo,
JobInProgress>(FIFO_JOB_QUEUE_COMPARATOR));
}
/**
* For clients that want to provide their own job priorities.
* @param jobQueue A collection whose iterator returns jobs in priority order.
*/
protected JobQueueJobInProgressListener(Map<JobSchedulingInfo,
JobInProgress> jobQueue) {
this.jobQueue = Collections.synchronizedMap(jobQueue);
}

其中,第一个构造器调用重载的第二个构造器。可以看到,调度器使用一个队列jobQueue来保存提交的作业。这个队列使用一个TreeMap对象实现,TreeMap的特点是底层使用红黑树实现,可以按照键来排序,并且由于是平衡树,效率较高。作为键的是一个JobSchedulingInfo对象,作为值就是提交的作业对应的JobInProgress对象。另外,由于TreeMap本身不是线程安全的,这里使用了集合类的同步方法构造了一个线程安全的Map。使用带有排序功能的数据结构的目的是使作业在队列中按照优先级的大小排列,这样每次调度器只需从队列头部获得作业即可。

作业的顺序由优先级决定,而优先级信息包含在JobSchedulingInfo对象中:

static class JobSchedulingInfo {
private JobPriority priority;
private long startTime;
private JobID id;
...
}
该对象包含了作业的优先级、ID和开始时间等信息。在Hadoop中,作业的优先级有以下五种:VERY_HIGH、HIGH、NORMAL、LOW、VERY_LOW。这些字段是通过作业的JobStatus对象初始化的。由于该对象作为TreeMap的键,因此要实现自己的equals方法和hashCode方法:
    @Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != JobSchedulingInfo.class) {
return false;
} else if (obj == this) {
return true;
}
else if (obj instanceof JobSchedulingInfo) {
JobSchedulingInfo that = (JobSchedulingInfo)obj;
return (this.id.equals(that.id) &&
this.startTime == that.startTime &&
this.priority == that.priority);
}
return false;
}

我们看到,两个JobSchedulingInfo对象相等的条件是类型一致,并且作业ID、开始时间和优先级都相等。hashCode的计算比较简单:

    @Override
public int hashCode() {
return (int)(id.hashCode() * priority.hashCode() + startTime);
}

注意,监听器的第一个构造器有一个比较器参数,用于定义 JobSchedulingInfo的比较方式:

    static final Comparator<JobSchedulingInfo> FIFO_JOB_QUEUE_COMPARATOR
= new Comparator<JobSchedulingInfo>() {
public int compare(JobSchedulingInfo o1, JobSchedulingInfo o2) {
int res = o1.getPriority().compareTo(o2.getPriority());
if (res == 0) {
if (o1.getStartTime() < o2.getStartTime()) {
res = -1;
} else {
res = (o1.getStartTime() == o2.getStartTime() ? 0 : 1);
}
}
if (res == 0) {
res = o1.getJobID().compareTo(o2.getJobID());
}
return res;
}
};

从上面看出,首先比较作业的优先级,若优先级相等则比较开始时间(FIFO),若再相等则比较作业ID。 我们在实现自己的调度器时可能要定义自己的作业队列,那么作业在队列中的顺序(即JobSchedulingInfo的比较器)就要仔细定义,这是调度器能够正常运行基础。

Hadoop中的作业调度采用pull方式,即TaskTracker定时向JobTracker发送心跳信息索取一个新的任务,这些信息包括数据结点上作业和任务的运行情况,以及该TaskTracker上的资源使用情况。JobTracker会依据以上信息更新作业队列的状态,并调用调度器选择一个或多个任务以心跳响应的形式返回给TaskTracker。从上面描述可以看出,JobTracker和taskScheduler之间的互相利用关系:前者利用后者为TaskTracker分配任务;后者利用前者更新队列和作业信息。接下来,我们一步步详述该过程。
首先,当一个心跳到达JobTracker时(实际上这是一个来自TaskTracker的远程过程调用heartbeat方法,协议接口是InterTrackerProtocol),会执行两种动作:更新状态和下达命令 【1】 。下达命令稍后关注。有关更新状态的一些代码片段如下:
    if (!processHeartbeat(status, initialContact, now)) {
if (prevHeartbeatResponse != null) {
trackerToHeartbeatResponseMap.remove(trackerName);
}
return new HeartbeatResponse(newResponseId,
new TaskTrackerAction[] {new ReinitTrackerAction()});
}

具体的心跳处理,由私有函数processHeartbeat完成。该函数中有以下两个方法调用:

    updateTaskStatuses(trackerStatus);
updateNodeHealthStatus(trackerStatus, timeStamp);

分别用来更新任务的状态和结点的健康状态。在第一个方法中有下面代码片段:

      TaskInProgress tip = taskidToTIPMap.get(taskId);
// Check if the tip is known to the jobtracker. In case of a restarted
// jt, some tasks might join in later
if (tip != null || hasRestarted()) {
if (tip == null) {
tip = job.getTaskInProgress(taskId.getTaskID());
job.addRunningTaskToTIP(tip, taskId, status, false);
} // Update the job and inform the listeners if necessary
JobStatus prevStatus = (JobStatus)job.getStatus().clone();
// Clone TaskStatus object here, because JobInProgress
// or TaskInProgress can modify this object and
// the changes should not get reflected in TaskTrackerStatus.
// An old TaskTrackerStatus is used later in countMapTasks, etc.
job.updateTaskStatus(tip, (TaskStatus)report.clone());
JobStatus newStatus = (JobStatus)job.getStatus().clone();
// Update the listeners if an incomplete job completes
if (prevStatus.getRunState() != newStatus.getRunState()) {
JobStatusChangeEvent event =
new JobStatusChangeEvent(job, EventType.RUN_STATE_CHANGED,
prevStatus, newStatus);
updateJobInProgressListeners(event);
}
} else {
LOG.info("Serious problem. While updating status, cannot find taskid "
+ report.getTaskID());
}

这里的job对象通过从TaskTracker那里得到的task状态信息中抽取出来。注意,这里拷贝了原有作业状态的一个副本,然后修改这个副本的相关信息,调用的是updateJobStatus方法,更新任务的状态信息和JobInProgress的相关信息,如map和reduce任务的进度等,这里不展开了。这些信息的更新可以为调度器的工作提供依据。

作业状态的更新是通过updateJobInProgressListeners方法实现,该方法的参数是一个JobStatusChangeEvent对象,表示作业状态变化的事件。这种事件的类型可以是运行状态改变、开始时间改变、优先级改变等等。用户也可以根据需要自定义事件类型。事件对象维护了两个JobStatus对象,分别表示事件发生前后作业的状态。
进入该方法后,我们又看到了熟悉的观察者模式:
  // Update the listeners about the job
// Assuming JobTracker is locked on entry.
private void updateJobInProgressListeners(JobChangeEvent event) {
for (JobInProgressListener listener : jobInProgressListeners) {
listener.jobUpdated(event);
}
}

这次每个监听器要回调jobUpdated方法,表示作业有更新。对于 jobQueueJobInProgressListener来说是这样做的:

  @Override
public synchronized void jobUpdated(JobChangeEvent event) {
JobInProgress job = event.getJobInProgress();
if (event instanceof JobStatusChangeEvent) {
// Check if the ordering of the job has changed
// For now priority and start-time can change the job ordering
JobStatusChangeEvent statusEvent = (JobStatusChangeEvent)event;
JobSchedulingInfo oldInfo =
new JobSchedulingInfo(statusEvent.getOldStatus());
if (statusEvent.getEventType() == EventType.PRIORITY_CHANGED
|| statusEvent.getEventType() == EventType.START_TIME_CHANGED) {
// Make a priority change
reorderJobs(job, oldInfo);
} else if (statusEvent.getEventType() == EventType.RUN_STATE_CHANGED) {
// Check if the job is complete
int runState = statusEvent.getNewStatus().getRunState();
if (runState == JobStatus.SUCCEEDED
|| runState == JobStatus.FAILED
|| runState == JobStatus.KILLED) {
jobCompleted(oldInfo);
}
}
}
}

首先,获取作业更新 前 的状态。然后根据事件的类型,进行相应的处理。比如,如果优先级变化了,则要重新排列队列中作业的顺序。这里直接取出原有作业,重新插入队列。插入后,作业会自动重新排序,体现了TreeMap的优越性。再比如,如果作业状态变为完成,那么就从队列中删除该作业。

  private void reorderJobs(JobInProgress job, JobSchedulingInfo oldInfo) {
synchronized (jobQueue) {
jobQueue.remove(oldInfo);
jobQueue.put(new JobSchedulingInfo(job), job);
}
}

下面就是调度器中最关键的一步了:任务选择。此时,作业队列中信息已经更新完毕,可以选择一些任务返回给TaskTracker执行了。heartbeat方法接下来会有这样的代码:

  List<Task> tasks = getSetupAndCleanupTasks(taskTrackerStatus);
if (tasks == null ) {
tasks = taskScheduler.assignTasks(taskTrackers.get(trackerName));
}

如果不需要setup和cleanup,就说明需要选择map或reduce任务。调用TaskScheduler的assignTasks方法完成任务选择。由于篇幅限制,我打算将这部分内容放到下一篇文章中,并关注heartbeat中JobTracker下达的命令过程以及JobInProgress和TaskInProgress对调度有影响的一些字段。请看下一篇文章: MapReduce调度与执行原理之任务调度(续)

MapReduce调度与执行原理之任务调度的更多相关文章

  1. MapReduce调度与执行原理之任务调度(续)

    前言 :本文旨在理清在Hadoop中一个MapReduce作业(Job)在提交到框架后的整个生命周期过程,权作总结和日后参考,如有问题,请不吝赐教.本文不涉及Hadoop的架构设计,如有兴趣请参考相关 ...

  2. MapReduce调度与执行原理之作业初始化

    前言 :本文旨在理清在Hadoop中一个MapReduce作业(Job)在提交到框架后的整个生命周期过程,权作总结和日后参考,如有问题,请不吝赐教.本文不涉及Hadoop的架构设计,如有兴趣请参考相关 ...

  3. MapReduce调度与执行原理系列文章

    转自:http://blog.csdn.net/jaytalent?viewmode=contents MapReduce调度与执行原理系列文章 一.MapReduce调度与执行原理之作业提交 二.M ...

  4. MapReduce调度与执行原理之作业提交

    前言 :本文旨在理清在Hadoop中一个MapReduce作业(Job)在提交到框架后的整个生命周期过程,权作总结和日后参考,如有问题,请不吝赐教.本文不涉及Hadoop的架构设计,如有兴趣请参考相关 ...

  5. erlang虚拟机代码执行原理

     转载:http://blog.csdn.NET/mycwq/article/details/45653897 erlang是开源的,很多人都研究过源代码.但是,从erlang代码到c代码,这是个不小 ...

  6. springmvc执行原理及自定义mvc框架

    springmvc是spring的一部分,也是一个优秀的mvc框架,其执行原理如下: (1)浏览器提交请求经web容器(比如tomcat)转发到中央调度器dispatcherServlet. (2)中 ...

  7. MapReduce作业的执行流程

    MapReduce任务执行总流程 一个MapReduce作业的执行流程是:代码编写 -> 作业配置 -> 作业提交 -> Map任务的分配和执行 -> 处理中间结果 -> ...

  8. Hadoop架构设计、执行原理具体解释

    1.Map-Reduce的逻辑过程 如果我们须要处理一批有关天气的数据.其格式例如以下: 依照ASCII码存储.每行一条记录 每一行字符从0開始计数,第15个到第18个字符为年 第25个到第29个字符 ...

  9. Golang调度器GMP原理与调度全分析(转 侵 删)

    该文章主要详细具体的介绍Goroutine调度器过程及原理,包括如下几个章节. 第一章 Golang调度器的由来 第二章 Goroutine调度器的GMP模型及设计思想 第三章 Goroutine调度 ...

随机推荐

  1. SQL中日期格式转换为年月日

    )

  2. 动态加载 js

    要实现动态加载JS脚本有4种方法: 1.直接document.write <script language="javascript"> document.write(& ...

  3. Ueditor和CKeditor 两款编辑器的使用与配置

    一丶ueditor 百度编辑器 1.官方文档,演示,下载地址:http://ueditor.baidu.com/website/index.html 2.百度编辑器的好:Editor是由百度web前端 ...

  4. G - 好老师

    G - 好老师 Time Limit:1000MS     Memory Limit:131072KB     64bit IO Format:%lld & %llu Submit Statu ...

  5. Hadoop学习之Mapreduce执行过程详解

    一.MapReduce执行过程 MapReduce运行时,首先通过Map读取HDFS中的数据,然后经过拆分,将每个文件中的每行数据分拆成键值对,最后输出作为Reduce的输入,大体执行流程如下图所示: ...

  6. HelloX项目github协同开发指南

    概述 为了提高协同开发效率,HelloX项目已托管到github网站上.根据目前的开发进展,创建了下列几个子项目: HelloX操作系统内核项目:https://github.com/hellox-p ...

  7. android之获取应用中的图片资源_获取找你妹中的图片资源

    一直不知道原来获取一个应用中的图片资源这么简单,刚才直接把apk解压,就得到了里面的一下文件,搜索一下就全部把图片资源找出来了,想要模仿应用或者自己不会ui的话,用现成的资源方便多了. 也没多少说的, ...

  8. Static block start new thread

    Static block start new thread public class StaticThreadInit { static{ Threadt = newThread(){ public ...

  9. 基于mini2440的看门狗(裸机)

    在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入停滞状态,发生 ...

  10. 图画hadoop -- 生态圈