MapReduce学习笔记(1)

1. MapReduce编程模型- Hadoop架构

Hadoop由HDFS分布式存储、MapReduce分布式计算、Yarn资源调度三部分组成。

  • MapReduce是采用一种分而治之的思想设计出来的分布式计算框架
  • MapReduce由两个阶段组成:
    • Map阶段(将大任务切分成一个个小的任务)
    • Reduce阶段(汇总小任务的结果)
  • 什么是分而治之:
    • 比如一复杂、计算量大、耗时长的的任务,称为“大任务”;
    • 此时使用单台服务器无法计算或较短时间内计算出结果时,可将此大任务切分成一个个小的任务,小任务分别在不同的服务器上并行的执行
    • 最终再汇总每个小任务的结果

在这里插入图片描述

1.1 Map阶段
  • map阶段有一个关键的map()函数;
  • 此函数的输入是键值对
  • 输出是一系列键值对,输出写入本地磁盘
1.2 Reduce阶段
  • reduce阶段有一个关键的函数reduce()函数

  • 此函数的输入也是键值对(即map的输出(kv对))

  • 输出也是一系列键值对,结果最终写入HDFS

1.3 MapReduce模型图

在这里插入图片描述

2. MapReduce编程示例

MapReduce的词频统计为例:统计一批英文文章当中,每个单词出现的总次数。

2.1 MapReduce原理图

在这里插入图片描述

  • Map阶段
    • 假设MR程序的输入文件“Gone With The Wind”有三个block,分别是block1、block2、block3
    • MR编程时,每个block对应一个分片split
    • 每一个split对应一个map任务(map task)
    • 如图共3个map任务(map1、map2、map3);这3个任务的逻辑一样,所以以第一个map任务(map1)为例分析
    • map1读取block1的数据;一次读取block1的一行数据;
      • 产生键值对(key/value),作为map()的参数传入,调用map();
      • 假设当前所读行是第一行
      • 将当前所读行的行首相对于当前block开始处的字节偏移量作为key(0)
      • 当前行的内容作为value(Dear Bear River)
    • map()内
      • (按需求,写业务代码),将value当前行内容按空格切分,得到三个单词Dear | Bear | River
      • 将每个单词变成键值对,输出出去(Dear, 1) | (Bear, 1) | (River, 1);最终结果写入map任务所在节点的本地磁盘中(内里还有细节,讲到shuffle时,再细细展开)
      • block的第一行的数据被处理完后,接着处理第二行;逻辑同上
      • 当map任务将当前block中所有的数据全部处理完后,此map任务即运行结束
    • 其它的每一个map任务都是如上逻辑
  • Reduce阶段
    • reduce任务(reduce task)的个数由自己写的程序编程指定,main()内的job.setNumReduceTasks(4)指定reduce任务是4个(reduce1、reduce2、reduce3、reduce4)
    • 每一个reduce任务的逻辑一样,所以以第一个reduce任务(reduce1)为例分析
    • map1任务完成后,reduce1通过网络,连接到map1,将map1输出结果中属于reduce1的分区的数据,通过网络获取到reduce1端(拷贝阶段)
    • 同样也如此连接到map2、map3获取结果
    • 最终reduce1端获得4个(Dear, 1)键值对;由于key键相同,它们分到同一组;
    • 4个(Dear, 1)键值对,转换成[Dear, Iterable(1, 1, 1, )],作为两个参数传入reduce()
    • 在reduce()内部,计算Dear的总数为4,并将(Dear, 4)作为键值对输出
    • 每个reduce任务最终输出文件(内部里还有细节,讲到shuffle时,再细细展开),文件写入到HDFS
2.2 MR中key的作用
  • MapReduce编程中,key有特殊的作用:

    • ①数据中,若要针对某个值进行分组、聚合时,需将此值作为MR中的reduce的输入的key

    • 如当前的词频统计例子,按单词进行分组,每组中对出现次数做聚合(计算总和);所以需要将每个单词作为reduce输入的key,MapReduce框架自动按照单词分组,进而求出每组即每个单词的总次数

    • ②另外,key还具有可排序的特性,因为MR中的key类需要实现WritableComparable接口;而此接口又继承Comparable接口(可查看源码)

    • MR编程时,要充分利用以上两点;结合实际业务需求,设置合适的key

