MapReduce
简介
MapReduce 是一个分布式运算程序的编程框架,是用户开发“基于 Hadoop 的数据分析应用”的核心框架。
优点
1)MapReduce 易于编程
它简单的实现一些接口,就可以完成一个分布式程序,这个分布式程序可以分布到大量廉价的 PC 机器上运行。也就是说你写一个分布式程序,跟写一个简单的串行程序是一模一样的。就是因为这个特点使得 MapReduce 编程变得非常流行。
2)良好的扩展性
当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力。
3)高容错性
MapReduce 设计的初衷就是使程序能够部署在廉价的 PC 机器上,这就要求它具有很高的容错性。比如其中一台机器挂了,它可以把上面的计算任务转移到另外一个节点上运行,不至于这个任务运行失败,而且这个过程不需要人工参与,而完全是由 Hadoop 内部完成的。
4)适合 PB 级以上海量数据的离线处理
可以实现上千台服务器集群并发工作,提供数据处理能力。
缺点
1)不擅长实时计算
MapReduce 无法像 MySQL 一样,在毫秒或者秒级内返回结果。
2)不擅长流式计算
流式计算的输入数据是动态的,而 MapReduce 的输入数据集是静态的,不能动态变化这是因为 MapReduce 自身的设计特点决定了数据源必须是静态的。
3)不擅长 DAG(有向无环图)计算
多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下MapReduce并不是不能做,而是使用后,每个 MapReduce 作业的输出结果都会写入到磁盘,会造成大量的磁盘 IO,导致性能非常的低下。
架构
MapReduce过程
工作流程一:
工作流程二:
一个完整的mapreduce程序在分布式运行时有三类实例进程:
1)MrAppMaster:负责整个程序的过程调度及状态协调
2)MapTask:负责map阶段的整个数据处理流程
3)ReduceTask:负责reduce阶段的整个数据处理流程
工作全流程详解:
上面图一和图二中的流程是整个MapReduce最全工作流程,主要包括MapTask阶段、Shuffle阶段和ReduceTask阶段,而Shuffle阶段和MapTask阶段、ReduceTask阶段都存在交集,具体流程如下:
1.准备好待处理的文本
2.客户端submit()前,获取待处理数据的信息,然后根据参数配置形成一个任务分配的规划
3.客户端向Yarn集群提出请求创建Mr appmaster并提交切片等相关信息:job.split、wc.jar(集群模式才需要)、job.xml
4.Yarn调用ResourceManager来创建Mr appmaster,而Mr appmaster则会根据切片的个数来创建几个Map Task。于是,MapTask进程开始工作。
5.MapTask们从文件中读取各自需要处理的数据,默认是TextInputFormat格式(可以自定义)。其实是里面的RecorderReader来读取,每读取一行之后返回给Mapper
6.在Mapper中调用map()方法来对每一行数据进行相关的业务上的逻辑运算处理
7.在用户编写map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。而在该函数内部,它会将生成的key/value分区(调用Partitioner),并写入一个环形内存缓冲区中(环形缓冲区默认大小是100M,数据处理的特点:左侧写索引或者是元数据信息,右侧写数据,写满80%后溢写到磁盘,然后反向又开始写,如此反复)。自数据进入到环形缓冲区后,Shuffle过程正式开始。
8.进入到环形缓冲区之后在溢写之前会对数据进行一次排序。排序的方式是,先按照分区编号Partition进行排序,然后按照key进行排序。这样,经过排序后,数据以分区为单位聚集在一起,且同一分区内所有数据按照key有序。
9.排完序之后就溢出到文件(分区且区内有序),整个过程会多次溢出到多个文件
10.在所有数据都溢出到文件之后,开始Merge归并排序(对同一个分区内溢出的多个有序的结果文件合并成一个大的溢出文件且完成归并排序)
11.之后的Combiner合并为可选流程:分区内合并和压缩。之后,写入到磁盘。至此MapTask的执行过程基本结束。
12.在所有Map Task任务都完成之后,根据分区的数量来启动相应数量的Reduce Task,并告知ReduceTask处理数据范围(数据分区)(有几个分区就启动几个Reduce Task,每个Reduce Task专门处理同一个分区的数据,比如处理MapTask1中partition0和MapTask2中partition0的数据)
13.ReduceTask根据自己的分区号,去各个MapTask机器上拷贝相应分区内的数据到本地内存缓冲区,缓冲区不够的话就溢写到磁盘。待所有数据拷贝完毕之后,ReduceTask会将这些文件再进行归并排序
14.排好序之后按照相同的key分组。至此Shuffle的过程基本结束。
15.在分组之后一次读取一组数据到Reducer,调用reduce()方法进行聚合处理
16.之后通过context.write默认以TextOutputFormat格式经RecordWriter下入到文件。最后,ReduceTask过程结束。
注意:
Shuffle中的缓冲区大小会影响到MapReduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快。缓冲区的大小可以通过参数调整,参数:io.sort.mb默认100M
数据类型
序列化和反序列化
序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储到磁
盘(持久化)和网络传输。
反序列化就是将收到字节序列(或其他数据传输协议)或者是磁盘的持久化数据,转换
成内存中的对象。
具体实现 bean 对象序列化步骤如下 7 步。
(1)必须实现 Writable 接口
(2)反序列化时,需要反射调用空参构造函数,所以必须有空参构造
public FlowBean()implements Writable {
super();
}
(3)重写序列化方法
@Override
public void write(DataOutput out) throws IOException {
out.writeLong(upFlow);
out.writeLong(downFlow);
out.writeLong(sumFlow);
}
(4)重写反序列化方法
@Override
public void readFields(DataInput in) throws IOException {
upFlow = in.readLong();
downFlow = in.readLong();
sumFlow = in.readLong();
}
(5)注意反序列化的顺序和序列化的顺序完全一致
(6)要想把结果显示在文件中,需要重写 toString(),可用"\t"分开,方便后续用。
(7)如果需要将自定义的 bean 放在 key 中传输,则还需要实现 Comparable 接口,因为
MapReduce 框中的 Shuffle 过程要求对 key 必须能排序。
本地调试环境搭建
1.windows环境安装jdk1.8
2.解压hadoop文件,设置环境变量,并拷贝对版本的hadoop.dll和winutils.exe到bin目录下
3.修改对应core-site.xml,hdfs-site.xml,mapred-site.xml,yarn-site.xml,并修改hadoop-env.cmd
4.初始化:hdfs namenode -format
core-site.xml
<property>
<name>fs.defaultFS</name>
<value>hdfs://localhost:9000</value>
</property>
hdfs-site.xml
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>D:\software\hadoop\data\namenode</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>D:\software\hadoop\data\datanode</value>
</property>
<!-- mapred-site.xml-->
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
yarn-site.xml
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.nodemanager.aux-services.mapreduce.shuffle.class</name>
<value>org.apache.hadoop.mapred.ShuffleHandler</value>
</property>
hadoop-env.cmd
set JAVA_HOME=C:\PROGRA~1\Java\jdk1.8.0_271
set HADOOP_LOG_DIR=%HADOOP_LOG_DIR%\log
java操作mapreduce
单个MapReduce分为:Mapper、Reducer 和 Driver。 也可以多个MapReduce串行执行
1.Mapper阶段
(1)用户自定义的Mapper要继承自己的父类
(2)Mapper的输入数据是KV对的形式(KV的类型可自定义)
(3)Mapper中的业务逻辑写在map()方法中
(4)Mapper的输出数据是KV对的形式(KV的类型可自定义)
(5)map()方法(MapTask进程)对每一个<K,V>调用一次
2.Reducer阶段
(2)Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
(3)Reducer的业务逻辑写在reduce()方法中
(4)ReduceTask进程对每一组相同k的<k,v>组调用一次reduce()方法
3.Driver阶段
相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是
封装了MapReduce程序相关运行参数的job对象
WordCount案例
案例说明:
读取一个文件,按空格对文件内容分词,最终按单词排序输出每个单词出现的次数。
<!-- MAVEN包-->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.version}</version>
</dependency>
//2.编写好mapreduce代码本地测试(两种常见方式)
//3.上传jar包到服务器运行
hadoop jar apache-hadoop-1.0-SNAPSHOT.jar com.wxl.hadoop.mapReduce.wordCount.WordCountDriver /mapreduce/input/word.txt /mapreduce/output
方式一:
package com.wxl.hadoop.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.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;
import java.util.Date;
public class WordCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
///本地测试,正式环境需要注释掉//
Date date = new Date();//保证输出的目录不重复
args = new String[]{"D:\\ideawork\\bigdata\\apache-hadoop\\src\\main\\resources\\mapreduce\\input\\word.txt",
"D:\\ideawork\\bigdata\\apache-hadoop\\src\\main\\resources\\mapreduce\\output\\" + date.getTime()};
// 1 获取配置信息以及获取 job 对象
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 关联本 Driver 程序的 jar
job.setJarByClass(WordCountDriver.class);
// 3 关联 Mapper 和 Reducer 的 jar
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 4 设置 Mapper 输出的 kv 类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5 设置最终输出 kv 类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6 设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 7 提交 job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
//Map阶段
class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
Text k = new Text();//分割后的单词
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 输出
for (String word : words) {
//排除空值
if (word.trim() == "" || word.length() == 0) {
continue;
}
System.out.println("map输出>>>" + word);
// 设置输出的key为切割的单词
k.set(word);
// 按单词和计数输出
context.write(k, v);
}
}
}
//Reducer
class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
int sum;
IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// 1 累加求和
sum = 0;
for (IntWritable count : values) {
sum += count.get();
}
// 2 输出
v.set(sum);
// 得到最终的结果
context.write(key, v);
}
}
方式二:
package com.wxl.hadoop.mapReduce.wordCount;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
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 org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import java.io.IOException;
import java.util.Date;
public class WordCountMapReduce extends Configured implements Tool {
public static void main(String[] args) throws Exception {
///本地测试,正式环境需要注释掉//
Date date = new Date();//保证输出的目录不重复
args = new String[]{"D:\\ideawork\\bigdata\\apache-hadoop\\src\\main\\resources\\mapreduce\\input\\word.txt",
"D:\\ideawork\\bigdata\\apache-hadoop\\src\\main\\resources\\mapreduce\\output\\" + date.getTime()};
// run job
int status = ToolRunner.run(new WordCountMapReduce(), args);
// exit program
System.exit(status);
}
// Driver
public int run(String[] args) throws Exception {
// 1 获取配置信息以及获取 job 对象
Configuration conf = super.getConf();
//设置job名
Job job = Job.getInstance(conf, this.getClass().getSimpleName());
// 2 关联本 Driver 程序的 jar
job.setJarByClass(this.getClass());
// 3 关联 Mapper 和 Reducer 的 jar
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 4 设置 Mapper 输出的 kv 类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5 设置最终输出 kv 类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6 设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// submit job
boolean status = job.waitForCompletion(true);
return status ? 0 : 1;
}
//Map阶段
public static class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
Text k = new Text();//分割后的单词
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 输出
for (String word : words) {
//排除空值
if (word.trim() == "" || word.length() == 0) {
continue;
}
System.out.println("map输出>>>" + word);
// 设置输出的key为切割的单词
k.set(word);
// 按单词和计数输出
context.write(k, v);
}
}
}
//Reducer
public static class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
int sum;
IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// 1 累加求和
sum = 0;
for (IntWritable count : values) {
sum += count.get();
}
// 2 输出
v.set(sum);
// 得到最终的结果
context.write(key, v);
}
}
}