MapReduce入门
1. MapReduce计算模型介绍
1.1 MapReduce思想
MapReduce的思想核心是分而治之
, 适用于大量复杂的任务处理场景(大规模数据处理场景) .
没有依赖关系 可以拆分的复杂任务适用之。
- 分 局部处理阶段
- 合 全局汇总阶段
Map负责"分"
, 将没有依赖关系
的数据进行适当的拆分 , 并行计算
Reduce负责"合"
, 即对map阶段的结果进行全局的汇总
1.2 Hadoop MapReduce设计构思
MapReduce 是一个分布式运算程序的编程框架
,核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序
,并发运行在Hadoop 集群上。
Hadoop MapReduce 构思体现在如下的三个方面:
-
如何对付大数据处理 : 分而治之
-
构建抽象模型 : Map和Reduce
MapReduce用Map和Reduce两个函数提供了高层的并行编程抽象模型
Map : 对一组数据元素进行某种重复式的处理 ;
Reduce : 对Map的中间结果进行某种进一步的结果整理 .
map : in(k1,v1)–>out(k2,v2)
reduce : in(k2,v2)–>out(k3,v3)
由上可以看出MapReduce 处理的数据类型是<key,value>键值对
- 统一构架 , 隐藏系统层细节
程序员仅需要关心其应用层的具体计算问题,仅需编写少量的处理应用本身计算问题的程序代码 , 系统层的细节交给计算框架去处理 .
1.3 MapReduce框架结构
一个完整的MapReduce程序在分布式运行时有三类实例进行 :
- MRAppMaster : 负责整个程序的过程调度及状态协调
- MapTask : 负责map阶段的整个数据处理流程
- ReduceTask : 负责reduce阶段的整个数据处理流程
2 . MapReduce编程规范及示例编写
2.1 编程规范
(1) 用户编写的程序分成三个部分:Mapper
,Reducer
,Driver
(提交运行 mr 程序的客户端)
(2)Mapper 的输入数据是 KV 对的形式(KV 的类型可自定义)
(3)Mapper 的输出数据是 KV 对的形式(KV 的类型可自定义)
(4)Mapper 中的业务逻辑写在 map()方法中
(5)map()方法(maptask 进程)对每一个<K,V>调用一次
(6)Reducer 的输入数据类型对应 Mapper 的输出数据类型,也是 KV
(7)Reducer 的业务逻辑写在 reduce()方法中
(8)Reducetask 进程对每一组相同 k 的<k,v>组调用一次 reduce()方法
(9)用户自定义的 Mapper 和 Reducer 都要继承各自的父类
(10)整个程序需要一个 Drvier 来进行提交,提交的是一个描述了各种必要信息的 job 对象
-
代码层面
类1 继承Mapper 负责map阶段业务逻辑编写 类2 继承Reducer 负责reduce阶段业务逻辑编写 类3 运行时主类 main 参数组成
-
运行层面
类1---->maptask 类2---->reducetask 类3---->Driver
注意:在MR编程中数据都是以<key,value>键值对形式存在的。
2.2 WordCount示例编写
pom文件
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>2.7.4</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>com.itck.mr.wc.mapper.WordCountDriver</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>
主函数在编写玩之后添加 , 添加形式为主函数的全路径
- 编写WordCountMapper.java
继承Mapper
KEYIN 表示map输入中key的类型 在默认行为下是 String VALUEIN 表示map输入中value的类型 在默认行为下是 String KEYOUT 表示map输出中key的类型 在本需求中 是单词 String VALUEOUT 表示map输出中value的类型 在本需求中 是单词次数1 int
通过本身的TextInputStream进行一行一行的读取 , 将该行的起始偏移量作为k , 改行的内容作为value , 组成键值对
继承Mapper , 同时用hadoop自带的序列化机制 Writable
- Long---->LongWritable
- int----->intWritable
- String–>Text
- null---->nullWritable
重写map
4.1 拿到一行转成String ==> hello qing hadoop
4.2 按照分隔符切割这一行内容==> [hello,qing,hadoop]
4.3 遍历单词数组 单词出现就标记1
使用框架context把数据写出去
context.write(new Text(word), new IntWritable(1));==><hello,1><qing,1><hadoop,1>
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;
/**
* @author CoderCK
* @Title: com.itck.mr.wc.mapper
* @ProjectName example-mr
* @Description: TODO 本类就是mr程序map阶段程序运行所在的类 也就是maptask
* KEYIN 表示map输入中key的类型 在默认行为下是 String
* mr默认读取数据行为:TextInputFormat 一行一行读取文件
* 读取这一行的时候 把这一行起始偏移量作为k 这一行内容作为v 构成了输入的ky键值对
*
* KEYOUT 表示map输出中key的类型 在本需求中 是单词 String
* VALUEOUT 表示map输出中value的类型 在本需求中 是单词次数1 int
*
* long string 等类型是java自带的类型 在序列化传递的时候 hadoop认为其序列化机制效率不高 垃圾
* 因此hadoop自己封装了一套序列化机制 Writable
*
* Long---->LongWritable
* int----->intWritable
* String-->Text
* null---->nullWritable
* @create 2018/11/15 21:04
**/
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
/**
* 该方法就是map阶段具体业务逻辑编写的地方
* 该方法调用的次数跟读取数据组件相关 : TextInputFormat读取一行数据-->封装成一个kv对-->调用一次map方法
*
* <0,hello king qing hadoop>
* <24,hi boy hadoop king>
**/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//拿到一行转成String
String line = value.toString();//hello king qing hadoop
//按照分隔符切割这一行内容
String[] words = line.split(" ");//[hello,king,qing,hadoop]
//遍历单词数组 单词出现就标记1
for (String word : words) {
//使用框架context 把数据写出去
context.write(new Text(word),new IntWritable(1));//<hello,1><king,1><qing,1><hadoop,1>
}
}
}
- 编写WordCountReducer
继承Reducer
KEYIN 表示reduce阶段输入的kv中key类型 也就是map输出中k的类型 在本需求中 就是单词 Text VALUEIN 表示reduce阶段输入的kv中value类型 也就是map输出中v的类型 在本需求中 就是单词的次数1 intwritable KEYOUT 表示reduce阶段输出的kv中key类型 本需求中 是单词 Text VALUEOUT 表示reduce阶段输出的kv中value类型 本需求中 是单词的总次数 intwritable
传递到Reducer的数据类型为
<hello,1> <hadoop,1> <allen,1> <hello,1> <hello,1> <hadoop,1> <allen,1> <hello,1>
此时reduce把接收到的所有kv数据按照
k的字典序(a-z)
排序==><allen,1><allen,1><hadoop,1><hadoop,1><hello,1><hello,1><hello,1><hello,1>把key相同的键值对组成一组 , 形成新的kv对 , 然后调用一次reduce方法
==><allen,1><allen,1>
<hadoop,1><hadoop,1>
<hello,1><hello,1><hello,1><hello,1>
在新的kv对中 , k就是这一组共同的k , v就是这一组所有v组成的一个迭代器
<allen,1><allen,1>===><allen,[1,1]>
<hadoop,1><hadoop,1>===><hadoop,[1,1]>
<hello,1><hello,1><hello,1><hello,1>===><hello,[1,1,1,1]>
reduce方法调用的次数取决于数据可以分为多少组 , 因为相同会作为一组调用
定义一个计数的变量 , 初始值为0 , 用于记录每个数据共出现多少次
int count=0
遍历values的迭代器 , 用于记录单词出现的总次数
count+=value.get()
将结果写出
context.write(key,new IntWritable(count));
==><allen,2>
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* @author CoderCK
* @Title: com.itck.mr.wc.mapper
* @ProjectName example-mr
* @Description: TODO 该类就是mr程序 reduce阶段运行的类 也是就reducetask
*
* KEYIN 表示reduce阶段输入的kv中key类型 也就是map输出中k的类型 在本需求中 就是单词 Text
* VALUEIN 表示reduce阶段输入的kv中value类型 也就是map输出中v的类型 在本需求中 就是单词的次数1 intwritable
*
* KEYOUT 表示reduce阶段输出的kv中key类型 本需求中 是单词 Text
* VALUEOUT 表示reduce阶段输出的kv中value类型 本需求中 是单词的总次数 intwritable
* @create 2018/11/15 21:28
**/
public class WordCountReducer extends Reducer<Text,IntWritable,Text,IntWritable> {
/**
* 本方法就是reduce阶段具体业务逻辑所在的地方
* <hello,1> <hadoop,1> <allen,1> <hello,1>
* <hello,1> <hadoop,1> <allen,1> <hello,1>
*
* 1、reduce把接受的所有kv数据按照k的字典序(a-z)排序
* <allen,1><allen,1><hadoop,1><hadoop,1><hello,1><hello,1><hello,1><hello,1>
* 2、把key相同的键值对组成一组 形成新的kv对 然后调用一次reduce方法
* <allen,1><allen,1>
* <hadoop,1><hadoop,1>
* <hello,1><hello,1><hello,1><hello,1>
* 在新的kv对中,k就是这一组共同的k v就是这一组所有v组成的一个迭代器
* <allen,1><allen,1>---------> <allen,[1,1]>
* <hadoop,1><hadoop,1>-------> <hadoop,[1,1]>
* <hello,1><hello,1><hello,1><hello,1>---> <hello,[1.1.1.1]>
*
* Q:reduce方法会被调用多少次?
* 取决于数据可以被分为多少组 因为相同会作为一组调用
**/
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//<allen,[1,1]>
int count = 0;
//遍历values的迭代器
for (IntWritable value : values) {
count+=value.get();
}
//把结果写出
context.write(key,new IntWritable(count));
}
}
- 编写主类WordCountDriver.java
首先创建Job用于获取
- 设置mr本地执行
conf.set("mapreduce.framework.name","local")
, 默认是hdfs执行 - 执行本次mr程序运行的主类是
job.setJarByClass(WordCountDriver.class);
- 指定本次mr程序map reduce类是什么
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
- 指定map阶段输出的数据类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
- 指定reduce阶段输出的数据类型 也就是mr最终的输出
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
- 指定mr程序输入 输出的数据位置
FileInputFormat.setInputPaths(job,"/wordcount/input")
FileOutputFormat.setOutputPath(job,new Path("/wordcount/output"));
- job提交
boolean b = job.waitForCompletion(true);
- 程序退出
System.exit(b?0:1);
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;
/**
* @author CoderCK
* @Title: com.itck.mr.wc.mapper
* @ProjectName example-mr
* @Description: TODO 本类就是mr程序运行的主类 主要用于各个组件的具体指定 比如map是什么类 reduce 是什么类 输入输出数据类型是啥 输出输入数据在哪
* @create 2018/11/15 21:53
**/
public class WordCountDriver {
public static void main(String[] args) throws Exception{
Configuration conf = new Configuration();
//设置mr本地执行
conf.set("mapreduce.framework.name","local");
Job job = Job.getInstance(conf);
//指定本次mr程序运行的主类是
job.setJarByClass(WordCountDriver.class);
//指定本次mr程序map reduce类是什么
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//指定map阶段输出的数据类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//指定reduce阶段输出的数据类型 也就是mr最终的输出
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//指定mr程序输入 输出的数据位置 , 此处是在hdfs上进行的
FileInputFormat.setInputPaths(job,("/wordcount/input"));
FileOutputFormat.setOutputPath(job,new Path("/wordcount/output"));
//指定mr程序输入 输出的数据位置 , 在window本地上执行
FileInputFormat.setInputPaths(job,new Path("J:\\bigdatatest\\input"));
FileOutputFormat.setOutputPath(job,new Path("J:\\bigdatatest\\output"));
//job提交
boolean b = job.waitForCompletion(true);
//程序退出
System.exit(b?0:1);
}
}
3. MapReduce 程序运行模式
3.1 集群运行模式
(1)将 mapreduce 程序提交给 yarn 集群,分发到很多的节点上并发执行
(2)处理的数据和输出结果应该位于 hdfs 文件系统
(3)提交集群的实现步骤:
将程序打成 JAR 包,然后在集群的任意一个节点上用 hadoop 命令启动
hadoop jar example-mr-1.0-SNAPSHOT.jar 即可实现程序的运行 , 等待程序运行完成即可查看结果
mr的输出目录 默认不需要提前创建
3.2 本地运行模式
(1)mapreduce 程序是被提交给 LocalJobRunner 在本地以单进程的形式运行
(2)而处理的数据及输出结果可以在本地文件系统,也可以在 hdfs 上
(3)怎样实现本地运行?写一个程序,不要带集群的配置文件
本质是程序的 conf 中是否有 mapreduce.framework.name=local
即conf.set("mapreduce.framework.name","local");
以及
yarn.resourcemanager.hostname 参数即
FileInputFormat.setInputPaths(job,new Path("J:\\bigdatatest\\input"));
FileOutputFormat.setOutputPath(job,new Path("J:\\bigdatatest\\output"));
(4)本地模式非常便于进行业务逻辑的 debug,只要在 IDEA 中打断点即可
mr的输出目录 默认不需要提前创建