Yarn源码分析之MapReduce作业中任务Task调度整体流程(一)

简介:         v2版本的MapReduce作业中,作业JOB_SETUP_COMPLETED事件的发生,即作业SETUP阶段完成事件,会触发作业由SETUP状态转换到RUNNING状态,而作业状态转换中涉及作业信息的处理,是由SetupCompletedTransition来完成的,它主要做了...

        v2版本的MapReduce作业中,作业JOB_SETUP_COMPLETED事件的发生,即作业SETUP阶段完成事件,会触发作业由SETUP状态转换到RUNNING状态,而作业状态转换中涉及作业信息的处理,是由SetupCompletedTransition来完成的,它主要做了四件事:

        1、通过设置作业Job的成员变量setupProgress为1,标记作业setup已完成;

        2、调度作业Job的Map Task;

        3、调度作业的JobReduce Task;

        4、如果没有task了,则生成JOB_COMPLETED事件并交由作业的事件处理器eventHandler进行处理。

        本文,我们就将研究作业Job中Task是如何被调度的。


        首先看下SetupCompletedTransition中transition()方法关于作业Job中Task调度的代码,如下:

      // 调度作业Job的Map Task
      job.scheduleTasks(job.mapTasks, job.numReduceTasks == 0);
      // 调度作业Job的Reduce Task
      job.scheduleTasks(job.reduceTasks, true);
        它实际上是通过Job,也就是JobImpl的scheduleTasks()完成的,这个方法需要两个参数,第一个是作业Job待调度任务的任务ID集合taskIDs,第二个参数是表示是否恢复任务输出的标志位recoverTaskOutput,对于Map-Only型作业中Map任务和所有类型作业的Reduce任务,都需要恢复,标志位recoverTaskOutput为true,具体代码如下:
  protected void scheduleTasks(Set<TaskId> taskIDs,
      boolean recoverTaskOutput) {
	  
	// 遍历传入的任务集合taskIDs中的每个TaskId,对taskID做以下处理:
    for (TaskId taskID : taskIDs) {
    	
      // 根据taskID从集合completedTasksFromPreviousRun中移除对应元素,并获取被移除的元素TaskInfo实例taskInfo
      TaskInfo taskInfo = completedTasksFromPreviousRun.remove(taskID);
      
      if (taskInfo != null) {// 若存在taskID对应任务信息TaskInfo实例taskInfo
    	  
    	// 构造T_RECOVER类型任务恢复事件TaskRecoverEvent,交给eventHandler处理,标志位recoverTaskOutput表示是否恢复任务的输出,
    	// 对于Map-Only型Map任务和所有的Reduce任务,都需要恢复,标志位recoverTaskOutput为true
        eventHandler.handle(new TaskRecoverEvent(taskID, taskInfo,
            committer, recoverTaskOutput));
      } else {
    	  
    	// 否则,构造T_SCHEDULE类型任务调度事件TaskEvent,交给eventHandler处理
        eventHandler.handle(new TaskEvent(taskID, TaskEventType.T_SCHEDULE));
      }
    }
  }
        scheduleTasks()方法遍历传入的任务集合taskIDs中的每个TaskId实例taskID,对taskID做以下处理:

       1、根据taskID从集合completedTasksFromPreviousRun中移除对应元素,并获取被移除的元素TaskInfo实例taskInfo;

       2、若存在taskID对应任务信息TaskInfo实例taskInfo,构造T_RECOVER类型任务恢复事件TaskRecoverEvent,交给eventHandler处理,标志位recoverTaskOutput表示是否恢复任务的输出,对于Map-Only型Map任务和所有的Reduce任务,都需要恢复,标志位recoverTaskOutput为true;

       3、否则,构造T_SCHEDULE类型任务调度事件TaskEvent,交给eventHandler处理。

        我们先看T_SCHEDULE类型任务调度事件TaskEvent的处理,它是交由Job的eventHandler来处理的,而这个eventHandler是在Job被创建时(即构造JobImpl实例时)由MRAppMaster的dispatcher来赋值的,而在MRAppMaster中,dispatcher被创建后就会注册任务事件的处理器TaskEventDispatcher实例,代码如下:

