前言 :本文旨在理清在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收到了来自TaskTracker的心跳后,是如何选择任务的呢?是通过assignTasks方法。下面详细分析该方法。在分析之前,首先提一下Hadoop的调度器调度模型。通常情况下,Hadoop会以队列为单位管理作业和资源。有了队列就产生所谓三级调度模型:调度器依次选择一个队列,队列中的一个作业,作业中的一个任务,最终将任务分配给有空闲slot的TaskTracker。assignTasks的实现也遵循这个模型:
  1. Collection<JobInProgress> jobQueue = jobQueueJobInProgressListener.getJobQueue();

对于FIFO调度器而言,队列即为对应监听器中使用的作业队列。然后,声明一个列表,用于保存选择的任务:

  1. // Assigned tasks
  2. List<Task> assignedTasks = new ArrayList<Task>();

接下来,计算队列中正在运行的和等待运行的map和reduce任务的数量:

  1. // Compute (running + pending) map and reduce task numbers across pool
  2. int remainingReduceLoad = 0;
  3. int remainingMapLoad = 0;
  4. synchronized (jobQueue) {
  5. for (JobInProgress job : jobQueue) {
  6. if (job.getStatus().getRunState() == JobStatus.RUNNING) {
  7. remainingMapLoad += (job.desiredMaps() - job.finishedMaps());
  8. if (job.scheduleReduces()) {
  9. remainingReduceLoad +=
  10. (job.desiredReduces() - job.finishedReduces());
  11. }
  12. }
  13. }
  14. }

其中,job.scheduleReduces方法判断当前map任务的总体进度是否满足reduce任务开始调度的条件,map任务完成的比例是否超过变量mapred.reduce.slowstart.completed.maps的值,若超过则计算reduce任务的剩余任务数。接下来,计算map和reduce任务的负载因子:

  1. // Compute the 'load factor' for maps and reduces
  2. double mapLoadFactor = 0.0;
  3. if (clusterMapCapacity > 0) {
  4. mapLoadFactor = (double)remainingMapLoad / clusterMapCapacity;
  5. }
  6. double reduceLoadFactor = 0.0;
  7. if (clusterReduceCapacity > 0) {
  8. reduceLoadFactor = (double)remainingReduceLoad / clusterReduceCapacity;
  9. }

map任务负载因子定义为当前剩余的(正在执行的和等待开始的)map任务的总数与集群总的map资源数(map slot数目)的商值。reduce任务负载因子同理。计算负载因子的目的是根据TaskTracker的负载情况和集群总的负载情况将所有任务均衡地调度到各个TaskTracker以便均衡地使用各个结点上的资源。根据这种思想,可以计算出某个TaskTracker当前可用的slot数目:

  1. final int trackerCurrentMapCapacity =
  2. Math.min((int)Math.ceil(mapLoadFactor * trackerMapCapacity),
  3. trackerMapCapacity);
  4. int availableMapSlots = trackerCurrentMapCapacity - trackerRunningMaps;
  5. boolean exceededMapPadding = false;
  6. if (availableMapSlots > 0) {
  7. exceededMapPadding =
  8. exceededPadding(true, clusterStatus, trackerMapCapacity);
  9. }

由此可见,可用slot定义为:根据集群总体负载均衡还有多少slot应该可用的数目减去实际已经在用的slot数目。注意,exceededMapPadding表示是否有足够的slot预留给推测执行的任务。所谓推测执行,是Hadoop为了防止某些任务执行过慢,为一些较慢任务启动一个备份任务,让该任务做相同的事情,并最终选用最先成功运行完成的任务计算结果为最终结果。推测执行机制日后关注。下面就是任务选择过程:

  1. int numLocalMaps = 0;
  2. int numNonLocalMaps = 0;
  3. scheduleMaps:
  4. for (int i=0; i < availableMapSlots; ++i) {
  5. synchronized (jobQueue) {
  6. for (JobInProgress job : jobQueue) {
  7. if (job.getStatus().getRunState() != JobStatus.RUNNING) {
  8. continue;
  9. }
  10. Task t = null;
  11. // Try to schedule a node-local or rack-local Map task
  12. t =
  13. job.obtainNewNodeOrRackLocalMapTask(taskTrackerStatus,
  14. numTaskTrackers, taskTrackerManager.getNumberOfUniqueHosts());
  15. if (t != null) {
  16. assignedTasks.add(t);
  17. ++numLocalMaps;
  18. // Don't assign map tasks to the hilt!
  19. // Leave some free slots in the cluster for future task-failures,
  20. // speculative tasks etc. beyond the highest priority job
  21. if (exceededMapPadding) {
  22. break scheduleMaps;
  23. }
  24. // Try all jobs again for the next Map task
  25. break;
  26. }
  27. // Try to schedule a node-local or rack-local Map task
  28. t =
  29. job.obtainNewNonLocalMapTask(taskTrackerStatus, numTaskTrackers,
  30. taskTrackerManager.getNumberOfUniqueHosts());
  31. if (t != null) {
  32. assignedTasks.add(t);
  33. ++numNonLocalMaps;
  34.  
  35. // We assign at most 1 off-switch or speculative task
  36. // This is to prevent TaskTrackers from stealing local-tasks
  37. // from other TaskTrackers.
  38. break scheduleMaps;
  39. }
  40. }
  41. }
  42. }
  43. int assignedMaps = assignedTasks.size();

