Hadoop2.8.5 MapReduce计算框架

版权声明:未经本人同意,不得转载 https://blog.csdn.net/u013928208/article/details/86407320

Hadoop 中 YARN 子系统的使命是为用户提供大数据的计算框架。早期的Hadoop ,甚至早期的 YARN 都只提供一种计算框架,那就是 MapReduce。 Hadoop 后来有了一些新的发展,除 MapReduce 外又提供了称为 Chain 和 Stream 的计算框架,一来使用户不必非得用 Java 编程;二来更允许用户利用 Linux 上的 Utility 工具软件搭建更像“数据流”的结构。 YARN 为一个 MapReduce 作业在某一 NM 节点上建立起类似于“项目组长”角色的 MRAppMaster ;然后由它把这个作业分解成多个任务,再对每个任务进行尝试,为其在集群中的某个 NM 节点 分配一个 容器,并为其准 备一个 “容器投运上 下文(ContainerLaunchContext )” CLC ,然后发起该容器在目标 NM 节点上投运。 CLC 中包含着投运容器所需的各种信息,其中十分重要的就是对于宿主操作系统的命令行。在 Mapper 和Reducer 容器的 CLC 里,这个命令行是“ ~ / bin / java … YarnChild …”,那就是要启动一个 java虚拟机, 并让它执行 java 类 YarnC hild 。至于具的 Mapper 或 Reducer ,则要由 YarnChild 向MRAppMaster 领取并加以本地化,然后投运。

1. 作业的认领

hadoop-mapreduce-client-app\src\main\java\org\apache\hadoop\mapred\YarnChild.java

在 MRAppMaster 那 儿 有 个 TaskAttemptListenerImpl 对 象,是 MRAppMaster 在 其serviceInit ()中与Container Launcher 等一起创建的,相当于 MRAppMaster 中的一个职能部门。 YarnChild 需要跟MRAppMaster 所在节点上的 TaskAttemptListenerImp 建立联系,以维持与所属 MRAppMaster 的关系,这种关系就像婴孩跟母亲之间的“脐带( umbilical )”一样。在这条 umbilical 上的通信规程是 TaskUmbilicalProtocol 。当然,这又是通过 RPC 和 PB 实现的。 TaskAttemptListenerImpl 实现了这个规程,时刻倾听来自那些由MRAppMaster 所发起的 YarnChild 的请求。

class YarnChild {
	public static void main(String[] args) throws Throwable {
    Thread.setDefaultUncaughtExceptionHandler(new YarnUncaughtExceptionHandler());
    final JobConf job = new JobConf(MRJobConfig.JOB_CONF_FILE);
    String host = args[0]; //命令行中带入 MRAppMaster 所在的主机名或 IP 地址
    int port = Integer.parseInt(args[1]); //命令行中带入 MRAppMaster 的端口号
    //Socket 的 IP 地址和端口号
    final InetSocketAddress address = NetUtils.createSocketAddrForHost(host, port);
    final TaskAttemptID firstTaskid = TaskAttemptID.forName(args[2]);
    long jvmIdLong = Long.parseLong(args[3]);
    JVMId jvmId = new JVMId(firstTaskid.getJobID(),
        firstTaskid.getTaskType() == TaskType.MAP, jvmIdLong);
    ......
    }

    //创建作业认领脐带协议
    //与 MRAppMaster 的关系就像婴孩与母亲的关系,中间要有条脐带 umbilical
    final TaskUmbilicalProtocol umbilical =
      taskOwner.doAs(new PrivilegedExceptionAction<TaskUmbilicalProtocol>() {
      @Override
      public TaskUmbilicalProtocol run() throws Exception {
        //这个脐带其实就是一个 proxy
        return (TaskUmbilicalProtocol)RPC.getProxy(TaskUmbilicalProtocol.class,
            TaskUmbilicalProtocol.versionID, address, job);
      }
    });

    //获取 JVM 的进程号 pid
    JvmContext context = new JvmContext(jvmId, "-1000");
    Task task = null;
    UserGroupInformation childUGI = null;
    ScheduledExecutorService logSyncer = null;

