本文继《Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(一)》,接着讲述MapReduce作业在MRAppMaster上处理总流程,继上篇讲到作业初始化之后的作业启动,关于作业初始化主体流程的详细介绍,请参见《Yarn源码分析之MRAppMaster上MapReduce作业初始化解析》一文。

(三)启动

作业的启动是通过MRAppMaster的startJobs()方法实现的,其代码如下:

  1. /**
  2. * This can be overridden to instantiate multiple jobs and create a
  3. * workflow.
  4. *
  5. * TODO:  Rework the design to actually support this.  Currently much of the
  6. * job stuff has been moved to init() above to support uberization (MR-1220).
  7. * In a typical workflow, one presumably would want to uberize only a subset
  8. * of the jobs (the "small" ones), which is awkward with the current design.
  9. */
  10. @SuppressWarnings("unchecked")
  11. protected void startJobs() {
  12. /** create a job-start event to get this ball rolling */
  13. // 构造作业启动事件JobStartEvent实例startJobEvent
  14. JobEvent startJobEvent = new JobStartEvent(job.getID(),
  15. recoveredJobStartTime);
  16. /** send the job-start event. this triggers the job execution. */
  17. // 将作业启动事件JobStartEvent实例startJobEvent交由事件分发器dispatcher的事件处理器处理
  18. dispatcher.getEventHandler().handle(startJobEvent);
  19. }

很简单,首先构造作业启动事件JobStartEvent实例startJobEvent,然后将作业启动事件JobStartEvent实例startJobEvent交由事件分发器dispatcher的事件处理器处理。我们首先看下事件分发器dispatcher是如何初始化的,其在MRAppMaster服务初始化的serviceInit()方法中,关键代码如下:

  1. dispatcher = createDispatcher();

再来看下createDispatcher()方法,如下:

  1. protected Dispatcher createDispatcher() {
  2. return new AsyncDispatcher();
  3. }

就是创建一个AsyncDispatcher对象,其代表的是一个事件异步分发器AsyncDispatcher,我们曾经在《Yarn源码分析之事件异步分发器AsyncDispatcher》一文中专门介绍过这个AsyncDispatcher。AsyncDispatcher其实是一个生产者-消费者模型的事件异步分发器。在其内部有一个待分发事件队列eventQueue,并有一个GenericEventHandler类型的事件处理器handlerInstance,由其handle()方法负责将外部事件event添加到待分发队列eventQueue中,等到AsyncDispatcher中的消费者eventHandlingThread不断的获取待分发队列eventQueue中的事件,分发并交由之前注册的事件类型对应的事件处理器处理。关于这部分的内容请阅读《Yarn源码分析之事件异步分发器AsyncDispatcher》一文,此处不再做过多介绍。

那么dispatcher中是如何注册JobStartEvent事件的处理器的呢?注册的事件处理器又是谁呢?还是在服务初始化的方法中,如下:

  1. //register the event dispatchers
  2. dispatcher.register(JobEventType.class, jobEventDispatcher);

通过查看JobStartEvent的源码我们知道,JobStartEvent继承自JobEvent,它也是一种JobEvent,所以其处理会交给jobEventDispatcher来处理。细心的读者获取会发现,在此之前,dispatcher已经注册过一个JobEventType对应的事件处理器,NoopEventHandler类型的eater了,代码如下:

  1. NoopEventHandler eater = new NoopEventHandler();
  2. //We do not have a JobEventDispatcher in this path
  3. dispatcher.register(JobEventType.class, eater);

我们先看下NoopEventHandler的定义,如下:

  1. /**
  2. * Eats events that are not needed in some error cases.
  3. */
  4. private static class NoopEventHandler implements EventHandler<Event> {
  5. @Override
  6. public void handle(Event event) {
  7. //Empty
  8. }
  9. }

四个字,空空如也!那么,读者在这里可能就有疑问了,到底是由jobEventDispatcher还是eater来处理作业启动JobStartEvent事件内。这里要说的是,这两次注册实际上是形成了一个JobEventType事件类型的链式事件处理器,它会将事件挨个通过链式事件处理器中的每个处理器进行处理,这在《Yarn源码分析之事件异步分发器AsyncDispatcher》一文中的register()方法介绍中也提到过了,读者可自行查看。而这里,既然eater为空,不对事件做任何处理,我们还是看看jobEventDispatcher吧。