对于某个空闲的slot,从队列中选择一个正在执行的作业,并调用obtainNewNodeOrRackLocalMapTask方法获得一个具有数据本地性地任务。若找到了这样的任务,将其放入结果列表中,并检查刚才获得的exceedingMapPadding的值。若不满足,则跳出最外层循环,重新为每个slot分配任务,以期有新的空闲slot产生,从而满足推测执行的需求。当找到一个数据本地性任务后,马上跳出对队列的遍历,为下一个slot分配任务。

若没有找到具有数据本地性的任务,就调用obtainNewNonLocalMapTask方法获取一个非本地性的任务。如果找到了这样的任务,就将其放入结果列表中,然后跳出最外层循环,重新为每个slot分配任务。也就是说,一旦找到了一个非本地性任务,那么不能再继续获取任务,防止对于其他slot来说具有本地性地任务被抢夺。
这里解释一下数据本地性。在分布式环境中,为了减少任务执行过程中的网络传输开销,通常将任务调度到输入数据所在的计算节点,也就是让数据在本地进行计算 【1】 。Hadoop中数据本地性有三个等级:node-local(同节点),rack-local(同机架)和off-switch(跨机架)。选择任务时即按照上述顺序依次进行。

  1. int target = findNewMapTask(tts, clusterSize, numUniqueHosts, maxLevel,
  2. status.mapProgress());
  3. if (target == -1) {
  4. return null;
  5. }
  6. Task result = maps[target].getTaskToRun(tts.getTrackerName());
  7. if (result != null) {
  8. addRunningTaskToTIP(maps[target], result.getTaskID(), tts, true);
  9. resetSchedulingOpportunities();
  10. }
  11. return result;
其中,findNewMapTask方法的第四个参数指定了获取任务的本地性等级,maxLevel表示最高。在obtainNewNonLocalMapTask方法中则使用的是

NON_LOCAL_CACHE_LEVEL。在findNewMapTask方法中可以看到,运行失败的任务总是被优先选择,让它们能够快速重新执行;然后按照数据本地性选择尚未运行的任务;最后是查找正在运行的任务,为较慢的任务启动备份(推测执行)。有兴趣可以看源码这里不展示了。

对于reduce任务来说选择过程十分类似,只不过reduce任务不涉及数据本地性,因为它的输入来自map任务的输出,来自所有map任务的结点。
  1. synchronized (jobQueue) {
  2. for (JobInProgress job : jobQueue) {
  3. if (job.getStatus().getRunState() != JobStatus.RUNNING ||
  4. job.numReduceTasks == 0) {
  5. continue;
  6. }
  7. Task t =
  8. job.obtainNewReduceTask(taskTrackerStatus, numTaskTrackers,
  9. taskTrackerManager.getNumberOfUniqueHosts()
  10. );
  11. if (t != null) {
  12. assignedTasks.add(t);
  13. break;
  14. }
  15. // Don't assign reduce tasks to the hilt!
  16. // Leave some free slots in the cluster for future task-failures,
  17. // speculative tasks etc. beyond the highest priority job
  18. if (exceededReducePadding) {
  19. break;
  20. }
  21. }
  22. }

注意,每一次心跳只分配一个reduce任务。
最后,我们关注一下当要执行的任务获得以后,如何返回给TaskTracker,以及JobTracker下达的一些命令。

重新来看心跳方法heartbeat。它的返回值是一个HeartbeatResponse类型,其中有一个重要的字段:
  1. TaskTrackerAction[] actions;

这个数组就用于JobTracker向TaskTracker下达命令,包括执行刚刚选择的任务的指令。具体的命令种类有以下五种:

