一、MapReduce的概念
MapReduce概念:是分布式并行离线计算框架,是分布式运算程序的编程模型,是用户基于hadoop的数据分析应用的核心框架
核心功能:是将用户编写的自定义业务逻辑和框架自带的各组件整合成一个完整的分布式运算程序,并发运行在hadoop集群之上
MapReduce与HDFS的解决问题的原理是相似的,HDFS将大的文件分成若干个小的文件,然后将他们分别存储在集群中的各节点中。同样的原理,MapReduce是将一个复杂的运算切分成若干个子运算,然后将他们分别提交给集群中各节点,由各节点并行运算。
背景:
- 海量的数据在单机上处理会由于资源的限制,无法胜任
- 一旦将单节点扩展到集群中运行时,将极大增加程序的复杂度和开发难度
- 引入MapReduce框架后,开发人员可以将绝大部分工作集中在业务逻辑的开发上,而将分布式计算中的复杂性交由框架来处理
二、Job提交流程
- 客户端提交任务到RM
- RM生成jodID、Path,并将job加入到调度队列,并返回客户端Path
- 客户端将资源(jar、配置文件等)上传到HDFS上,返回RM上传成功的信息
- RM分配一个资源来启动appMaster
- NM到HDFS上拉取计算资源
- 拉取成功后,向客户端询问启动脚本
- 返回启动命令并通知NM启动appMaster
- NM启动appMaster
- appMaster向RM申请资源来运行MapTask
- RM分配资源(20个资源)
- NM去拉取计算资源
- 拉取成功后向AppMaster询问启动脚本
- appMaster发送启动脚本并通知NM启动程序
- 启动任务
- appMaster监控任务的执行,当有一个MapTask任务完成后,就可以调度reduceTask
- appMaster调度reduceTask,过程循环9-14
- appMaster监控任务的结束,向RM返回任务执行成功的结果并通知RM可以回收资源
- RM通知NM回收资源
三、mapreduce关键名词
job:
用户的每一个计算强求成为一个作业
task:
每一个作业都需要分拆,交由多个主机来完成,拆分出来的执行单元就叫任务
task分为三种类型:
1、MAP:负责map阶段的整个数据处理流程
2、REDUCE:负责reduce阶段的整个数据处理流程
3、MrAppMster:负责整个程序的过程调度以及状态协调
四、MapReduce的运行流程
- 一个mr程序启动的时候,最先启动的是MrAppMaster。MrAppMaster启动根据本地job的描述信息计算出需要的maptask的数量,向集群申请相应的数量的maptask程序,
- maptask启动之后,根据给定的数据切片的范围来进行数据处理。主体流程:
(1)利用客户指定的inputformat来获取一个recordReader读取数据,形成key-value键值对
(2)将key-value键值对数据传递给用户自定义的map方法,做逻辑运算,并将map方法输出的数据key-value收集到缓存中
(3)将缓存中的key-value键值对数据按照k分区排序后不断溢写到磁盘中 - MrAppMaster监控所有的maptask的运行情况,当maptask中一个或者全部任务完成后会根据用户的设置来启动相应的reduceTask进程,并告知task要处理的数据范围
- reducetask启动后,根据MrAppMaster告知待处理数据所在位置,从若干台maptask运行机器上获取到若干个maptask的输出文件,并在本地进行重新的归并排序,然后按照向提供key的key-value键值对数据分为一组,调用用户自定义的redue方法进行逻辑运算,并收集运算输出的结果key-value键值对,然后调用用户指定的outputformate将结果输出到外部存储中
五、编写MapReduce
- 用户编写的程序分为3个部分:Mapper、Reduce、Driver(提交mr程序的客户端)
- Mapper的输入数据时key-value键值对形式(key-value类型是可以自定义的)
- Mapper的输出数据时key-value键值对形式(key-value类型是可以自定义的)
- Mapper中的业务逻辑都写在map方法中
- map方法(maptask进程读取出来的)对每一个key-value键值对数
据都调用一次 - reduce的输入数据类型对应Mapper输出数据类型
- reduce的业务逻辑编写在reduce方法中
- reduce方法由reduceTask调用,reduceTask进行对每一组相同key的key-value键值对数据调用一个reduce方法
- 用户自定义的Mapper和Reducer都要继承自各自的父类
- 整个程序需要一个Driver类来进行任务的提交 ,提交的是一个描述了必要信息的job对象
六、统计单词的个数
需求:统计海量文本文件中,每个单词出现的频率
package com.qfedu.bigdata.mr;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
/**
* public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
* 框架在调用咱们自己写的map业务方法时,会将数据作为参数(一个key一个value)传递给map方法
* KEYIN:是框架mapTask要传递给map方法的输入参数中的key的类型
* VALUEIN:是框架mapTask要传递给map方法的输入参数中的value的类型
*
* 在默认情况下,框架传入的key是框架从待处理数据中读取的某一行的起始偏移量,所以类型为Long
* 框架传入的value是从待处理数据中读取到的某一行数据的内容,所以类型String
*
* 但是,Long还是String都是java原生的数据类型,序列化的效率比较低,所以hadoop对其进行了封装,有替代品:
* LongWritable/Text
*
* map方法处理完成数据后需要返回结果,返回的结果也是KV(一个key一个value)
* KEYOUT:是我们自定义的map逻辑处理完成数据后返回的结果中的key的类型
* VALUEOUT:是我们自定义的map逻辑处理完成数据后返回的结果中的value的类型
*
* 自定义的map方法的调用规律:maptask每读取一行数据就会调用一次map方法
*
*/
public class wordCount {
static class wordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable>{
private Text k = new Text();
private IntWritable v = 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、循环单词,输出数量1
for (String word:words) {
k.set(word);
context.write(k,v);
}
}
}
/**
* public class Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
* reduce方法要接收的输入参数是KV键值对,一个key一个value的迭代器
* KEYIN:是框架要传递给reduce方法的输入参数中的key的类型 --对应map方法中的输出的key的类型
* VALUEIN:是框架要传递给reduce方法的输入参数中的value的类型 --对应map方法中的输出的value的类型
*
* KEYOUT:reduce方法处理完成数据后返回的结果中key的数据类型
* VALUEOUT:reduce方法处理完成数据后返回的结果中value的数据类型
*
* reduce的调用规律:框架会从map阶段的输出结果中挑出所有key相同的kv键值对数据组成一组,调用一次reduce方法
*/
static class wordCountReducer extends Reducer<Text,IntWritable,Text,IntWritable>{
private IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//flume,1
//hadoop,1
//hbase,1
//hdfs,1
//hello,1
//hello,1
//hello,1
//hive,1
//<hello,[1,1,1,1,1,1,1,1]>
int count=0; //累计单词出现的次数
//遍历迭代器,累计数据
for (IntWritable cnt:values) {
count+=cnt.get();
}
v.set(count);
context.write(key,v);
}
}
/**
* 相当于一个yarn集群的客户端
* 需要在此封装我们的mr程序的相关运行参数,指定jar包,最后提交给yarn
* @param args
*/
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//配置hdfs的客户端访问集群
Configuration conf = new Configuration();
conf.set("fs.defaultFS","hdfs://host01:9000"); //若改为本地运行,则应注释这一行,
//并且修改FileInputFormat.setInputPaths和FileOutputFormat.setOutputPath的Path内容
//获取Job对象
Job job = Job.getInstance(conf, "wordcount");
//指定本job的jar包所在的路径
job.setJarByClass(wordCount.class);
//指定本业务要使用的Mapper业务类
job.setMapperClass(wordCountMapper.class);
//指定mapper类的输出类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//指定本业务要使用的Reducer类
job.setReducerClass(wordCountReducer.class);
//指定job的最终输出的数据的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//指定job的输入数据的所在目录
FileInputFormat.setInputPaths(job,new Path(args[0]));
//指定job的输出结果的存放目录
FileOutputFormat.setOutputPath(job,new Path(args[1]));
//将job提交给yarn执行
// job.submit(); // 提交后不能监控任务的运行情况
//提交后需要能查看job的运行状态
boolean b = job.waitForCompletion(true);
System.exit(b?0:1);
}
}
maven打包后提交到Linux客户端的hdfs
①创建一个用于测试的文件,并上传到hdfs
启动hdfs
vi word.dat
hdfs dfs -mkdir -p /wordcount/input
② hdfs dfs -put word.dat /wordcount/input/ /wordcount/output/
hadoop jar 包名 程序的包名+类名 第一个参数(输入数据所在目录) 第二个参数(输出数据所在的参数)