Hadoop整个MR的过程源码解析(二)-map端任务的执行-map输出的过程源码解析

本文深入解析Hadoop MapReduce中map任务的输出过程,特别是当有无reduce阶段时的不同处理。在无reduce阶段时,map输出直接写入HDFS;存在reduce阶段时,涉及分区、排序、溢写和combiner等关键步骤,详细解读了这些过程的源码,为MR优化提供参考。
摘要由CSDN通过智能技术生成

接着上文所说的map的输入以及通过调用读取器的nextKeyValue判断是否还有下一条数据,读取数据,重新赋值偏移量,以及给key赋值偏移量pos(当前行相对于整个文件的一个位置),value(当前行的数据),完成了map阶段的输入以及重新定义,接下来该聊的就是map的输出阶段,该阶段是比较复杂的,分为两个分支;第一分支是没有reduce的阶段,在日常的MR的过程中最简单的优化手段就是在尽量的避免reduce的产生,因为reduce阶段存在shuffle阶段,只要有reduce阶段的发生,那么shuffle阶段就是不可避免的,而在整个MR的过程中,最耗时的就是shuffle阶段,现阶段的大部分对于MR的优化基本是都是旨在减少甚至避免shuffle对整个过程造成的影响,此处就不多讲了,在接下来的文章中会将MR的调优仔细讲一下,有需要的话,可以参考之后的文章。另外一个分支就是存在reduce阶段,这个分支将是最主要也是最为复杂的阶段。

现在进入主题,进入map输出阶段的源码分析,入口就是在自定义的map的context.write()方法。

进入代码,就会发现其实是调用的mapContext的Write方法。

而这write方法其实是上一篇中在初始化MapContext的时候根据是否存在reduce阶段,选择output方式之后加入mapcontext的,代码如下;

所以问题就回到了,这个时候的output到底是啥呢?话不多说,上代码;

这下对这个output算是有一定了解了吧,对的,其实如果不存在reduce阶段的话,map的输出底层是调用hdfs的输出,将结果直接输出,不存在排序相关的过程,接下来就是直接输出的时候的相关源码了;

 /**
         * 直接输出相关的源码
         * 对容器进行初始化
         */
        NewDirectOutputCollector(MRJobConfig jobContext,
                                 JobConf job, TaskUmbilicalProtocol umbilical, TaskReporter reporter)
                throws IOException, ClassNotFoundException, InterruptedException {
            /*****************接下来是集群的监控或者通信服务,暂时不用关注*******************/
            this.reporter = reporter;
            mapOutputRecordCounter = reporter
                    .getCounter(TaskCounter.MAP_OUTPUT_RECORDS);
            fileOutputByteCounter = reporter
                    .getCounter(FileOutputFormatCounter.BYTES_WRITTEN);
            List<Statistics> matchedStats = null;
            if (outputFormat instanceof org.apache.hadoop.mapreduce.lib.output.FileOutputFormat) {
                matchedStats = getFsStatistics(org.apache.hadoop.mapreduce.lib.output.FileOutputFormat
                        .getOutputPath(taskContext), taskContext.getConfiguration());
            }
            fsStats = matchedStats;
            /*****************end*******************/

            long bytesOutPrev = getOutputBytes(fsStats);
            /**
             * 获取当前输出格式化类的输出器
             * 这个方式可以联想到输入格式化类有自己默认的读取器,输出格式化类也有着自己的默认的读取器
             * out = new LineRecordWriter<K, V>(fileOut, keyValueSeparator);
             */
            out = outputFormat.getRecordWriter(taskContext);
            long bytesOutCurr = getOutputBytes(fsStats);
            fileOutputByteCounter.increment(bytesOutCurr - bytesOutPrev);
        }

        /**
         * 真正的写出的过程
         * @param key
         * @param value
         * @throws IOException
         * @throws InterruptedException
         */
        @Override
        @SuppressWarnings("unchecked")
        public void write(K key, V value)
                throws IOException, InterruptedException {
            reporter.progress();
            long bytesOutPrev = getOutputBytes(fsStats);
            /**
             * 此处调用了LineRecordWriter的write方法
             */
            out.write(key, value);
            long bytesOutCurr = getOutputBytes(fsStats);
            fileOutputByteCounter.increment(bytesOutCurr - bytesOutPrev);
            mapOutputRecordCounter.increment(1);
        }
