上一篇文章浅析了FairScheduler的assignTasks()方法,介绍了FairScheduler任务调度的原理。略过了最后一步通过JobScheduler获取Task时调用JobInProgress的五个方法:obtainNewNodeLocalMapTask(),obtainNewNodeOrRackLocalMapTask(),obtainNewMapTask(),obtainNewReduceTask()。这篇文章将对这四个方法进行简单的源代码解析。obtainNewNodeLocalMapTask(),obtainNewNodeOrRackLocalMapTask(),obtainNewMapTask()这三个方法都是选择一个Map任务,其内部调用的方法也是一样的,都是obtainNewMapTaskCommon()方法,不同的只是maxCacheLevel参数值不同:

obtainNewNodeLocalMapTask():maxCacheLevel==1;

obtainNewNodeOrRackLocalMapTask:maxCacheLevel==maxLevel;

obtainNewMapTask:maxCacheLevel==anyCacheLevel(maxLevel+1)。

下面着重分析obtainNewMapTaskCommon()方法。

1.JobInProgress.obtainNewMapTaskCommon():

  1. public synchronized Task obtainNewMapTaskCommon(
  2. TaskTrackerStatus tts, int clusterSize, int numUniqueHosts,
  3. int maxCacheLevel) throws IOException {
  4. if (!tasksInited) {
  5. LOG.info("Cannot create task split for " + profile.getJobID());
  6. try { throw new IOException("state = " + status.getRunState()); }
  7. catch (IOException ioe) {ioe.printStackTrace();}
  8. return null;
  9. }
  10.  
  11. int target = findNewMapTask(tts, clusterSize, numUniqueHosts, maxCacheLevel,
  12. status.mapProgress());
  13. if (target == -1) {
  14. return null;
  15. }
  16.  
  17. Task result = maps[target].getTaskToRun(tts.getTrackerName());
  18. if (result != null) {
  19. addRunningTaskToTIP(maps[target], result.getTaskID(), tts, true);
  20. // DO NOT reset for off-switch!
  21. if (maxCacheLevel != NON_LOCAL_CACHE_LEVEL) {
  22. resetSchedulingOpportunities();
  23. }
  24. }
  25. return result;
  26. }

首先判断Job是否初始化,即tasksInited是否为true,未初始化则不调度任务。接着调用findNewMapTask()方法获取一个新的Map任务,同时将maxCacheLevel参数传递过去,该参数的作用是选择不同LocalLevel的Map任务。下面看看findNewMapTask()方法。

2.JobInProgress.findNewMapTask():

首先介绍下该方法的返回值,该方法不是直接返回一个MapTask,而是返回一个maps[]数组的索引值,这个maps[]数组在Job初始化时创建,存放该Job所有的Map任务,根据索引值就可以知道对应的MapTask。

  1. if (numMapTasks == 0) {
  2. if(LOG.isDebugEnabled()) {
  3. LOG.debug("No maps to schedule for " + profile.getJobID());
  4. }
  5. return -1;
  6. }

首先判断该Job的Map任务数是否为0,numMapTasks是在Job进行初始化(initTasks()方法)时根据输入文件的分片数确定的,即一个Split对应一个Map任务。该值为0表示该Job没有Map任务,所以返回-1,即没有满足条件的Map任务。

  1. String taskTracker = tts.getTrackerName();
  2. TaskInProgress tip = null;
  3.  
  4. //
  5. // Update the last-known clusterSize
  6. //
  7. this.clusterSize = clusterSize;
  8.  
  9. if (!shouldRunOnTaskTracker(taskTracker)) {
  10. return -1;
  11. }

判断是否能够在该TT上运行任务,主要根据该Job在该TT上是否有过失败的任务来判断。下面看看该方法。

3.JobInProgress.shouldRunOnTaskTracker():

  1. private boolean shouldRunOnTaskTracker(String taskTracker) {
  2. //
  3. // Check if too many tasks of this job have failed on this
  4. // tasktracker prior to assigning it a new one.
  5. //
  6. int taskTrackerFailedTasks = getTrackerTaskFailures(taskTracker);
  7. if ((flakyTaskTrackers < (clusterSize * CLUSTER_BLACKLIST_PERCENT)) &&
  8. taskTrackerFailedTasks >= maxTaskFailuresPerTracker) {
  9. if (LOG.isDebugEnabled()) {
  10. String flakyTracker = convertTrackerNameToHostName(taskTracker);
  11. LOG.debug("Ignoring the black-listed tasktracker: '" + flakyTracker
  12. + "' for assigning a new task");
  13. }
  14. return false;
  15. }
  16. return true;
  17. }
  1. private int getTrackerTaskFailures(String trackerName) {
  2. String trackerHostName = convertTrackerNameToHostName(trackerName);
  3. Integer failedTasks = trackerToFailuresMap.get(trackerHostName);
  4. return (failedTasks != null) ? failedTasks.intValue() : 0;
  5. }

