MapReduce案例

500 阅读7分钟

#博学谷IT学习技术支持#

5.1 WordCount业务需求

WordCount中文叫做单词统计、词频统计,指的是使用程序统计某文本文件中,每个单词出现的总次数。这个是大数据计算领域经典的入门案例,虽然业务极其简单,但是希望能够通过案例感受背后MapReduce的执行流程和默认的行为机制,这才是关键、

#输入数据 1.txt
hello hadoop hello hello
hadoop allen hadoop
.....

#输出结果
hello 3
hadoop 3
allen 1

5.2 MapReduce编程思路

map阶段的核心:把输入的数据经过切割,全部标记1。因此输出就是<单词,1>。 shuffle阶段核心:经过默认的排序分区分组,key相同的单词会作为一组数据构成新的kv对。 reduce阶段核心:处理shuffle完的一组数据,该组数据就是该单词所有的键值对。对所有的1进行累加求和,就是该单词的总次数。最终输出<单词,总次数>。

5.3 WordCount编程实现

  1. 编程环境搭建

 <? xml version="1.0" encoding="UTF-8" ?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.itcast</groupId>
    <artifactId>test-mapreduce</artifactId>
    <version>1.0</version>

    <dependencies>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>3.1.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>3.1.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>3.1.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-mapreduce-client-core</artifactId>
            <version>3.1.4</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass></mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
  1. Mapper类编写

public class WordCountMapper extends Mapper<LongWritable, Text,Text,LongWritable> {
     //Mapper输出kv键值对  <单词,1> 
     private Text keyOut = new Text();
    private final static LongWritable valueOut = new LongWritable(1);

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
         //将读取的一行内容根据分隔符进行切割
         String[] words = value.toString().split("\s+");
         //遍历单词数组
         for (String word : words) {
            keyOut.set(word);
             //输出单词,并标记1
             context.write(new Text(word),valueOut);
        }
    }
}
  1. Reducer类编写

public class WordCountReducer extends Reducer<Text, LongWritable,Text,LongWritable> {

    private LongWritable result = new LongWritable();

    @Override
    protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
         //统计变量
         long count = 0;
         //遍历一组数据,取出该组所有的value
         for (LongWritable value : values) {
             //所有的value累加 就是该单词的总次数
             count +=value.get();
        }
        result.set(count);
         //输出最终结果<单词,总次数> 
         context.write(key,result);
    }
}
  1. 客户端驱动类编写

方式1:直接构建作业启动
public class WordCountDriver_v1 {
    public static void main(String[] args) throws Exception {
         //配置文件对象
         Configuration conf = new Configuration();
         // 创建作业实例
         Job job = Job.getInstance(conf, WordCountDriver_v1.class.getSimpleName());
         // 设置作业驱动类
         job.setJarByClass(WordCountDriver_v1.class);
         // 设置作业mapper reducer类
         job.setMapperClass(WordCountMapper.class);
        job.setReducerClass(WordCountReducer.class);
         // 设置作业mapper阶段输出key value数据类型
         job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);
         //设置作业reducer阶段输出key value数据类型 也就是程序最终输出数据类型
         job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(LongWritable.class);
         // 配置作业的输入数据路径
         FileInputFormat.addInputPath(job, new Path(args[0]));
         // 配置作业的输出数据路径
         FileOutputFormat.setOutputPath(job, new Path(args[1]));
         //判断输出路径是否存在 如果存在删除
         FileSystem fs = FileSystem.get(conf);
        if(fs.exists(new Path(args[1]))){
            fs.delete(new Path(args[1]),true);
        }
         // 提交作业并等待执行完成
         boolean resultFlag = job.waitForCompletion(true);
         //程序退出
         System.exit(resultFlag ? 0 :1);
    }
}
方式2:Tool工具类创建启动
public class WordCountDriver_v2 extends Configured implements Tool {

    @Override
    public int run(String[] args) throws Exception {
         // 创建作业实例
         Job job = Job.getInstance(getConf(), WordCountDriver_v2.class.getSimpleName());
         // 设置作业驱动类
         job.setJarByClass(WordCountDriver_v2.class);

         // 设置作业mapper reducer类
         job.setMapperClass(WordCountMapper.class);
        job.setReducerClass(WordCountReducer.class);

         // 设置作业mapper阶段输出key value数据类型
         job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);

         //设置作业reducer阶段输出key value数据类型 也就是程序最终输出数据类型
         job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(LongWritable.class);

         // 配置作业的输入数据路径
         FileInputFormat.addInputPath(job, new Path(args[0]));
         // 配置作业的输出数据路径
         FileOutputFormat.setOutputPath(job, new Path(args[1]));

