概述
-
MapReduce是Doug根据的Google的<The Google MapReduce>来仿照实现的
-
MapReduce将整个计算过程拆分为了两个大阶段:Map(映射)阶段和Reduce(规约)阶段
入门案例
-
案例:统计这个文件中每一个字符出现的次数
-
MapReduce刚开始的时候,会先对要处理的文件进行切片(Split)。
-
Split是逻辑切分,是在划分任务量
-
Block是物理切分,是在确定存储单位
-
-
实际过程中,考虑到数据要跨节点传输,为了减少跨节点的数量,所以Split=Block/n。在MapReduce中,默认Split和Block等大
-
Mapper类
package com.lcs.charcount; 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; // Mapper // 在MapReduce中,要求传输的数据能够被序列化 // 而MapReduce使用的不是Java的原生序列化机制,而是采用了新的序列化机制 // KEYIN - 输入的键的类型,是这一行的字节偏移量 // VALUEIN - 输入的值的类型,是这一行数据 // KEYOUT - 输出的键的类型。当前案例中,输出的键是字符 // VALUEOUT - 输出的值的类型。当前案例中,输出的值是次数 public class CharCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> { private final IntWritable once = new IntWritable(1); // MapTask的处理逻辑是放在map方法中 // 切片中的每一行都会调用一次map方法进行处理 // key:键。每一行的字节偏移量 // value:值。这一行数据 // context:环境参数 @Override protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException { // 将这一行中的字符拆分出来 char[] cs = value.toString().toCharArray(); // for (char c : cs) { context.write(new Text(String.valueOf(c)), once); } } }
-
Reducer类
package com.lcs.charcount; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException; // KEYIN,VALUEIN - 输入的键和值。Reducer的数据是Mapper产生的,那么就意味着Mapper的输出类型就是Reducer的输入类型 // KEYOUT,VALUEOUT - 输出的键和值。当前案例中,最终输出的字符和对应的次数 public class CharCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> { // key:键。当前案例中,键是字符 // values:值。Reduce开始的时候会将相同的键对应的值放到一起,这个过程称之为分组。分组结束之后,会将值放入迭代器中,每一个键调用一次reduce方法 @Override protected void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException { // key:h // values:1,1,1,1,1,1.... // 定义变量记录和 int sum = 0; // 遍历values for (IntWritable value : values) { sum += value.get(); } context.write(key, new IntWritable(sum)); } }
-
Driver类(入口类)
package com.lcs.charcount; import org.apache.hadoop.conf.Configuration; 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; import java.io.IOException; public class CharCountDriver { public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException { // 环境参数 Configuration conf = new Configuration(); // 申请任务 Job job = Job.getInstance(conf); // 指定入口类 job.setJarByClass(CharCountDriver.class); // 指定Mapper类 job.setMapperClass(CharCountMapper.class); // 指定Reducer类 job.setReducerClass(CharCountReducer.class); // 指定Mapper的输出类型 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); // 指定Reducer的输出类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); // 指定输入路径 FileInputFormat.addInputPath(job, new Path("hdfs://10.16.3.181:9000/txt/characters.txt")); // 指定输出路径,要求输出路径必须不存在 FileOutputFormat.setOutputPath(job, new Path("hdfs://10.16.3.181:9000/result/char_count")); // 提交任务 job.waitForCompletion(true); } }
组件
序列化 -
Writable
-
统计每一个人花费的上行流量、下行流量以及总流量(文件:flow.txt)
-
在MapReduce中,各个节点之间基本上都是通过RPC的方式来进行调用,也因此要求传输的数据必须被序列化
-
Hadoop并没有使用Java的原生序列化机制,底层默认采用了AVRO来进行的序列化。在AVRO的基础上,MapReduce进行了封装,从而简化了序列化操作 - 让需要被序列化的对象对应的类实现接口
Writable
,覆盖其中的write
和readFields
方法即可 -
由于AVRO的限制,所以要求被序列化的类中必须有无参构造,同时不允许属性值为
null
-
MapReduce针对常用的类都提供了序列化形式
Java类 序列化类 Byte ByteWritable Short ShortWritable Integer IntWritable Long LongWritable Float FloatWritable Double DoubleWritable Boolean BooleanWritable String Text Null NullWritable Array ArrayWritable Map MapWritable 分区 -
Partitioner
-
按地区,统计每一个人花费的总流量(文件:flow.txt)
-
Partitioner(分区)的作用是对数据进行分类
-
默认情况下,MapReduce中,只有一个1个分区,所以也只有1个ReduceTask。如果进行了分类,那么就需要指定多个ReduceTask。每一个分区都需要对应一个ReduceTask,每一个ReduceTask都会产生一个结果文件 - 分区的数量决定了ReduceTask的数量,ReduceTask的数量决定了结果文件的数量
-
在MapReduce中,会对分区进行编号,编号是从0开始的
-
排序 -
Comparable
-
在MapReduce中,无论逻辑是否需要,默认会对MapTask的输出的键进行排序,也因此要求放在Mapper输出的键对应的类必须实现
Comparable
接口。考虑到还要进行序列化,需要实现WritableComparable
接口 -
如果需要指定自己的排序规则,那么需要定义类实现
WritableComparable
接口 -
案例:先按照月份升序排序,如果是同一个月,那么按照利润降序排序(文件:profit.txt)
-
合并 -
Combiner
-
Combiner是在不改变结果的前提下,减少ReduceTask的计算条数
-
如果需要使用Combiner,只需要在入口类中添加
job.setCombinerClass(XXXReducer.class);
-
可以传递结果的运算,可以使用Combiner,例如求和、求积、去重、取最值等;不能传递结果的运算,不能使用Combiner,例如求平均值等