该方法从Job中保存的trackerToFailuresMap队列中获取该TT上所有的失败任务数。提一下,trackerToFailuresMap队列信息也是在TT通过心跳向JT时更新的,即updateTaskStatus()方法。flakyTaskTrackers值记录该Job在多少个TT上面失败的任务数大于maxTaskFailuresPerTracker(即一个Job在一个TT上可允许失败的最大数),当一个Job在一个TT上拥有的失败任务数大于maxTaskFailuresPerTracker时则表示该Job不可再在该TT上执行任何任务,但是当一个Job在超过(clusterSize * CLUSTER_BLACKLIST_PERCENT)个TT上失败的话,则不去考虑该Job是否在该TT上失败,因为可能是Job自身的问题,而非单个TT的问题。总之该方法根据Job的失败任务信息来判断是否应该在一个TT上执行任务。

接着返回到JobInProgress.findNewMapTask()方法。

4.JobInProgress.findNewMapTask():

  1. // Check to ensure this TaskTracker has enough resources to
  2. // run tasks from this job
  3. long outSize = resourceEstimator.getEstimatedMapOutputSize();
  4. long availSpace = tts.getResourceStatus().getAvailableSpace();
  5. if(availSpace < outSize) {
  6. LOG.warn("No room for map task. Node " + tts.getHost() +
  7. " has " + availSpace +
  8. " bytes free; but we expect map to take " + outSize);
  9.  
  10. return -1; //see if a different TIP might work better.
  11. }

判断该TT是否有够该Job的Map任务使用的资源,主要是根据该Job已完成的Map任务的输出情况来估算一个Map任务可能的输出大小。

  1. long getEstimatedMapOutputSize() {
  2. long estimate = 0L;
  3. if (job.desiredMaps() > 0) {
  4. estimate = getEstimatedTotalMapOutputSize() / job.desiredMaps();
  5. }
  6. return estimate;
  7. }
  1. protected synchronized long getEstimatedTotalMapOutputSize() {
  2. if(completedMapsUpdates < threshholdToUse) {
  3. return 0;
  4. } else {
  5. long inputSize = job.getInputLength() + job.desiredMaps();
  6. //add desiredMaps() so that randomwriter case doesn't blow up
  7. //the multiplication might lead to overflow, casting it with
  8. //double prevents it
  9. long estimate = Math.round(((double)inputSize *
  10. completedMapsOutputSize * 2.0)/completedMapsInputSize);
  11. if (LOG.isDebugEnabled()) {
  12. LOG.debug("estimate total map output will be " + estimate);
  13. }
  14. return estimate;
  15. }
  16. }

具体估算方法是根据(该Job的输入大小/已完成的Map任务的输入大小)*(该Job已完成的所有Map任务的总输出大小)*2估算出该Job全部Map任务大概的输出大小,然后除以该Job的Map数量即一个Map任务的可能输出大小(至于这些值的跟新基本都是通过心跳通信)。如果TT上可使用的资源小于该Job一个Map任务可能的输出大小则不能在该TT上执行Map任务。

5.JobInProgress.findNewMapTask():

接下来就是该方法的关键部分,首先看下作者们对该部分的一个注释:

  1. // When scheduling a map task:
  2. // 0) Schedule a failed task without considering locality
  3. // 1) Schedule non-running tasks
  4. // 2) Schedule speculative tasks
  5. // 3) Schedule tasks with no location information
  6.  
  7. // First a look up is done on the non-running cache and on a miss, a look
  8. // up is done on the running cache. The order for lookup within the cache:
  9. // 1. from local node to root [bottom up]
  10. // 2. breadth wise for all the parent nodes at max level
  11. // We fall to linear scan of the list ((3) above) if we have misses in the
  12. // above caches

第一部分主要是说明选择任务的顺序:失败的Task(不去考虑本地性),未运行的任务,推测执行的任务,输入文件没有对应的Location信息的任务。第二部分是说明在选择每个任务时对集群上所有节点的遍历方式:自下往上一次遍历以及从根节点横向遍历。

