• FairScheduler的任务调度机制——assignTasks(续)


    上一篇文章浅析了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():

    public synchronized Task obtainNewMapTaskCommon(
          TaskTrackerStatus tts, int clusterSize, int numUniqueHosts, 
          int maxCacheLevel) throws IOException {
        if (!tasksInited) {
          LOG.info("Cannot create task split for " + profile.getJobID());
          try { throw new IOException("state = " + status.getRunState()); }
          catch (IOException ioe) {ioe.printStackTrace();}
          return null;
        }
    
        int target = findNewMapTask(tts, clusterSize, numUniqueHosts, maxCacheLevel, 
                                    status.mapProgress());
        if (target == -1) {
          return null;
        }
    
        Task result = maps[target].getTaskToRun(tts.getTrackerName());
        if (result != null) {
          addRunningTaskToTIP(maps[target], result.getTaskID(), tts, true);
          // DO NOT reset for off-switch!
          if (maxCacheLevel != NON_LOCAL_CACHE_LEVEL) {
            resetSchedulingOpportunities();
          }
        }
        return result;
      }

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

    2.JobInProgress.findNewMapTask():


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

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

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

    String taskTracker = tts.getTrackerName();
        TaskInProgress tip = null;
        
        //
        // Update the last-known clusterSize
        //
        this.clusterSize = clusterSize;
    
        if (!shouldRunOnTaskTracker(taskTracker)) {
          return -1;
        }

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

    3.JobInProgress.shouldRunOnTaskTracker():

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

    该方法从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():

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

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

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

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

    5.JobInProgress.findNewMapTask():

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

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

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

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

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

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

    6.JobInProgress.findTaskFromList():

    private synchronized TaskInProgress findTaskFromList(
          Collection<TaskInProgress> tips, TaskTrackerStatus ttStatus,
          int numUniqueHosts,
          boolean removeFailedTip) {
        Iterator<TaskInProgress> iter = tips.iterator();
        while (iter.hasNext()) {
          TaskInProgress tip = iter.next();
    
          // Select a tip if
          //   1. runnable   : still needs to be run and is not completed
          //   2. ~running   : no other node is running it
          //   3. earlier attempt failed : has not failed on this host
          //                               and has failed on all the other hosts
          // A TIP is removed from the list if 
          // (1) this tip is scheduled
          // (2) if the passed list is a level 0 (host) cache
          // (3) when the TIP is non-schedulable (running, killed, complete)
          if (tip.isRunnable() && !tip.isRunning()) {
            // check if the tip has failed on this host
            if (!tip.hasFailedOnMachine(ttStatus.getHost()) || 
                 tip.getNumberOfFailedMachines() >= numUniqueHosts) {
              // check if the tip has failed on all the nodes
              iter.remove();
              return tip;
            } else if (removeFailedTip) { 
              // the case where we want to remove a failed tip from the host cache
              // point#3 in the TIP removal logic above
              iter.remove();
            }
          } else {
            // see point#3 in the comment above for TIP removal logic
            iter.remove();
          }
        }
        return null;
      }

    该方法的作用是从一个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():

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

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

    protected synchronized void scheduleMap(TaskInProgress tip) {
        
        if (runningMapCache == null) {
          LOG.warn("Running cache for maps is missing!! " 
                   + "Job details are missing.");
          return;
        }
        String[] splitLocations = tip.getSplitLocations();
    
        // Add the TIP to the list of non-local running TIPs
        if (splitLocations == null || splitLocations.length == 0) {
          nonLocalRunningMaps.add(tip);
          return;
        }
    
        for(String host: splitLocations) {
          Node node = jobtracker.getNode(host);
    
          for (int j = 0; j < maxLevel; ++j) {
            Set<TaskInProgress> hostMaps = runningMapCache.get(node);
            if (hostMaps == null) {
              // create a cache if needed
              hostMaps = new LinkedHashSet<TaskInProgress>();
              runningMapCache.put(node, hostMaps);
            }
            hostMaps.add(tip);
            node = node.getParent();
          }
        }
      }

    该方法主要将被选中的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():

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

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

    // 1. check from local node to the root [bottom up cache lookup]
        //    i.e if the cache is available and the host has been resolved
        //    (node!=null)
        if (node != null) {
          Node key = node;
          int level = 0;
          // maxCacheLevel might be greater than this.maxLevel if findNewMapTask is
          // called to schedule any task (local, rack-local, off-switch or
          // speculative) tasks or it might be NON_LOCAL_CACHE_LEVEL (i.e. -1) if
          // findNewMapTask is (i.e. -1) if findNewMapTask is to only schedule
          // off-switch/speculative tasks
          int maxLevelToSchedule = Math.min(maxCacheLevel, maxLevel);
          for (level = 0;level < maxLevelToSchedule; ++level) {
            List <TaskInProgress> cacheForLevel = nonRunningMapCache.get(key);
            if (cacheForLevel != null) {
              tip = findTaskFromList(cacheForLevel, tts, 
                  numUniqueHosts,level == 0);
              if (tip != null) {
                // Add to running cache
                scheduleMap(tip);
    
                // remove the cache if its empty
                if (cacheForLevel.size() == 0) {
                  nonRunningMapCache.remove(key);
                }
    
                return tip.getIdWithinJob();
              }
            }
            key = key.getParent();
          }
          
          // Check if we need to only schedule a local task (node-local/rack-local)
          if (level == maxCacheLevel) {
            return -1;
          }
        }

    这一部分是从未运行的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任务。

    //2. Search breadth-wise across parents at max level for non-running 
        //   TIP if
        //     - cache exists and there is a cache miss 
        //     - node information for the tracker is missing (tracker's topology
        //       info not obtained yet)
    
        // collection of node at max level in the cache structure
        Collection<Node> nodesAtMaxLevel = jobtracker.getNodesAtMaxLevel();
    
        // get the node parent at max level
        Node nodeParentAtMaxLevel = 
          (node == null) ? null : JobTracker.getParentNode(node, maxLevel - 1);
        
        for (Node parent : nodesAtMaxLevel) {
    
          // skip the parent that has already been scanned
          if (parent == nodeParentAtMaxLevel) {
            continue;
          }
    
          List<TaskInProgress> cache = nonRunningMapCache.get(parent);
          if (cache != null) {
            tip = findTaskFromList(cache, tts, numUniqueHosts, false);
            if (tip != null) {
              // Add to the running cache
              scheduleMap(tip);
    
              // remove the cache if empty
              if (cache.size() == 0) {
                nonRunningMapCache.remove(parent);
              }
              LOG.info("Choosing a non-local task " + tip.getTIPId());
              return tip.getIdWithinJob();
            }
          }
        }

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

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

    这一种方式是从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. Check bottom up for speculative tasks from the running cache
          if (node != null) {
            Node key = node;
            for (int level = 0; level < maxLevel; ++level) {
              Set<TaskInProgress> cacheForLevel = runningMapCache.get(key);
              if (cacheForLevel != null) {
                tip = findSpeculativeTask(cacheForLevel, tts, 
                                          avgProgress, currentTime, level == 0);
                if (tip != null) {
                  if (cacheForLevel.size() == 0) {
                    runningMapCache.remove(key);
                  }
                  return tip.getIdWithinJob();
                }
              }
              key = key.getParent();
            }
          }

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

    10.JobInProgress.findSpeculativeTask():

    protected synchronized TaskInProgress findSpeculativeTask(
          Collection<TaskInProgress> list, TaskTrackerStatus ttStatus,
          double avgProgress, long currentTime, boolean shouldRemove) {
        
        Iterator<TaskInProgress> iter = list.iterator();
    
        while (iter.hasNext()) {
          TaskInProgress tip = iter.next();
          // should never be true! (since we delete completed/failed tasks)
          if (!tip.isRunning() || !tip.isRunnable()) {
            iter.remove();
            continue;
          }
    
          if (tip.hasSpeculativeTask(currentTime, avgProgress)) {
            // Check if this tip can be removed from the list.
            // If the list is shared then we should not remove.
            if(shouldRemove){
              iter.remove();
            }
            if (!tip.hasRunOnMachine(ttStatus.getHost(),
                                   ttStatus.getTrackerName())) {
              return tip;
            }
          } else {
            if (shouldRemove && tip.hasRunOnMachine(ttStatus.getHost(),
                                             ttStatus.getTrackerName())) {
              iter.remove();
            }
          }
        }
        return null;
      }

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

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

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

    boolean hasSpeculativeTask(long currentTime, double averageProgress) {
        //
        // REMIND - mjc - these constants should be examined
        // in more depth eventually...
        //
          
        if (!skipping && activeTasks.size() <= MAX_TASK_EXECS &&
            (averageProgress - progress >= SPECULATIVE_GAP) &&
            (currentTime - startTime >= SPECULATIVE_LAG) 
            && completes == 0 && !isOnlyCommitPending()) {
          return true;
        }
        return false;
      }

    判断条件有点多,主要是: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():

    // 2. Check breadth-wise for speculative tasks
          
          for (Node parent : nodesAtMaxLevel) {
            // ignore the parent which is already scanned
            if (parent == nodeParentAtMaxLevel) {
              continue;
            }
    
            Set<TaskInProgress> cache = runningMapCache.get(parent);
            if (cache != null) {
              tip = findSpeculativeTask(cache, tts, avgProgress, 
                                        currentTime, false);
              if (tip != null) {
                // remove empty cache entries
                if (cache.size() == 0) {
                  runningMapCache.remove(parent);
                }
                LOG.info("Choosing a non-local task " + tip.getTIPId() 
                         + " for speculation");
                return tip.getIdWithinJob();
              }
            }
          }
    
          // 3. Check non-local tips for speculation
          tip = findSpeculativeTask(nonLocalRunningMaps, tts, avgProgress, 
                                    currentTime, false);
          if (tip != null) {
            LOG.info("Choosing a non-local task " + tip.getTIPId() 
                     + " for speculation");
            return tip.getIdWithinJob();
          }
        }

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

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

    11.JobInProgress.obtainNewMapTaskCommon():

    int target = findNewMapTask(tts, clusterSize, numUniqueHosts, maxCacheLevel, 
                                    status.mapProgress());
        if (target == -1) {
          return null;
        }
    
        Task result = maps[target].getTaskToRun(tts.getTrackerName());
        if (result != null) {
          addRunningTaskToTIP(maps[target], result.getTaskID(), tts, true);
          // DO NOT reset for off-switch!
          if (maxCacheLevel != NON_LOCAL_CACHE_LEVEL) {
            resetSchedulingOpportunities();
          }
        }
        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,以上如有错误之处还望指出,谢谢!

  • 相关阅读:
    Vue学习笔记
    用vue-cli3搭建vue项目
    Vue 封装可向左向右查看图片列表的组件
    css修改整个项目的滚动条样式
    Vue 可输入可下拉组件的封装
    es6 实现数组的操作
    JS 实现兼容IE浏览器报警提示声音
    SPRINGBOOT9--AOP的使用(本例展示统一处理Web请求日志)
    SPRINGBOOT8--log4j日志记录
    SPRINGBOOT7--使用@Async实现异步调用
  • 原文地址:https://www.cnblogs.com/fuhaots2009/p/3476517.html
Copyright © 2020-2023  润新知