    try {
      int idleLoopCount = 0;
      JvmTask myTask = null;;
      // poll for new task
      for (int idle = 0; null == myTask; ++idle) {
        long sleepTimeMilliSecs = Math.min(idle * 500, 1500);
        //从 MRAppMaster 认领任务,这是个 JvmTask
        MILLISECONDS.sleep(sleepTimeMilliSecs);
        myTask = umbilical.getTask(context);
      }
      if (myTask.shouldDie()) {
        return;
      }
      //通过 Proxy 与 MRAppMaster 通信,获取一个 JvmTask
      task = myTask.getTask();
      YarnChild.taskid = task.getTaskID();
      //作业配置
      configureTask(job, task, credentials, jt);
      // Create a final reference to the task for the doAs block
      final Task taskFinal = task;
      childUGI.doAs(new PrivilegedExceptionAction<Object>() {
        @Override
        public Object run() throws Exception {
          // use job-specified working directory
          setEncryptedSpillKeyIfRequired(taskFinal);
          //工作目录
          FileSystem.get(job).setWorkingDirectory(job.getWorkingDirectory());
          taskFinal.run(job, umbilical); // 运行具体的MapTask或ReduceTask
          return null;
        }
      });
    } 
  }
}

hadoop-mapreduce-client-app\src\main\java\org\apache\hadoop\mapred\TaskAttemptListenerImpl.java

TaskAttemptListenerImpl 内 部 维 持 着 两 个 集 合,一 个 是 launchedJVMs ,另 一 个 jvmIDToActiveAttemptMap 前者记录着下属所有已经登记为已投运但还没有领走任务的JVM ;后者则记录着所有尚未被领走的任务及其所属的 JVM。

public class TaskAttemptListenerImpl extends CompositeService implements 
			 TaskUmbilicalProtocol, TaskAttemptListener {
    //应对来自 YarnChild 的认领请求
	public JvmTask getTask(JvmContext context) throws IOException {
    JVMId jvmId = context.jvmId;
    JvmTask jvmTask = null;
    WrappedJvmID wJvmID = new WrappedJvmID(jvmId.getJobId(), jvmId.isMap,
        jvmId.getId());
    //这个 JVM 的 task 已经被领走了
    if (!jvmIDToActiveAttemptMap.containsKey(wJvmID)) {
      jvmTask = TASK_FOR_INVALID_JVM;
    } else {
      if (!launchedJVMs.contains(wJvmID)) { //这个 JVM 尚未登记
        jvmTask = null;
      } else { // JVM 已登记,任务尚未被领走
        //从 jvmIDToActiveAttemptMap 集合中取出预先安排好的任务
        org.apache.hadoop.mapred.Task task = jvmIDToActiveAttemptMap.remove(wJvmID);
        launchedJVMs.remove(wJvmID);
        task.setEncryptedSpillKey(encryptedSpillKey);
        //将此任务包装在一个 JvmTask 中
        jvmTask = new JvmTask(task, false);
      }
    }
    return jvmTask;
  }
}

2. 作业运行框架

hadoop-mapreduce-client-core\src\main\java\org\apache\hadoop\mapred\MapTask.java

public class MapTask extends Task {
 public void run(final JobConf job, final TaskUmbilicalProtocol umbilical)
    throws IOException, ClassNotFoundException, InterruptedException {
    this.umbilical = umbilical;
    if (isMapTask()) { //Map作业
      if (conf.getNumReduceTasks() == 0) { 
       //如果本作业中没有 ReduceTask ,那 Mapper 的输出就不需要排序
       //所以 Map 操作的工作量就是这个任务的全部工作量
        mapPhase = getProgress().addPhase("map", 1.0f);
      } else {
        //Map 阶段的工作量算 2 / 3
        mapPhase = getProgress().addPhase("map", 0.667f);
        //输出排序阶段的工作量算 1 / 3
        sortPhase  = getProgress().addPhase("sort", 0.333f);
      }
    }
    //作业进度报告
    TaskReporter reporter = startReporter(umbilical);
    boolean useNewApi = job.getUseNewMapper();
    //初始化作业
    initialize(job, getJobID(), reporter, useNewApi);
    
    if (useNewApi) { //采用新 API
      //参数 splitIndex 表明这个 Map 任务的输入来自输入文件中的哪一个 Split
      runNewMapper(job, splitMetaInfo, umbilical, reporter);
    } else {
      runOldMapper(job, splitMetaInfo, umbilical, reporter);
    }
    done(umbilical, reporter); //通知 MRAppMaster ,本任务已经完成
  }

  public Progress getSortPhase() {
    return sortPhase;
  }

