1.MapReduce Partition分区
MapReduce输出结果文件个数探究
在map过程中,会并发的执行多个task(小任务),在reduce过程中,结果只会有一个task处理
map的输出文件命名格式为part-m-nnnnn,而reduce的输出文件为part-r-nnnnn
在MapReduce运行的过程中,map任务将其中间结果输出写入到本地磁盘,reduce任务实现对多个map输出结果的处理,输出结果到HDFS中,并会将保存到本地磁盘的中间结果文件删除
以上是单个reduce任务的输出结果
当有多个reduce任务时,map和reduce任务中会有一个shuffle的过程,用于并行地执行多个不同的部分
mapreduce中,通过Job提供地方法可以修改reducetask的个数
对应的方法
public void setNumReduceTasks(int tasks)
该方法的源码
public void setNumReduceTasks(int tasks) throws IllegalStateException {
// 用于判断Job是否可以使用该方法
this.ensureState(Job.JobState.DEFINE);
// 通过配置项对象设置reducetask的个数
this.conf.setNumReduceTasks(tasks);
}
没有设置ReduceTask个数的结果和设置了的结果
未设置,默认reducetask只有一个,map任务shufffle操作只有一次
设置,map任务的shuffle操作为指定的次数,这里设置的个数为3
同时,reduce任务的输出文件也分成了三部分
Partition分区
当MapReduce中有多个reducetask执行的时候,此时maptask的输出就会面临一个问题:
究竟将自己的输出数据交给哪一个reducetask来处理?这就是所谓的数据分区(partition)问题
当改变reducetask个数的时候,作为maptask就会涉及到分区的问题,即:MapTask输出的结果如何分配给各个ReduceTask来处理
MapReduce默认分区规则为HashPartitioner,分区的结果和map输出的key有关
@Public
@Stable
public class HashPartitioner<K2, V2> implements Partitioner<K2, V2> {
public HashPartitioner() {
}
public void configure(JobConf job) {
}
public int getPartition(K2 key, V2 value, int numReduceTasks) {
return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}
}
由该方法可知,分区与map输出的key有关,与对应的reducetask数量有关
Partitioner注意事项
reducetask个数的改变导致了数据分区的产生,而不是有数据分区导致了reducetask个数改变
数据分区的核心是分区规则。即如何分配数据给各个reducetask
默认的规则可以保证只要map阶段输出的key一样,数据就一定可以分区到同一个reducetask,但是不能保证数据平均分区
reducetask个数的改变还会导致输出结果文件不再是一个整体,而是输出到多个文件中
2.MapReduce Combiner规约
MapReduce是一种具有两个执行阶段的分布式计算程序,Map阶段和Reduce阶段之间会涉及到跨网络数据传递
每一个MapTask都可能会产生大量的本地输出,这就导致跨网络传输数据量变大,网络IO性能低
比如WordCount单词统计案例,假如文件中有1000个单词,其中999个为hello,这将产生999个<hello,1>的键值对在网络中传递,性能及其低下
Hadoop允许用户针对map任务的输出指定一个combiner
Combiner本质就是Reducer,combiner和reducer的区别在于运行的位置:
combiner是在每一个maptask所在的节点本地运行,是局部聚合
reducer是对所有maptask的输出结果计算,是全局聚合
combiner通俗来讲,是将map的输出进行优化,以WordCount为例,文件中只有两个词,hello为999个,world为1个
在不使用combiner时,map的输出为<hello,1>,对应的reduce的输入为<hello,[1,1,1,1,1,....]>
此时从本地磁盘读取数据,数据量比较的大,不利于传输
使用combiner,combiner本质是reducer(使用reducer的代码),map任务输出调用combiner,对应的combiner输出为<hello,999>,传输到reduce任务时,网络带宽会降低航舵,利于数据的传输
注意事项
Combiner能够应用的前提是不能影响最终的业务逻辑,而且,Combiner的输出kv应该跟reducer的输入kv类型要对应起来
在进行求解平均数,排序的过程中,不能使用combiner
以求平均数为例假定有多个map任务输出为<1950,23><1950,2>,<1950,40>和<1950,30><1950,3>
不使用combiner,reduce任务操作后的结果为mean(23,2,40,30,3)=19.5
使用combiner,reduce任务操作后的结果为mean(23,2,40,30,3)=mean(mean(23,2,40),mean(30,3)=19.1
两者有偏差,因此要慎用combiner
Combiner组件不是禁用,而是慎用。用的好提高程序性能,用不好,改变程序结果且不易发现
3.MapReduce基于两种方法的案例
统计男女生数量
Job测试类
package com.prettyspider.mapreduce.countnumberofsex;
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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
/**
* @author prettyspider
* @ClassName WordCount
* @description: TODO
* @date 2023/10/10 2:17
* @Version V1.0
*/
public class CountNumberOfSex {
public static void main(String[] args) throws Exception{
if (args.length != 2) {
System.out.println("Usage:WordCount <input path> <output path>");
System.exit(0);
}
Job job = new Job();
job.setJarByClass(CountNumberOfSex.class);
job.setJobName("CountNumberOfSex");
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 设置ReduceTask
job.setNumReduceTasks(3);
job.setMapperClass(CountNumberOfSexMapper.class);
// 设置combiner
job.setCombinerClass(CountNumberOfSexReducer.class);
job.setReducerClass(CountNumberOfSexReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
System.exit(job.waitForCompletion(true)?0:1);
}
}
Mapper类
package com.prettyspider.mapreduce.countnumberofsex;
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;
import java.util.StringTokenizer;
/**
* @author prettyspider
* @ClassName WordcountMapper
* @description: TODO
* @date 2023/10/9 13:40
* @Version V1.0
*/
public class CountNumberOfSexMapper extends Mapper<LongWritable,Text,Text, IntWritable> {
private static final IntWritable num = new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException {
StringTokenizer lines = new StringTokenizer(value.toString());
while (lines.hasMoreTokens()) {
String regex = "-";
String sex = lines.nextToken().split(regex)[1];
// 获取键
String keyName = sex;
//写入内容中
context.write(new Text(keyName),num);
}
}
}
Reducer类
package com.prettyspider.mapreduce.countnumberofsex;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* @author prettyspider
* @ClassName WordcountReducer
* @description: TODO
* @date 2023/10/9 13:56
* @Version V1.0
*/
public class CountNumberOfSexReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
private static final IntWritable count = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable value : values) {
int num = value.get();
sum+=num;
}
count.set(sum);
context.write(key,count);
}
}
测试数据提取码:4mib