下面来看第一中选择方式:从失败的任务中选择。

  1. // 0) Schedule the task with the most failures, unless failure was on this
  2. // machine
  3. tip = findTaskFromList(failedMaps, tts, numUniqueHosts, false);
  4. if (tip != null) {
  5. // Add to the running list
  6. scheduleMap(tip);
  7. LOG.info("Choosing a failed task " + tip.getTIPId());
  8. return tip.getIdWithinJob();
  9. }

failedMaps中存放着所有的失败任务信息,直接调用findTaskFromList()方法从failedMaps中选择一个任务。下面三种方式也都是调用该方法,不同的只是传入的List不同,所以看下findTaskFromList()方法。

6.JobInProgress.findTaskFromList():

  1. private synchronized TaskInProgress findTaskFromList(
  2. Collection<TaskInProgress> tips, TaskTrackerStatus ttStatus,
  3. int numUniqueHosts,
  4. boolean removeFailedTip) {
  5. Iterator<TaskInProgress> iter = tips.iterator();
  6. while (iter.hasNext()) {
  7. TaskInProgress tip = iter.next();
  8.  
  9. // Select a tip if
  10. // 1. runnable : still needs to be run and is not completed
  11. // 2. ~running : no other node is running it
  12. // 3. earlier attempt failed : has not failed on this host
  13. // and has failed on all the other hosts
  14. // A TIP is removed from the list if
  15. // (1) this tip is scheduled
  16. // (2) if the passed list is a level 0 (host) cache
  17. // (3) when the TIP is non-schedulable (running, killed, complete)
  18. if (tip.isRunnable() && !tip.isRunning()) {
  19. // check if the tip has failed on this host
  20. if (!tip.hasFailedOnMachine(ttStatus.getHost()) ||
  21. tip.getNumberOfFailedMachines() >= numUniqueHosts) {
  22. // check if the tip has failed on all the nodes
  23. iter.remove();
  24. return tip;
  25. } else if (removeFailedTip) {
  26. // the case where we want to remove a failed tip from the host cache
  27. // point#3 in the TIP removal logic above
  28. iter.remove();
  29. }
  30. } else {
  31. // see point#3 in the comment above for TIP removal logic
  32. iter.remove();
  33. }
  34. }
  35. return null;
  36. }

该方法的作用是从一个TaskInProgress列表中选择一个适合在TT上执行的Task》从代码中的注释可以看出选择的前提是Task是可运行的(!failed && (completes == 0),即未失败也未完成),且非正在运行中(no other node is running it),且该Task没有在该TT所在的HOST上有过失败任务(一个Task会存在多个TaskAttempt任务,TaskAttempt听名字就可以知道是一个Task的多次尝试执行,失败了就再来一次,再失败再来,直到超出一个限度才会标志这个Task失败),或者该Task的失败次数大于等于集群中所有的HOST数量(表示该Task在所有HOST都失败过),满足上面三个条件的Task即可返回。后面就是判断是否将该Task从队列中移除,注释给出了三种会移除的情况:该Task已被调度,即被选中;选择的Task的本地化等级是NODE;该Task处于不可运行状态(运行中,或者完成,或者被kill掉了)。了解了该方法的原理则后面的内容就简单了。下面回到JobInProgress.findNewMapTask()方法。

7.JobInProgress.findNewMapTask():

  1. tip = findTaskFromList(failedMaps, tts, numUniqueHosts, false);
  2. if (tip != null) {
  3. // Add to the running list
  4. scheduleMap(tip);
  5. LOG.info("Choosing a failed task " + tip.getTIPId());
  6. return tip.getIdWithinJob();
  7. }

依旧看这段代码,当findTaskFromList()方法成功返回一个Task后,需要将该Task添加到运行中的队列去,

  1. protected synchronized void scheduleMap(TaskInProgress tip) {
  2.  
  3. if (runningMapCache == null) {
  4. LOG.warn("Running cache for maps is missing!! "
  5. + "Job details are missing.");
  6. return;
  7. }
  8. String[] splitLocations = tip.getSplitLocations();
  9.  
  10. // Add the TIP to the list of non-local running TIPs
  11. if (splitLocations == null || splitLocations.length == 0) {
  12. nonLocalRunningMaps.add(tip);
  13. return;
  14. }
  15.  
  16. for(String host: splitLocations) {
  17. Node node = jobtracker.getNode(host);
  18.  
  19. for (int j = 0; j < maxLevel; ++j) {
  20. Set<TaskInProgress> hostMaps = runningMapCache.get(node);
  21. if (hostMaps == null) {
  22. // create a cache if needed
  23. hostMaps = new LinkedHashSet<TaskInProgress>();
  24. runningMapCache.put(node, hostMaps);
  25. }
  26. hostMaps.add(tip);
  27. node = node.getParent();
  28. }
  29. }
  30. }