dispatcher.register(TaskEventType.class, new TaskEventDispatcher());
        而这个任务事件处理器TaskEventDispatcher中处理任务事件TaskEvent的handle()方法定义如下:

  private class TaskEventDispatcher implements EventHandler<TaskEvent> {
    @SuppressWarnings("unchecked")
    @Override
    public void handle(TaskEvent event) {
      Task task = context.getJob(event.getTaskID().getJobId()).getTask(
          event.getTaskID());
      ((EventHandler<TaskEvent>)task).handle(event);
    }
  }
        它实际上是通过作业Job中相关任务Task的handle()方法来处理的,而这个任务Task的实现则是TaskImpl,其中对于各种任务事件的处理,也是类似作业Job,由一个任务Task的状态机进行处理,关于任务Task的状态机,我们会有专门的文章进行介绍,这里,您只需要知道在TaskImpl中,对于上述两种任务状态机中任务状态的转换、触发事件及事件处理者定义如下:
  private static final StateMachineFactory
               <TaskImpl, TaskStateInternal, TaskEventType, TaskEvent> 
            stateMachineFactory 
           = new StateMachineFactory<TaskImpl, TaskStateInternal, TaskEventType, TaskEvent>
               (TaskStateInternal.NEW)
		
	// 省略部分代码
        .addTransition(TaskStateInternal.NEW, TaskStateInternal.SCHEDULED, 
                  TaskEventType.T_SCHEDULE, new InitialScheduleTransition())		
	// 省略部分代码
	.addTransition(TaskStateInternal.NEW,
                   EnumSet.of(TaskStateInternal.FAILED,
                   TaskStateInternal.KILLED,
                   TaskStateInternal.RUNNING,
                   TaskStateInternal.SUCCEEDED),
                   TaskEventType.T_RECOVER, new RecoverTransition())
	// 省略部分代码
        由此可见,对于T_RECOVER类型任务恢复事件TaskRecoverEvent,Task状态机指定由RecoverTransition处理,并且任务Task的状态会由NEW转换为RUNNING、FAILED、KILLED、SUCCEEDED等,而对于T_SCHEDULE类型任务调度事件TaskEvent,则由Task状态机指定为InitialScheduleTransition处理,并且任务Task的状态会由NEW转换为SCHEDULED。下面,我们挨个进行分析。


        一、T_SCHEDULE类型任务调度事件TaskEvent

        由InitialScheduleTransition进行处理,任务Task的状态会由NEW转换为SCHEDULED,InitialScheduleTransition代码如下:

  private static class InitialScheduleTransition
    implements SingleArcTransition<TaskImpl, TaskEvent> {

    @Override
    public void transition(TaskImpl task, TaskEvent event) {
    	
      // 添加并调度任务运行尝试TaskAttempt,Avataar.VIRGIN表示它是第一个Attempt,
      // 而剩余的Avataar.SPECULATIVE表示它是为拖后腿任务开启的一个Attempt,即推测执行原理
      task.addAndScheduleAttempt(Avataar.VIRGIN);
      // 设置任务的调度时间scheduledTime为当前时间
      task.scheduledTime = task.clock.getTime();
      // 发送任务启动事件
      task.sendTaskStartedEvent();
    }
  }
         InitialScheduleTransition的处理逻辑比较简单,大体如下:

        1、调用addAndScheduleAttempt()方法,添加并调度任务运行尝试TaskAttempt,Avataar.VIRGIN表示它是第一个Attempt,而剩余的Avataar.SPECULATIVE表示它是为拖后腿任务开启的一个Attempt,即推测执行原理;

        2、设置任务的调度时间scheduledTime为当前时间;

        3、发送任务启动事件。

        其中,1中的addAndScheduleAttempt()方法实现如下:

  // This is always called in the Write Lock
  private void addAndScheduleAttempt(Avataar avataar) {
	  
	// 调用addAttempt()方法,创建一个任务运行尝试TaskAttempt实例attempt,
	// 并将其添加到attempt集合attempts中,还会设置attempt的Avataar属性
    TaskAttempt attempt = addAttempt(avataar);
    
    // 将attempt的id添加到正在执行的attempt集合inProgressAttempts中
    inProgressAttempts.add(attempt.getID());
    
    //schedule the nextAttemptNumber
    // 调度TaskAttempt
    
    // 如果集合failedAttempts大小大于0,说明该Task之前有TaskAttempt失败过,此次为重新调度,
    // TaskAttemp事件类型为TA_RESCHEDULE,
    if (failedAttempts.size() > 0) {
      eventHandler.handle(new TaskAttemptEvent(attempt.getID(),
          TaskAttemptEventType.TA_RESCHEDULE));
    } else {
      // 否则为TaskAttemp事件类型为TA_SCHEDULE
      eventHandler.handle(new TaskAttemptEvent(attempt.getID(),
          TaskAttemptEventType.TA_SCHEDULE));
    }
  }
        addAndScheduleAttempt()方法处理逻辑如下:

        1、调用addAttempt()方法,创建一个任务运行尝试TaskAttempt实例attempt,并将其添加到attempt集合attempts中,还会设置attempt的Avataar属性;

        2、将attempt的id添加到正在执行的attempt集合inProgressAttempts中;

        3、调度TaskAttempt:如果集合failedAttempts大小大于0,说明该Task之前有TaskAttempt失败过,此次为重新调度,TaskAttemp事件类型为TA_RESCHEDULE,否则为TaskAttemp事件类型为TA_SCHEDULE。

        而addAttempt()方法实现如下:

  private TaskAttemptImpl addAttempt(Avataar avataar) {
	  
	// 调用createAttempt()方法创建任务运行尝试TaskAttemptImpl实例attempt
    TaskAttemptImpl attempt = createAttempt();
    
    // 设置attempt的Avataar属性
    attempt.setAvataar(avataar);
    
    // 记录debug级别日志信息:Created attempt ... ...
    if (LOG.isDebugEnabled()) {
      LOG.debug("Created attempt " + attempt.getID());
    }
    
    // 将创建的任务运行尝试TaskAttemptImpl实例attempt与其ID的对应关系添加到TaskImpl的任务运行尝试集合attempts中,
    // attempts先被初始化为Collections.emptyMap()
    // this.attempts = Collections.emptyMap();
    switch (attempts.size()) {
      case 0:
    	  
    	// 如果attempts大小为0,即为Collections.emptyMap(),则将其更换为Collections.singletonMap(),并加入该TaskAttemptImpl实例attempt
        attempts = Collections.singletonMap(attempt.getID(),
            (TaskAttempt) attempt);
        break;
        
      case 1:
    	  
    	// 如果attempts大小为1,即为Collections.singletonMap(),则将其替换为LinkedHashMap,并加入之前和现在的TaskAttemptImpl实例attempt
        Map<TaskAttemptId, TaskAttempt> newAttempts
            = new LinkedHashMap<TaskAttemptId, TaskAttempt>(maxAttempts);
        newAttempts.putAll(attempts);
        attempts = newAttempts;
        attempts.put(attempt.getID(), attempt);
        break;

      default:
    	// 如果attempts大小大于1,说明其实一个LinkedHashMap,直接put吧
        attempts.put(attempt.getID(), attempt);
        break;
    }

    // 累加TaskAttempt计数器nextAttemptNumber
    ++nextAttemptNumber;
    
    // 返回TaskAttemptImpl实例attempt
    return attempt;
  }
        其处理逻辑如下:

        1、调用createAttempt()方法创建任务运行尝试TaskAttemptImpl实例attempt;

        2、设置attempt的Avataar属性;

        3、记录debug级别日志信息:Created attempt ... ...;

        4、将创建的任务运行尝试TaskAttemptImpl实例attempt与其ID的对应关系添加到TaskImpl的任务运行尝试集合attempts中,attempts先被初始化为Collections.emptyMap():

              4.1、如果attempts大小为0,即为Collections.emptyMap(),则将其更换为Collections.singletonMap(),并加入该TaskAttemptImpl实例attempt;

              4.2、如果attempts大小为1,即为Collections.singletonMap(),则将其替换为LinkedHashMap,并加入之前和现在的TaskAttemptImpl实例attempt;

              4.3、如果attempts大小大于1,说明其实一个LinkedHashMap,直接put吧;

        5、累加TaskAttempt计数器nextAttemptNumber;

        6、返回TaskAttemptImpl实例attempt。

         继续往下追踪createAttempt()方法,其在TaskImpl中代码如下:

  protected abstract TaskAttemptImpl createAttempt();
        这是一个抽象方法,由其子类实现,而它的子类有两个,表示Map任务的MapTaskImpl和表示Reduce任务的ReduceTaskImpl,其createAttempt()方法分别实现如下:

         1、MapTaskImpl.createAttempt()

  @Override
  protected TaskAttemptImpl createAttempt() {
    return new MapTaskAttemptImpl(getID(), nextAttemptNumber,
        eventHandler, jobFile,
        partition, taskSplitMetaInfo, conf, taskAttemptListener,
        jobToken, credentials, clock, appContext);
  }
        生成一个MapTaskAttemptImpl实例,传入表示Attempt序号的nextAttemptNumber、事件处理器eventHandler、作业文件jobFile、分区信息partition、分片元数据信息taskSplitMetaInfo等关键变量。

        2、ReduceTaskImpl.createAttempt()

  @Override
  protected TaskAttemptImpl createAttempt() {
    return new ReduceTaskAttemptImpl(getID(), nextAttemptNumber,
        eventHandler, jobFile,
        partition, numMapTasks, conf, taskAttemptListener,
        jobToken, credentials, clock, appContext);
  }

        生成一个ReduceTaskAttemptImpl实例,除不需要分片元数据信息taskSplitMetaInfo,和需要一个Map任务数numMapTasks外,其他与MapTaskAttemptImpl基本相同。

        TaskAttempt生成了,接下来就应该进行调度执行了。我们再折回去看看addAndScheduleAttempt()方法中,发送的TA_SCHEDULE或TA_RESCHEDULE类型的TaskAttemptEvent,其与JobImpl、TaskImpl一样,是由TaskAttempt状态机负责处理的,如下所示:

     // 在事件TaskAttemptEventType.TA_SCHEDULE的触发下,经过RequestContainerTransition的处理,
     // TaskAttempt的状态由NEW转换成UNASSIGNED
     .addTransition(TaskAttemptStateInternal.NEW, TaskAttemptStateInternal.UNASSIGNED,
         TaskAttemptEventType.TA_SCHEDULE, new RequestContainerTransition(false))
     
     // 在事件TaskAttemptEventType.TA_SCHEDULE的触发下,经过RequestContainerTransition的处理,
     // TaskAttempt的状态由NEW转换成UNASSIGNED
     .addTransition(TaskAttemptStateInternal.NEW, TaskAttemptStateInternal.UNASSIGNED,
         TaskAttemptEventType.TA_RESCHEDULE, new RequestContainerTransition(true))
     // 上述二者的区别是RequestContainerTransition传入的标志位rescheduled,前者为false,后者为true
        在事件TaskAttemptEventType.TA_SCHEDULE的触发下,经过RequestContainerTransition的处理,TaskAttempt的状态由NEW转换成UNASSIGNED;在事件TaskAttemptEventType.TA_SCHEDULE的触发下,经过RequestContainerTransition的处理,TaskAttempt的状态由NEW转换成UNASSIGNED;上述二者的区别是RequestContainerTransition传入的标志位rescheduled,前者为false,后者为true。

        我们再看下RequestContainerTransition的实现,代码如下:

  @SuppressWarnings("unchecked")
    @Override
    public void transition(TaskAttemptImpl taskAttempt, 
        TaskAttemptEvent event) {
      // Tell any speculator that we're requesting a container
      
      // taskAttempt的事件处理器eventHandler处理SpeculatorEvent事件,告诉所有的speculator,此时正在申请一个容器
      taskAttempt.eventHandler.handle
          (new SpeculatorEvent(taskAttempt.getID().getTaskId(), +1));
      //request for container
      
      // 申请容器
      if (rescheduled) {// Task的Attempt重新调度
    	  
    	// 构造容器申请事件ContainerRequestEvent,并交由taskAttempt的事件处理器eventHandler处理,
    	// 这个eventHandler实际上是MRAppMaster中的dispatcher,依次经过TaskImpl、TaskAttemptImpl的创建传递过来的,
        taskAttempt.eventHandler.handle(
            ContainerRequestEvent.createContainerRequestEventForFailedContainer(
                taskAttempt.attemptId, 
                taskAttempt.resourceCapability));
      } else {// Task的Attempt第一次调度
    	  
    	// 构造容器申请事件ContainerRequestEvent,并交由taskAttempt的事件处理器eventHandler处理,
        taskAttempt.eventHandler.handle(new ContainerRequestEvent(
            taskAttempt.attemptId, taskAttempt.resourceCapability,
            taskAttempt.dataLocalHosts.toArray(
                new String[taskAttempt.dataLocalHosts.size()]),
            taskAttempt.dataLocalRacks.toArray(
                new String[taskAttempt.dataLocalRacks.size()])));
      }
      
      // 两者创建的ContainerRequestEvent事件的区别是,rescheduled时,不需要考虑Node和Lock位置属性,因为此时Attempt之前已经失败过,此时应当能够以完成Attempt为首要任务,
      // 同时,两者的事件类型都是ContainerAllocator.EventType.CONTAINER_REQ,
      // MRAppMaster中的dispatcher针对该事件ContainerAllocator.EventType注册的事件处理器是LocalContainerAllocator或RMContainerAllocator
    }
        RequestContainerTransition的transition()方法处理逻辑如下:

        1、TaskAttempt的事件处理器eventHandler处理SpeculatorEvent事件,告诉所有的speculator,此时正在申请一个容器;

        2、申请容器:

              2.1、如果是Task的Attempt重新调度,构造容器申请事件ContainerRequestEvent,并交由taskAttempt的事件处理器eventHandler处理,这个eventHandler实际上是MRAppMaster中的dispatcher,依次经过TaskImpl、TaskAttemptImpl的创建传递过来的;

              2.2、否则如果是Task的Attempt第一次调度,构造容器申请事件ContainerRequestEvent,并交由taskAttempt的事件处理器eventHandler处理。

        两者创建的ContainerRequestEvent事件的区别是,rescheduled时,不需要考虑Node和Lock位置属性,因为此时Attempt之前已经失败过,此时应当能够以完成Attempt为首要任务,同时,两者的事件类型都是ContainerAllocator.EventType.CONTAINER_REQ,MRAppMaster中的dispatcher针对该事件ContainerAllocator.EventType注册的事件处理器是LocalContainerAllocator或RMContainerAllocator。

        关于Yarn容器等资源申请与分配RMContainerAllocator的介绍,我会在以后的文章中为大家讲解,这里,你只需要了解其执行的大体流程即可:

        1、RMContainerAllocator首先间接继承自AbstractService,它是Hadoop中的一种服务,有服务初始化serviceInit()及服务启动serviceStart()方法要执行;

        2、RMContainerAllocator针对容器请求分配事件,是一个双重生产者-消费者模式,第一层生产者通过其handle()方法,将容器请求分配ContainerAllocatorEvent加入其内部eventQueue队列,第一层消费者通过其内部事件处理线程eventHandlingThread,不断的从事件队列eventQueue中take事件进行消费,而消费的方式是做为第二层生产者,将事件按照任务类型放入调度请求列表scheduledRequests、pendingReduces中,scheduledRequests是一个复杂的区分Map和Reduce任务的会立即被调度的请求列表,而pendingReduces只是存储等待被调度的Reduce任务请求的列表,其会根据Yarn中资源情况和Map任务完成情况确定是将事件移送至(即rampUp)scheduledRequests,还是从scheduledRequests移回Reduce任务调度请求至pendingReduces(即rampDown),而第二层的消费者则是RMContainerAllocator祖先父类RMCommunicator中的心跳线程allocatorThread,它周期性的调用heartbeat()方法,从Yarn的RM中获取可用资源,然后消费scheduledRequests列表中的请求,进行容器分配;

        3、RMContainerAllocator中,对于Map任务来说,它经历的数据结构,或者生命周期为scheduled->assigned->completed,而Reduce任务则是pending->scheduled->assigned->completed;

        4、经过一些的复杂逻辑后,包括综合判断资源情况、任务本地性、优先调度失败任务、Map任务完成比例、针对拖后退的任务进行推测执行等,无论是Map任务还是Reduce任务,最终在分配到容器Container后,都会发送一个TaskAttemptContainerAssignedEvent事件,交由TaskAttemptImpl的状态机中ContainerAssignedTransition进行处理,而其方法则最终会构造ContainerRemoteLaunchEvent事件,进行Container远程加载,在远程或本机或本进程Container中Launch任务尝试进行任务的执行。

        关于RMContainerAllocator,因为其结构、处理逻辑比较复杂,我会专门写文章进行分析,敬请期待!


        二、T_RECOVER类型任务恢复事件TaskRecoverEvent

        未完待续!敬请关注后续文章!











