第1章 MapReduce概述
1.1 MapReduce定义
MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。
MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上。
1.2 MapReduce优缺点
1.2.1 优点
1)MapReduce 易于编程
它简单的实现一些接口,就可以完成一个分布式程序,这个分布式程序可以分布到大量廉价的PC机器上运行。也就是说你写一个分布式程序,跟写一个简单的串行程序是一模一样的。就是因为这个特点使得MapReduce编程变得非常流行。
2)良好的扩展性
当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力。
3)高容错性
MapReduce设计的初衷就是使程序能够部署在廉价的PC机器上,这就要求它具有很高的容错性。比如其中一台机器挂了,它可以把上面的计算任务转移到另外一个节点上运行,不至于这个任务运行失败,而且这个过程不需要人工参与,而完全是由Hadoop内部完成的。
4)适合PB级以上海量数据的离线处理
可以实现上千台服务器集群并发工作,提供数据处理能力。
1.2.2 缺点
1)不擅长实时计算
MapReduce无法像MySQL一样,在毫秒或者秒级内返回结果。
2)不擅长流式计算
流式计算的输入数据是动态的,而MapReduce的输入数据集是静态的,不能动态变化。这是因为MapReduce自身的设计特点决定了数据源必须是静态的。
3)不擅长DAG(有向无环图)计算
多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下,MapReduce并不是不能做,而是使用后,每个MapReduce作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下。
1.3 MapReduce核心思想(☆)
对于MapReduce程序来说,往往需要分成至少2个阶段:
(1) Map阶段:将数据转化成k-v形式;
(2)Reduce阶段:针对按key排好序的数据 进行分组处理,一个key一组;
map阶段将数据拆分,并行处理;
reduce阶段是聚合的;
1.4 MapReduce进程
一个完整的MapReduce程序在分布式运行时有三类实例进程:
(1)MrAppMaster:负责整个程序的过程调度及状态协调。
(2)MapTask:负责Map阶段的整个数据处理流程。
(3)ReduceTask:负责Reduce阶段的整个数据处理流程。
1.5 MapReduce常用数据序列化类型
1.6 MapReduce编程规范
MapReduce编程其实就是编写三个类:Mapper、Reducer和Driver
1.6.1 Mapper类
说明:
- Mapper类用于编写MapReduce程序的mapper阶段;
- mapper阶段的输入和输出都是KV形式的数据
- Mapper类中的map()方法是针对每一个KV调用一次!
查看源码
Mapper类的三个方法:
- map() : 用于编写处理单个kv的逻辑,输出也必须是kv;
- run():不需要我们手动编写,其内部循环调用map()方法,所以map()对每一条记录调用一次!
1.6.2 Reducer类
注意这里和mapper类中run()的区别:
- reducer类中是nextKey 是以key为单位做处理的,也就是说reduce()方法是处理一组key
- Mapper类中是nextKeyValue是以kv为单位做处理的,也就是说map()方法是处理一个kv
1.6.3 Driver类
- 相当于Yarn集群的客户端,因为我们在Driver类中提交MapReduce程序给Yarn;
- 提交内容:封装了MapReduce程序运行相关参数的Job对象;
Driver类是任务的驱动类,此类不需要继承任何类和接口
Driver类的任务是:给任务job类设置一些列的配置,并提交任务
以wordCount为例:
在整个MapReduce阶段还有很多可控部分,都需要自定义并且在Driver类中进行配置!这里只展示了一部分,很多都是默认的设置
1.7 WordCount案例
需求:将一个文件,内容如下,统计每个单词的数量
atguigu atguigu
ss ss
cls cls
jiao
banzhang
xue
hadoop
1.7.1 编写Mapper
package com.fantasy.mapreduce.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;
public class wordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private Text text = new Text();//提到外面 因为map是循环操作,所以避免创建过多对象
private IntWritable cnt = new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// keyIn 是行偏移量
// valueIn 是一行数据
String[] words = value.toString().split(" ");
for (String word : words) {
text.set(word);
context.write(text, cnt);
}
}
}
map()的输入和输出都是kv类型:
- 输入的kv是经过FileInputFormat处理过后的kv,这里先不介绍,只要知道key是字符偏移量,value是文本中的一行字符串即可
- 输出的key是单词,value是1,这个结果会经过shuffle阶段排序分组后,传递给reducer
- context是整个上下文环境,用于传递数据和job信息
1.7.2 编写Reducer
package com.fantasy.mapreduce.wordcount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class wordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable value : values) {
sum += value.get();
}
v.set(sum);
context.write(key,v);
}
}
reduce()的输入是kv,key是word,value是 maper输出的value的集合,如下图所示,reducer的输入的value是一个同key的集合
1.7.3 编写Driver
package com.fantasy.mapreduce.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;
public class wordCountDriver {
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
//1.获取配置信息 和 Job对象
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//2.关联本Driver程序的jar
job.setJarByClass(wordCountDriver.class);
//3.关联Mapper类 和Reducer类
job.setMapperClass(wordCountMapper.class);
job.setReducerClass(wordCountReducer.class);
//4.设置Mapper和Reducer输出类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//5.设置输入输出文件路径
FileInputFormat.setInputPaths(job, new Path("D:\\tmp_study\\Hadoop\\01_worcountInput"));
FileOutputFormat.setOutputPath(job, new Path("D:\\tmp_study\\Hadoop\\01_worcountOutput"));
//6.提交job
boolean result = job.waitForCompletion(true);
System.exit(result? 0 : 1);
}
}
1.7.4 将MR程序部署到集群运行
(1)在pom文件中配置打包插件
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!--下面这个是将项目依赖的jar包也给打包进来,如果运行环境有了这些jar包,是可以不用的 -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
打包完,第一个是不带任何依赖的;第二个是带依赖的
(2)修改Driver类中,输入目录和输出目录,使用命令行参数
//5.设置输入输出文件路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
(3)重新打包并上传文本文件到hdfs上
将小的jar包上传就行了,因为服务器上有hadoop环境
hdfs dfs -mkdir /wcinput/
hdfs dfs -put wordcount.txt /wcinput/
(4) 运行jar包,注意在jar包所在目录运行,在哪都行,能找到jar包就行
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop jar wc.jar com.fantasy.mapreduce.wordcount.wordCountDriver /wcinput /wcoutput
注意: jar包名字为Driver类全限定名
(5)在yarn ui上查看任务
hdfs上的结果数据:
- 实际开发中,就是在windows中编写代码,然后打包jar上传到服务器,然后将原始数据集上传到HDFS,然后用hadoop命令执行;
- 如果有很多个任务,很多个jar包,以后需要将任务写到脚本中,并由任务调度器来调度任务;做成任务流