         //判断输出路径是否存在 如果存在删除
         FileSystem fs = FileSystem.get(getConf());
        if(fs.exists(new Path(args[1]))){
            fs.delete(new Path(args[1]),true);
        }

         // 提交作业并等待执行完成
         return job.waitForCompletion(true) ? 0 : 1;

    }


    public static void main(String[] args) throws Exception {
         //配置文件对象
         Configuration conf = new Configuration();
         //使用工具类ToolRunner提交程序
         int status = ToolRunner.run(conf, new WordCountDriver_v2(), args);
         //退出客户端程序 客户端退出状态码和MapReduce程序执行结果绑定
         System.exit(status);
    }
}

6. MapReduce程序运行

所谓的运行模式讲的是:mr程序是单机运行还是分布式运行?mr程序需要的运算资源是yarn分配还是单机系统分配? 运行在何种模式 取决于下述这个参数:

  • mapreduce.framework.name=yarn 集群模式
  • mapreduce.framework.name=local 本地模式 如果不指定 默认是什么模式呢? 默认是local模式 在mapred-default.xml中有定义。如果代码中、运行的环境中有配置,会默认覆盖default配置。

6.1 本地模式运行

  • mapreduce程序是被提交给LocalJobRunner在本地以单进程的形式运行。而处理的数据及输出结果可以在本地文件系统,也可以在hdfs上
  • 本质是程序的conf中是否有mapreduce.framework.name=local
  • 本地模式非常便于进行业务逻辑的debug。
  • 右键直接运行main方法所在的主类即可。

6.2 集群模式运行

  • 将mapreduce程序提交给yarn集群,分发到很多的节点上并发执行。处理的数据和输出结果应该位于hdfs文件系统
  • 提交集群的实现步骤:
  • 将程序打成jar包,然后在集群的任意一个节点上用命令启动
  • hadoop jar wordcount.jar cn.itcast.bigdata.mrsimple.WordCountDriver args
  • yarn jar wordcount.jar cn.itcast.bigdata.mrsimple.WordCountDriver args

7. MapReduce输入输出梳理

MapReduce框架运转在<key,value>键值对上,也就是说,框架把作业的输入看成是一组<key,value>键值对,同样也产生一组<key,value>键值对作为作业的输出,这两组键值对可能是不同的。

  1. 输入特点

默认读取数据的组件叫做TextInputFormat。 关于输入路径:

  • 如果指向的是一个文件 处理该文件
  • 如果指向的是一个文件夹(目录) 就处理该目录所有的文件 当成整体来处理。
  1. 输出特点

默认输出数据的组件叫做TextOutputFormat。 输出路径不能提前存在 否则执行报错 对输出路径进行检测判断


8. MapReduce流程简单梳理

8.1 执行流程图

8.2 Map阶段执行过程

  • 第一阶段是把输入目录下文件按照一定的标准逐个进行逻辑切片,形成切片规划。默认情况下,Split size = Block size。每一个切片由一个MapTask处理。(getSplits)
  • 第二阶段是对切片中的数据按照一定的规则解析成<key,value>对。默认规则是把每一行文本内容解析成键值对。key是每一行的起始位置(单位是字节),value是本行的文本内容。(TextInputFormat)
  • 第三阶段是调用Mapper类中的map方法。上阶段中每解析出来的一个<k,v>,调用一次map方法。每次调用map方法会输出零个或多个键值对。
  • 第四阶段是按照一定的规则对第三阶段输出的键值对进行分区。默认是只有一个区。分区的数量就是Reducer任务运行的数量。默认只有一个Reducer任务。
  • 第五阶段是对每个分区中的键值对进行排序。首先,按照键进行排序,对于键相同的键值对,按照值进行排序。比如三个键值对<2,2>、<1,3>、<2,1>,键和值分别是整数。那么排序后的结果是<1,3>、<2,1>、<2,2>。如果有第六阶段,那么进入第六阶段;如果没有,直接输出到文件中。
  • 第六阶段是对数据进行局部聚合处理,也就是combiner处理。键相等的键值对会调用一次reduce方法。经过这一阶段,数据量会减少。本阶段默认是没有的。

8.3 Redue阶段执行过程

  • 第一阶段是Reducer任务会主动从Mapper任务复制其输出的键值对。Mapper任务可能会有很多,因此Reducer会复制多个Mapper的输出。
  • 第二阶段是把复制到Reducer本地数据,全部进行合并,即把分散的数据合并成一个大的数据。再对合并后的数据排序。
  • 第三阶段是对排序后的键值对调用reduce方法。键相等的键值对调用一次reduce方法,每次调用会产生零个或者多个键值对。最后把这些输出的键值对写入到HDFS文件中。

总结:

在整个MapReduce程序的开发过程中,我们最大的工作量是覆盖map函数和覆盖reduce函数。