该方法主要将被选中的Task(这里任务都是Map任务)添加到Job中保存运行中任务信息的队列中(nonLocalRunningMaps和runningMapCache),nonLocalRunningMaps保存那些输入文件没有Location信息的Task,而runningMapCache则保存输入文件存在Location的Task。runningMapCache是一个Map,key是Node,而value是一个TaskInProgress对象的集合,说明该Map保持的是一个Node-->其上运行的所有Task的一个映射关系,这里的Node是拥有该Task的输入文件块的所有节点。当有一个Task需要添加到runningMapCache时不仅需要为其建立到Task的输入文件所在的所有Node到该Task的关系,而且分别为其建立输入文件所在Node的父节点到该Task的关系,循环知道遍历的深度等于maxLevel。这样做的好处是可以很方便的知道一个Node上运行的所有Task信息,包括其子节点上运行的Task。继续返回到JobInProgress.findNewMapTask()方法。

接下来就是简单地返回该Task在maps[]数组中的索引值。到这里第一种选择Task的方式完成了,下面看看后面几种方式。

8.JobInProgress.findNewMapTask():

  1. Node node = jobtracker.getNode(tts.getHost());

获取该TT所在的HOST对应的Node对象。

  1. // 1. check from local node to the root [bottom up cache lookup]
  2. // i.e if the cache is available and the host has been resolved
  3. // (node!=null)
  4. if (node != null) {
  5. Node key = node;
  6. int level = 0;
  7. // maxCacheLevel might be greater than this.maxLevel if findNewMapTask is
  8. // called to schedule any task (local, rack-local, off-switch or
  9. // speculative) tasks or it might be NON_LOCAL_CACHE_LEVEL (i.e. -1) if
  10. // findNewMapTask is (i.e. -1) if findNewMapTask is to only schedule
  11. // off-switch/speculative tasks
  12. int maxLevelToSchedule = Math.min(maxCacheLevel, maxLevel);
  13. for (level = 0;level < maxLevelToSchedule; ++level) {
  14. List <TaskInProgress> cacheForLevel = nonRunningMapCache.get(key);
  15. if (cacheForLevel != null) {
  16. tip = findTaskFromList(cacheForLevel, tts,
  17. numUniqueHosts,level == 0);
  18. if (tip != null) {
  19. // Add to running cache
  20. scheduleMap(tip);
  21.  
  22. // remove the cache if its empty
  23. if (cacheForLevel.size() == 0) {
  24. nonRunningMapCache.remove(key);
  25. }
  26.  
  27. return tip.getIdWithinJob();
  28. }
  29. }
  30. key = key.getParent();
  31. }
  32.  
  33. // Check if we need to only schedule a local task (node-local/rack-local)
  34. if (level == maxCacheLevel) {
  35. return -1;
  36. }
  37. }

