文章目录
前言
hadoop由四部分组成:hdfs(分布式文件系统),MapReduce(一个分布式的离线并行计算程序框架框架),yarn(作业调度与资源管理平台),common(支持其他模块的工具模块,Configuration、RPC、序列化机制、日志操作)
本文是对MapReduce的入门讲解以及一个入门案例的实现
一、MapReduce的定义
MapReduce是一个分布式的分布式运算程序的编程框架,是用户开发“基于hadoop的数据分析应用”的核心框架。
现在公司大多使用spark,Spark是借鉴了MapReduce发展起来的,它优化了MapReduce的缺点,继承了MapReduce的有有点,所以MapReduce的思想是非常重要要的。
MapReduce的核心功能是将开发者自己编写的逻辑代码和框架中的默认组件整合成一个完整的分布式运算程序,并发运行在一个hadoop集群上。
框架是一个半成品,我们需要将自己的业务逻辑代码和框架融合形成一个完整的运算程序。
二、MapReduce的优缺点
1.优点
- 易于编程
- 良好的扩展性
- 高容错性
- 适合PB量级以上的大量数据离线处理处理
2.缺点
MapReduce不适合做实时计算,流式计算,DAG(有向图)计算。
三、MapReduce的核心编程思想
- MapReduce运算程序一般分为两个阶段:map阶段、reduce阶段。
- 第一阶段(map阶段)的map task并发实例,完全并行运行,互不相干。
- 第二阶段(reduce阶段)的reduce task实例也是并行的,互不相干,但是他们依赖于第一阶段的map task的输出。
- 一个MapReduce程序只能包含一个map阶段、一个reduce阶段,如果开发者的业务逻辑复杂,可以编写多个mr程序,串行运行。
一个完整的MapReduce程序在分布式运行时有三类实例进程:
- MrAppMaster:负责整个程序的过程调度及状态协调。
- MapTask:负责map阶段的整个数据处理流程。
- ReduceTask:负责reduce的短的整个数据处理流程。
四、MapReduce编程规范(八股文)
用户编写的程序分为三个部分:Mapper(用于生成maptask),Reduce(用于生成reduce task),Driver(用于将Mapper和Reduce组装起来提交到yarn运行)。
1.Mapper阶段
这个阶段框架会帮我们把数据通过kv对的方式读取出来,再将我们处理过的数据以kv对的方式传递给reduce。
(1)用户自定义的Mapper要继承父类
(2)Mapper的输入数据是以kv对的方式(根据文本数据自定义数据类型)
(3)重写map()方法实现自己的业务 逻辑
(4)Mapper的输出数据是以kv对的形式(根据自己的业务逻辑定义数据类型)
(5)map()方法(maptask进程)对每一个<K,V>调用一次
2.Reduce阶段
这个阶段接收来自mapper输出的kv对数据,数据经过处理后再以kv对的方式存储到文件。
(1)用户自定义的Reduce需要继承父类
(2)Reduce的输入数据类型与Mapper的输出数据类型对应
(3)reduce()方法中写自己的业务逻辑代码
(4)处理后的数据以kv对的方式存储到文件中(数据类型根据自己的业务逻辑自定义)
(5)Reducetask进程对每一组相同k的<k,v>组调用一次reduce()方法
3.Driver阶段
相当于yarn集群的客户端,用于提交我们写好的整个程序到yarn集群,提交的是封装了MapReduce程序相关运行参数的job对象。然后由yarn去执行我们写好的map和reduce,分别生成maptask和reduce task。
五、WordCount案例实操
1.需求
在给定的文本文件中统计输出每一个单词出现的总次数,注意,这里我们先不把结果分别放到两个文件里面,先放到一个文件里面。
2.数据准备
准备一个存储单词的文本,如图
3.分析
按照mapreduce编程规范,分别编写Mapper,Reducer,Driver,如图所示:
4. 代码实现
(1)导入相应的依赖坐标+日志添加
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.7.2</version>
</dependency>
</dependencies>
(2)在项目的src/main/resources目录下,新建一个文件,命名为“log4j.properties”,在文件中填入:
log4j.rootLogger=debug, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n log4j.appender.logfile=org.apache.log4j.FileAppender log4j.appender.logfile.File=target/spring.log log4j.appender.logfile.layout=org.apache.log4j.PatternLayout log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n |
(3)mr程序编写
1)编写mapper类
package com.bigdata.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;
// KEYIN, VALUEIN 输入数据的key、value的类型 有默认的
//KEYIN 输入数据key的类型 每行行首的偏移量 是个数字,用什么表示?不用int 不用long,用LongWritable表示,也不能用Intwritable
//VALUEIN 输入数据的value的类型 每行的内容,即字符串,用什么表示?不用String ,用Text表示
// KEYOUT, VALUEOUT 输出数据的key、value的类型 取决于自己的业务需要,处理成什么,自己说了算
// KEYOUT 输出数据的key的类型,这里我们想要输出单词,即字符串,用Text表示
// VALUEOUT 数据数据value的类型,这里想要输出单词出现的次数,即1,用Intwritable表表示
public class WordcountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
@Override // 重写Mapper类的方法,将自己的业务逻辑写在这里 框架会帮助我们读取数据,每读取一行,将每行行首的偏移量封装进key
//将每行的内容封装到value,调用一次map方法,我们在map方法里面实现对key、value的处理
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 按行读取数据,将其转换成字符串 hello world
String lineOffset = key.toString();
System.out.println("行首偏移量:"+lineOffset);
String line = value.toString();
// 2 按照空格切割 [hello,world]
String[] words = line.split(" ");
// 3 遍历切割后的数组,组装键值对
for (String word : words) {
Text k = new Text();
k.set(word);
IntWritable v = new IntWritable(1);
// 4 将键值对kv写出去
context.write(k,v);
}
}
}
2)编写reducer类
package com.bigdata.wordcount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
// KEYIN 输入数据key的类型,这里跟mapper的keyout对应 单词的类型 即Text
// VALUEIN 输出数据value的类型,这里跟mapper的valueout对应, 单词的个数,即IntWritable
// KEYOUT 输出数据key的类型,这里取决于对输入数据处理后的结果,我们输出key是单词,用Text表示
// VALUEOUT 输出数据value的类型,这里取决于对输入数据处理后的结果,我们输出value是单词出现的总次数,用IntWritable表示
// <hello,1>
// <world,1>
// <oozie,1>
// <bigdata,1>
// <hadoop,1>
// <hadoop,1>
public class WordcountReduce extends Reducer<Text,IntWritable,Text,IntWritable> {
@Override// 在这里重写父类的方法,把自己在reduce阶段的业务逻辑写在这里
//reduce称之为分组调用,根据key进行分组,只要key相同,则这些kv对就会被调用进一次reduce方法
// 这些相同key的kv们的k被封装进了key,那些v们被封装进了迭代器values
// 我们要把values里面v们取出来进行累加
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// <hadoop,1>
// <hadoop,1>
// 定义单词出现的总次数
int sum = 0;
for (IntWritable value : values) {
int i = value.get();
sum = sum + i;
}
//组装<单词,总次数> 键值对
IntWritable v = new IntWritable(sum);
// 将键值对写出
context.write(key,v);
}
}
3)编写驱动类
package com.bigdata.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任务,并且将任务提交给yarn
public class WordcountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 1 设置jar包位置
job.setJarByClass(WordcountDriver.class);
// 2 设置mapper类,reduce类'
job.setMapperClass(WordcountMapper.class);
job.setReducerClass(WordcountReduce.class);
// 3 设置mapper的keyout,valueout
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 4 设置最终输出的keyout,valueout
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 5 设置输入,输出路径
FileInputFormat.setInputPaths(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job,new Path(args[1]));
// 5 提交job到yarn集群
boolean b = job.waitForCompletion(true);
System.out.println("是否成功:"+b);
}
}
5.本地测试
(1)在windows环境上配置HADOOP_HOME环境变量
(2)在eclipse/idea上运行程序
6.集群上测试
(1)将程序打成jar包,然后拷贝到hadoop集群中
(2)启动hadoop集群
(3)执行wordcount程序
[hadoop@hadoop003 software]$ hadoop jar wc.jar com.bigdata.wordcount.WordcountDriver /user/hadoop/input /user/bigdata/output1
总结
理解MapReduce的核心编程思想以及mr程序的编程规范,熟练MapReduce编程框架。