MapReduce工作原理的步骤解析
1、当maptask启动之后,InputFormat也就被启动了,用默认的TextInputFormat类型的RecordReader方法读取文件。这种方式的目的是:(1)数据切分:按照一行一行地分成若干个split,以便确定MapTask个数以及对应的split。(2)为Mapper提供输入数据:读取给定的split的数据,解析成一个个的(key:行的偏移量/value:读取到的行的内容),供Mapper使用。
2、mapper的map方法入口会得到map(k,v),并且进行逻辑计算处理形成新的map(k,v),并输出到OutPutCollector收集器
3、OutPutCollector负责收集mapper发出的数据,当map里的内容收集完毕后,再逐条发送给环形缓冲区
4、环形缓冲区默认大小为100M,采用先进先出机制,当环形缓冲区到达80%时,会触发spill溢出。这时,先存进的数据就先输出,输出的同时也在读取数据,读取到的数据就会覆盖在前面的数据上,直至读取完OutPutCollector收集器的数据。
5、spill溢出前需要对数据进行分区计算(HashPartitioner)和排序,相同partition值为同一分区,然后会把环形缓冲区中的数据来按照partition值排序、同一partition内的按照key排序。(如果采用Combiner方法,则会在同一个分区块下,相同key值进行局部的value合并。如:a1,a1,a1,a1,a1,a1就会写成a6)
(1)HashPartitioner分区规则:
HashPartitioner的getPartition方法返回值是int类型的,每条数据都要进来计算一下数据的分区,然后给每条数据打上一个逻辑标识,计算每一条数据要去哪一个reduceTask里去。
逻辑编号:(key.hashCode() & Integer.MAX_VALUE) % numberReduceTasks
因为key.hashCode()有可能是负数,所以要&Integer.MAX_VALUE,这样就永远是一个正整数。&按位与。
numberReduceTasks指多少个reduceTask。
(2)因为在同一个分区块下,同一个键得到的getPartition的返回值是一样的,所以同一个键会进同一个reducer
6、将环形缓冲区中排序后的内存数据不断地spill溢出,如果map阶段处理的数据量较大,可能会溢出多个文件;
7、多个溢出文件会被merge合成溢出的大文件,合并采用归并排序。这个大文件是mapTask最终结果,并且是分区的、区内有序的。(如果采用Combiner方法,则要把不同分区块下,相同key值进行局部的value合并)
8、等所有的DateNode都完成MapTask后,RreduceTask会根据自己的分区号,去各个mapTask节点上拷贝相同partition的数据到reduceTask
9、reduceTask会把来自不同mapTask的、但同一分区的结果文件,再进行merge合并成一个大文件(归并排序),大文件内容按照key排序(如果采用Combiner方法,只需要把不同分区块的相同(key,value)归并,不需要相加)
10、合并成大文件后,后面进入reduceTask的逻辑运算过程
11、最后通过OutputFormat方法将结果数据写到part-r-000""结果文件中
Shuffle:
MapReduce计算模型一般包括两个重要的阶段:Map是映射,负责数据的过滤分发;Reduce是规约,负责数据的计算归并。Reduce的数据来源于Map,Map的输出即是Reduce的输入,Reduce需要通过Shuffle来获取数据。 从Map输出到Reduce输入的整个过程可以广义地称为Shuffle(步骤4~步骤9)。Shuffle横跨Map端和Reduce端,在Map端包括Spill过程,在Reduce端包括copy和sort过程
WordCount案例
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));
}
}