MapReduce的框架原理
执行流程
-
[split]计算分片
在map之前,根据输入文件创建inputSplit ➢每个InputSplit对应一个Mapper任务 ➢输入分片存储的是分片长度和记录数据位置的数组 block和split的区别 1、block是数据的物理表示 2、split是块中数据的逻辑表示 3、split划分是在记录的边界处 4、split的数量应不大于block的数量(一般相等)
-
[map]调用map()方法对数据进行处理
MapTask并行度由客户端提交job时的切片个数决定 MapTask工作机制:Read读取阶段->Map处理阶段->Collect收集阶段->Spill溢写阶段->Combine阶段
-
[shffule]主要负责将map端生成的数据传递给reduce端
-
Partition 分区:用于在Map端对key进行分区
➢默认使用的是HashPartitioner 获取key的哈希值 使用key的哈希值对Reduce任务数求模 ➢自定义Partitioner 继承抽象类Partitioner,重写getPartition方法 job.setPartitionerClass(MyPartitioner.class)
-
Combiner 合并:是对每一个 maptask 的输出先进行局部汇总,以减小网络传输量
Combiner和Reducer的区别在于运行的位置: ➢ Combiner 是在每一个MapTask所在的节点运行; ➢ Reducer是接收全局所有Mapper的输出结果
-
-
[reduce]对Shffule阶段传来的数据进行最后的整理合并
*ReduceTask的数量可以直接手动设置:job.setNumReduceTasks(num); 1、reducetask=0 ,表示没有 reduce 阶段,输出文件个数和 map 个数一致 2、reducetask 默认值就是 1,所以输出文件个数为一个 3、如果数据分布不均匀,就有可能在 reduce 阶段产生数据倾斜 4、如果分区数不是1,但是reducetask为1,不执行分区过程 *ReduceTask工作机制:Copy阶段->Merge阶段->Sort阶段->Reduce阶段
MapReduce具体工作流程
1、Client向ResourceManager提交任务申请,RM找到NodeManager并启动一个
AppMaster,AM通过获取到的分片信息,向RM申请资源,并启动相应数量的maptask
和ReduceTask;
2、在maptask上读取文件,由TextInputFormat指定读取规则,调用RecordReader
方法按行读取,将行号和每行数据组成文件块进行返回,返回的LongWritable和Text
将作为Mapper中map方法的入口数据;
3、每次获取的行的偏移量和每一行内容通过map()方法进行逻辑运算形成新的键值
对,通过context对象写到OutputCollector中;
4、OutputCollector把收集到的键值对发送到环形缓冲区,就进入了shuffle过程.
环形缓冲区的默认大小为100M,当数据累计到其大小的80%,就会触发溢出;
5、spill溢出前是需要对数据进行分区和排序的,如果没有设置分区器,就会调用系统
默认的分区方法,对环形缓冲区的每个键值对hash一个partition值,相同值得分为同
一个区,分区数跟NumReduceTasks相关。然后再对Key值进行升序排序;
6、环形缓冲区中分区排序后的数据溢出到文件中,如果map阶段处理的数据量较大,
则会溢出多个小文件。如果设置了combiner,相同Key的value值相加,当处理数据量
较大时,目的是让尽可能少的数据写入到磁盘,提高运行效率,否则文件中将存在多个
相同的Key;
7、多个溢出的小文件会被归并排序为一个大文件,大文件中的数据仍然是分区且分
区有序的;
8、当mrAppmaster监控到所有的map task任务完成后,reduce task会根据自己的分
区号去每个不同的map task节点上去取相同分区的数据,然后将取过来的数据在进行
merge合并成一个大文件,大文件是按照k有序的,此时,shuffle过程结束;
9、在reduce task运算过程中,首先调用groupingComparator对大文件的数据进行
分组,每次取出一组键值,通过自定义的reduce()方法进行逻辑处理,然后根据
OutPutFormat指定的输出格式将数据写到hdfs文件中。输出文件的个数与reduce
个数一致。
WordCount的MapReduce代码实现
public class WordMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
//LongWritable, Text(输入类型,行号和每一行内容),Text, IntWritable(输出类型)
private Text word = new Text();
private IntWritable count = new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] words = value.toString().replaceAll(",|\"|\\.|\\?|!|:|;","").split(" ");
for (String _word : words) {
word.set(_word);
//向reducer传输内容
context.write(word,count);
}
}
}
public class WordReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
private IntWritable count = new IntWritable(0);
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
for(IntWritable value:values){
sum += value.get();
}
count.set(sum);
context.write(key,count);
}
}
public class WordTest {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//创建HDFS访问路径配置信息
Configuration config = new Configuration();
config.set("fs.defaultFS","hdfs://192.168.37.200:9000");
//创建mapreduce计算任务
Job job = Job.getInstance(config,"wordCount");
//设置主类,定位外部jar资源
job.setJarByClass(WordTest.class);
//设置任务数:和文件分片数和所存储的DN数有关
job.setNumReduceTasks(2);
//设置分区器Partitioner
job.setPartitionerClass(WordPartitioner.class);
//设置Combiner
job.setCombinerClass(WordReducer.class);
//设置mapper
job.setMapperClass(WordMapper.class);
//设置reducer
job.setReducerClass(WordReducer.class);
//设置mapper输出键值类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//设置reducer输出键值类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//设置HDFS输入文件绑定job
FileInputFormat.setInputPaths(job,new Path("/kb10/Pollyanna.txt"));
FileOutputFormat.setOutputPath(job,new Path("/kb10/wordCount2"));
//等待所有job步骤完成
System.out.println(job.waitForCompletion(true));
}
}
MapReduce优化
-
数据输入
合并小文件:在执行mr任务前将小文件进行合并
-
Map阶段
1.减少溢写(spill)次数 2.减少合并(merge)次数 3.map之后先进行combine处理,减少 I/O
-
Reduce阶段
合理设置map和reduce数 合理设置reduce端的buffer
-
IO传输
尽量避免IO传输
-
数据倾斜问题
数据频率倾斜—某一个区域的数据量要远远大于其他区域 数据大小倾斜—部分记录的大小远远大于平均值 解决数据倾斜问题: 方法1:抽样和范围分区 方法2:自定义分区 方法3:Combine 方法4:采用Map Join
-
常用的调优参数
资源相关参数 shuffle性能优化的关键参数 容错相关参数