MapReduce是hadoop的核心组件之一,hadoop要分布式包括两部分,一是分布式文件系统hdfs,二是分布式计算框,就是mapreduce,缺一不可,也就是说,可以通过mapreduce很容易在hadoop平台上进行分布式的计算编程 。
MapReduce最早是由Google公司研究提出的一种面向大规模数据处理的并行计算模型和方法。Google公司设计MapReduce的初衷主要是为了解决其搜索引擎中大规模网页数据的并行化处理。2003年和2004年,Google公司在国际会议上分别发表了两篇关于Google分布式文件系统和MapReduce的论文,公布了Google的GFS和MapReduce的基本原理和主要设计思想
下面我给出一个mapreduce与之前传统并行计算框架的比较:
传统并行计算架构 | MapReduce | |
集群架构/容错性 | 共享式(共享内存/存储),容错性差 | 非共享式,容错性好 |
硬件/价格/扩展性 | 刀片服务器,价格高,扩展性差 | 普通pc即可,扩展性强 |
适用场景 | 实时,细粒度计算,计算密集型 | 批处理,非实时,数据密集型 |
1.MApReduce将并行计算过程高度抽象到2个最基本的函数:map()和reduce()
2.采用“分而治之”策略,大规模的数据集合会被切分为许多独立的分片(split),这些分片可以被多个map任务并行处理(Hadoop为每个split分配一个map任务,理想分片大小为一个HDFS块)
Mapper负责“分”,即把复杂的任务分解为若干个“简单的任务”来处理。
“简单的任务”包含三层含义: 一是数据或计算的规模相对原任务要大大缩小;二是就近计算原则,即任务会分配到存放着所需数据的节点上进行计算;三是这些小任务可以并行计算,彼此间几乎没有依赖关系。
Reducer负责对map阶段的结果进行汇总。至于需要多少个Reducer,用户可以根据具体问题,通过在mapred-site.xml配置文件里设置参数mapred.reduce.tasks的值,缺省值为1
3.MapReduce框架采用Master/Slave架构,包括一个Master和若干个Slave。Master上运行JobTracker,Slave上运行TaskTracker
之前的博文中我提到过Hadoop到现在有1.x 2.x的版本,相对应的 作为Hadoop原生的分布式并行计算框架,MapReduce也会有相对应的1.x 2.x版本。
首先,简要介绍MapReduce1.x。
虽然现在MapReduce1.x的版本几乎已经不在实际中应用了,这里我还是简单做个小结。
MapReduce1.x的架构:
MapReduce的整个工作过程如上图所示,它包含如下4个独立的实体:
实体一:客户端,用来提交MapReduce作业。
实体二:JobTracker,用来协调作业的运行。
作业的管理者
将作业分解成一堆的任务:Task (MapTask和ReduceTask)
将任务分派给TAskTracker运行
作业的监控、容错处理(task作业挂了,重启task的机制)
在一定的时间间隔内,JT没有收到TT的心跳信息,TT可能是挂了,TT上运行的任务会被指派到其他TT上去执行
实体三:TaskTracker,用来处理作业划分后的任务。
任务的执行者
在TT上执行我们的Task(MapTask和ReduceTask)
会与JT进行交互:执行/启动/停止作业,发送心跳信息给JT
MapTask:自己开放开发的map任务交由改Task出来
解析每天记录的数据,交给自己的map方法处理
将map的输出结果写到本地磁盘(有些作业仅有map没有reduce==>HDFS)
ReduceTask:将MapTask输出的数据进行读取
按照数据进行分组传给我们自己编写的reduce方法处理
输出结果写到HDFS
实体四:HDFS,用来在其它实体间共享作业文件。
HDFS这一部分,更多内容详见我的上一篇博文HDFS笔记
接下来 介绍MapReduce2.x
这里 我们这样理解,ResourceManager(RM)等同于JT,NodeManager(NM)等同于TT。
补充2.x里面的新内容:
MapReduce编程之Combiner:
作用相当于本地的reducer
减少Map Tasks输出的数据量级数据网络传输量
Combiner使用场景:求和 次数(可以)
平均数(不行)
MapReduce编程之Partitioner:
Partitioner决定MapTask输出的数据交由那个ReducerTask处理
默认实现:分发的key的hash值对Reduce Task个数取模
讲述MapReduce计算框架,我们通过下面这张图讲解。
简单来说,MapREduce执行的全过程包括一下几个阶段:从HDFS中读入数据,执行map任务输出中间结果,通过shuffle阶段吧中间结果分区排序整理后发送给reduce任务,执行reduce任务得到最终结果并写入HDFS中。在执行的全过程中,shuffle阶段最为重要和关键。
(2020.1.9更新)
这里有一点注意: reduce任务的数量并非输入数据的大小决定的,他是可以独立指定的。
这里提到的图2-4 是MapReduce程序中最常用的模型。
我们在后面的博客中会提到一个MapJoin和ReduceJoin,而MapJoin采用的就是图2-5的形式,他在Hive中比较常见。
接下来就mapreduce的执行过程,编写一个入门的小程序单词计数。希望通过一下代码 读者体会mapreduce的计算框架
package MapReduce;
import java.io.IOException;
import java.net.URI;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
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.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
/**
* hadoop入门第一个小程序 利用MApReduce多文件中的单词计数
* @author xjh 2018.07.28
*
*/
public class WordCount {
public static final String HDFS_PATH="hdfs://msiPC:9000";
//该类中包含两个内部类
/**
*Mapper类
* map方法完成工作就是读取文件
* 将文件中每个单词作为key键,值设置为1,
* 然后将此键值对设置为map的输出,即reduce的输入
*
*/
public static class WordCountMap extends
Mapper {
private final IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String line = value.toString(); //接受到的每一行数据
StringTokenizer token = new StringTokenizer(line);
while (token.hasMoreTokens()) {
word.set(token.nextToken());
context.write(word, one); //将单词作为key(value=1)写入context中
}
}
/**
* StringTokenizer:字符串分隔解析类型
* 之前没有发现竟然有这么好用的工具类
* java.util.StringTokenizer
* 1. StringTokenizer(String str) :
* 构造一个用来解析str的StringTokenizer对象。
* java默认的分隔符是“空格”、“制表符(‘\t’)”、“换行符(‘\n’)”、“回车符(‘\r’)”。
* 2. StringTokenizer(String str, String delim) :
* 构造一个用来解析str的StringTokenizer对象,并提供一个指定的分隔符。
* 3. StringTokenizer(String str, String delim, boolean returnDelims) :
* 构造一个用来解析str的StringTokenizer对象,并提供一个指定的分隔符,同时,指定是否返回分隔符。
*/
}
/**
* reduce的输入即是map的输出,将相同键的单词的值进行统计累加
* 即可得出单词的统计个数,最后把单词作为键,单词的个数作为值,
* 输出到设置的输出文件中保存
*/
public static class WordCountReduce extends
Reducer {
public void reduce(Text key, Iterable values,
Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get(); //相同单词进行累加
}
context.write(key, new IntWritable(sum));
}
}
public static void main(String[] args) throws Exception {
//main()函数首先进行Hadoop的相关配置类
Configuration conf = new Configuration(); //创建COnfiguration
//因为HDFS默认情况下是输出文件路径事先不存在 所以 我们在这里新增一个对 已存在输出文件清理的功能
//除了在代码中实现,我们还可以在hdfs 命令行手动删除(比较low的方法)
Path outputPath=new Path(args[1]);
FileSystem file = FileSystem.get(new URI(HDFS_PATH),conf); //这里需要加上hdfs的URI地址
if(file.exists(outputPath)){
//如果存在,则删除
file.delete(outputPath,true);
System.out.println("output path exists,but it's deleted...");
}
Job job = new Job(conf); //创建job
job.setJarByClass(WordCount.class); //设置job的处理类
job.setJobName("wordcount"); //job名称
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
job.setMapperClass(WordCountMap.class); //配置Map方法的类
job.setReducerClass(WordCountReduce.class); //配置Reduce方法的类
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
FileInputFormat.addInputPath(job, new Path(args[0])); //作业处理输入路径
FileOutputFormat.setOutputPath(job, new Path(args[1])); //作业处理输出路径
job.waitForCompletion(true); //作业提交
}
}
这个Mapper类是一个泛型类,他有四个形参类型,分别指定mao函数的输入键,输入值,输出键,输出值。这里的LongWritable类型可理解为Java中的Long型,Text型可理解为Java中的String类型,IntWritable类型可理解为Java的Integer类型。
map()方法的输入 是一个键和一个值,程序中我们首先将包含有一行输入的text文本转换成String形式,然后进行分词和计数。map方法提供Context实例作为输入内容的写入。
相对应的Reducer类也有四个形式参数类型用于指定输入和输出类型。由上述的理论知识,我们知道Reducer类的输入类型必须匹配mao函数的输出类型:即Text类型和IntWritable类型, 而且你可以看到Reducer类的输出类型同样是Text类型和IntWritable类型。