Hadoop2源码分析-MapReduce篇

本文详细介绍了Hadoop MapReduce V1与V2的架构对比,包括第一代架构的流程、问题与改进,以及第二代架构的资源管理、任务调度与设计优势。通过分析WordCount V2与Spark WordCount代码实现,阐述了MapReduce V2在资源使用、编程模型拓展方面的改进。文章最后总结了V2架构的主要变化与重构思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.概述
  前面我们已经对Hadoop有了一个初步认识,接下来我们开始学习Hadoop的一些核心的功能,其中包含mapreduce,fs,hdfs,ipc,io,yarn,今天为大家分享的是mapreduce部分,其内容目录如下所示:
  • MapReduce V1
  • MapReduce V2
  • MR V1和MR V2的区别
  • MR V2的重构思路
  本篇文章的源码是基于hadoop-2.6.0-src.tar.gz来完成的。代码下载地址,请参考《Hadoop2源码分析-准备篇》。
2.MapReduce V1
  下面我们给出第一代的MapReduce的架构图,如下所示:
  上图描述了第一代MapReduce框架的流程以及设计思路,下面为大家解释下这张图的具体含义:
  • 当我们编写完MR作业后,需要通过JobClient来提交一个job,提交的信息会发送到JobTracker模块,这个模块是第一代MapReduce计算框架的核心之一,它负责与集群中的其他节点维持心跳,为提交的作业分配资源,管理提交的作业的正常运作(失败,重启等)。
  • 第一代MapReduce的另一个核心的功能是TaskTracker,在各个TaskTracker安装节点上,它的主要功能是监控自己所在节点的资源使用情况。
  • TaskTracker监控当前节点的Tasks的运行情况,其中包含Map Task和Reduce Task,最后由Reduce Task到Reduce阶段,将结果输送到HDFS的文件系统中;其中的具体流程如图中描述的1-7步骤。TaskTracker在监控期间,需要把这些信息通过心跳机制发送给JobTracker,JobTracker收集到这些信息后,给新提交的作业分配其他的资源,避免重复资源分配。
  可以看出,第一代的MapReduce架构简单清晰,在刚面世的那几年,也曾获得总多企业的支持和认可。但随着分布式集群的规模和企业业务的增长,第一代框架的问题也逐渐暴露出来,主要有以下问题:
  • JobTracker是第一代MapReduce的入口点,若是JobTracker服务宕机,整个服务将会瘫痪,存在单点问题。
  • JobTracker负责的事情太多,完成来太多的任务,占用过多的资源,当Job数非常多的时候,会消耗很多内存,容易出现性能瓶颈。
  • 对TaskTracker而言,Task担当的角色过于简单,没有考虑到CPU及内存的使用情况,若存在多个大内存的Task被集中调度,容易出现内存溢出。
  • 另外,TaskTracker把资源强制分为map task slot和reduce task slot,若是MR任务中只存在其中一个(map或是reduce),会出现资源浪费的情况,资源利用率低。
  • 从开发人员的角度来说,源码分析的时候,阅读性不够友好,代码量大,任务不清晰,给开发人员在修复BUG和维护的时候增大了难度。
3.MapReduce V2
  在Hadoop V2中,加入了YARN的概念,所以MapReduce V2的架构和MapReduce V1的架构有些许的变化,如下图所示:
  从上图中,我们可以清晰的看出,架构重构的基本思想在于将JobTracker的两个核心的功能单独分离成独立的组件了。分离后的组件分别为资源管理(Applications Manager)和任务调度器(Resource Scheduler)。新的资源管理器(Resource Manager)管理整个系统的资源分配,而每一个Node Manager下的App Master(Application Master)负责对应的调度和协调工作,而在实际中,App Master从Resource Manager上获得资源,让Node Manager来协同工作和任务监控。
  从图中我们可以看出,Resource Manager是支持队列分层的,这些队列可以从集群中获取一定比例的资源,也就是说Resource Manager可以算得上是一个调度器,它在执行的过程当中本身不负责对应用的监控和状态的定位跟踪。
  Resource Manager在内存,CPU,IO等方面是动态分配的,相比第一代MapReduce计算框架,在资源使用上大大的加强了资源使用的灵活性。上图中的Node Manager是一个代理框架,负责应用程序的执行,监控应用程序的资源利用率,并将信息上报给资源管理器。另外,App Master所担当的角色职责包含:在运行任务是,向任务调度器动态的申请资源,对应用程序的状态进行监控,处理异常情况,如若出现问题,会在其他节点进行重启。