//接下来是进入LineRecordWriter内部查看write的实现,需要注意的是,输出的时候是不经过缓冲区,直接将key,value进行输出
public synchronized void write(K key, V value)
      throws IOException {

      boolean nullKey = key == null || key instanceof NullWritable;
      boolean nullValue = value == null || value instanceof NullWritable;
      if (nullKey && nullValue) {
        return;
      }
      if (!nullKey) {
        writeObject(key);
      }
      /**
       * 判断key和value是否为空,不过我不知道为什么会这么判断,key值会为空吗?
       */
      if (!(nullKey || nullValue)) {
//此处的out为 DataOutputStream extends FilterOutputStream
        out.write(keyValueSeparator);
      }
      if (!nullValue) {
        writeObject(value);
      }
      out.write(newline);
    }

再往下走其实就没什么必要性,因为底层调用的hdfs的底层的一个output的工具,个人觉得没什么必要性继续往下追,所以基本也就到此,浅尝辄止了。

接下来才是重头戏,就是在存在reduce的情况下的输出到底是什么样的呢?

好了,上代码,代码如下;

首先明确一点,此处的output已经变了;

output = new NewOutputCollector(taskContext, job, umbilical, reporter);

在接下来的这段代码,将会说明reduce的个数,到底是靠什么去规定的,以及分区器是干什么的;

         /**
         * 初始化一个可排序的容器,注意此时是支持排序的,为的是输出有序的数据
         * 初始化分区器,这时候加载分区器,如果没有自定义,就会默认选择hashPartition
         * 如果reduce没有指定,默认只有一个,则每次指挥返回0号分区
         */
        NewOutputCollector(org.apache.hadoop.mapreduce.JobContext jobContext,
                           JobConf job,
                           TaskUmbilicalProtocol umbilical,
                           TaskReporter reporter
        ) throws IOException, ClassNotFoundException {
            //存储key,value,partition的容器--》MapOutputCollector--》MapOutputbuffer
            collector = createSortingCollector(job, reporter);
            /**
             * 分区个数等于设定的reduce的个数
             * reduce的个数是跟分区数相同的
             * reduece的 个数是可以人为指定的,所以reduce的个数,并行度,其实是由人为的去控制的
             * 重写分区器partitioner的作用是,通过人为的干预分组过程,尽量避免出现数据倾斜的问题
             */
            partitions = jobContext.getNumReduceTasks();
            if (partitions > 1) {
                //自定义分区器,继承org.apache.hadoop.mapreduce.Partitioner<K,V> -》根据数据抽样自定义分区器可有效防止在进行mr的时候出现数据倾斜的现象
                //默认是hash分区器
                //自定义分区器的作用是为了在一定程度上避免大量数据倾斜的产生
                partitioner = (org.apache.hadoop.mapreduce.Partitioner<K, V>)
                        ReflectionUtils.newInstance(jobContext.getPartitionerClass(), job);
            } else {
                //获取分区号
                partitioner = new org.apache.hadoop.mapreduce.Partitioner<K, V>() {
                    @Override
                    public int getPartition(K key, V value, int numPartitions) {
                        return partitions - 1;
                    }
                };
            }
        }

记住这段代码;partitions = jobContext.getNumReduceTasks();

你的分区数是由你的reduce的个数决定的,那么reduce的个数哪来的呢?就是我们自己在编写代码的时候自己设置的,也就是说我们自己可以人为的规定reduce的分区个数,但是在决定分区个数的时候最好是先做抽样,避免分区设置太多很多都是无用的,或者分区数太少,reduce的并行度起不来,导致任务执行很长时间。

接下来的重点就是;

就是这个创建可排序的容器,上代码;

private <KEY, VALUE> MapOutputCollector<KEY, VALUE>
    createSortingCollector(JobConf job, TaskReporter reporter)
            throws IOException, ClassNotFoundException {
        MapOutputCollector.Context context =
                new MapOutputCollector.Context(this, job, reporter);
        /**
         * 容器的底层实现是MapOutputBuffer
         */
        Class<?>[] collectorClasses = job.getClasses(
                JobContext.MAP_OUTPUT_COLLECTOR_CLASS_ATTR, MapOutputBuffer.class);
        int remainingCollectors = collectorClasses.length;
        for (Class clazz : collectorClasses) {
            try {
                if (!MapOutputCollector.class.isAssignableFrom(clazz)) {
                   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值