文章目录
1、MapReduce定义
一个分布式运算程序的编程框架
1.1 分布式
指多台服务器共同完成计算
1.2 编程框架
- 如果没有框架
很多底层,服务器之间的交互代码都得自己完成 - 有框架
用户只关心自己的业务逻辑代码
核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序
2、MapReduce优缺点
2.1 优点
-
易于编程
用户只关心自己的业务逻辑代码,实现框架接口 -
良好的扩展性
可以动态增加服务器,解决计算资源不够的问题 -
高容错性
任何一台服务器挂掉,可以将任务转移到其他节点 -
适合海量数据的计算
TB、PB,可以实现几千台服务器并发共同计算
2.2 缺点
-
不擅长实时计算
无法像MySQL一样,在毫秒或者秒级内返回结果
一般的任务都是 分钟/小时/最长7天 级别 -
不擅长流式计算
MapReduce的输入数据集是静态的,不能动态变化
SparkStreaming、Flink擅长 -
不擅长DAG(有向无环图)计算
多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出
在这种情况下,MapReduce并不是不能做,而是使用后,每个MapReduce作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下。
Spark擅长
3、MapReduce核心思想
分布式的运算程序往往需要分成至少2个阶段
3.1 两个阶段
3.1.1 MapTask:对任务切分
例如:
1.将 1280M 的文件切分成10份
- 一个MapTask默认处理一个文件块大小的数据
2.10份文件并行计算,互不影响
- 按行读取数据
3.1.2 ReduceTask:对任务合并
依赖于MapReduce输出的 KV 数据进行输出
3.2 例子:
3.2.1 MapTask:对任务切分
任务:统计 a-p字母 和 q-z字母 出现次数
- 按行读取数据
- 根据空格切分行内单词
- 生成KV键值对( 单词 , 1)
- 根据任务分区(分区1:a-p 分区2:q-z),生成两个溢写文件
3.2.2 ReduceTask:对任务合并
- Reduce1:统计a-p字母出现次数;Reduce2:统计q-z字母出现次数
4、MapReduce进程
4.1 MrAppMaster
负责整个程序的过程调度及状态协调
- Yarn 中, Application Master 做为任务的老大,是 MrAppMaster的父类。
- 一个任务会有1个或者多个MrAppMaster (多个MrAppMaster : 串行执行,就是多个MR效率不高)
- 笼统意义上讲:一个job、一个任务、一个MR都是一回事
4.2 MapTask
负责Map阶段整个数据处理流程
JPS显示yarnchild进程
4.3 ReduceTask
负责Reduce阶段整个数据处理流程
JPS显示yarnchild进程
5、官方WordCount源码
5.1 路径
/opt/module/hadoop-3.1.3/share/hadoop/mapreduce
5.2 案例
hadoop-mapreduce-examples-3.1.3.jar
5.3 对Jar包反编译
里面有很多官方的MR案例
5.4 一个MR一般包含3个类
5.4.1 Driver驱动程序:主方法
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
if (otherArgs.length < 2) {
System.err.println("Usage: wordcount <in> [<in>...] <out>");
System.exit(2);
}
Job job = Job.getInstance(conf, "word count");
job.setJarByClass(WordCount.class);
job.setMapperClass(TokenizerMapper.class);
job.setCombinerClass(IntSumReducer.class);
job.setReducerClass(IntSumReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
for (int i = 0; i < otherArgs.length - 1; i++) {
FileInputFormat.addInputPath(job, new Path(otherArgs[i]));
}
FileOutputFormat.setOutputPath(job, new Path(otherArgs[(otherArgs.length - 1)]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
5.4.2 继承Mapper类
public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> {
private static final IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object key, Text value, Mapper<Object, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
this.word.set(itr.nextToken());
context.write(this.word, one);
}
}
}
- 继承Mapper类
- 思考泛型的类型是什么
四个参数分别是:输入K,输入V,输出K,输出V
5.4.3 继承Reduce类
public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
this.result.set(sum);
context.write(key, this.result);
}
}
6、常用数据序列化类型
Java类型 | HadoopWritable类型 |
---|---|
Boolean | BooleanWritable |
Byte | ByteWritable |
Int | IntWritable |
Float | FloatWritable |
Long | LongWritable |
Double | DoubleWritable |
String | Text(特殊,其他都是加Writable) |
Map | MapWritable |
Array | ArrayWritable |
Null | NullWritable |
7、MapReduce编程规范
7.1 Driver驱动程序:主方法
相当于Yarn的客户端,用于提交任务到yarn集群。提交的是封装了MapReduce程序相关运行参数的job对象。然后由yarn去执行我们写好的mapper、reduce,分别生成MapTask、ReduceTask。
7.2 继承Mapper类
- 用户自定义的Mapper要继承父类
- Mapper的输入数据是K,V对的形式
- 输入K:偏移量
- 输入V:值
例子:
数据 |
---|
a |
b b |
偏移量 |
---|
a |
0123 |
b b |
456789 |
偏移量从0开始
23、89是回车换行
输入 |
---|
(0,a) |
(4,b b) |
-
重写map()方法
- 编写业务逻辑
-
Mapper的输出数据是K,V对的形式
输出 |
---|
(a,1) |
(b,1) |
(b,1) |
- map()方法(MapTask进程)对每一个K,V调用一次
一行处理一次
7.3 继承Reduce类
- 用户自定义的Reducer要继承父类
- Reducer的输入数据是K,V对的形式,是Mapper的输出
- 重写reduce()方法
- 编写业务逻辑
- reduce()方法(ReduceTask进程)对每一组相同的K,V调用一次
输入 |
---|
(a,1) |
(b,1) |
(b,1) |
输出 |
---|
(a,(1)) |
(b,(1,1)) |
8、WordCount案例实操
8.1 需求分析
a、读取统计单词出现的次数
b、并按照key的首字母排序
8.1.1 Mapper
- 将MapTask传给我们的文本内容先转换成String
- 根据空格切分单词
- 将单词输出为 <单词,1>
8.1.2 Reducer
- 汇总各个key的个数
- 输出该key的总次数
8.1.3 Driver
- 获取配置信息,获取Job对象实例
- 指定Jar包所在的本地路径
- 后续会通过反射的方式定位出jar包
- 关联Mapper、Reducer
- 指定Mapper输出数据的k,v类型
- 指定最终输出数据的k,v类型
- Reducer并不是最终一步
- Reducer后还有Outputformat,Outputformat后的数据类型才是整个MapReduce计算完后的数据类型
- 有的MapReduce可能没有Reducer
- 指定输入文件所在目录
- 指定输出文件所在目录
- Job输出结果所在路径,不能提前存在
- 提交作业
8.2 本地测试
8.2.1 导入依赖
和 HDFS API 导入的依赖一致
1、hadoop-client 客户端
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.1.3</version>
</dependency>
a、导入Hadoop客户端依赖 3.1.3 (和服务器依赖一致)
b、相当于导入一个本地模式的Hadoop,子依赖含有hdfs、yarn、mr等
2、junit 单元测试
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
3、slf4j-log4j 打印日志
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
在Resource(一般放置静态文件,如:图片、音频和视频)中加入配置文件,修改slf4j日志级别为INFO
- INFO:INFO以及上
- INFO和 ERROR
- ERROR:ERROR以及上
- ERROR
8.2.2 Mapper
8.2.2.1 继承Mapper类
导包
-
import org.apache.hadoop.mapreduce.Mapper;
- 2.X 或者 3.X 版本
- MR只负责计算 -
import org.apache.hadoop.mapred.Mapper;
- 1.X 版本
- MR负责计算+资源调度
泛型
位置 | 内容 | 泛型 |
---|---|---|
KEYIN | 偏移量 | LongWritable |
VALUEIN | 一行数据 | Text |
KEYOUT | 单词 | Text |
VALUEOUT | 1 | IntWritable |
Text 导包要注意:
import org.apache.hadoop.io.Text;
Mapper源码
- 抽象类
- Context- 上下文对象
- 负责连接 Mapper、Reducer和系统整体代码之间的通讯
- 上下文对象
- 方法
- Run()- 先去执行setup()
- 在任务开始时调用一次
- 可以重写初始化方法,初始化连接代码
- 中间过程
- 循环遍历
- 执行map()
- 每一行被调用一次
- 一般都需要重写该方法
- 最后执行cleanup()
- 在任务结束后被调用一次
- 先去执行setup()
8.2.2.2 重写map()
普通版
对每一条数据都要new一个Text对象和一个IntWriterable对象
优化后
public class WordCountMapper extends Mapper<LongWritable , Text , Text , IntWritable> {
private Text outKey = new Text();
private IntWritable outValue = new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1、获取一行数据
String line = value.toString();
// 2、切割
String[] words = line.split(" ");
// 3、循环写出
for (String word : words) {
outKey.set(word);
context.write(outKey, outValue);
}
}
}
只会被new一个对象
8.2.3 Reducer
8.2.3.1 继承Reducer类
泛型
位置 | 内容 | 泛型 |
---|---|---|
KEYIN | 单词 | Text |
VALUEIN | 1 | IntWritable |
KEYOUT | 单词 | Text |
VALUEOUT | N | IntWritable |
Text 导包要注意:
import org.apache.hadoop.io.Text;
Reducer源码
- 方法
- Run()- 和Mapper一致
- 先去执行setup()
- 在任务开始时调用一次
- 可以重写初始化方法,初始化连接代码
- 中间过程
- 循环遍历
- 执行reduce()
- 每一行被调用一次
- 一般都需要重写该方法
- 最后执行cleanup()
- 在任务结束后被调用一次
8.2.3.2 重写reduce()
public class WordCountReducer extends Reducer<Text, IntWritable,Text, IntWritable> {
private IntWritable outV = new IntWritable();
private int sum;
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
sum = 0;
// 累加
for (IntWritable value : values) {
sum += value.get();
}
outV.set(sum);
// 写出
context.write(key,outV);
}
}
8.2.4 Driver
public class WordCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1、获取配置信息,获取Job对象实例
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2、指定Jar包所在的本地路径 通过全类名反射Jar包在什么位置
job.setJarByClass(WordCountDriver.class);
// 3、关联Mapper、Reducer
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 4、指定Mapper输出数据的k,v类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5、指定最终输出数据的k,v类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6、指定输入文件所在目录
FileInputFormat.setInputPaths(job,new Path("D:\\input\\hello.txt"));
// 7、指定输出文件所在目录
FileOutputFormat.setOutputPath(job,new Path("D:\\hadoop\\output1"));
// 8、提交作业
boolean result = job.waitForCompletion(true);
//成功返回0
System.exit(result;0;1);
}
}
8.2.5 本地测试
测试结果
-
前两个文件
crc循环冗余校验 -
第三个文件
成功标志 -
最后一个文件
atguigu 2
banzhang 1
cls 2
hadoop 1
jiao 1
ss 2
xue 1
hadoop默认会把数据按首字母排序
后续讲shuffle会提到
debug调试
-
第一个指:
跳过当前行,向下走一行 -
第二个指:
进入方法里面 -
第三个指:
强制进入更底层的方法里面 -
第四个指:
退出方法 -
最左边绿色指:
快速进入下一个断点
8.3 提交到集群测试
将本地的代码发送的虚拟机上跑
8.3.1 打包
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<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>
maven插件3.6.1
jdk 1.8
将依赖的jar打包进来
8.3.2 修改 driver
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1、获取配置信息,获取Job对象实例
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2、指定Jar包所在的本地路径 通过全类名反射Jar包在什么位置
job.setJarByClass(WordCountDriver.class);
// 3、关联Mapper、Reducer
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 4、指定Mapper输出数据的k,v类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5、指定最终输出数据的k,v类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6、指定输入文件所在目录
FileInputFormat.setInputPaths(job,new Path(args[0]));
// 7、指定输出文件所在目录
FileOutputFormat.setOutputPath(job,new Path(args[1]));
// 8、提交作业
boolean result = job.waitForCompletion(true);
//成功返回0
System.exit(result?0:1);
}
- args[0]
- jar包第一个参数
- args[1]
- jar包第二个参数
8.3.3 上传集群
因为集群已经有相应的依赖了,只需要选择较小的jar包
命令
hadoop jar wc.jar com.atguigu.mapreduce.workcount2.WordCountDriver /input /output
选择主方法的全类名 (idea -> copy reference)
参数1 + 参数 2