目录
Shuffle机制
什么是Shuffle
数据从Map阶段传递给Reduce阶段的过程就叫Shuffle,所以Shuffle的作用范围是Map阶段数据输出到Reduce阶段数据输入这一整个中间过程,Shuffle机制是整个MapReduce框架中最核心的部分,就是将maptask输出的处理结果数据,分发给reducetask,并在分发的过程中,对数据按key进行了分区和排序
总的一句话:shuffle就是在map方法之后,reduce方法之前的操作,其操作包括了分区和排序,若是没有reduce的话就不进行shuffle操作
Shuffle图解
工作机制
- Collect阶段:一个切片Input Split对应一个Mapper,将MapTask的结果输出到默认大小为100M的环形缓冲区,保存的是key/value序列化数据,Partition分区信息等。
- Spill阶段:当内存中的数据量达到一定的阀值(默认阀值80%)的时候,就会将溢出数据写入本地磁盘,写入到磁盘的时候它并不是简单地将数据溢出写入,而是先进行分区,再在每个分区里对数据进行合并(Combiner)。
- .Merge阶段:最后它会将溢出的临时文件进行一次合并操作,并且是相同分区号的数据进行合并以确保一个MapTask最终只产生一个中间数据文件。
- .Copy阶段:ReduceTask启动Fetcher线程到已经完成MapTask的节点上复制一份属于自己的数据,这些数据默认保存在内存的缓冲区中,当内存的缓冲区达到一定的阀值的时候,就会将数据写到磁盘之上。
- AgainMerge阶段:数据再次进行合并,将多个溢写文件归并成一个溢写文件
- Sort阶段:在对数据进行合并的同时,会进行排序操作,由于MapTask阶段已经对数据进行了局部的排序,ReduceTask只需保证Copy的数据的最终整体有效性即可
Partition分区类别与规则
- Partition分区:按照一定的分区规则,将key value的list进行分区。
- 分区的创建分为默认的和自定义两种。
默认分区
- 默认分区是根据key的hashCode对ReduceTasks个数取模得到的。
- 用户没法控制哪个key存储到哪个分区。
public class HashPartitioner<K,V> extends Partitioner<K,V>{
public int getPartition(K key,V value, int numReduceTasks){
return(key.hashCode()& Integer.MAX_VALUE) % numReduceTasks;
}
}
自定义分区
- 自定义类继承Partitioner,重写getPartition()方法
public class xxx extends Partitioner<Text, xxx>{
@Override
public int getPartition(Text key,xxx value, int numReduceTasks){
// 控制分区代码逻辑
...
...
...
return Partition;
}
}
- 设置自定义Partitioner:
job.setPartitionerClass(CustomPartitioner.class);
- 根据自定义Partitioner的逻辑来设置ReduceTask的数量:
job.setNumReduceTasks(n);
分区规则
-
如果
ReduceTask的数量=1
,则不管MapTask端输出多少个分区文件,最终结果都交给这一个ReduceTask,只会产生一个结果文件part-r-00000 -
如果
1<ReduceTask的数量<getPartition的结果数
,则有一部分分区数据无处安放,会报错 -
如果
ReduceTask的数量>getPartition的结果数
,则会多产生几个空的输出文件part-r-000xx -
分区号必须从零开始,逐一累加
int n=5; //设置5个分区
job.setNumReduceTasks(n)
当n=1时
job.setNumReduceTasks(1) 输出一个结果
当n=2,3,4时
job.setNumReduceTasks(2/3/4) 输出错误
当n=6时
job.setNumReduceTasks(6) 正常运行,但是会产生空文件
Partition分区实例
实现目的
在原有的基础上实现
将手机号159、135、137、139开头都分别放到一个独立的4个文件中,其他开头的放到一个文件中
!!!
红框里的java类为新建的
编写Partition类
调用serialization包里的类
package Partition;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import serialization.FlowBean;
import serialization.FlowMapper;
import serialization.FlowReducer;
import java.io.IOException;
public class PartitionerDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1 获取job实例
Job job = Job.getInstance(new Configuration());
// 2.设置类路径
job.setJarByClass(PartitionerDriver.class);
// 3 指定本业务job要使用的mapper/Reducer业务类
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
// 4 指定自定义数据分区
job.setPartitionerClass(SetPartition.class);
// 5 同时指定相应数量的reduce task
job.setNumReduceTasks(5);
// 6 指定mapper输出数据的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
// 7 指定最终输出的数据的kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
// 8 指定job的输入原始文件所在目录
FileInputFormat.setInputPaths(job, new Path("E:\\hadoop\\mapreducedemo\\Input"));
FileOutputFormat.setOutputPath(job, new Path("E:\\hadoop\\mapreducedemo\\output"));
// 9 将job中配置的相关参数,以及job所用的java类所在的jar包,提交给yarn去运行
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}