4.MR V1和MR V2的区别
  在和大家分析完 MR V1 和 MR V2 的架构后,我们来看看二者有哪些变化。在MR V2版本中,大部分的API接口都是兼容的保留下来,MR V1中的JobTracker和TaskTracker被替换成相应的Resource Manager,Node Manager。对比于MR V1中的Task的监控,重启等内热都交由App Master来处理,Resource Manager提供中心服务,负责资源的分配与调度。Node Manager负责维护Container的状态,并将收集的信息上报给Resource Manager,以及负责和Resource Manager维持心跳。
  MR V2中加入Yarn的概念后,体现以下设计优点:
  • 减少来资源消耗,让监控每一个作业更加分布式了。
  • 能够支持更多的变成模型,如:Spark,Storm,以及其他待开发的编程模型。
  • 将资源以内存量的概念来描述,比MR V1中的slot更加合理。
  另外,在工程目录结构也有了些许的变化,如下表所示:
改变目录 MR V1 MR V2 描述
配置文件 ${HADOOP_HOME}/conf ${HADOOP_HOME}/etc/hadoop MR V2中的配置文件路径修改为etc/hadoop目录下
脚本 ${HADOOP_HOME}/bin ${HADOOP_HOME}/sbin和${HADOOP_HOME}/bin 在MR V2中启动,停止等命令都位于sbin目录下,操作hdfs的命令存放在bin目录下
JAVA_HOME ${HADOOP_HOME}/conf/hadoop-env.sh ${HADOOP_HOME}/etc/hadoop/hadoop-env.sh和${HADOOP_HOME}/etc/hadoop/yarn-env.sh 在MR V2中需要同时在hadoop-env.sh和yarn-env.sh中配置JDK的路径
  由于添加Yarn特性,与第一代MR的框架变化较大,第一代的核心配置文件许多项也在新框架中摒弃了,具体新框架的核心配置文件信息,请参考《配置高可用的Hadoop平台》。