在这里插入图片描述

2.3 创建MAVEN工程
  • 使用IDEA创建maven工程
  • pom.xml如下,主要用到的dependencies有
    <properties>
        <cdh.version>2.6.0-cdh5.14.2</cdh.version>
    </properties>

    <repositories>
        <repository>
            <id>cloudera</id>
            <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
        </repository>
    </repositories>

    <dependencies>

        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>2.6.0-mr1-cdh5.14.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>${cdh.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>${cdh.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-mapreduce-client-core</artifactId>
            <version>${cdh.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
2.4 MR参考代码
  • 创建包com.zsc.wordcount

  • 在包中创建自定义mapper类、自定义reducer类、包含main类

2.4.1 Mapper代码

package com.zsc.wordcount;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

/**
 * 类Mapper<LongWritable, Text, Text, IntWritable>的四个泛型分别表示
 * map方法的输入的键的类型kin、值的类型vin;输出的键的类型kout、输出的值的类型vout
 * kin指的是当前所读行行首相对于split分片开头的字节偏移量,所以是long类型,对应序列化类型LongWritable
 * vin指的是当前所读行,类型是String,对应序列化类型Text
 * kout根据需求,输出键指的是单词,类型是String,对应序列化类型是Text
 * vout根据需求,输出值指的是单词的个数,1,类型是int,对应序列化类型是IntWritable
 *
 */
public class WordCountMap extends Mapper<LongWritable, Text, Text, IntWritable> {

    /**
     * 处理分片split中的每一行的数据;针对每行数据,会调用一次map方法
     * 在一次map方法调用时,从一行数据中,获得一个个单词word,再将每个单词word变成键值对形式(word, 1)输出出去
     * 输出的值最终写到本地磁盘中
     * @param key 当前所读行行首相对于split分片开头的字节偏移量
     * @param value  当前所读行
     * @param context
     * @throws IOException
     * @throws InterruptedException
     */
    public void map(LongWritable key, Text value, Context context)
            throws IOException, InterruptedException {
        //当前行的示例数据(单词间空格分割):Dear Bear River
        //取得当前行的数据
        String line = value.toString();
        //按照\t进行分割,得到当前行所有单词
        String[] words = line.split("\t");

        for (String word : words) {
            //将每个单词word变成键值对形式(word, 1)输出出去
            //同样,输出前,要将kout, vout包装成对应的可序列化类型,如String对应Text,int对应IntWritable
            context.write(new Text(word), new IntWritable(1));
        }
    }
}

2.4.2 Reducer代码

package com.zsc.wordcount;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

/**
 *
 * Reducer<Text, IntWritable, Text, IntWritable>的四个泛型分别表示
 * reduce方法的输入的键的类型kin、输入值的类型vin;输出的键的类型kout、输出的值的类型vout
 * 注意:因为map的输出作为reduce的输入,所以此处的kin、vin类型分别与map的输出的键类型、值类型相同
 * kout根据需求,输出键指的是单词,类型是String,对应序列化类型是Text
 * vout根据需求,输出值指的是每个单词的总个数,类型是int,对应序列化类型是IntWritable
 *
 */
public class WordCountReduce extends Reducer<Text, IntWritable, Text, IntWritable> {
    /**
     *
     * key相同的一组kv对,会调用一次reduce方法
     * 如reduce task汇聚了众多的键值对,有key是hello的键值对,也有key是spark的键值对,如下
     * (hello, 1)
     * (hello, 1)
     * (hello, 1)
     * (hello, 1)
     * ...
     * (spark, 1)
     * (spark, 1)
     * (spark, 1)
     *
     * 其中,key是hello的键值对被分成一组;merge成[hello, Iterable(1,1,1,1)],调用一次reduce方法
     * 同样,key是spark的键值对被分成一组;merge成[spark, Iterable(1,1,1)],再调用一次reduce方法
     *
     * @param key 当前组的key
     * @param values 当前组中,所有value组成的可迭代集和
     * @param context reduce上下文环境对象
     * @throws IOException
     * @throws InterruptedException
     */
    public void reduce(Text key, Iterable<IntWritable> values,
                          Context context) throws IOException, InterruptedException {
        //定义变量,用于累计当前单词出现的次数
        int sum = 0;

        for (IntWritable count : values) {
            //从count中获得值,累加到sum中
            sum += count.get();
        }

        //将单词、单词次数,分别作为键值对,输出
        context.write(key, new IntWritable(sum));// 输出最终结果
    };
}

2.4.3 Main程序入口

package com.zsc.wordcount;

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.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;

/**
 *
 * MapReduce程序入口
 * 注意:
 *  导包时,不要导错了;
 *  另外,map\reduce相关的类,使用mapreduce包下的,是新API,如org.apache.hadoop.mapreduce.Job;;
 */
public class WordCountMain {
    //若在IDEA中本地执行MR程序,需要将mapred-site.xml中的mapreduce.framework.name值修改成local
    //参数 c:/test/README.txt c:/test/wc
    public static void main(String[] args) throws IOException,
            ClassNotFoundException, InterruptedException {

        //判断一下,输入参数是否是两个,分别表示输入路径、输出路径
       if (args.length != 2 || args == null) {
            System.out.println("please input Path!");
            System.exit(0);
        }

        Configuration configuration = new Configuration();
       
        //调用getInstance方法,生成job实例
        Job job = Job.getInstance(configuration, WordCountMain.class.getSimpleName());

        //设置job的jar包,如果参数指定的类包含在一个jar包中,则此jar包作为job的jar包; 参数class跟主类在一个工程即可;一般设置成主类
        job.setJarByClass(WordCountMain.class);

        //通过job设置输入/输出格式
        //MR的默认输入格式是TextInputFormat,输出格式是TextOutputFormat;所以下两行可以注释掉
//        job.setInputFormatClass(TextInputFormat.class);
//        job.setOutputFormatClass(TextOutputFormat.class);

        //设置输入/输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        //设置处理Map阶段的自定义的类
        job.setMapperClass(WordCountMap.class);
        //设置map combine类,减少网路传出量
        job.setCombinerClass(WordCountReduce.class);
        //设置处理Reduce阶段的自定义的类
        job.setReducerClass(WordCountReduce.class);
        //注意:如果map、reduce的输出的kv对类型一致,直接设置reduce的输出的kv对就行;如果不一样,需要分别设置map, reduce的输出的kv类型
        //注意:此处设置的map输出的key/value类型,一定要与自定义map类输出的kv对类型一致;否则程序运行报错
//        job.setMapOutputKeyClass(Text.class);
//        job.setMapOutputValueClass(IntWritable.class);

        //设置reduce task最终输出key/value的类型
        //注意:此处设置的reduce输出的key/value类型,一定要与自定义reduce类输出的kv对类型一致;否则程序运行报错
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 提交作业
        job.waitForCompletion(true);

    }
}
  • 程序运行有两种方式,分别是windows本地运行、集群运行(打包为jar包上传至集群运行)

3. MapReduce编程:数据清洗

应用:mapreduce在企业中,可以用于对海量数据的数据清洗;当然,随着新一代大数据框架的出现,也可以使用spark、flink等框架,做数据清洗

3.1 需求
  • 现有一批日志文件,日志来源于用户使用搜狗搜索引擎搜索新闻,并点击查看搜索结果过程;
  • 但是,日志中有一些记录损坏,现需要使用MapReduce来将这些损坏记录(如记录中少字段、多字段)从日志文件中删除,此过程就是传说中的数据清洗
  • 并且在清洗时,要统计损坏的记录数。
3.2 日志数据结构
  • 日志格式:每行记录有6个字段;分别表示时间datetime、用户ID userid、新闻搜索关键字searchkwd、当前记录在返回列表中的序号retorder、用户点击链接的顺序cliorder、点击的URL连接cli-url在这里插入图片描述

    关于retorder、cliorder说明:
    在这里插入图片描述

3.3 逻辑分析
  • MapReduce程序一般分为map阶段,将任务分而治之;reduce阶段,将map阶段的结果进行聚合

  • 但有些mapreduce应用不需要数据聚合的操作,也就是说不需要reduce阶段。即编程时,不需要编写自定义的reducer类;在main()中调用job.setNumReduceTasks(0)设置。本例的数据清洗就是属于此种情况

  • 统计损坏的记录数,可使用自定义计数器的方式实现
    在这里插入图片描述

  • map方法的逻辑:取得每一行数据,与每条记录的固定格式比对,是否符合:

    • 若符合,则是完好的记录,原样输出
    • 若不符合,则是损坏的记录,不用做其他处理,对自定义计数器累加即可
3.4 MR代码

3.4.1 Mapper类

package com.zsc.dataclean;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Counter;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

/**
 *
 * 现对sogou日志数据,做数据清洗;将不符合格式要求的数据删除
 * 每行记录有6个字段;
 * 分别表示时间datetime、用户ID userid、新闻搜索关键字searchkwd、当前记录在返回列表中的序号retorder、用户点击链接的顺序cliorder、点击的URL连接cliurl
 * 日志样本:
 * 20111230111308  0bf5778fc7ba35e657ee88b25984c6e9        nba直播 4       1       http://www.hoopchina.com/tv
 *
 */
public class DataClean {
    /**
     *
     * 基本上大部分MR程序的main方法逻辑,大同小异;将其他MR程序的main方法代码拷贝过来,稍做修改即可
     * 实际开发中,也会有很多的复制、粘贴、修改
     *
     * 注意:若要IDEA中,本地运行MR程序,需要将resources/mapred-site.xml中的mapreduce.framework.name属性值,设置成local
     * @param args
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //判断一下,输入参数是否是两个,分别表示输入路径、输出路径
        if (args.length != 2 || args == null) {
            System.out.println("please input Path!");
            System.exit(0);
        }

        Configuration configuration = new Configuration();

        //调用getInstance方法,生成job实例
        Job job = Job.getInstance(configuration, DataClean.class.getSimpleName());

        //设置jar包,参数是包含main方法的类
        job.setJarByClass(DataClean.class);

        //设置输入/输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        //设置处理Map阶段的自定义的类
        job.setMapperClass(DataCleanMapper.class);

        //注意:此处设置的map输出的key/value类型,一定要与自定义map类输出的kv对类型一致;否则程序运行报错
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(NullWritable.class);

        //注意:因为不需要reduce聚合阶段,所以,需要显示设置reduce task个数是0
        job.setNumReduceTasks(0);

        // 提交作业
        job.waitForCompletion(true);
    }

    /**
     * 
     * 自定义mapper类
     * 注意:若自定义的mapper类,与main方法在同一个类中,需要将自定义mapper类,声明成static的
     */
    public static class DataCleanMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
        //为了提高程序的效率,避免创建大量短周期的对象,出发频繁GC;此处生成一个对象,共用
        NullWritable nullValue = NullWritable.get();

        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            //自定义计数器,用于记录残缺记录数
            Counter counter = context.getCounter("DataCleaning", "damagedRecord");

            //获得当前行数据
            //样例数据:20111230111645  169796ae819ae8b32668662bb99b6c2d        塘承高速公路规划线路图  1       1       http://auto.ifeng.com/roll/20111212/729164.shtml
            String line = value.toString();

            //将行数据按照记录中,字段分隔符切分
            String[] fields = line.split("\t");

            //判断字段数组长度,是否为6
            if(fields.length != 6) {
                //若不是,则不输出,并递增自定义计数器
                counter.increment(1L);
            } else {
                //若是6,则原样输出
                context.write(value, nullValue);
            }
        }
    }
}

4.4.2 运行结果
在这里插入图片描述

  • ①reduce 0%,job就已经successfully,表示此MR程序没有reduce阶段

  • ②DataCleaning是自定义计数器组名;damagedRecord是自定义的计数器;值为6,表示有6条损坏记录

  • 图中part-m-00000中的m表示,此文件是由map任务生成

4. MapReduce编程:用户搜索次数

4.1 需求

使用MR编程,统计sogou日志数据中,每个用户搜索的次数,结果写入HDFS

4.2 数据结构
  • 数据来源自“MapReduce编程:数据清洗”中的输出结果,仍然是sogou日志数据
4.3 逻辑分析
  • MR编程时,若要针对某个值对数据进行分组、聚合时,如当前的词频统计例子,需要将每个单词作为reduce输入的key,从而按照单词分组,进而求出每组即每个单词的总次数
  • 此例类似上述情况:
    • 统计每个用户的搜索次数,将userid放到reduce输入的key的位置;
    • 对userid进行分组
    • 进而统计每个用户的搜索次数
4.4 MR代码
package com.zsc.searchcount;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
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;

import java.io.IOException;

/**
 *
 * 本MR示例,用于统计每个用户搜索并查看URL链接的次数
 */
public class UserSearchCount {

    /**
     *
     * @param args C:\test\datacleanresult c:\test\usersearch
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws InterruptedException
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //判断一下,输入参数是否是两个,分别表示输入路径、输出路径
        if (args.length != 2 || args == null) {
            System.out.println("please input Path!");
            System.exit(0);
        }

        Configuration configuration = new Configuration();

        //调用getInstance方法,生成job实例
        Job job = Job.getInstance(configuration, UserSearchCount.class.getSimpleName());

        //设置jar包,参数是包含main方法的类
        job.setJarByClass(UserSearchCount.class);

        //通过job设置输入/输出格式
        //MR的默认输入格式是TextInputFormat,输出格式是TextOutputFormat;所以下两行可以注释掉
//        job.setInputFormatClass(TextInputFormat.class);
//        job.setOutputFormatClass(TextOutputFormat.class);

        //设置输入/输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        //设置处理Map阶段的自定义的类
        job.setMapperClass(SearchCountMapper.class);
        //设置map combine类,减少网路传出量 
        //job.setCombinerClass(WordCountReduce.class);
        //设置处理Reduce阶段的自定义的类
        job.setReducerClass(SearchCountReducer.class);

        //如果map、reduce的输出的kv对类型一致,直接设置reduce的输出的kv对就行;如果不一样,需要分别设置map, reduce的输出的kv类型
        //注意:此处设置的map输出的key/value类型,一定要与自定义map类输出的kv对类型一致;否则程序运行报错
//        job.setMapOutputKeyClass(Text.class);
//        job.setMapOutputValueClass(IntWritable.class);

        //设置reduce task最终输出key/value的类型
        //注意:此处设置的reduce输出的key/value类型,一定要与自定义reduce类输出的kv对类型一致;否则程序运行报错
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        //提交作业
        job.waitForCompletion(true);
    }

    public static class SearchCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {

        //定义共用的对象,减少GC压力
        Text userIdKOut = new Text();
        IntWritable vOut = new IntWritable(1);

        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

            //获得当前行的数据
            //样例数据:20111230111645  169796ae819ae8b32668662bb99b6c2d        塘承高速公路规划线路图  1       1       http://auto.ifeng.com/roll/20111212/729164.shtml
            String line = value.toString();

            //切分,获得各字段组成的数组
            String[] fields = line.split("\t");

            //因为要统计每个user搜索并查看URL的次数,所以将userid放到输出key的位置
            //注意:MR编程中,根据业务需求设计key是很重要的能力
            String userid = fields[1];

            //设置输出的key的值
            userIdKOut.set(userid);
            //输出结果
            context.write(userIdKOut, vOut);
        }
    }

    public static class SearchCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {

        //定义共用的对象,减少GC压力
        IntWritable totalNumVOut = new IntWritable();

        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            int sum = 0;

            for(IntWritable value: values) {
                sum += value.get();
            }

            //设置当前user搜索并查看总次数
            totalNumVOut.set(sum);
            context.write(key, totalNumVOut);
        }
    }
}

4.4.1 结果

  • 运行参数
C:\test\datacleanresult c:\test\usersearch
  • 运行结果:
    在这里插入图片描述

5. Shuffle

shuffle主要指的是map端的输出作为reduce端输入的过程

5.1 shuffle简图

在这里插入图片描述

5.2 shuffle细节图

在这里插入图片描述

  • 分区使用了分区器,默认分区器是HashPartitioner

    public class HashPartitioner<K2, V2> implements Partitioner<K2, V2> {
    
      public void configure(JobConf job) {}
    
      /** Use {@link Object#hashCode()} to partition. */
      public int getPartition(K2 key, V2 value,
                              int numReduceTasks) {
        return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
      }
    
    }
    
5.3 map端
  • 每个map任务都有一个对应的环形内存缓冲区;输出是kv对,先写入到环形缓冲区(默认大小100M),当内容占据80%缓冲区空间后,由一个后台线程将缓冲区中的数据溢出写到一个磁盘文件
  • 在溢出写的过程中,map任务可以继续向环形缓冲区写入数据;但是若写入速度大于溢出写的速度,最终造成100m占满后,map任务会暂停向环形缓冲区中写数据的过程;只执行溢出写的过程;直到环形缓冲区的数据全部溢出写到磁盘,才恢复向缓冲区写入
  • 后台线程溢写磁盘过程,有以下几个步骤:
    • 先对每个溢写的kv对做分区;分区的个数由MR程序的reduce任务数决定;默认使用HashPartitioner计算当前kv对属于哪个分区;计算公式:(key.hashCode() & Integer.MAX_VALUE) % numReduceTasks
    • 每个分区中,根据kv对的key做内存中排序;
    • 若设置了map端本地聚合combiner,则对每个分区中,排好序的数据做combine操作;
    • 若设置了对map输出压缩的功能,会对溢写数据压缩
  • 随着不断的向环形缓冲区中写入数据,会多次触发溢写(每当环形缓冲区写满100m),本地磁盘最终会生成多个溢出文件
  • 合并溢写文件:在map task完成之前,所有溢出文件会被合并成一个大的溢出文件;且是已分区、已排序的输出文件
  • 小细节:
    • 在合并溢写文件时,如果至少有3个溢写文件,并且设置了map端combine的话,会在合并的过程中触发combine操作;
    • 但是若只有2个或1个溢写文件,则不触发combine操作(因为combine操作,本质上是一个reduce,需要启动JVM虚拟机,有一定的开销)
5.4 reduce端
  • reduce task会在每个map task运行完成后,通过HTTP获得map task输出中,属于自己的分区数据(许多kv对)

  • 如果map输出数据比较小,先保存在reduce的jvm内存中,否则直接写入reduce磁盘

  • 一旦内存缓冲区达到阈值(默认0.66)或map输出数的阈值(默认1000),则触发归并merge,结果写到本地磁盘

  • 若MR编程指定了combine,在归并过程中会执行combine操作

  • 随着溢出写的文件的增多,后台线程会将它们合并大的、排好序的文件

  • reduce task将所有map task复制完后,将合并磁盘上所有的溢出文件

  • 默认一次合并10个

  • 最后一批合并,部分数据来自内存,部分来自磁盘上的文件

  • 进入“归并、排序、分组阶段”

  • 每组数据调用一次reduce方法

5.5 Shuffle总结
  • map端
    • map()输出结果先写入环形缓冲区
    • 缓冲区100M;写满80M后,开始溢出写磁盘文件
    • 此过程中,会进行分区、排序、combine(可选)、压缩(可选)
    • map任务完成前,会将多个小的溢出文件,合并成一个大的溢出文件(已分区、排序)
  • reduce端
    • 拷贝阶段:reduce任务通过http将map任务属于自己的分区数据拉取过来
    • 开始merge及溢出写磁盘文件
    • 所有map任务的分区全部拷贝过来后,进行阶段合并、排序、分组阶段
    • 每组数据调用一次reduce()
    • 结果写入HDFS
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值