JobTracker任务调度器之JobQueueTaskScheduler

    在客户端把作业提交给JobTracker节点之后,JobTracker节点就可以使用任务调度器TaskScheduler将这个Job的任务分配给那些合适的TaskTracker节点来执行。当然在JobTracker调度该Job之前,必须要确保该Job的JobInProgress被初始化了,即将Job划分为若干个map任务和reduce任务。在JobTracker中有一个基于优先级的FIFO任务调度器JobQueueTaskScheduler。对于目前的Hadoop任务调度设计来看,它是一个“拉”的过程,即每一个TaskTracker节点主动向JobTracker节点请求作业的任务,而不是当有新作业的时候,JobTracker节点主动给TaskTracker节点分配任务。先来看看它的类图:



        在上面的类图中,我们可以看出JobQueueTaskScheduler类依赖于两个JobInProgressListener的实现类,其中JobQueueJobInProgressListener类被用来按照优先级队列的方式来管理Job,EagerTaskInitializationListener类被用来初始化Job,即对Job进行map任务和reduce任务的切分。关于这两个JobInProgressListener类的具体实现,我在这里不再作详细的阐述,因为我将会在以后专门进行详细地讨论,有兴趣的童鞋可以自己查先研究它的源代码。所以,当JobTracker给某个TaskTracker分配任务时,它就会调用TaskScheduler的assignTasks(TaskTrackerStatus)方法,让TaskScheduler给该TaskTracker分配任务。那么,究竟TaskScheduler是如何给TaskTracker任务分配任务的,这就得看TaskScheduler的具体实现了,Hadoop允许用户自定义TaskScheduler来根据自己的实际情况来调度Job任务,这个具体实现可以通过配置文件中的mapred.jobtracker.taskScheduler项来设置。TaskScheduler开放给了用户四个可用的方法:

  /*开始任务调度器*/ 
  public void start() throws IOException {
    // do nothing
  }

  /*关闭任务调度器*/
  public void terminate() throws IOException {
    // do nothing
  }

  /*给一个TaskTracker节点分配若干个任务*/
  public abstract List<Task> assignTasks(TaskTrackerStatus taskTracker) throws IOException;

  /*获取属于某一个队列的所有JobInProgress*/
  public abstract Collection<JobInProgress> getJobs(String queueName);

        下面以JobQueueTaskScheduler为例,来详细的讨论如何给一个TaskTracker分配任务。


1.分配map任务

   无论怎样,任务调度器JobQueueTaskScheduler总是按照优先级的FIFO来调度每一个Job的Map任务,但对于每一个Job,它只给一次机会,也就是说它顶多只调度该Job的一个Map任务,然后就再调度其它Job的一个Map任务给当前的TaskTracker节点,同时优先分配一个Job的本地任务,当给当前的TaskTracker节点分配了一个非本地任务时,任务调度器JobQueueTaskScheduler就不会再给该TaskTracker节点分配Map任务了,尽管该TaskTracker节点还有空闲的Map Slot。这主要考虑到TaskTracker节点执行非本地任务的代价(CPU不是主要的,关键是网络带宽)。