5.MR V2的重构思路
  在V2中的MapReduce重构的思路主要有以下几点:
  • 层次化的管理:分层级对资源的调度和分配进行管理。
  • 资源管理方式:由第一代的slot作为资源单位元,调整为更加细粒的内存单位元。
  • 编程模型拓展:V2版的设计支持除MapReduce以外的编程模型。
  MapReduce:WordCount V2,代码如下:
  1. package cn.hdfs.mapreduce.example;

  2. import java.io.BufferedReader;
  3. import java.io.FileReader;
  4. import java.io.IOException;
  5. import java.net.URI;
  6. import java.util.ArrayList;
  7. import java.util.HashSet;
  8. import java.util.List;
  9. import java.util.Set;
  10. import java.util.StringTokenizer;

  11. import org.apache.hadoop.conf.Configuration;
  12. import org.apache.hadoop.fs.Path;
  13. import org.apache.hadoop.io.IntWritable;
  14. import org.apache.hadoop.io.Text;
  15. import org.apache.hadoop.mapreduce.Job;
  16. import org.apache.hadoop.mapreduce.Mapper;
  17. import org.apache.hadoop.mapreduce.Reducer;
  18. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
  19. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
  20. import org.apache.hadoop.mapreduce.Counter;
  21. import org.apache.hadoop.util.GenericOptionsParser;
  22. import org.apache.hadoop.util.StringUtils;

  23. /**
  24. * @date Apr 17, 2015
  25. *
  26. * @author dengjie
  27. */
  28. public class WordCount2 {

  29.   public static class TokenizerMapper
  30.        extends Mapper<Object, Text, Text, IntWritable>{

  31.     static enum CountersEnum { INPUT_WORDS }

  32.     private final static IntWritable one = new IntWritable(1);
  33.     private Text word = new Text();

  34.     private boolean caseSensitive;
  35.     private Set<String> patternsToSkip = new HashSet<String>();

  36.     private Configuration conf;
  37.     private BufferedReader fis;

  38.     @Override
  39.     public void setup(Context context) throws IOException,
  40.         InterruptedException {
  41.       conf = context.getConfiguration();
  42.       caseSensitive = conf.getBoolean("wordcount.case.sensitive", true);
  43.       if (conf.getBoolean("wordcount.skip.patterns", true)) {
  44.         URI[] patternsURIs = Job.getInstance(conf).getCacheFiles();
  45.         for (URI patternsURI : patternsURIs) {
  46.           Path patternsPath = new Path(patternsURI.getPath());
  47.           String patternsFileName = patternsPath.getName().toString();
  48.           parseSkipFile(patternsFileName);
  49.         }
  50.       }
  51.     }

  52.     private void parseSkipFile(String fileName) {
  53.       try {
  54.         fis = new BufferedReader(new FileReader(fileName));
  55.         String pattern = null;
  56.         while ((pattern = fis.readLine()) != null) {
  57.           patternsToSkip.add(pattern);
  58.         }
  59.       } catch (IOException ioe) {
  60.         System.err.println("Caught exception while parsing the cached file '"
  61.             + StringUtils.stringifyException(ioe));
  62.       }
  63.     }

  64.     @Override
  65.     public void map(Object key, Text value, Context context
  66.                     ) throws IOException, InterruptedException {
  67.       String line = (caseSensitive) ?
  68.           value.toString() : value.toString().toLowerCase();
  69.       for (String pattern : patternsToSkip) {
  70.         line = line.replaceAll(pattern, "");
  71.       }
  72.       StringTokenizer itr = new StringTokenizer(line);
  73.       while (itr.hasMoreTokens()) {
  74.         word.set(itr.nextToken());
  75.         context.write(word, one);
  76.         Counter counter = context.getCounter(CountersEnum.class.getName(),
  77.             CountersEnum.INPUT_WORDS.toString());
  78.         counter.increment(1);
  79.       }
  80.     }
  81.   }

  82.   public static class IntSumReducer
  83.        extends Reducer<Text,IntWritable,Text,IntWritable> {
  84.     private IntWritable result = new IntWritable();

  85.     public void reduce(Text key, Iterable<IntWritable> values,
  86.                        Context context
  87.                        ) throws IOException, InterruptedException {
  88.       int sum = 0;
  89.       for (IntWritable val : values) {
  90.         sum += val.get();
  91.       }
  92.       result.set(sum);
  93.       context.write(key, result);
  94.     }
  95.   }

  96.   public static void main(String[] args) throws Exception {
  97.     Configuration conf = new Configuration();
  98.     GenericOptionsParser optionParser = new GenericOptionsParser(conf, args);
  99.     String[] remainingArgs = optionParser.getRemainingArgs();
  100.     if (!(remainingArgs.length != 2 || remainingArgs.length != 4)) {
  101.       System.err.println("Usage: wordcount <in> <out> [-skip skipPatternFile]");
  102.       System.exit(2);
  103.     }
  104.     Job job = Job.getInstance(conf, "word count");
  105.     job.setJarByClass(WordCount2.class);
  106.     job.setMapperClass(TokenizerMapper.class);
  107.     job.setCombinerClass(IntSumReducer.class);
  108.     job.setReducerClass(IntSumReducer.class);
  109.     job.setOutputKeyClass(Text.class);
  110.     job.setOutputValueClass(IntWritable.class);

  111.     List<String> otherArgs = new ArrayList<String>();
  112.     for (int i=0; i < remainingArgs.length; ++i) {
  113.       if ("-skip".equals(remainingArgs[i])) {
  114.         job.addCacheFile(new Path(remainingArgs[++i]).toUri());
  115.         job.getConfiguration().setBoolean("wordcount.skip.patterns", true);
  116.       } else {
  117.         otherArgs.add(remainingArgs[i]);
  118.       }
  119.     }
  120.     FileInputFormat.addInputPath(job, new Path(otherArgs.get(0)));
  121.     FileOutputFormat.setOutputPath(job, new Path(otherArgs.get(1)));

  122.     System.exit(job.waitForCompletion(true) ? 0 : 1);
  123.   }
  124. }
复制代码

  Spark:WordCount,代码如下:
  1. package com.hdfs.spark.example

  2. /**
  3. * @date Apr 17, 2015
  4. *
  5. * @author dengjie
  6. */
  7. import org.apache.spark.SparkConf
  8. import org.apache.spark.SparkContext
  9. import org.apache.spark.SparkContext._

  10. /**
  11. * 统计字符出现次数
  12. */
  13. object WordCount {
  14.    def main(args: Array[String]) {
  15.      if (args.length < 1) {
  16.        System.err.println("Usage: <file>")
  17.        System.exit(1)
  18.      }

  19.      val conf = new SparkConf()
  20.      val sc = new SparkContext(conf)     
  21.      val line = sc.textFile(args(0))      
  22.      line.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_+_).collect().foreach(println)      
  23.      sc.stop()   

  24. }
复制代码

6.结束语
  这篇文章就和大家分享到这里,如果大家在研究和学习的过程中有什么疑问,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!

转自:董的博客 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值