1. ReinitTrackerAction
2. LaunchTaskAction
3. KillTaskAction
4. KillJobAction
5. CommitTaskAction
两种情况下JobTracker会下达ReinitTrackerAction命令:丢失上次心跳应答信息或者丢失TaskTracker状态信息。这两种状态为不一致状态。
  1. short newResponseId = (short)(responseId + 1);
  2. status.setLastSeen(now);
  3. if (!processHeartbeat(status, initialContact, now)) {
  4. if (prevHeartbeatResponse != null) {
  5. trackerToHeartbeatResponseMap.remove(trackerName);
  6. }
  7. return new HeartbeatResponse(newResponseId,
  8. new TaskTrackerAction[] {new ReinitTrackerAction()});
  9. }
LaunchTaskAction命令即包含了需要执行的任务。JobTracker在选择任务时首先选择的是辅助型任务,例如job-cleanup task,task-cleanup task和job-setup task。这些任务在调用assignTasks方法之前就已经选择,因此优先级最高。
  1. List<Task> tasks = getSetupAndCleanupTasks(taskTrackerStatus);
  2. if (tasks == null ) {
  3. tasks = taskScheduler.assignTasks(taskTrackers.get(trackerName));
  4. }
  5. if (tasks != null) {
  6. for (Task task : tasks) {
  7. expireLaunchingTasks.addNewTask(task.getTaskID());
  8. actions.add(new LaunchTaskAction(task));
  9. }
  10. }
KillTaskAction封装了需要杀死的任务。杀死的原因可能是任务失败,用户通过kill命令杀死等。KillJobAction封装了待清理的作业。清理的工作主要是删除临时目录。作业完成或失败时都会导致该作业被清理。最后,CommitTaskAction封装了需要提交的任务。Hadoop将一个成功运行完成的Task Attempt(一个任务的多个备份任务)结果文件从临时目录转移到最终目录的过程称为任务提交。后三种命令生成的代码如下:
  1. // Check for tasks to be killed
  2. List<TaskTrackerAction> killTasksList = getTasksToKill(trackerName);
  3. if (killTasksList != null) {
  4. actions.addAll(killTasksList);
  5. }
  6.  
  7. // Check for jobs to be killed/cleanedup
  8. List<TaskTrackerAction> killJobsList = getJobsForCleanup(trackerName);
  9. if (killJobsList != null) {
  10. actions.addAll(killJobsList);
  11. }
  12.  
  13. // Check for tasks whose outputs can be saved
  14. List<TaskTrackerAction> commitTasksList = getTasksToSave(status);
  15. if (commitTasksList != null) {
  16. actions.addAll(commitTasksList);
  17. }

至此,任务调度功流程大体框架全部结束,接下来就是任务在TaskTracker上的具体执行过程了。请关注后续文章。






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

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

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

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

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

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

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

  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. Java并发编程总结2——慎用CAS(转)

    一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实 ...

  2. Sublime Text 2 - There are no packages available for installation

    解决Sublime Text 2 package Control 无法安装插件的问题 错误提示 here are no packages available for installation 问题解决 ...

  3. Tree(未解决。。。)

    Tree Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submi ...

  4. 网络收发之cycleBuf

    #pragma once #include <iostream> #include <string> class cyclebuffer { protected: volati ...

  5. iOS开发中遇到的bug

    报错:The operation couldn’t be completed. (LaunchServicesError error 0.) 解决办法:重置模拟器

  6. Resource is out of sync with the file system: 解决办法

    在eclipse中,启动server时报此错,是因为文件系统不同步造成的,解决方法有两个: (1)选中工程,右键,选择F5(手动刷新): (2)Window->Preferences->G ...

  7. ajax.js

    /**通用ajax服务的定义对象 * services可以是单个服务对象,也可以是service服务数组 * 具体服务的定义请参考appendServices成员函数 */ function Serv ...

  8. ThinkPHP第三天(公共函数Common加载,dump定义,模板文件,定义替换__PUBLIC__)

    1.公共函数定义 自动加载:在项目的common文件夹中定义,公共函数文件命名规则为common.php,只有命名成common.php才能被自动载入. 动态加载:可以修改配置项‘LOAD_EXT_F ...

  9. Regex阅读笔记(三)之固化分组

    符号:?> 使用?>的匹配与正常的匹配无区别,但是如果匹配进行到此结构之后,此结构体的所有备用状态都会放弃,也就是括号内的子表达式中未尝试过的备用状态都不复存在了. 例如'(\.\d\d( ...

  10. C# 一些小东东

    在C#中,如果有一个方法我们不想继续使用,需要废弃的时候,可以在该方法前面加上一个[Obsolete]. string[] arr={"a","b"} if $ ...