• Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(一)


    我们知道,如果想要在Yarn上运行MapReduce作业,仅需实现一个ApplicationMaster组件即可,而MRAppMaster正是MapReduce在Yarn上ApplicationMaster的实现,由其控制MR作业在Yarn上的执行。如此,随之而来的一个问题就是,MRAppMaster是如何控制MapReduce作业在Yarn上运行的,换句话说,MRAppMaster上MapReduce作业处理总流程是什么?这就是本文要研究的重点。

            通过MRAppMaster类的定义我们就能看出,MRAppMaster继承自CompositeService,而CompositeService又继承自AbstractService,也就是说MRAppMaster也是Hadoop中的一种服务,我们看下服务启动的serviceStart()方法中关于MapReduce作业的处理,关键代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. @SuppressWarnings("unchecked")  
    2. @Override  
    3. protected void serviceStart() throws Exception {  
    4.   
    5.   // ......省略部分代码  
    6.   
    7.   // 调用createJob()方法创建作业Job实例job  
    8.   // /////////////////// Create the job itself.  
    9.   job = createJob(getConfig(), forcedState, shutDownMessage);  
    10.   
    11.   // End of creating the job.  
    12.   
    13.   // ......省略部分代码  
    14.   
    15.   // 作业初始化失败标志位initFailed默认为false,即初始化成功,没有错误  
    16.   boolean initFailed = false;  
    17.   if (!errorHappenedShutDown) {  
    18.       
    19.     // create a job event for job intialization  
    20.     // 创建一个Job初始化事件initJobEvent  
    21.     JobEvent initJobEvent = new JobEvent(job.getID(), JobEventType.JOB_INIT);  
    22.    
    23.     // Send init to the job (this does NOT trigger job execution)  
    24.     // This is a synchronous call, not an event through dispatcher. We want  
    25.     // job-init to be done completely here.  
    26.     // 调用jobEventDispatcher的handle()方法,处理Job初始化事件initJobEvent,即将Job初始化事件交由事件分发器jobEventDispatcher处理,  
    27.     jobEventDispatcher.handle(initJobEvent);  
    28.   
    29.     // If job is still not initialized, an error happened during  
    30.     // initialization. Must complete starting all of the services so failure  
    31.     // events can be processed.  
    32.     // 获取Job初始化结果initFailed  
    33.     initFailed = (((JobImpl)job).getInternalState() != JobStateInternal.INITED);  
    34.   
    35.     // JobImpl's InitTransition is done (call above is synchronous), so the  
    36.     // "uber-decision" (MR-1220) has been made.  Query job and switch to  
    37.     // ubermode if appropriate (by registering different container-allocator  
    38.     // and container-launcher services/event-handlers).  
    39.   
    40.     // ......省略部分代码  
    41.    
    42.     // Start ClientService here, since it's not initialized if  
    43.     // errorHappenedShutDown is true  
    44.       
    45.     // 启动客户端服务clientService  
    46.     clientService.start();  
    47.   }  
    48.     
    49.   //start all the components  
    50.   // 调用父类的serviceStart(),启动所有组件  
    51.   super.serviceStart();  
    52.   
    53.   // finally set the job classloader  
    54.   // 最终设置作业类加载器  
    55.   MRApps.setClassLoader(jobClassLoader, getConfig());  
    56.   
    57.   if (initFailed) {  
    58.     // 如果作业初始化失败,构造作业初始化失败JOB_INIT_FAILED事件,并交由事件分发器jobEventDispatcher处理  
    59.     JobEvent initFailedEvent = new JobEvent(job.getID(), JobEventType.JOB_INIT_FAILED);  
    60.     jobEventDispatcher.handle(initFailedEvent);  
    61.   } else {  
    62.     // All components have started, start the job.  
    63.     // 调用startJobs()方法启动作业  
    64.     startJobs();  
    65.   }  
    66. }  

            通过MRAppMaster服务启动的serviceStart()方法我们大致知道,MapReduce作业在MRAppMaster中经历了创建--初始化--启动三个主要过程,剪去枝叶,保留主干,具体如下:

            1、创建:调用createJob()方法创建作业Job实例job;

            2、初始化:

                  2.1、创建一个Job初始化事件initJobEvent;

                  2.2、调用jobEventDispatcher的handle()方法,处理Job初始化事件initJobEvent,即将Job初始化事件交由事件分发器jobEventDispatcher处理;

                  2.3、获取Job初始化结果initFailed;

                  2.4、如果作业初始化失败,构造作业初始化失败JOB_INIT_FAILED事件,并交由事件分发器jobEventDispatcher处理。

            3、启动:调用startJobs()方法启动作业。

             实际上,作业启动后不可能永远都不停止,MRAppMaster最终会将作业停止,这也是作业处理流程的第四步,即最后一步,作业停止!在哪里处理的呢?我们先卖个关子,请您暂时忽略这个问题,我们稍后会给出答案!

            下面,我们针对MapReduce作业的上述三个主要过程,分别展开描述。

            一、创建

            首先看作业创建,createJob()方法如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** Create and initialize (but don't start) a single job.  
    2.  * @param forcedState a state to force the job into or null for normal operation.  
    3.  * @param diagnostic a diagnostic message to include with the job. 
    4.  */  
    5. protected Job createJob(Configuration conf, JobStateInternal forcedState,   
    6.     String diagnostic) {  
    7.   
    8.   // create single job  
    9. / 创建一个作业Job实例newJob,其实现为JobImpl  
    10.   Job newJob =  
    11.       new JobImpl(jobId, appAttemptID, conf, dispatcher.getEventHandler(),  
    12.           taskAttemptListener, jobTokenSecretManager, jobCredentials, clock,  
    13.           completedTasksFromPreviousRun, metrics,  
    14.           committer, newApiCommitter,  
    15.           currentUser.getUserName(), appSubmitTime, amInfos, context,   
    16.           forcedState, diagnostic);  
    17.     
    18.   // 将新创建的作业newJob的jobId与其自身的映射关系存储到应用运行上下文信息context中的jobs集合中  
    19.   ((RunningAppContext) context).jobs.put(newJob.getID(), newJob);  
    20.   
    21.   // 异步事件分发器dispatcher注册作业完成事件JobFinishEvent对应的事件处理器,通过createJobFinishEventHandler()方法获得  
    22.   dispatcher.register(JobFinishEvent.Type.class,  
    23.       createJobFinishEventHandler());       
    24.   // 返回新创建的作业newJob  
    25.   return newJob;  
    26. // end createJob()  

            其主要逻辑如下:

            1、创建一个作业Job实例newJob,其实现为JobImpl,传入作业艾迪jobId、应用尝试艾迪appAttemptID、任务尝试监听器taskAttemptListener、输出提交器committer、用户名currentUser.getUserName()、应用运行上下文信息context等关键成员变量;

            2、将新创建的作业newJob的jobId与其自身的映射关系存储到应用运行上下文信息context中的jobs集合中;

            3、异步事件分发器dispatcher注册作业完成事件JobFinishEvent对应的事件处理器,通过createJobFinishEventHandler()方法获得;

            4、返回新创建的作业newJob。

            关于作业创建中的一些细节,我们暂时先不做过多关注,留待以后的文章专门进行分析。这里,我们先重点看看第3步,异步事件分发器dispatcher注册作业完成事件JobFinishEvent对应的事件处理器,通过createJobFinishEventHandler()方法获得,而createJobFinishEventHandler()方法代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.  * create an event handler that handles the job finish event. 
    3.  * @return the job finish event handler. 
    4.  */  
    5. protected EventHandler<JobFinishEvent> createJobFinishEventHandler() {  
    6.   return new JobFinishEventHandler();  
    7. }  

            也就是说,当作业被创建后,它就被定义了作业完成事件JobFinishEvent的处理器为JobFinishEventHandler,而JobFinishEventHandler的定义如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. private class JobFinishEventHandler implements EventHandler<JobFinishEvent> {  
    2.   @Override  
    3.   public void handle(JobFinishEvent event) {  
    4.     // Create a new thread to shutdown the AM. We should not do it in-line  
    5.     // to avoid blocking the dispatcher itself.  
    6.     new Thread() {  
    7.         
    8.       @Override  
    9.       public void run() {  
    10.         shutDownJob();  
    11.       }  
    12.     }.start();  
    13.   }  
    14. }  

            这就是我们上面没有详细介绍的第四步--作业停止,它最终是调用的shutDownJob()方法,并开启一个新的线程来完成作业停止的,我们稍后再做介绍。

            二、初始化

            我们再来看作业的初始化,它是通过创建一个Job初始化事件JobEvent实例initJobEvent,事件类型为JobEventType.JOB_INIT,然后交由事件分发器jobEventDispatcher处理的。我们先来看下这个jobEventDispatcher的定义及实例化,如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. // 作业事件分发器  
    2. private JobEventDispatcher jobEventDispatcher;  

             jobEventDispatcher是一个JobEventDispatcher类型的作业事件分发器,其实例化为:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. this.jobEventDispatcher = new JobEventDispatcher();  

            而JobEventDispatcher的定义如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. private class JobEventDispatcher implements EventHandler<JobEvent> {  
    2.   @SuppressWarnings("unchecked")  
    3.   @Override  
    4.   public void handle(JobEvent event) {  
    5.     // 从应用运行上下文信息context中根据jobId获取Job实例,即JobImpl对象,调用其handle()方法,处理对应事件  
    6.     ((EventHandler<JobEvent>)context.getJob(event.getJobId())).handle(event);  
    7.   }  
    8. }  

            很简单,从应用运行上下文信息context中根据jobId获取Job实例,即JobImpl对象,调用其handle()方法,处理对应事件,而这个Job实例,还记得上面描述的吗,就是在Job最初被创建时,被添加到应用运行上下文信息context中jobs集合中的,key为jobId,value就是JobImpl对象。context的实现RunningAppContext中,根据jobId获取job实例的代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. @Override  
    2. public Job getJob(JobId jobID) {  
    3.   return jobs.get(jobID);  
    4. }  

            好了,我们就看下JobImpl中handle()方法是如何对类型为JobEventType.JOB_INIT的JobEvent进行处理的吧!

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. @Override  
    2. /** 
    3.  * The only entry point to change the Job. 
    4.  */  
    5. public void handle(JobEvent event) {  
    6.   if (LOG.isDebugEnabled()) {  
    7.     LOG.debug("Processing " + event.getJobId() + " of type "  
    8.         + event.getType());  
    9.   }  
    10.   try {  
    11.     writeLock.lock();  
    12.     JobStateInternal oldState = getInternalState();  
    13.     try {  
    14.        getStateMachine().doTransition(event.getType(), event);  
    15.     } catch (InvalidStateTransitonException e) {  
    16.       LOG.error("Can't handle this event at current state", e);  
    17.       addDiagnostic("Invalid event " + event.getType() +   
    18.           " on Job " + this.jobId);  
    19.       eventHandler.handle(new JobEvent(this.jobId,  
    20.           JobEventType.INTERNAL_ERROR));  
    21.     }  
    22.     //notify the eventhandler of state change  
    23.     if (oldState != getInternalState()) {  
    24.       LOG.info(jobId + "Job Transitioned from " + oldState + " to "  
    25.                + getInternalState());  
    26.       rememberLastNonFinalState(oldState);  
    27.     }  
    28.   }  
    29.     
    30.   finally {  
    31.     writeLock.unlock();  
    32.   }  
    33. }  

            最核心的就是通过语句getStateMachine().doTransition(event.getType(), event)进行处理,实际上这牵着到了Yarn中MapReduce作业的状态机,为了本文叙述的流畅性、简洁性、重点明确性,我们对于作业状态机先不做解释,这部分内容留待以后的文章专门进行介绍,这里你只要知道作业初始化最终是通过JobImpl静态内部类InitTransition的transition()方法来实现的就行。我们看下InitTransition的transition()方法,如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.      * Note that this transition method is called directly (and synchronously) 
    3.      * by MRAppMaster's init() method (i.e., no RPC, no thread-switching; 
    4.      * just plain sequential call within AM context), so we can trigger 
    5.      * modifications in AM state from here (at least, if AM is written that 
    6.      * way; MR version is). 
    7.      */  
    8.     @Override  
    9.     public JobStateInternal transition(JobImpl job, JobEvent event) {  
    10.         
    11.       // 调用作业度量指标体系metrics的submittedJob()方法,提交作业  
    12.       job.metrics.submittedJob(job);  
    13.         
    14.       // 调用作业度量指标体系metrics的preparingJob()方法,开始作业准备  
    15.       job.metrics.preparingJob(job);  
    16.   
    17.       // 新旧API创建不同的作业上下文JobContextImpl实例  
    18.       if (job.newApiCommitter) {  
    19.         job.jobContext = new JobContextImpl(job.conf,  
    20.             job.oldJobId);  
    21.       } else {  
    22.         job.jobContext = new org.apache.hadoop.mapred.JobContextImpl(  
    23.             job.conf, job.oldJobId);  
    24.       }  
    25.         
    26.       try {  
    27.             
    28.         // 调用setup()方法,完成作业启动前的部分初始化工作  
    29.         setup(job);  
    30.           
    31.         // 设置作业job对应的文件系统fs  
    32.         job.fs = job.getFileSystem(job.conf);  
    33.   
    34.         //log to job history  
    35.         // 创建作业已提交事件JobSubmittedEvent实例jse  
    36.         JobSubmittedEvent jse = new JobSubmittedEvent(job.oldJobId,  
    37.               job.conf.get(MRJobConfig.JOB_NAME, "test"),   
    38.             job.conf.get(MRJobConfig.USER_NAME, "mapred"),  
    39.             job.appSubmitTime,  
    40.             job.remoteJobConfFile.toString(),  
    41.             job.jobACLs, job.queueName,  
    42.             job.conf.get(MRJobConfig.WORKFLOW_ID, ""),  
    43.             job.conf.get(MRJobConfig.WORKFLOW_NAME, ""),  
    44.             job.conf.get(MRJobConfig.WORKFLOW_NODE_NAME, ""),  
    45.             getWorkflowAdjacencies(job.conf),  
    46.             job.conf.get(MRJobConfig.WORKFLOW_TAGS, ""));  
    47.           
    48.         // 将作业已提交事件JobSubmittedEvent实例jse封装成作业历史事件JobHistoryEvent交由作业的时事件处理器eventHandler处理  
    49.         job.eventHandler.handle(new JobHistoryEvent(job.jobId, jse));  
    50.         //TODO JH Verify jobACLs, UserName via UGI?  
    51.   
    52.         // 调用createSplits()方法,创建分片,并获取任务分片元数据信息TaskSplitMetaInfo数组taskSplitMetaInfo  
    53.         TaskSplitMetaInfo[] taskSplitMetaInfo = createSplits(job, job.jobId);  
    54.           
    55.         // 确定Map Task数目numMapTasks:分片元数据信息数组的长度,即有多少分片就有多少numMapTasks  
    56.         job.numMapTasks = taskSplitMetaInfo.length;  
    57.         // 确定Reduce Task数目numReduceTasks,取作业参数mapreduce.job.reduces,参数未配置默认为0  
    58.         job.numReduceTasks = job.conf.getInt(MRJobConfig.NUM_REDUCES, 0);  
    59.   
    60.         // 确定作业的map和reduce权重mapWeight、reduceWeight  
    61.         if (job.numMapTasks == 0 && job.numReduceTasks == 0) {  
    62.           job.addDiagnostic("No of maps and reduces are 0 " + job.jobId);  
    63.         } else if (job.numMapTasks == 0) {  
    64.           job.reduceWeight = 0.9f;  
    65.         } else if (job.numReduceTasks == 0) {  
    66.           job.mapWeight = 0.9f;  
    67.         } else {  
    68.           job.mapWeight = job.reduceWeight = 0.45f;  
    69.         }  
    70.   
    71.         checkTaskLimits();  
    72.   
    73.         // 根据分片元数据信息计算输入长度inputLength,也就是作业大小  
    74.         long inputLength = 0;  
    75.         for (int i = 0; i < job.numMapTasks; ++i) {  
    76.           inputLength += taskSplitMetaInfo[i].getInputDataLength();  
    77.         }  
    78.   
    79.         // 根据作业大小inputLength,调用作业的makeUberDecision()方法,决定作业运行模式是Uber模式还是Non-Uber模式  
    80.         job.makeUberDecision(inputLength);  
    81.           
    82.         // 根据作业的Map、Reduce任务数目之和,外加10,  
    83.         // 初始化任务尝试完成事件TaskAttemptCompletionEvent列表taskAttemptCompletionEvents  
    84.         job.taskAttemptCompletionEvents =  
    85.             new ArrayList<TaskAttemptCompletionEvent>(  
    86.                 job.numMapTasks + job.numReduceTasks + 10);  
    87.           
    88.         // 根据作业的Map任务数目,外加10,  
    89.         // 初始化Map任务尝试完成事件TaskCompletionEvent列表mapAttemptCompletionEvents  
    90.         job.mapAttemptCompletionEvents =  
    91.             new ArrayList<TaskCompletionEvent>(job.numMapTasks + 10);  
    92.           
    93.         // 根据作业的Map、Reduce任务数目之和,外加10,  
    94.         // 初始化列表taskCompletionIdxToMapCompletionIdx  
    95.         job.taskCompletionIdxToMapCompletionIdx = new ArrayList<Integer>(  
    96.             job.numMapTasks + job.numReduceTasks + 10);  
    97.   
    98.         // 确定允许Map、Reduce任务失败百分比,  
    99.         // 取参数mapreduce.map.failures.maxpercent、mapreduce.reduce.failures.maxpercent,  
    100.         // 参数未配置均默认为0,即不允许Map和Reduce任务失败  
    101.         job.allowedMapFailuresPercent =  
    102.             job.conf.getInt(MRJobConfig.MAP_FAILURES_MAX_PERCENT, 0);  
    103.         job.allowedReduceFailuresPercent =  
    104.             job.conf.getInt(MRJobConfig.REDUCE_FAILURES_MAXPERCENT, 0);  
    105.   
    106.         // create the Tasks but don't start them yet  
    107.         // 创建Map Task  
    108.         createMapTasks(job, inputLength, taskSplitMetaInfo);  
    109.         // 创建Reduce Task  
    110.         createReduceTasks(job);  
    111.   
    112.         // 调用作业度量指标体系metrics的endPreparingJob()方法,结束作业准备  
    113.         job.metrics.endPreparingJob(job);  
    114.           
    115.         // 返回作业内部状态,JobStateInternal.INITED,即已经初始化  
    116.         return JobStateInternal.INITED;  
    117.       } catch (Exception e) {  
    118.             
    119.         // 记录warn级别日志信息:Job init failed,并打印出具体异常  
    120.         LOG.warn("Job init failed", e);  
    121.         // 调用作业度量指标体系metrics的endPreparingJob()方法,结束作业准备  
    122.         job.metrics.endPreparingJob(job);  
    123.         job.addDiagnostic("Job init failed : "  
    124.             + StringUtils.stringifyException(e));  
    125.         // Leave job in the NEW state. The MR AM will detect that the state is  
    126.         // not INITED and send a JOB_INIT_FAILED event.  
    127.           
    128.         // 返回作业内部状态,JobStateInternal.NEW,即初始化失败后的新建  
    129.         return JobStateInternal.NEW;  
    130.       }  
    131.     }  

            为了主体逻辑清晰,我们去掉部分细节,保留主干,将作业初始化总结如下:

            1、调用setup()方法,完成作业启动前的部分初始化工作,实际上最重要的两件事就是:

                   1.1、获取并设置作业远程提交路径remoteJobSubmitDir;

                   1.2、获取并设置作业远程配置文件remoteJobConfFile;

            2、调用createSplits()方法,创建分片,并获取任务分片元数据信息TaskSplitMetaInfo数组taskSplitMetaInfo:

                 通过SplitMetaInfoReader的静态方法readSplitMetaInfo(),从作业远程提交路径remoteJobSubmitDir中读取作业分片元数据信息,也就是每个任务的分片元数据信息,以此确定Map任务数、作业运行方式等一些列后续内容;

            3、确定Map Task数目numMapTasks:分片元数据信息数组的长度,即有多少分片就有多少numMapTasks;

            4、确定Reduce Task数目numReduceTasks,取作业参数mapreduce.job.reduces,参数未配置默认为0;

            5、根据分片元数据信息计算输入长度inputLength,也就是作业大小;

            6、根据作业大小inputLength,调用作业的makeUberDecision()方法,决定作业运行模式是Uber模式还是Non-Uber模式:

                  小作业会通过Uber模式运行,相反,大作业会通过Non-Uber模式运行,可参见《Yarn源码分析之MRAppMaster:作业运行方式Local、Uber、Non-Uber》一文!

            7、确定允许Map、Reduce任务失败百分比,取参数mapreduce.map.failures.maxpercent、mapreduce.reduce.failures.maxpercent,参数未配置均默认为0,即不允许Map和Reduce任务失败;

            8、创建Map Task;

            9、创建Reduce Task;

            10、返回作业内部状态,JobStateInternal.INITED,即已经初始化;

            11、如果出现异常:

                    11.1、记录warn级别日志信息:Job init failed,并打印出具体异常;

                    11.2、返回作业内部状态,JobStateInternal.NEW,即初始化失败后的新建;

             未完待续,后续作业初始化部分详细描述、作业启动、作业停止等内容,请关注《Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(二)》

  • 相关阅读:
    CUDA运行时 Runtime(一)
    CUDA C++程序设计模型
    CUDA C++编程手册(总论)
    深度学习到底有哪些卷积?
    卷积神经网络去雾去雨方法
    马斯克如何颠覆航天? 1/5385成本,c++和python编程!
    CUDA 9中张量核(Tensor Cores)编程
    利用表达式调用全局变量计算出错原因
    述函数的作用,浏览器执行函数的过程
    表达式的差异和相同点
  • 原文地址:https://www.cnblogs.com/jirimutu01/p/5556373.html
Copyright © 2020-2023  润新知