mapreduce原理解释
理解MapReduce思想
MapReduce思想在生活中处处可见。或多或少都曾接触过这种思想。MapReduce的思想核心是“分而治之”,适用于大量复杂的任务处理场景(大规模数据处理场景)。即使是发布过论文实现分布式计算的谷歌也只是实现了这种思想,而不是自己原创。
Map负责“分”,即把复杂的任务分解为若干个“简单的任务”来并行处理。可以进行拆分的前提是这些小任务可以并行计算,彼此间几乎没有依赖关系。
Reduce负责“合”,即对map阶段的结果进行全局汇总。
这两个阶段合起来正是MapReduce思想的体现。
Hadoop MapReduce设计构思
MapReduce是一个分布式运算程序的编程框架,核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在Hadoop集群上。
既然是做计算的框架,那么表现形式就是有个输入(input),MapReduce操作这个输入(input),通过本身定义好的计算模型,得到一个输出(output)。
对许多开发者来说,自己完完全全实现一个并行计算程序难度太大,而MapReduce就是一种简化并行计算的编程模型,降低了开发并行应用的入门门槛。
Hadoop MapReduce构思体现在如下的三个方面:
- 如何对付大数据处理:分而治之
对相互间不具有计算依赖关系的大数据,实现并行最自然的办法就是采取分而治之的策略。并行计算的第一个重要问题是如何划分计算任务或者计算数据以便对划分的子任务或数据块同时进行计算。不可分拆的计算任务或相互间有依赖关系的数据无法进行并行计算! - 构建抽象模型:Map和Reduce
MapReduce借鉴了函数式语言中的思想,用Map和Reduce两个函数提供了高层的并行编程抽象模型。
Map: 对一组数据元素进行某种重复式的处理;
Reduce: 对Map的中间结果进行某种进一步的结果整理。
MapReduce中定义了如下的Map和Reduce两个抽象的编程接口,由用户去编程实现:
map: (k1; v1) → [(k2; v2)]
reduce: (k2; [v2]) → [(k3; v3)]
Map和Reduce为程序员提供了一个清晰的操作接口抽象描述。通过以上两个编程接口,大家可以看出MapReduce处理的数据类型是<key,value>键值对。 - 统一构架,隐藏系统层细节
如何提供统一的计算框架,如果没有统一封装底层细节,那么程序员则需要考虑诸如数据存储、划分、分发、结果收集、错误恢复等诸多细节;为此,MapReduce设计并提供了统一的计算框架,为程序员隐藏了绝大多数系统层面的处理细节。
MapReduce最大的亮点在于通过抽象模型和计算框架把需要做什么(what need to do)与具体怎么做(how to do)分开了,为程序员提供一个抽象和高层的编程接口和框架。程序员仅需要关心其应用层的具体计算问题,仅需编写少量的处理应用本身计算问题的程序代码。如何具体完成这个并行计算任务所相关的诸多系统层细节被隐藏起来,交给计算框架去处理:从分布代码的执行,到大到数千小到单个节点集群的自动调度使用。
MapReduce框架结构
一个完整的mapreduce程序在分布式运行时有三类实例进程:
1、MRAppMaster:负责整个程序的过程调度及状态协调
2、MapTask:负责map阶段的整个数据处理流程
3、ReduceTask:负责reduce阶段的整个数据处理流程
通过JavaAPI操作mapreduce
以wordcount(单词计数)程序为例,以下是原始数据,数据上传到了hdfs的/wordcount目录下:
写一个继承于Mapper的自定义类
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;
//自定义一个Mapper类来接收key1,value1,通过处理获得key2,value2
public class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable>
{
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException
{
String[] words=value.toString().split(",");
for(String word:words)
{
context.write(new Text(word),new IntWritable(1));
}
}
}
写一个继承于Reducer的自定义类
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
//自定义一个Reduce类来接收key2,value2,通过处理获得key3,value3
public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable>
{
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException
{
int sum=0;
for(IntWritable v:values)
{
sum+=v.get();
}
context.write(key,new IntWritable(sum));
}
}
完成主类
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.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class WordCountRun extends Configured implements Tool
{
public static void main(String[] args) throws Exception
{
//ToolRunner执行完毕后,会返回一个状态码,状态码为0则执行成功
int run= ToolRunner.run(new Configuration(),new WordCountRun(),args);
System.exit(run);
}
@Override
public int run(String[] strings) throws Exception
{
//Job的getInstance方法需要两个参数,第一个是一个Configuration的实例,第二个是jobName,可以自己随便定义
//Job用于组装mapreduce各阶段的任务
Job job = Job.getInstance(super.getConf(), "word-count");
//打包到集群上面运行时候,必须要添加以下配置,指定程序的main函数所在的类
job.setJarByClass(WordCountRun.class);
//指定job需要执行的任务的原始数据的类型
job.setInputFormatClass(TextInputFormat.class);
//org.apache.hadoop.mapreduce.lib.input包下TextInputFormat,把job和需要操作的文件所在的目录添加进来
//一定是目录,不要加路径,否则会报空指针错误
TextInputFormat.addInputPath(job,new Path("hdfs://node01:8020/wordcount"));
//指定map
job.setMapperClass(WordCountMapper.class);
//指定key2的类型
job.setMapOutputKeyClass(Text.class);
//指定value2的类型
job.setMapOutputValueClass(IntWritable.class);
//指定reduce
job.setReducerClass(WordCountReducer.class);
//指定key3的类型
job.setOutputKeyClass(Text.class);
//指定value3的类型
job.setOutputValueClass(IntWritable.class);
//指定job输出数据的类型
job.setOutputFormatClass(TextOutputFormat.class);
//org.apache.hadoop.mapreduce.lib.input包下TextOutputFormat,把job和输出的目录添加进来
//注意输出目录一定不能是hdfs上已经存在的目录,否则会报错
TextOutputFormat.setOutputPath(job,new Path("hdfs://node01:8020/wordcountOutput"));
//接收任务执行是否成功
boolean result=job.waitForCompletion(true);
if(result)
return 0;
else
return 1;
}
}
运行程序
这个用于mapreduce的程序,需要打包后将jar包发送到Linux机器上才能运行,打包使用IDEA的打包功能,将original开头的较小的那个jar发送到Linux机器上,使用hadoop jar jar路径 主类在jar包中的相对路径
来运行。
本地运行mapreduce程序
如果一定要在本地运行mapreduce的java程序的话,需要作如下修改:
1、修改main方法:
public static void main(String[] args) throws Exception
{
Configuration configuration=new Configuration();
configuration.set("mapreduce.framework.name","local");
configuration.set("yarn.resourcemanager.hostname","local");
//ToolRunner执行完毕后,会返回一个状态码,状态码为0则执行成功
int run= ToolRunner.run(configuration,new WordCountRun(),args);
System.exit(run);
}
2、修改主类中重写自Tool接口的run方法
这里是把hdfs系统上的目录,换为本地的目录,当使用本地运行时,读取的文件和输出的文件都必须在本地,这种运行模式是为了方便调试程序而存在的。