这一部分是从未运行的Map任务中选择一个可执行的Map任务。首先计算maxLevelToSchedule,该值是maxCacheLevel和maxLevel的较小的值,注释给出的解释是maxCacheLevel(调用该方法传入的参数值)可能会比该Job的maxLevel属性值大,所以选择两者之中最小的值作为选择的最大本地等级值(maxLevelToSchedule)。接下来就是自下往上寻找满足条件的Map任务,知道遍历深度达到maxLevelToSchedule。方法较简单,只是从nonRunningMapCache中选择出对应的Node上所有的未执行的Map任务集合,然后调用同上面一样的findTaskFromList()方法从TaskInProgress集合中选择一个适合在该TT上执行的Map任务,选择一个Map任务之后还是一样的步骤调用scheduleMap()方法将其添加到运行中的队列中。当循环结束之后,如果未选择出一个Map任务,则到下面判断如果level==maxCacheLevel,这里level是循环结束时的值,即level==maxLevelToSchedule,而maxLevelToSchedule==Math.min(maxCacheLevel, maxLevel),那么如果要使level==maxCacheLevel,则maxCacheLevel必须是小于等于maxLevel,从前面三个方法内部调用obtainNewMapTaskCommon()方法时传的maxCacheLevel参数值可以看出,obtainNewNodeLocalMapTask()传的值是1,obtainNewNodeOrRackLocalMapTask()传的值是maxLevel,而obtainNewMapTask传的值是anyCacheLevel(=maxLevel+1),所以这里满足level==maxCacheLevel条件的是obtainNewNodeLocalMapTask和obtainNewNodeOrRackLocalMapTask两个方法,即选择Node级别和TackNode级别的Map任务。而对于这两个任务是不需要进行下面两种方式选择Map任务的:推测执行任务和NoLocal任务,因为这两个方式选择的任务都不满足Node级别和TackNode级别,而是Any级别的,即也就只有obtainNewMapTask()这一个方法(其实还有一个方法obtainNewNonLocalMapTask(),传的maxCacheLevel参数值是NON_LOCAL_CACHE_LEVEL,即-1,这个方法会跳过注视中的1)方式)。下面继续看如何从根节点横向选择Map任务。

  1. //2. Search breadth-wise across parents at max level for non-running
  2. // TIP if
  3. // - cache exists and there is a cache miss
  4. // - node information for the tracker is missing (tracker's topology
  5. // info not obtained yet)
  6.  
  7. // collection of node at max level in the cache structure
  8. Collection<Node> nodesAtMaxLevel = jobtracker.getNodesAtMaxLevel();
  9.  
  10. // get the node parent at max level
  11. Node nodeParentAtMaxLevel =
  12. (node == null) ? null : JobTracker.getParentNode(node, maxLevel - 1);
  13.  
  14. for (Node parent : nodesAtMaxLevel) {
  15.  
  16. // skip the parent that has already been scanned
  17. if (parent == nodeParentAtMaxLevel) {
  18. continue;
  19. }
  20.  
  21. List<TaskInProgress> cache = nonRunningMapCache.get(parent);
  22. if (cache != null) {
  23. tip = findTaskFromList(cache, tts, numUniqueHosts, false);
  24. if (tip != null) {
  25. // Add to the running cache
  26. scheduleMap(tip);
  27.  
  28. // remove the cache if empty
  29. if (cache.size() == 0) {
  30. nonRunningMapCache.remove(parent);
  31. }
  32. LOG.info("Choosing a non-local task " + tip.getTIPId());
  33. return tip.getIdWithinJob();
  34. }
  35. }
  36. }

这一种方式直接从根节点集合中选择任务,JT中nodesAtMaxLevel集合保存着所有没有父节点的Node信息,即在集群中处于最高级等的Node,下面就是直接遍历nodesAtMaxLevel中所有的节点选择满足条件的Map任务。同上一步也是从nonRunningMapCache集合中选择出对应Node上所有的未运行的Map任务,该方法基本同上一步一样,只是选择的Node不同。上一种方式是从TT所在的Node开始,自下而上选择Map任务,而此处则直接选择最高等级的Node上的Map任务。显然这一步不考虑任何的Map任务本地化因素。下面再看如何选择No-Local任务。

  1. // 3. Search non-local tips for a new task
  2. tip = findTaskFromList(nonLocalMaps, tts, numUniqueHosts, false);
  3. if (tip != null) {
  4. // Add to the running list
  5. scheduleMap(tip);
  6.  
  7. LOG.info("Choosing a non-local task " + tip.getTIPId());
  8. return tip.getIdWithinJob();
  9. }

这一种方式是从nonLocalMaps中选择一个Map任务,nonLocalMaps保存的任务是那些在任务初始化时未找到输入文件所在的Location信息的任务,这些任务是无法放到nonRunningMapCache中的。

以上三种方式其实都是一种方式——对应注释上的1)——选择未运行的任务,只是这里分成三种不同的选择方式:1)从TT所在节点自下而上选择满足Node和RackNode本地化要求的任务,2)直接从所有最高等级的Node上选择任务,3)选择那些输入文件没有Location信息的No-Local任务。下面接着看注释中提到的第三种选择方式——选择推测执行任务。

9.JobInProgress.findNewMapTask():