2.分配reduce任务

     给一个计算节点分配map/reduce任务的源代码如下:

 /**
   * 给一个计算节点分配若干个任务
   */
  public synchronized List<Task> assignTasks(TaskTrackerStatus taskTracker)throws IOException {

    ClusterStatus clusterStatus = taskTrackerManager.getClusterStatus();		
    final int numTaskTrackers = clusterStatus.getTaskTrackers();				//当前集群可用的计算节点数量
    final int clusterMapCapacity = clusterStatus.getMaxMapTasks();				//当前集群执行map任务的总计算能力(Map Slot的总数量)
    final int clusterReduceCapacity = clusterStatus.getMaxReduceTasks();		//当前集群执行reduce任务的总计算能力(Reduce Slot的总数量)

    //当前集群待调度的作业队列
    Collection<JobInProgress> jobQueue = jobQueueJobInProgressListener.getJobQueue();

    // Get map + reduce counts for the current tracker.
    final int trackerMapCapacity = taskTracker.getMaxMapTasks();				//当前计算节点执行map任务的总计算能力(Map Slot的最大数量)
    final int trackerReduceCapacity = taskTracker.getMaxReduceTasks();		//当前计算节点执行reduce任务的总计算能力(Reduce Slot的最大数量)
    final int trackerRunningMaps = taskTracker.countMapTasks();				//当前计算节点正在使用的map计算能力(正在使用Map Slot执行map任务的数量)
    final int trackerRunningReduces = taskTracker.countReduceTasks();			//当前计算节点正在使用的reduce计算能力(正在使用Reduce Slot执行reduce任务的数量)

    //用来存储给该计算节点分配的任务
    List<Task> assignedTasks = new ArrayList<Task>();

    int remainingReduceLoad = 0;	//当前集群执行map尚需的计算能力
    int remainingMapLoad = 0;		//当前集群执行reduce尚需的计算能力
    //通过所有正在执行的作业情况,统计当前尚需的计算能力
    synchronized (jobQueue) {
      for (JobInProgress job : jobQueue) {
        if (job.getStatus().getRunState() == JobStatus.RUNNING) {
          remainingMapLoad += (job.desiredMaps() - job.finishedMaps());
            //当前作业是否可以开始调度reduce任务
          if (job.scheduleReduces()) {
            remainingReduceLoad += (job.desiredReduces() - job.finishedReduces());
          }
          
        }
      }
    }
    LOG.debug("There are "+remainingMapLoad+" running/pending map tasks and "+remainingReduceLoad+" running/pending reduce tasks in the cluster now!");
    

    //计算当前map任务的负载
    double mapLoadFactor = 0.0;
    if(clusterMapCapacity > 0) {
      mapLoadFactor = (double)remainingMapLoad / clusterMapCapacity;
    }
    //计算当前reduce任务的负载
    double reduceLoadFactor = 0.0;
    if (clusterReduceCapacity > 0) {
      reduceLoadFactor = (double)remainingReduceLoad / clusterReduceCapacity;
    }
        
     //基于当前集群map任务的负载及当前节点节点的总计算能力,估算此时其应该使用的计算能力
    final int trackerCurrentMapCapacity = Math.min((int)Math.ceil(mapLoadFactor * trackerMapCapacity), trackerMapCapacity);
     //计算当前计算节点剩余的计算能力
    int availableMapSlots = trackerCurrentMapCapacity - trackerRunningMaps;
    boolean exceededMapPadding = false;
    if(availableMapSlots > 0) {
      exceededMapPadding = exceededPadding(true, clusterStatus, trackerMapCapacity);
    }
    int numLocalMaps = 0;
    int numNonLocalMaps = 0;
    if(availableMapSlots > 0) LOG.debug("Try to assign "+availableMapSlots+" map tasks to TaskTracker["+taskTracker.trackerName+"]..");
    else LOG.debug("Can not assign map tasks to TaskTracker["+taskTracker.trackerName+"], because it doesn't have any free map slot or it is overloaded.");
    scheduleMaps:
    for (int i=0; i < availableMapSlots; ++i) {
    	
      synchronized (jobQueue) {
        for (JobInProgress job : jobQueue) {
          if (job.getStatus().getRunState() != JobStatus.RUNNING) {
            continue;
          }
          
          Task t = null;
          
           //尝试给当前计算节点分配一个本地map任务
          t = job.obtainNewLocalMapTask(taskTracker, numTaskTrackers,taskTrackerManager.getNumberOfUniqueHosts());
          if(t != null) {
        	  LOG.debug("assign a local map Task["+t.getTaskID()+"] to TaskTracker["+taskTracker.trackerName+"]");
            assignedTasks.add(t);
            ++numLocalMaps;
            
             //需要当前计算节点保留部分map计算能力,所以结束对当前计算节点的map任务分配
            if(exceededMapPadding) {
              break scheduleMaps;
            }

            break;
          }
          
           //尝试给当前计算节点分配一个非本地map任务,如果分配成功则结束对该节点的map任务分配,
           //以避免它抢走了其它计算节点的本地map任务
          t = job.obtainNewNonLocalMapTask(taskTracker, numTaskTrackers,taskTrackerManager.getNumberOfUniqueHosts());
          if(t != null) {
            assignedTasks.add(t);
            ++numNonLocalMaps;

            break scheduleMaps;
          }
        }//for
      }
      
    }//for
    
    int assignedMaps = assignedTasks.size();

    //基于当前集群reduce任务的负载及当前节点节点的总计算能力,估算此时其应该使用的reduce计算能力
    final int trackerCurrentReduceCapacity = Math.min((int)Math.ceil(reduceLoadFactor * trackerReduceCapacity), trackerReduceCapacity);
    //计算当前节点剩余的reduce计算能力
    final int availableReduceSlots = Math.min((trackerCurrentReduceCapacity - trackerRunningReduces), 1);
    boolean exceededReducePadding = false;
    if(availableReduceSlots > 0) {
      exceededReducePadding = exceededPadding(false, clusterStatus, trackerReduceCapacity);
       //给当前计算节点至多分配一个reduce任务
      synchronized (jobQueue) {
    	 LOG.debug("try to assign 1 reduce task to TaskTracker["+taskTracker.trackerName+"]..");
        for (JobInProgress job : jobQueue) {
          if (job.getStatus().getRunState() != JobStatus.RUNNING || job.numReduceTasks == 0) {
            continue;
          }

           //尝试给当前计算节点分配一个reduce任务
          Task t = job.obtainNewReduceTask(taskTracker, numTaskTrackers, taskTrackerManager.getNumberOfUniqueHosts());
          
          //reduce任务分配成功,所以结束对当前计算节点的reduce任务分配
          if(t != null) {
            assignedTasks.add(t);
            break;
          }
          
          //reduce任务分配失败,但需要当前计算节点保留部分reduce计算能力,所以结束对当前计算节点的reduce任务分配
          if(exceededReducePadding) {
            break;
          }
        }//for
        
      }
    }
    
    if (LOG.isDebugEnabled()) {
      LOG.debug("Task assignments for " + taskTracker.getTrackerName() + " --> " +
                "[" + mapLoadFactor + ", " + trackerMapCapacity + ", " + 
                trackerCurrentMapCapacity + ", " + trackerRunningMaps + "] -> [" + 
                (trackerCurrentMapCapacity - trackerRunningMaps) + ", " +
                assignedMaps + " (" + numLocalMaps + ", " + numNonLocalMaps + 
                ")] [" + reduceLoadFactor + ", " + trackerReduceCapacity + ", " + 
                trackerCurrentReduceCapacity + "," + trackerRunningReduces + 
                "] -> [" + (trackerCurrentReduceCapacity - trackerRunningReduces) + 
                ", " + (assignedTasks.size()-assignedMaps) + "]");
    }

    return assignedTasks;
  }