 private <INKEY,INVALUE,OUTKEY,OUTVALUE> void runNewMapper(final JobConf job,
                    final TaskSplitIndex splitIndex,
                    final TaskUmbilicalProtocol umbilical,
                    TaskReporter reporter
                    ) throws IOException, ClassNotFoundException,
                             InterruptedException {
    // make a task context so we can get the classes
    org.apache.hadoop.mapreduce.TaskAttemptContext taskContext =
      new org.apache.hadoop.mapreduce.task.TaskAttemptContextImpl(job, 
                                                                  getTaskID(),
                                                                  reporter);
    //这就是用户给定的 Mapper 类,如果没有给定就采用默认的 Mapper.class
    org.apache.hadoop.mapreduce.Mapper<INKEY,INVALUE,OUTKEY,OUTVALUE> mapper =
      (org.apache.hadoop.mapreduce.Mapper<INKEY,INVALUE,OUTKEY,OUTVALUE>)
        ReflectionUtils.newInstance(taskContext.getMapperClass(), job);
    // make the input format
    org.apache.hadoop.mapreduce.InputFormat<INKEY,INVALUE> inputFormat =
      (org.apache.hadoop.mapreduce.InputFormat<INKEY,INVALUE>)
        ReflectionUtils.newInstance(taskContext.getInputFormatClass(), job);
    // 根据 splitIndex 找到输入文件(是 HDFS 文件)中的某个块
    // HDFS 文件的每个块都是宿主文件系统中的一个文件
    org.apache.hadoop.mapreduce.InputSplit split = null;
    split = getSplitDetails(new Path(splitIndex.getSplitLocation()),
        splitIndex.getStartOffset());
    //带 Tracking 功能的 RecordReader ,这个对象将为 Mapper 提供输入
    org.apache.hadoop.mapreduce.RecordReader<INKEY,INVALUE> input =
      new NewTrackingRecordReader<INKEY,INVALUE>
        (split, inputFormat, reporter, taskContext);
    job.setBoolean(JobContext.SKIP_RECORDS, isSkipping());
    org.apache.hadoop.mapreduce.RecordWriter output = null;
    
    // 没有 Reducer , Mapper 的输出直接就是整个作业的输出
    if (job.getNumReduceTasks() == 0) {
      output = 
        new NewDirectOutputCollector(taskContext, job, umbilical, reporter);
    } else {
      output = new NewOutputCollector(taskContext, job, umbilical, reporter);
    }
    ......
    try {
      input.initialize(split, mapperContext);
      //执行 Mapper 的 run ()函数
      mapper.run(mapperContext);
      mapPhase.complete();
      //Map 阶段结束,进入排序阶段
      setPhase(TaskStatus.Phase.SORT);
      statusUpdate(umbilical);
    }
  }
  
}

hadoop-mapreduce-client-core\src\main\java\org\apache\hadoop\mapreduce\Mapper.java

public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
  
  protected void map(KEYIN key, VALUEIN value, 
                     Context context) throws IOException, InterruptedException {
    context.write((KEYOUT) key, (VALUEOUT) value);
  }
  
  public void run(Context context) throws IOException, InterruptedException {
    setup(context);
    try {
     //只要还有下一个 KV 对,就继续循环
      while (context.nextKeyValue()) {
        map(context.getCurrentKey(), context.getCurrentValue(), context);
      }
    } finally {
      cleanup(context);
    }
  }
}

hadoop-mapreduce-client-core\src\main\java\org\apache\hadoop\mapred\ReduceTask.java

整个过程分成三个阶段,即拷贝(copy )、排序(sort )和归并( reduce )。 ReduceTask 与 MapTask 不是一对一的关系,好几个MapTask 的输出要由一个 ReduceTask 加以处理,所以首先要将各个具体 MapTask 的输出拷贝到 ReduceTask 所在的节点上。