这一中方式同上面一种方式一样,也分为三个不同的选择方式(同上)。当然,这一中方法发生的条件是hasSpeculativeMaps==true,即该Job拥有推测执行任务,或者说可以启用推测执行任务,该参数由mapred.map.tasks.speculative.execution参数值决定,默认是true。下面分别看看三种方式。

  1. // 1. Check bottom up for speculative tasks from the running cache
  2. if (node != null) {
  3. Node key = node;
  4. for (int level = 0; level < maxLevel; ++level) {
  5. Set<TaskInProgress> cacheForLevel = runningMapCache.get(key);
  6. if (cacheForLevel != null) {
  7. tip = findSpeculativeTask(cacheForLevel, tts,
  8. avgProgress, currentTime, level == 0);
  9. if (tip != null) {
  10. if (cacheForLevel.size() == 0) {
  11. runningMapCache.remove(key);
  12. }
  13. return tip.getIdWithinJob();
  14. }
  15. }
  16. key = key.getParent();
  17. }
  18. }

第一种方式同上一种方式一样,依然是选择本地化Map任务(推测任务),不同的是这里是从runningMapCache中选择出Node上所有正在运行中的Task集合,从中选择一个Map任务对其启动推测执行任务。下面看看findSpeculativeTask()方法。

10.JobInProgress.findSpeculativeTask():

  1. protected synchronized TaskInProgress findSpeculativeTask(
  2. Collection<TaskInProgress> list, TaskTrackerStatus ttStatus,
  3. double avgProgress, long currentTime, boolean shouldRemove) {
  4.  
  5. Iterator<TaskInProgress> iter = list.iterator();
  6.  
  7. while (iter.hasNext()) {
  8. TaskInProgress tip = iter.next();
  9. // should never be true! (since we delete completed/failed tasks)
  10. if (!tip.isRunning() || !tip.isRunnable()) {
  11. iter.remove();
  12. continue;
  13. }
  14.  
  15. if (tip.hasSpeculativeTask(currentTime, avgProgress)) {
  16. // Check if this tip can be removed from the list.
  17. // If the list is shared then we should not remove.
  18. if(shouldRemove){
  19. iter.remove();
  20. }
  21. if (!tip.hasRunOnMachine(ttStatus.getHost(),
  22. ttStatus.getTrackerName())) {
  23. return tip;
  24. }
  25. } else {
  26. if (shouldRemove && tip.hasRunOnMachine(ttStatus.getHost(),
  27. ttStatus.getTrackerName())) {
  28. iter.remove();
  29. }
  30. }
  31. }
  32. return null;
  33. }

选择依据是!tip.isRunning() || !tip.isRunnable(),即该Task处于运行中,且未完成,才能对此启动推测执行任务。

这里简单介绍下推测执行任务,推测执行任务是Hadoop的一种容错机制,即如果一个Task运行的时间同其他同类的Task所需的时间长很多(且还未完成)时,则根据实际情况考虑启动一个同样的Task,这时集群中就有两个同样的任务同时运行,哪个先完成则提交哪个Task,而kill掉另外一个Task。推测执行任务虽然能够能够更好的保证一个Task在正常时间内完成,但是代价是需要消耗更多的资源。

下面是调用hasSpeculativeTask()方法判断该Task是否可以启动一个推测执行任务。

  1. boolean hasSpeculativeTask(long currentTime, double averageProgress) {
  2. //
  3. // REMIND - mjc - these constants should be examined
  4. // in more depth eventually...
  5. //
  6.  
  7. if (!skipping && activeTasks.size() <= MAX_TASK_EXECS &&
  8. (averageProgress - progress >= SPECULATIVE_GAP) &&
  9. (currentTime - startTime >= SPECULATIVE_LAG)
  10. && completes == 0 && !isOnlyCommitPending()) {
  11. return true;
  12. }
  13. return false;
  14. }

判断条件有点多,主要是:1)skipping==false,即未开启跳过模式;2)该Task正在运行的任务数是否大于MAX_TASK_EXECS(1),大于MAX_TASK_EXECS则表示已经有一个推测执行的任务在运行了;3)averageProgress(Map任务或者Reduce任务完成的进度)是否大于SPECULATIVE_GAP(20%);4)任务运行的时间是否已超过SPECULATIVE_LAG(60*1000);5)Task的完成数==0;6)Task是否处于等待提交状态。

最后调用TaskInProgress的hasRunOnMachine()方法判断该Task是否在该TT上有正在运行中的TaskAttempt,且在该TT上是否有失败过。到这里findSpeculativeTask()方法就完成了。该方法首先根据Task的运行状态判断是否满足推测执行的条件,然后根据Task的一系列属性判断是否开启推测执行,最后根据该Task在该TT是否有正在运行的TaskAttempt以及是否有过失败记录最终决定是否在该TT上运行该Task的推测执行任务。继续回到JobInProgress.findNewMapTask()