3.计算一个TaskTracker节点是否需要保留部分计算能力(Map/Reduce Slots)

       在任务调度器JobQueueTaskScheduler的实现中,如果在集群中的TaskTracker节点比较多的情况下,它总是会想办法让若干个TaskTracker节点预留一些空闲的slots(计算能力),以便能够快速的处理优先级比较高的Job的Task或者发生错误的Task,以保证已经被调度的作业的完成。它的具体实现如下:

 /**
   * 预留计算能力的集群最小规模
  */
  private static final int MIN_CLUSTER_SIZE_FOR_PADDING = 3;

  /**
   * 判断当前集群是否需要预留一部分map/reduce计算能力来执行那些失败的、紧急的或特殊的任务
   */
  private boolean exceededPadding(boolean isMapTask, ClusterStatus clusterStatus, int maxTaskTrackerSlots) { 
	  
	 //当前集群可用的计算节点数量
    int numTaskTrackers = clusterStatus.getTaskTrackers();
    //当前集群正在执行的map/reduce任务总数量 
    int totalTasks = (isMapTask) ? clusterStatus.getMapTasks() : clusterStatus.getReduceTasks();
    //当前集群执行的map/reduce任务的总计算能力
    int totalTaskCapacity = isMapTask ? clusterStatus.getMaxMapTasks() : clusterStatus.getMaxReduceTasks();

    //当前集群待调度的作业队列
    Collection<JobInProgress> jobQueue = jobQueueJobInProgressListener.getJobQueue();

    boolean exceededPadding = false;	//当前集群应该预留的计算能力
    synchronized (jobQueue) {
      int totalNeededTasks = 0;
      for(JobInProgress job : jobQueue) {
        if(job.getStatus().getRunState() != JobStatus.RUNNING || job.numReduceTasks == 0) {
          continue;
        }

        //统计当前集群的map/reduce计算需求,计算应该预留多少map/reduce计算力
        totalNeededTasks += isMapTask ? job.desiredMaps() : job.desiredReduces();
        int padding = 0;
        if(numTaskTrackers > MIN_CLUSTER_SIZE_FOR_PADDING) {
          padding = Math.min(maxTaskTrackerSlots, (int) (totalNeededTasks * padFraction));
        }
        
        //当前集群剩余的map/reduce计算能力不足预留的,则让当前计算节点预留一部分map/reduce计算能力
        if(totalTasks + padding >= totalTaskCapacity) {
          exceededPadding = true;
          break;
        }
      }//for
    }

    return exceededPadding;
  }

    其中,全局变量padFraction的默认值为0.01,也可通过配置文件中的mapred.jobtracker.taskalloc.capacitypad项来设置。还要值得说明的是,任务调度器TaskScheduler只负责调度作业的正式Map/Reduce任务,而作业的其它辅助任务都是交由JobTracker来调度的,如JobSetup、JobCleanup、TaskCleanup任务等。这一点是非常值得用户在自定义任务调度器的时候注意的。对于JobQueueTaskScheduler的任务调度实现原则可总结如下:
        1.先调度优先级高的作业,统一优先级的作业则先进先出;
        2.尽量使集群每一个TaskTracker达到负载均衡(这个均衡是task数量上的而不是实际的工作强度);
        3.尽量分配作业的本地任务给TaskTracker,但不是尽快分配作业的本地任务给TaskTracker,最多分配一个非本地任务给TaskTracker(一是保证任务的并发性,二是避免有些TaskTracker的本地任务被偷走),最多分配一个reduce任务;
         4.为优先级或者紧急的Task预留一定的slot;

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值