那么,jobEventDispatcher是如何定义及初始化的呢?其实这个jobEventDispatcher在Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(一)中的作业初始化事件JobEventType.JOB_INIT处理时已经讲到过了,它就是一个JobEventDispatcher对象,这里再回顾一下,其定义如下:

  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()方法,处理对应事件。那么在处理作业初始化事件时我们也提到过了,它是根据作业状态机的doTransition()方法根据事件类型来处理的,关于作业状态机,我们这里还是不做介绍,你还是只要知道作业启动事件是通过JobImpl的静态内部类StartTransition的transition()方法来处理的就行,其代码如下:

  1. public static class StartTransition
  2. implements SingleArcTransition<JobImpl, JobEvent> {
  3. /**
  4. * This transition executes in the event-dispatcher thread, though it's
  5. * triggered in MRAppMaster's startJobs() method.
  6. */
  7. @Override
  8. public void transition(JobImpl job, JobEvent event) {
  9. JobStartEvent jse = (JobStartEvent) event;
  10. // 设置作业的起始时间startTime
  11. if (jse.getRecoveredJobStartTime() != 0) {
  12. job.startTime = jse.getRecoveredJobStartTime();
  13. } else {
  14. job.startTime = job.clock.getTime();
  15. }
  16. // 创建作业已初始化事件JobInitedEvent实例jie
  17. JobInitedEvent jie =
  18. new JobInitedEvent(job.oldJobId,
  19. job.startTime,
  20. job.numMapTasks, job.numReduceTasks,
  21. job.getState().toString(),
  22. job.isUber());
  23. // 将作业已初始化事件JobInitedEvent实例jie包装成作业历史事件JobHistoryEvent,并交给作业的事件处理器eventHandler处理
  24. job.eventHandler.handle(new JobHistoryEvent(job.jobId, jie));
  25. // 创建作业信息变更事件JobInfoChangeEvent实例jice
  26. JobInfoChangeEvent jice = new JobInfoChangeEvent(job.oldJobId,
  27. job.appSubmitTime, job.startTime);
  28. // 将作业信息变更事件JobInfoChangeEvent实例jice包装成作业历史事件JobHistoryEvent,并交给作业的事件处理器eventHandler处理
  29. job.eventHandler.handle(new JobHistoryEvent(job.jobId, jice));
  30. // 调用作业度量指标metrics的runningJob()方法,标识作业已开始运行
  31. job.metrics.runningJob(job);
  32. // 构造提交作业Setup事件CommitterJobSetupEvent,并交由作业的事件处理器eventHandler处理
  33. job.eventHandler.handle(new CommitterJobSetupEvent(
  34. job.jobId, job.jobContext));
  35. }
  36. }

去掉关于作业历史信息等不是十分关键的细节,整体主体流程如下:

1、设置作业的起始时间startTime;

2、构造提交作业Setup事件CommitterJobSetupEvent,并交由作业的事件处理器eventHandler处理。

那么,作业的事件处理器eventHandler是什么呢?它又是如何处理提交作业Setup事件CommitterJobSetupEvent的呢?

我们先看下作业的事件处理器eventHandler,在MRAppMaster服务启动时创建作业JobImpl实例时,eventHandler是通过传入的dispatcher.getEventHandler()来初始化的,基于上面的陈述,这我们就不用讲了吧。

我们还是看下dispatcher是如何注册事件CommitterJobSetupEvent对应的事件处理器的吧,代码如下:

  1. dispatcher.register(CommitterEventType.class, committerEventHandler);

我们知道,CommitterJobSetupEvent继承自CommitterEvent,所以它实际上是通过committerEventHandler来处理的,那么什么是committerEventHandler呢?其初始化如下:

  1. //service to handle the output committer
  2. committerEventHandler = createCommitterEventHandler(context, committer);

通过调用createCommitterEventHandler()方法,构造了一个CommitterEventHandler实例,如下:

  1. protected EventHandler<CommitterEvent> createCommitterEventHandler(
  2. AppContext context, OutputCommitter committer) {
  3. return new CommitterEventHandler(context, committer,
  4. getRMHeartbeatHandler(), jobClassLoader);
  5. }