10.JobInProgress.findNewMapTask():

  1. // 2. Check breadth-wise for speculative tasks
  2.  
  3. for (Node parent : nodesAtMaxLevel) {
  4. // ignore the parent which is already scanned
  5. if (parent == nodeParentAtMaxLevel) {
  6. continue;
  7. }
  8.  
  9. Set<TaskInProgress> cache = runningMapCache.get(parent);
  10. if (cache != null) {
  11. tip = findSpeculativeTask(cache, tts, avgProgress,
  12. currentTime, false);
  13. if (tip != null) {
  14. // remove empty cache entries
  15. if (cache.size() == 0) {
  16. runningMapCache.remove(parent);
  17. }
  18. LOG.info("Choosing a non-local task " + tip.getTIPId()
  19. + " for speculation");
  20. return tip.getIdWithinJob();
  21. }
  22. }
  23. }
  24.  
  25. // 3. Check non-local tips for speculation
  26. tip = findSpeculativeTask(nonLocalRunningMaps, tts, avgProgress,
  27. currentTime, false);
  28. if (tip != null) {
  29. LOG.info("Choosing a non-local task " + tip.getTIPId()
  30. + " for speculation");
  31. return tip.getIdWithinJob();
  32. }
  33. }

这里的两种方式其实跟上面一样,无需过多的解释。

到这里findNewMapTask()就完成了,下面回到obtainNewMapTaskCommon()方法。

11.JobInProgress.obtainNewMapTaskCommon():

  1. int target = findNewMapTask(tts, clusterSize, numUniqueHosts, maxCacheLevel,
  2. status.mapProgress());
  3. if (target == -1) {
  4. return null;
  5. }
  6.  
  7. Task result = maps[target].getTaskToRun(tts.getTrackerName());
  8. if (result != null) {
  9. addRunningTaskToTIP(maps[target], result.getTaskID(), tts, true);
  10. // DO NOT reset for off-switch!
  11. if (maxCacheLevel != NON_LOCAL_CACHE_LEVEL) {
  12. resetSchedulingOpportunities();
  13. }
  14. }
  15. return result;

当调用findNewMapTask()方法得到一个maps[]数组的索引之后,就可以从maps[]数组中获取对应的Map任务。这里调用了一个TaskInProgress的getTaskToRun()方法,为Task生成一个唯一的AttemptId,然后调用addRunningTask()方法创建一个Task对象,方法内部还是比较简单的,主要是new一个Task对象,并为其创建一个TaskStatus对象,以及初始化一些参数值。

如果Task!=null,则调用addRunningTaskToTIP()方法处理一些记录值,如记录Task的locality值,以及是否第一个TaskAttempt对象,等等。

到此obtainNewMapTaskCommon()方法就完成了,则obtainNewNodeLocalMapTask(),obtainNewNodeOrRackLocalMapTask(),obtainNewMapTask()三个方法也就都完成了。而obtainNewReduceTask()该方法基本和前面三个方法大同小异,也就不需要过多的解释,不同的只是Reduce任务不需要考虑本地性,选择相对更简单些。

以上就是一个Job如何选择一个Map/Reduce任务来执行的过程,总体上来看对于Map任务需要考虑Map任务的本地性,以提高执行效率。而任务的选择顺序依次是:失败的任务>未运行的任务>推测执行任务。而对于Map任务第二三种任务(未运行的任务>推测执行任务)又分成从TT所在的Node自下而上选择、从根节点横向选择、选择No-Local任务三种不同的方式。

OK,以上如有错误之处还望指出,谢谢!

