大数据生态系统基础:Hadoop(七):Hadoop MapReduce 工作原理和 YARN架构

来源:互联网 发布:ip 端口号正则表达式 编辑:程序博客网 时间:2024/04/28 00:55

一、介绍

          Hadoop MapReduce是一种软件框架,可以轻松地编写应用程序,它可以以一种可靠的、容错的方式处理容量高达 T 字节的数据集的大型集群(数千个节点)。
        MapReduce作业通常将输入数据集分割成独立的块,以完全并行的方式处理映射任务。框架对映射的输出进行排序,然后将这些输出输入到reduce任务中。通常,作业的输入和输出都存储在文件系统中。框架负责调度任务,监视它们,并重新执行失败的任务。
       通常,计算节点和存储节点是相同的,即MapReduce框架和Hadoop分布式文件系统在同一组节点上运行。这种配置使框架能够有效地在数据已经存在的节点上调度任务,从而在集群中产生非常高的聚合带宽。

       MapReduce框架由单个的主ResourceManager、每个集群节点的一个worker节点和每个应用程序的MRAppMaster组成。


       最低限度,应用程序通过适当的接口和/或抽象类的实现来定义输入/输出位置、提供映map 和 reduce 的功能。还有其他作业参数包括作业配置。

       然后,Hadoop作业客户端将作业(jar/可执行等)和配置提交到ResourceManager,然后承担将软件/配置分配给worker、调度任务并监视它们的职责,为job客户端提供状态和诊断信息。

       虽然Hadoop框架是用Java实现的,但是MapReduce应用程序不需要用Java编写。

  • Hadoop streaming是一个实用程序,它允许用户创建和运行任何可执行程序(如shell实用程序)作为映射器和/或减速机。
  • Hadoop pipes是一个SWIG(http://www.swig.org,SWIG是一个软件开发工具,它把用C和C++编写的程序和各种高级编程语言连接起来),兼容的C++API,用于实现MapReduce应用程序(非JNI)。
二、YARN 架构体系
    YARN的基本思想是将资源管理和作业调度/监视的功能分解为独立的守护进程。其思想是拥有一个全局资源ResourceManager(RM)和每个应用程序 ApplicationMaster (AM)。应用程序要么是一个 job,要么是一个 DAG (directed acyclic graph,定向非循环图)job。

     ResourceManager和NodeManager形成了数据计算框架。ResourceManager是仲裁系统中所有应用程序中的资源的最终权威。NodeManager是每台机器框架代理负责容器,监测他们的资源使用(cpu、内存、磁盘、网络)和向ResourceManager /调度器提供报告。

    每个应用程序的ApplicationMaster实际上是一个特定于框架的库,它的任务是与ResourceManager协商资源,并与 NodeManager一起执行和监视任务。


       ResourceManager有两个主要组件:Scheduler调度器和ApplicationsManager。


      Scheduler负责将资源分配给各种运行的应用程序,这些应用程序受到熟悉的容量、队列等方面的限制,而Scheduler是纯粹的调度器,因为它不执行应用程序的状态监视或跟踪。另外,它也不能保证重新启动失败的任务,或者是由于应用程序失败或硬件故障。Scheduler根据应用程序的资源需求执行调度功能;它基于一个资源容器的抽象概念,它包含了内存、cpu、磁盘、网络等元素。

     Scheduler有一个可插拔的策略,它负责在不同队列、应用程序等中对集群资源进行分区。当前的 Scheduler,如CapacityScheduler 容量调度器和FairScheduler 调度器,都是一些插件的例子。

       ApplicationsManager负责接收工作job提交,协商第一个容器来执行应用程序特定的ApplicationMaster,并提供在失败时重新启动ApplicationMaster容器的服务。每个应用程序ApplicationMaster有责任从Scheduler中协商适当的资源容器,跟踪它们的状态并监视进程。

      在hadoop-2.x 中 MapReduce维护了与以前稳定版本的API的兼容性(版本号为-1.x)。这意味着所有的MapReduce作业都应该在只重新编译的情况下保持不变。
 
       YARN还通过 ReservationSystem预订系统支持资源预订的概念,这是一种允许用户指定资源配置时间和时间限制的组件(例如:最后期限)和保留资源以确保重要工作的可预见性执行。预订系统会对资源进行跟踪,对预订进行控制,并动态地指导底层的调度程序,以确保预订是完全填满的。

MapReduce的体系结构

      我们通过提交jar包,进行MapReduce处理,那么整个运行过程分为五个环节:

  1、向client端提交MapReduce job.

  2、随后yarn的ResourceManager进行资源的分配.

  3、由NodeManager进行加载与监控containers.

  4、通过applicationMaster与ResourceManager进行资源的申请及状态的交互,由NodeManagers进行MapReduce运行时job的管理.

  5、通过hdfs进行job配置文件、jar包的各节点分发。



Job 提交过程

  job的提交通过调用submit()方法创建一个JobSubmitter实例,并调用submitJobInternal()方法。整个job的运行过程如下:

  1、向ResourceManager申请application ID,此ID为该MapReduce的jobId。

  2、检查output的路径是否正确,是否已经被创建。

  3、计算input的splits。

  4、拷贝运行job 需要的jar包、配置文件以及计算input的split 到各个节点。

  5、在ResourceManager中调用submitAppliction()方法,执行job

Job 初始化过程

  1、当resourceManager收到了submitApplication()方法的调用通知后,scheduler开始分配container,随之ResouceManager发送applicationMaster进程,告知每个nodeManager管理器。

  2、由applicationMaster决定如何运行tasks,如果job数据量比较小,applicationMaster便选择将tasks运行在一个JVM中。那么如何判别这个job是大是小呢?当一个job的mappers数量小于10个只有一个reducer或者读取的文件大小要小于一个HDFS block时,(可通过修改配置项mapreduce.job.ubertask.maxmaps,mapreduce.job.ubertask.maxreduces以及mapreduce.job.ubertask.maxbytes 进行调整)

  3、在运行tasks之前,applicationMaster将会调用setupJob()方法,随之创建output的输出路径(这就能够解释,不管你的mapreduce一开始是否报错,输出路径都会创建)

Task 任务分配

  1、接下来applicationMaster向ResourceManager请求containers用于执行map与reduce的tasks(step 8),这里map task的优先级要高于reduce task,当所有的map tasks结束后,随之进行sort(这里是shuffle过程后面再说),最后进行reduce task的开始。(这里有一点,当map tasks执行了百分之5%的时候,将会请求reduce,具体下面再总结)

  2、运行tasks的是需要消耗内存与CPU资源的,默认情况下,map和reduce的task资源分配为1024MB与一个核,(可修改运行的最小与最大参数配置,mapreduce.map.memory.mb,mapreduce.reduce.memory.mb,mapreduce.map.cpu.vcores,mapreduce.reduce.reduce.cpu.vcores.)

Task 任务执行

  1、这时一个task已经被ResourceManager分配到一个container中,由applicationMaster告知nodemanager启动container,这个task将会被一个主函数为YarnChild的java application运行,但在运行task之前,首先定位task需要的jar包、配置文件以及加载在缓存中的文件

  2、YarnChild运行于一个专属的JVM中,所以任何一个map或reduce任务出现问题,都不会影响整个nodemanager的crash或者hang

  3、每个task都可以在相同的JVM task中完成,随之将完成的处理数据写入临时文件中。

Mapreduce数据流

运行进度与状态更新

  1、MapReduce是一个较长运行时间的批处理过程,可以是一小时、几小时甚至几天,那么Job的运行状态监控就非常重要。每个job以及每个task都有一个包含job(running,successfully completed,failed)的状态,以及value的计数器,状态信息及描述信息(描述信息一般都是在代码中加的打印信息),那么,这些信息是如何与客户端进行通信的呢?

  2、当一个task开始执行,它将会保持运行记录,记录task完成的比例,对于map的任务,将会记录其运行的百分比,对于reduce来说可能复杂点,但系统依旧会估计reduce的完成比例。当一个map或reduce任务执行时,子进程会持续每三秒钟与applicationMaster进行交互

Job 完成

   最终,applicationMaster会收到一个job完成的通知,随后改变job的状态为successful。最终,applicationMaster与task containers被清空。





三、MapReduce
        MapReduce 是两个过程, 一个是 Map 过程,一个是 Reduce 过程, 这是一个编程模型,意味着先进行 Map(映射)再进行 Reduce(简化)。Map 的过程,就是将一组 Key-Value(键值对)映射成一组新的键值对,这个映射过程就是一个处理过程;Reduce 过程就是将相同的键组进行处理,键组就是一组键值对。
      Map 过程对应一个抽象类 Mapper, Reduce 过程对应一个抽象类 Reducer。
      对MapReduce 的理解可以使用做菜的过程来理解。 Map 过程相当于准备每一种食材,对食材进行加工比如洗、过水等,然后等待下一步进行配菜,这配菜的过程就是 shuffle,最后由大厨师将这些加工过的食材进行 Reduce 过程,变成一道菜!很多种食材变成一道菜,就是 Reduce 了,当然Reduce 过程中,等食材加工完毕,就将需要的食材拿过来进行摆放,炒完装盘,这是 Reduce 的 shuffle。
      1、进行 Mapping 数据表
     将一序列的 KV 进行 Mapping 功能转换成一序列新的 KV。注意,在对旧的 KV 进行 Mapping 后旧的 KV 不消失,但是重新被新的数据填充,也就是洗菜的时候,这个菜还是这个菜,但是菜变干净了,而不会变成其它的菜!
       

       
   2、Reducing数据表
        这就开始了炒菜过程,也就是将数据聚合在一起,返回一个新的输出值。经过加工的食材经过炒制后变成一道新的菜肴。大量的数据就减少了,大规模的数据转变成了更小的总结数据。
       

        
            注意,大量数据经过 Reduce 是有相同键的数值才会被传到一个 reduce 函数里进行汇总,就和炒菜一样,安装一道菜所需要的食材进行炒制。所以,更形象的是
         
          例如,统计 hello 出现的次数,那么 Key 可以设置成 hello, 那么 hello 出现的次数就是数值。炒鱼香肉丝,那么鱼香肉丝可以作为 key,木耳、肉丝等就是数值。

        3、Map 端的 Shuffle
      上面说过,Map 产生的 KV 在送到 Reduce时并不是无序的,而是经过排序的过程就是 shuffle。也就是加工过的食材按照菜谱进行分类排序。 所有的数据交换都是在内存中进行的。
         

          

         首先map任务的output过程是一个环状的内存缓冲区,缓冲区的大小默认为100MB(可通过修改配置项mpareduce.task.io.sort.mb进行修改),当写入内存的大小到达一定比例,默认为80%(可通过mapreduce.map.sort.spill.percent配置项修改),便开始写入磁盘。

  在写入磁盘之前,线程将会指定数据写入与reduce相应的patitions中,最终传送给reduce.在每个partition中,后台线程将会在内存中进行Key的排序,(如果代码中有combiner方法,则会在output时就进行sort排序,这里,如果只有少于3个写入磁盘的文件,combiner将会在outputfile前启动,如果只有一个或两个,那么将不会调用)

  这里将map输出的结果进行压缩会大大减少磁盘IO与网络传输的开销(配置参数mapreduce.map .output.compress 设置为true,如果使用第三方压缩jar,可通过mapreduce.map.output.compress.codec进行设置)

   随后这些paritions输出文件将会通过HTTP发送至reducers,传送的最大启动线程通过mapreduce.shuffle.max.threads进行配置。


    4、Reduce 端的 shuffle
         Reduce 端的 shuffle有三个动作, Copy 、sort最后是 reduce
       
           

      首先上面每个节点的map都将结果写入了本地磁盘中,现在reduce需要将map的结果通过集群拉取过来,这里要注意的是,需要等到所有map任务结束后,reduce才会对map的结果进行拷贝,由于reduce函数有少数几个复制线程,以至于它可以同时拉取多个map的输出结果。默认的为5个线程(可通过修改配置mapreduce.reduce.shuffle.parallelcopies来修改其个数)

  这里有个问题,那么reducers怎么知道从哪些机器拉取数据呢? 

  当所有map的任务结束后,applicationMaster通过心跳机制(heartbeat mechanism),由它知道mapping的输出结果与机器host,所以reducer会定时的通过一个线程访问applicationmaster请求map的输出结果

  Map的结果将会被拷贝到reduce task的JVM的内存中(内存大小可在mapreduce.reduce.shuffle.input.buffer.percent中设置)如果不够用,则会写入磁盘。当内存缓冲区的大小到达一定比例时(可通过mapreduce.reduce.shuffle.merge.percent设置)或map的输出结果文件过多时(可通过配置mapreduce.reduce.merge.inmen.threshold),将会除法合并(merged)随之写入磁盘。

  这时要注意,所有的map结果这时都是被压缩过的,需要先在内存中进行解压缩,以便后续合并它们。(合并最终文件的数量可通过mapreduce.task.io.sort.factor进行配置) 最终reduce进行运算进行输出。



四、例子
      对于一个简单的 MR, 就是一个输入和输出的过程。
     (input) <k1, v1> -> map -> <k2, v2> -> combine -> <k2, v2> -> reduce -> <k3, v3> (output)
      map, combine, reduce
      下面是一个 wordcount 1.0的例子
     直接上源代码
    page225image15912
import java.io.IOException;import java.util.StringTokenizer;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Job;import org.apache.hadoop.mapreduce.Mapper;import org.apache.hadoop.mapreduce.Reducer;import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;public class WordCount {  public static class TokenizerMapper       extends Mapper<Object, Text, Text, IntWritable>{    private final static IntWritable one = new IntWritable(1);    private Text word = new Text();    public void map(Object key, Text value, Context context                    ) throws IOException, InterruptedException {      StringTokenizer itr = new StringTokenizer(value.toString());      while (itr.hasMoreTokens()) {        word.set(itr.nextToken());        context.write(word, one);      }    }  }  public static class IntSumReducer       extends Reducer<Text,IntWritable,Text,IntWritable> {    private IntWritable result = new IntWritable();    public void reduce(Text key, Iterable<IntWritable> values,                       Context context                       ) throws IOException, InterruptedException {      int sum = 0;      for (IntWritable val : values) {        sum += val.get();      }      result.set(sum);      context.write(key, result);    }  }  public static void main(String[] args) throws Exception {    Configuration conf = new Configuration();    Job job = Job.getInstance(conf, "word count");    job.setJarByClass(WordCount.class);    job.setMapperClass(TokenizerMapper.class);    job.setCombinerClass(IntSumReducer.class);    job.setReducerClass(IntSumReducer.class);    job.setOutputKeyClass(Text.class);    job.setOutputValueClass(IntWritable.class);    FileInputFormat.addInputPath(job, new Path(args[0]));    FileOutputFormat.setOutputPath(job, new Path(args[1]));    System.exit(job.waitForCompletion(true) ? 0 : 1);  }}

环境变量中需要有:
    
export HADOOP_CLASSPATH=${JAVA_HOME}/lib/tools.jar

  编译,创建一个 jar 
    
$ bin/hadoop com.sun.tools.javac.Main WordCount.java$ jar cf wc.jar WordCount*.class

这样就创建了一个 wc.jar
还需要 HDFS上的输入输出文件
 假定:
  • /user/joe/wordcount/input -  HDFS上的输入目录
  •  
    $ bin/hadoop fs -ls /user/joe/wordcount/input//user/joe/wordcount/input/file01/user/joe/wordcount/input/file02$ bin/hadoop fs -cat /user/joe/wordcount/input/file01Hello World Bye World$ bin/hadoop fs -cat /user/joe/wordcount/input/file02Hello Hadoop Goodbye Hadoop
   然后运行:
       
$ bin/hadoop jar wc.jar WordCount /user/joe/wordcount/input /user/joe/wordcount/output
 
自动创建了 output 目录,这个目录不要自己创建
 输出结果:
 
$ bin/hadoop fs -cat /user/joe/wordcount/output/part-r-00000Bye 1Goodbye 1Hadoop 2Hello 2World 2

原创粉丝点击