关于CommitterEventHandler的介绍,我们后续会写相关文章进行详细的介绍,这里,你只要知道,它类似AsyncDispatcher,也是一个生产者-消费者模式的事件分发器,而最终是通过其内部EventProcessor类型的事件处理线程eventHandlingThread来处理的,在EventProcessor中,有针对JOB_SETUP事件处理的逻辑,关键代码如下:

  1. switch (event.getType()) {
  2. case JOB_SETUP:
  3. handleJobSetup((CommitterJobSetupEvent) event);
  4. break;

继续追踪handleJobSetup()方法,如下:

  1. @SuppressWarnings("unchecked")
  2. protected void handleJobSetup(CommitterJobSetupEvent event) {
  3. try {
  4. committer.setupJob(event.getJobContext());
  5. context.getEventHandler().handle(
  6. new JobSetupCompletedEvent(event.getJobID()));
  7. } catch (Exception e) {
  8. LOG.warn("Job setup failed", e);
  9. context.getEventHandler().handle(new JobSetupFailedEvent(
  10. event.getJobID(), StringUtils.stringifyException(e)));
  11. }
  12. }

它做了两件事情,如下:

1、调用committer的setupJob()方法处理该CommitterJobSetupEvent事件;

2、又构造了一个JobSetupCompletedEvent事件,交由应用运行上下文context的事件处理器进行处理。

而首先要说的是,committer、context均是由MRAppMaster在创建CommitterEventHandler时传入的,其对应的对象类型分别是:

1、committer:

1.1、新版API是通过OutputFormat组件的getOutputCommitter()方法获取的;

1.2、旧版API是通过参数mapred.output.committer.class获取的,参数未配置默认为FileOutputCommitter。

2、context:RunningAppContext。

对于committer,我们这里以较为通用的FileOutputCommitter为例,看下其setupJob()方法,如下:

  1. /**
  2. * Create the temporary directory that is the root of all of the task
  3. * work directories.
  4. * @param context the job's context
  5. */
  6. public void setupJob(JobContext context) throws IOException {
  7. if (hasOutputPath()) {
  8. Path jobAttemptPath = getJobAttemptPath(context);
  9. FileSystem fs = jobAttemptPath.getFileSystem(
  10. context.getConfiguration());
  11. if (!fs.mkdirs(jobAttemptPath)) {
  12. LOG.error("Mkdirs failed to create " + jobAttemptPath);
  13. }
  14. } else {
  15. LOG.warn("Output Path is null in setupJob()");
  16. }
  17. }

实际上就做了一件事情,创建作业中所有任务工作的临时根目录。

再来看下context是如何处理JobSetupCompletedEvent的,还记得之前我们讲述的,RunningAppContext实际上什么都不干,而是交给了JobImpl对应的作业状态机了吗?我们就看下JobImpl中是如何处理JobSetupCompletedEvent事件的,其对应的处理在其静态内部类SetupCompletedTransition的transition()中,代码如下:

  1. private static class SetupCompletedTransition
  2. implements SingleArcTransition<JobImpl, JobEvent> {
  3. @Override
  4. public void transition(JobImpl job, JobEvent event) {
  5. // 通过设置作业的setupProgress为1,标记作业setup已完成
  6. job.setupProgress = 1.0f;
  7. // 调度作业的Map Task
  8. job.scheduleTasks(job.mapTasks, job.numReduceTasks == 0);
  9. // 调度作业的Reduce Task
  10. job.scheduleTasks(job.reduceTasks, true);
  11. // If we have no tasks, just transition to job completed
  12. // 如果没有task了,则生成JOB_COMPLETED事件并交由作业的事件处理器eventHandler进行处理
  13. if (job.numReduceTasks == 0 && job.numMapTasks == 0) {
  14. job.eventHandler.handle(new JobEvent(job.jobId,
  15. JobEventType.JOB_COMPLETED));
  16. }
  17. }
  18. }

是不是很简单,而且也很理所当然?处理流程如下:

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

2、调度作业的Map Task;

3、调度作业的Reduce Task;

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

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

Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(二)的更多相关文章

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

    我们知道,如果想要在Yarn上运行MapReduce作业,仅需实现一个ApplicationMaster组件即可,而MRAppMaster正是MapReduce在Yarn上ApplicationMas ...

  2. Spark源码分析之一:Job提交运行总流程概述

    Spark是一个基于内存的分布式计算框架,运行在其上的应用程序,按照Action被划分为一个个Job,而Job提交运行的总流程,大致分为两个阶段: 1.Stage划分与提交 (1)Job按照RDD之间 ...

  3. Yarn源码分析之MRAppMaster:作业运行方式Local、Uber、Non-Uber

    基于作业大小因素,MRAppMaster提供了三种作业运行方式:本地Local模式.Uber模式.Non-Uber模式.其中, 1.本地Local模式:通常用于调试: 2.Uber模式:为降低小作业延 ...

  4. Yarn源码分析之如何确定作业运行方式Uber or Non-Uber?

    在MRAppMaster中,当MapReduce作业初始化时,它会通过作业状态机JobImpl中InitTransition的transition()方法,进行MapReduce作业初始化相关操作,而 ...

  5. Android7.0 Phone应用源码分析(三) phone拒接流程分析

    本文主要分析Android拒接电话的流程,下面先来看一下拒接电话流程时序图 步骤1:滑动按钮到拒接图标,会调用到AnswerFragment的onDecline方法 com.android.incal ...

  6. Android7.0 Phone应用源码分析(四) phone挂断流程分析

    电话挂断分为本地挂断和远程挂断,下面我们就针对这两种情况各做分析 先来看下本地挂断电话的时序图: 步骤1:点击通话界面的挂断按钮,会调用到CallCardPresenter的endCallClicke ...

  7. HDFS源码分析数据块复制监控线程ReplicationMonitor(二)

    HDFS源码分析数据块复制监控线程ReplicationMonitor(二)

  8. HashMap源码分析(史上最详细的源码分析)

    HashMap简介 HashMap是开发中使用频率最高的用于映射(键值对 key value)处理的数据结构,我们经常把hashMap数据结构叫做散列链表: ObjectI entry<Key, ...

  9. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

随机推荐

  1. iOS中设置backBarButtonItem的title和action

    一. 设置title 在需要显示该返回键的前一个Controller中设置: 1: navigationItem.backBarButtonItem = UIBarButtonItem(title: ...

  2. mysql replication 复制的一些问题

    1   过大的复制延迟 mysql 的复制延迟是一个常见问题,现在已经有一些解决方案,如淘宝开发的一些工具 2 没有磁盘空间 复制导致磁盘空间塞满,二进制日志.中继日志或临时文件把磁盘塞满,slave ...

  3. 关于spring-data-jpa的排序问题

    本测试基于springBoot框架实现. pom.xml文件: <project xmlns="http://maven.apache.org/POM/4.0.0" xmln ...

  4. 你真的了解try{ return }finally{}中的return?(转载)

    发现一篇有意思的博文,分享一下 谁能给我我解释一下这段程序的结果为什么是:2.而不是:3 代码如下: class Test { public int aaa() { int x = 1; try { ...

  5. Myeclipse中文件已经上传到server文件夹下,文件也没有被占用,可是页面中无法读取和使用问题的解决方法

    这个问题是因为Myeclipse中文件不同步引起的.在Myeclipse中,project文件是由Myeclipse自己主动扫描加入的,假设在外部改动了project文件夹中的文件但又关闭了自己主动刷 ...

  6. JS断点调试

    断点调试在这种场景下能发挥很大的作用.上手这个办法也利益于我以前玩VB编程时也习惯了IDE的单步/断点调试,一般的纯Web开发入门的程序员我没看到几个会用的.其实难度不大,只是他们不懂得主动去探索 首 ...

  7. Yii使用公共函数

    在网站项目中,没必要把公用的函数写成一个工具类,有时候面向过程其实更方便. 在入口文件index.php里添加 require_once('protected/function.php'); 即可对其 ...

  8. Spark(九) -- SparkSQL API编程

    本文测试的Spark版本是1.3.1 Text文本文件测试 一个简单的person.txt文件内容为: JChubby,13 Looky,14 LL,15 分别是Name和Age 在Idea中新建Ob ...

  9. hadoop2.2.0伪分布式安装

    修改主机名和IP的映射关系 vi /etc/hosts 192.168.61.134 hadoop 关闭防火墙 #查看防火墙状态 service iptables status #关闭防火墙 serv ...

  10. 如何在不重启或重新格式化hadoop集群的情况下删除集群节点

    在master节点上的hadoop安装目录下 进入conf目录 配置hdfs-site.xml文件 添加节点如下: <property> <name>dfs.hosts.exc ...