MapReduce原理
来源:互联网 发布:尧十三 知乎 编辑:程序博客网 时间:2024/06/05 08:18
MapReduce程序的执行过程分为两个阶段:Mapper阶段和Reducer阶段。
其中Mapper阶段可以分为6个步骤:
第一阶段:
先将HDFS中的输入文件file按照一定的标准进行切片,默认切片的类为FileInputFormat。FileInputFormat这个类继承自InputFormat,InputFormat这个类会将文件file按照逻辑进行划分,划分成的每一个split切片将会被分配给一个Mapper任务,通过切片输入文件将会变成split1、split2、split3……等块;随后对输入切片split块按照一定的规则解析成键值对<k1,v1>,默认处理的类为TextInputFormat(TextInputFormat这个类继承自FileInputFormat)。其中k1就是我们常说的起始偏移量,v1就是行文本的内容。
注:
文件先被切分为split块,而后每一个split切片对应一个Mapper任务。FileInputFormat这个类先对输入文件进行逻辑上的划分,以64MB为单位,将原始数据从逻辑上分割成若干个split,每个split切片对应一个Mapper任务。
对于FileInputFormat这个类,我们需要注意:FileInputFormat这个类只划分比HDFS的block块大的文件,所以FileInputFormat划分的结果是这个文件或者是这个文件中的一部分。如果一个文件的大小比block块小,将不会被FileInputFormat这个类进行逻辑上的划分,此时每一个小文件都会当做一个split块并分配一个Mapper任务,导致效率低下。这也是Hadoop处理大文件的效率要比处理很多小文件的效率高的原因。
当FileInputFormat这个类将文件file切分成block块之后,TextInputFormat这个类随后将每个split块中的每行记录解析成一个一个的键值对,即<k1,v1>。
综上:我们可以简单理解为FileInputFormat这个类是将文件file切分成split块,而TextInputFormat这个类是负责将每一行记录解析为键值对<k1,v1>。
第二阶段:
调用自己编写的map逻辑,将输入的键值对<k1,v1>变成<k2,v2>。在这里要注意:每一个键值对<k1,v1>都会调用一次map函数。
第三阶段:
按照一定的规则对输出的键值对<k2,v2>进行分区:分区的规则是针对k2进行的,比如说k2如果是省份的话,那么就可以按照不同的省份进行分区,同一个省份的k2划分到一个区。注意:默认分区的类是HashPartitioner类,这个类默认只分为一个区,因此Reducer任务的数量默认也是1。
源码:org.apache.hadoop.mapreduce.lib.partition.HashPartitioner
public class HashPartitioner<K, V> extends Partitioner<K, V> {
/** Use {@link Object#hashCode()} to partition. */
public int getPartition(K key, V value,
int numReduceTasks) {
return (key.hashCode() &Integer.MAX_VALUE) % numReduceTasks;
}
}
第四阶段:
对每个分区中的键值对进行排序。注意:所谓排序是针对k2进行的,v2是不参与排序的,如果要让v2也参与排序,需要自定义排序的类。
第五阶段:
排序完之后要进行分组,即相同key的value放到同一个集合当中,例如在WordCount程序中的<hello,{1,1}>执行的就是这个步骤,但是要注意:分组也是针对key进行的,经过分组完之后,就得到了我们熟悉的键值对<k2,v2s>.
第六阶段(可选):
对分组后的数据进行归约处理。通过归约处理键值对<k2,v2s>变成了<k2,v2>,经过这一阶段,传送到Reducer任务端的数据量会减少。但是规约的使用是有条件的,所以这一阶段是可以选择的。
Mapper任务处理完之后,就进入到了我们的Reducer阶段:
Reducer任务的执行过程可以分为3个阶段:
第一阶段:
对多个Mapper任务的输出,按照不同的分区,通过网络拷贝到不同的Reducer节点上进行处理,将数据按照分区拷贝到不同的Reducer节点之后,对多个Mapper任务的输出在进行合并,排序。例如:在WordCount程序中,若一个Mapper任务输出了<hello,{1,1}>,另外一个Mapper任务的输出为<hello,{1,1,1}>,经过这次合并之后变为<hello,{1,1,1,1,1}>。
第二阶段:
调用自己的reduce逻辑,将键值对<k2,v2s>变为<k3,v3>。在这里注意:每一个键值对<k2,v2s>都会调用一次reduce函数。
第三阶段:
将Reducer任务的输出保存到指定的文件中。
最后,我们举一个例子,并且实现自定义排序的功能。
原始数据为(\t分隔符):
1 1
2 2
3 3
4 5
4 7
4 6
8 9
6 8
6 4
Java源码为:
package com.dream.mapreduce;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;
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.partition.HashPartitioner;
public class MyDefineSort {
public static String path1 = "hdfs://gpmaster:9000/sourcedata.txt";
publi cstatic String path2 = "hdfs://gpmaster:9000/targetDir";
public static void main(String[] args) throws IOException,
URISyntaxException,ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
FileSystem fileSystem = FileSystem.get(new URI(path1), conf);
//输出路径必须是不存在的
if(fileSystem.exists(new Path(path2)))
{
fileSystem.delete(newPath(path2), true);
}
Jobjob = Job.getInstance(conf, "MyDefineSort");
job.setJarByClass(MyDefineSort.class);
//编写MapReduce的运行步骤
FileInputFormat.setInputPaths(job,new Path(path1));
job.setInputFormatClass(TextInputFormat.class);
job.setMapperClass(MyMapper.class);
job.setMapOutputKeyClass(SortWritable.class);
job.setMapOutputValueClass(NullWritable.class);
job.setNumReduceTasks(1);
job.setPartitionerClass(HashPartitioner.class);
job.setReducerClass(MyReducer.class);
job.setOutputKeyClass(SortWritable.class);
job.setOutputValueClass(NullWritable.class);
FileOutputFormat.setOutputPath(job,new Path(path2));
//提交任务
job.waitForCompletion(true);
//查看结果
FSDataInputStream fr = fileSystem.open(newPath("hdfs://gpmaster:9000/targetDir/part-r-00000"));
IOUtils.copyBytes(fr,System.out, 1024, true);
}
public static class MyMapper extends Mapper<LongWritable, Text, SortWritable,NullWritable> {
protected void map(LongWritable k1, Text v1, Context context)
throws IOException, InterruptedException {
String[] splited = v1.toString().split("\t");
String num1 = splited[0];
String num2 = splited[1];
SortWritable vector = new SortWritable(Long.parseLong(num1),Long.parseLong(num2));
context.write(vector,NullWritable.get());
}
}
public static class MyReducer extends Reducer<SortWritable, NullWritable,SortWritable, NullWritable> {
protected void reduce(SortWritable k2, Iterable<NullWritable> v2s, Context context)
throws IOException, InterruptedException {
for(NullWritable v2 : v2s) {
context.write(k2,NullWritable.get());
}
}
}
}
class SortWritable implements WritableComparable<Object> {
Longnum1;
Longnum2;
public SortWritable() {
}
public SortWritable(long num1, long num2) {
this.num1= num1;
this.num2= num2;
}
//序列化
publicvoid write(DataOutput fw) throws IOException
{
fw.writeLong(num1);
fw.writeLong(num2);
}
public void readFields(DataInput fr) throws IOException {
this.num1= fr.readLong();
this.num2= fr.readLong();
}
public int compareTo(Object obj) {
SortWritable cc = (SortWritable) obj;
if(this.num1 != cc.num1)
//num1升序排列
return (int) (this.num1 - cc.num1);
else
//num2升序排列
return (int) (this.num2 - cc.num2);
}
public String toString() {
return this.num1 + "\t" + this.num2;
}
}
将上面代码打包为一个jar包,比如MyDefineSort.jar,并上传到Hadoop环境上,运行如下代码:
hadoop jar MyDefineSort.jar com.dream.mapreduce.MyDefineSort
输出的文件为:
hdfs dfs -ls hdfs://gpmaster:9000/targetDir/part-r-00000
结果内容为:
1 1
2 2
3 3
4 5
4 6
4 7
6 4
6 8
8 9
可以看出首先根据第一列升序排列,如果第一列相同时,再根据第二列升序排列。
- mapreduce(六):MapReduce原理
- MapReduce原理
- MapReduce原理
- MapReduce原理
- MapReduce原理
- MapReduce原理
- MapReduce原理
- MapReduce原理
- MapReduce原理
- MapReduce原理
- mapreduce原理
- MapReduce原理
- MapReduce原理
- MapReduce原理
- MapReduce原理
- mapreduce原理
- MapReduce原理
- mapreduce 原理
- const关键字、指针、数组、函数
- poj-2229-Sumsets
- Java 学习笔记(0x0F) 数组
- 129.在htaccess文件中隐藏index.php
- Java简介
- MapReduce原理
- iOS自学笔记之App开发初体验
- 浅谈RBF在角色绑定上并不好用的原因
- poj 2533 最长递增子序列
- JQuery中根据属性或属性值获得元素
- 19 联动菜单练习
- 自定义日期控件wheelview源码分析
- 【线性规划与网络流24题 17】运输问题
- Eclipse无法可视化编辑Android应用界面xml的应急对策