FairScheduler的任务调度机制——assignTasks(续)的更多相关文章

  1. FairScheduler的任务调度机制——assignTasks

    首先需要了解FairScheduler是如何在各个Pool之间分配资源,以及每个Pool如何在Job之间分配资源的.FairScheduler的分配资源发生在update()方法中,而该方法由一个线程 ...

  2. Spark内核-任务调度机制

    作者:十一喵先森 链接:https://juejin.im/post/5e1c414fe51d451cad4111d1 来源:掘金 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. ...

  3. ucos-ii的任务调度机制

    1.在ucos-ii中,有这么几张表来管理任务. A.OSTCBPrioTbl[],其结构为OS_TCB指针的数组,其元素个数为64, 每一个元素对应一个任务的优先级,ucos-ii最多可以有64个任 ...

  4. spark内核篇-任务调度机制

    在生产环境中,spark 部署方式一般都是 yarn-cluster 模式,本文针对该模式进行讲解,当然大体思路也适用于其他模式 基础概念 一个 spark 应用包含 job.stage.task 三 ...

  5. rontab踩坑(三):crontab定时任务调度机制与系统时间/时区的不一致

    解决方案: 因为我们的服务器在是肯尼亚: 我么查看一下localtime 是否和 时区一致? 可以看到是一致的. 应该是是配置改动后未重启! service crond restart

  6. MapReduce多用户任务调度器——容量调度器(Capacity Scheduler)原理和源码研究

    前言:为了研究需要,将Capacity Scheduler和Fair Scheduler的原理和代码进行学习,用两篇文章作为记录.如有理解错误之处,欢迎批评指正. 容量调度器(Capacity Sch ...

  7. Quartz任务调度基本使用

    转自:http://www.cnblogs.com/bingoidea/archive/2009/08/05/1539656.html 上一篇:定时器的实现.Java定时器Timer和Quartz介绍 ...

  8. Spring之实现任务调度

    现实生活中,我们经常会制定一些"任务"在什么时间完成什么事情.同样在各种的企业中也会遇到这种任务调度的需求,比如商家或者网站的月报表 之类的要在每个月的最后一天统计各种数据,备份每 ...

  9. Spring任务调度

    任务调度是大多数应用系统的常见需求之一,拿论坛来说:每个半个小时生成精华文章的RSS文件,每天凌晨统计论坛用户的积分排名,每隔30分钟执行对锁定过期的用户进行解锁.以上都是以时间为关注点的调度,事实上 ...

随机推荐

  1. asp.net中生成缩略图并添加版权实例代码

    这篇文章介绍了asp.net中生成缩略图并添加版权实例代码,有需要的朋友可以参考一下 复制代码代码如下: //定义image类的对象 Drawing.Image image,newimage; //图 ...

  2. PHP - PHPExcel操作xls文件

    读取中文的xls.csv文件会有问题,网上找了下资料,发现PHPExcel类库好用,官网地址:http://phpexcel.codeplex.com/ 1.读取xls文件内容 <?php // ...

  3. HTML5 改良的 input 元素的种类

    html5中增加改良的input 元素 . 在过去我们制作网页输入框,会用到不少JS验证,如今有了HTML5写这种效果已经没有那么麻烦了,下面我来给大家介绍两种HTML5的input的新增加的类型应用 ...

  4. python【第十七篇】jQuery

    1.jQuery是什么? jQuery是一个 JavaScript/Dom/Bom 库. jQuery 极大地简化了 JavaScript 编程. jQuery 很容易学习. 2.jQuery对象与D ...

  5. C# 使用Salt+Hash来为密码加密

    (一) 为什么要用哈希函数来加密密码 如果你需要保存密码(比如网站用户的密码),你要考虑如何保护这些密码数据,象下面那样直接将密码写入数据库中是极不安全的,因为任何可以打开数据库的人,都将可以直接看到 ...

  6. IOS7,做为开发者,你需要知道的变更

    IOS7即将发布,那么我们需要做些什么呢? 升级你的程序Icon至 120*120 更新一张包含状态栏大小的闪屏图片 还有些什么东西呢? IOS7中需要使用更加扁平化的设计,所以BUTTON的图片,边 ...

  7. csu 10月 月赛 F 题 ZZY and his little friends

    一道字典树异或的题,但是数据比较水,被大家用暴力给干掉了! 以前写过一个类似的题,叫做the longest xor in tree: 两个差不多吧! 好久没写字典树了,复习一下! 代码: #incl ...

  8. discuz论坛很给力

    11.10:老彭在搭建好论坛. 11.17:  主网站导航中加入“论坛” 11.20: 使用“T客在线”的版本将论坛全新改版. 新版论坛非常大气,让网站增色不少.

  9. PNG在IE6下背景问题

    png24.min.js 源代码: var DD_belatedPNG={ns:"DD_belatedPNG",imgSize:{},delay:10,nodesFixed:0,c ...

  10. 设计模式之观察者(Observer)模式 代码详解

    import java.util.ArrayList; import java.util.List; /** * User: HYY * Date: 13-10-28 * Time: 下午1:34 * ...