public class ReduceTask extends Task {
  public void run(JobConf job, final TaskUmbilicalProtocol umbilical)
    throws IOException, InterruptedException, ClassNotFoundException {
    job.setBoolean(JobContext.SKIP_RECORDS, isSkipping());

    if (isMapOrReduce()) {
      copyPhase = getProgress().addPhase("copy"); //从Mapper端Fetch结果
      sortPhase  = getProgress().addPhase("sort"); //排序
      reducePhase = getProgress().addPhase("reduce"); //归并
    }
    //创建负责报告进度的线程
    TaskReporter reporter = startReporter(umbilical);
    boolean useNewApi = job.getUseNewReducer();
    initialize(job, getJobID(), reporter, useNewApi);
    //用于解压缩
    codec = initCodec();
    RawKeyValueIterator rIter = null;
    ShuffleConsumerPlugin shuffleConsumerPlugin = null;
    //用户可以设置是否在 Mapper 与 Reducer 之间加一个 Combiner
    Class combinerClass = conf.getCombinerClass();
    CombineOutputCollector combineCollector = 
      (null != combinerClass) ? 
     new CombineOutputCollector(reduceCombineOutputCounter, reporter, conf) : null;
     //用户可以设置采用什么 shuffler ,默认 Shuffle.class
    Class<? extends ShuffleConsumerPlugin> clazz =
          job.getClass(MRConfig.SHUFFLE_CONSUMER_PLUGIN, Shuffle.class, ShuffleConsumerPlugin.class);
    //这通常就是 Shuffle.run ()
    // Shuffle 实现了 ShuffleConsumerPlugin 界面
    shuffleConsumerPlugin = ReflectionUtils.newInstance(clazz, job);
    rIter = shuffleConsumerPlugin.run();
    // free up the data structures
    mapOutputFilesOnDisk.clear();
    //当 shuffleConsumerPlugin 退出 run ()时, sort 阶段也就结束了
    sortPhase.complete();                         // sort is complete
    //接着就是 reduce 阶段
    setPhase(TaskStatus.Phase.REDUCE); 
    statusUpdate(umbilical);
    //与 Mapper 输出的 Key 相匹配
    Class keyClass = job.getMapOutputKeyClass();
    //与 Mapper 输出的 Value 相匹配
    Class valueClass = job.getMapOutputValueClass();
    //用于 Key 的比较运算
    RawComparator comparator = job.getOutputValueGroupingComparator();

    if (useNewApi) {
      runNewReducer(job, umbilical, reporter, rIter, comparator, 
                    keyClass, valueClass);
    } else {
      runOldReducer(job, umbilical, reporter, rIter, comparator, 
                    keyClass, valueClass);
    }

    shuffleConsumerPlugin.close();
    done(umbilical, reporter);
  }
 private <INKEY,INVALUE,OUTKEY,OUTVALUE> void runNewReducer(.....){
    try {
      //创建 Reducer 对象,并运行
      reducer.run(reducerContext);
    } finally {
      trackedRW.close(reducerContext);
    }
  }
}

hadoop-mapreduce-client-core\src\main\java\org\apache\hadoop\mapreduce\Reducer.java

与 Mapper 相比, Reducer 的不同之处在于: Mapper 的输入是 KV 对,而 Reducer 的输入是一个 K 后面可以有多个 V ,是个序列。

public class Reducer<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {

  protected void reduce(KEYIN key, Iterable<VALUEIN> values, Context context
                        ) throws IOException, InterruptedException {
    for(VALUEIN value: values) {
      context.write((KEYOUT) key, (VALUEOUT) value);
    }
  }
  
  public void run(Context context) throws IOException, InterruptedException {
    setup(context);
    try {
      while (context.nextKey()) {
        //归并
        reduce(context.getCurrentKey(), context.getValues(), context);
        // If a back up store is used, reset it
        Iterator<VALUEIN> iter = context.getValues().iterator();
        if(iter instanceof ReduceContext.ValueIterator) {
          ((ReduceContext.ValueIterator<VALUEIN>)iter).resetBackupStore();        
        }
      }
    } finally {
      cleanup(context);
    }
  }
}

3. Streaming和Chain

Hadoop 的典型应用中, 使用MapReduce框架来构建应用,对于一般用户而言那也太复杂了。后来Hadoop增加了这样的机制,称为 Hadoop 的“流计算(streaming )”机制,这个所谓“流(stream )”,实际上就是“数据流(dataflow )。

Hadoop 还有一种称为“链(Chain )”的机制,可以把多个 mapper 串接在一起,这些 mapper可以在 Mapper 节点上,也可以在 Reducer 节点上。显然,这个机制与上述的 Stream 机制相似,可是也有很大的不同。在 Chain 中,被串在一起的那些 mapper 都只能是 Hadoop 的Mapper 类对象(但可以不同),而且是运行于同一个 Java 虚拟机上(或者说同一个 JVM 进程中),而不是像 Stream 那样的外挂和“体外循环”。

Hadoop系统中还有一个Client机制,与RPC的Client不同,位于用户提交应用一端,Client 绕过了 Hadoop 常规的那套作业提交机制,也绕开了 MRAppMaster ,而另搞一套,配合 ApplicationMaster 取代了围绕着 MRAppMaster 的那 套机制。它并不提 供像MapReduce 那样现成的计算框架,而是为开发者留下了自行设计和构筑并行计算模型和系统(可以是数据流的,也可以不是)的余地。

OK,今天就到这。

猜你喜欢

转载自blog.csdn.net/u013928208/article/details/86407320
今日推荐