相关文章
|
1月前
|
分布式计算 资源调度 数据可视化
Hadoop-06-Hadoop集群 历史服务器配置 超详细 执行任务记录 JobHistoryServer MapReduce执行记录 日志聚合结果可视化查看
Hadoop-06-Hadoop集群 历史服务器配置 超详细 执行任务记录 JobHistoryServer MapReduce执行记录 日志聚合结果可视化查看
38 1
|
1月前
|
分布式计算 资源调度 Hadoop
Hadoop-05-Hadoop集群 集群WordCount 超详细 真正的分布式计算 上传HDFS MapReduce计算 YRAN查看任务 上传计算下载查看
Hadoop-05-Hadoop集群 集群WordCount 超详细 真正的分布式计算 上传HDFS MapReduce计算 YRAN查看任务 上传计算下载查看
50 1
|
2月前
|
资源调度 分布式计算 Hadoop
使用YARN命令管理Hadoop作业
本文介绍了如何使用YARN命令来管理Hadoop作业,包括查看作业列表、检查作业状态、杀死作业、获取作业日志以及检查节点和队列状态等操作。
54 1
使用YARN命令管理Hadoop作业
|
2月前
|
分布式计算 资源调度 Hadoop
在YARN集群上运行部署MapReduce分布式计算框架
主要介绍了如何在YARN集群上配置和运行MapReduce分布式计算框架,包括准备数据、运行MapReduce任务、查看任务日志,并启动HistoryServer服务以便于日志查看。
64 0
|
4月前
|
SQL 缓存 资源调度
实时计算 Flink版产品使用问题之在Flink on Yarn模式下,如何对job作业进行指标监控
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
5月前
|
分布式计算 资源调度 数据处理
YARN支持哪些非基于MapReduce的计算模型?
【6月更文挑战第19天】YARN支持哪些非基于MapReduce的计算模型?
68 11
|
5月前
|
数据采集 SQL 分布式计算
|
6月前
|
SQL 分布式计算 数据处理
【Hive】所有的Hive任务都会有MapReduce的执行吗?
【4月更文挑战第17天】【Hive】所有的Hive任务都会有MapReduce的执行吗?
|
6月前
|
资源调度 Kubernetes Java
Flink--day02、Flink部署(Yarn集群搭建下的会话模式部署、单作业模式部署、应用模式部署)
Flink--day022、Flink部署(Yarn集群搭建下的会话模式部署、单作业模式部署、应用模式部署)
374 5
|
2月前
|
资源调度 分布式计算 Hadoop
YARN(Hadoop操作系统)的架构
本文详细解释了YARN(Hadoop操作系统)的架构,包括其主要组件如ResourceManager、NodeManager和ApplicationMaster的作用以及它们如何协同工作来管理Hadoop集群中的资源和调度作业。
134 3
YARN(Hadoop操作系统)的架构

相关实验场景

更多
下一篇
无影云桌面