MapReduce自定义分区器partitioner
MapReduce自带分区器源码讲解
- 源码
import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; import org.apache.hadoop.mapreduce.Partitioner; /** Partition keys by their {@link Object#hashCode()}. */ @InterfaceAudience.Public @InterfaceStability.Stable /** *K指的是从Mapper出来的key *V指的是从Mapper出来的value **/ public class HashPartitioner<K, V> extends Partitioner<K, V> { /** Use {@link Object#hashCode()} to partition. */ public int getPartition(K key, V value, int numReduceTasks) { return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;//numReduceTasks是指的想要分的区也就是文件的个数,可以自己确定文件的输出个数。 } }
- 说明:因为在MapReduce处理后的结果输出文件是由reduce端输出的,reduce端的数据是由map端处理完通过shuffle过程拖至reduce端的,在shuffle过程中有个步骤就是分区,在Hadoop的这个过程中,每个map task处理完数据后,会先进行一次预聚合过程,聚合完成后把数据发送到Partitioner,由Partitioner来决定每条记录应该送往哪个reducer节点,如果我们没有自定义它的分区规则的话会自动使用是HashPartitioner,就是按照每个数据的hash值分入到不同的地方去,这也就是成为输出的文件是part-r-00000、part-r-00001、part-r-00003…等等的文件。
- 默认partitioner执行过程
- 首先获取得key值的哈希值
- 接着让这个哈希值处理后和设置的reduce数也就是numReduceTasks值求模
- 最后返回的这个模值就是reduce Task的编号,也就是该值需要去的那一个task的编号。
MapReduce自定义分区器的讲解
-
需求背景
- 自带的分区器只能够将key进行均衡的分布,通俗的说就是平均分布。因此他完成不了我们自己需要的那种方式去将不同要求的值放在不同的文件中去,所以自定义分区器的出现就存在了必要性。这样能够增加分区操作的多元化。
-
实例
- 有个文件,这个文件中出现了一些字符串,要求统计一下这些字符串每个字符串出现的次数。因为这些字符串有的是字母开头的,有些是数字开头的,有的是非字母也非数字开头的,现在有个要求就是在完成记录出现次数的同时要满足这些字符串按大写字母开头的放在一个文件下,小写字符开头的放在一个文件下,数字开头的放在一个文件下,剩下的放在另一个文件下这个要求。
-
做法
- 我们需要书写自己的分区器类,这个类很简单就是要继承Hadoop自带的Partitioner抽象类,重写其中的getPartition()方法,书写自己的分区规则。
-
文件
input.txt ------------------- hello world hi qianfeng Hi qianfeng Hello Qianfeng QQ 163.com 1603 @qq.com 123 123 (123) ------------------
-
代码
- 自定义分区器(partitioner)
import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Partitioner; /** * @description * @author: LuoDeSong 694118297@qq.com * @create: 2018-09-27 18:57:31 **/ /** * 继承Hadoop自带的Partitioner<Text, IntWritable>类 * 第一个参数Text是从mapper中传出的key * 第二个参数IntWritable是从mapper中传出的value */ public class MyPartitionar extends Partitioner<Text, IntWritable> { /* 重写了getPartition方法前两个参数是和类中的参数一致,后面一个参数是自定的文件的个数 这个里面的逻辑就是取出每个字符串的首字母进行匹配,是小写字母开头的话结果值返回的是1, 是大写字母开头的话返回的是2,是数字开头的话返回的是3,其余的返回的是0 */ @Override public int getPartition(Text key, IntWritable value, int numPartitions) { String flag = key.toString().substring(0, 1); if(flag.matches("[a-z]")) return 1 % numPartitions; if(flag.matches("[A-Z]")) return 2 % numPartitions; if(flag.matches("[0-9]")) return 3 % numPartitions; else return 4 % numPartitions; } }
- 书写mapper端
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; /** * @description * @author: LuoDeSong 694118297@qq.com * @create: 2018-09-27 18:52:57 **/ public class SplitWordMapper extends Mapper<LongWritable, Text, Text, IntWritable> { //拆分出每个字符串 @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] split = value.toString().split(" "); for (int i = 0; i < split.length; i++) { context.write(new Text(split[i]), new IntWritable(1)); } } }
- 书写reducer端
import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException; /** * @description * @author: LuoDeSong 694118297@qq.com * @create: 2018-09-27 21:08:00 **/ public class SplitWordReducer extends Reducer<Text, IntWritable, Text, Text> { @Override protected void setup(Context context) throws IOException, InterruptedException { context.write(new Text("统计结果如下:"), new Text("")); } //聚合计算出每个字符串出现的次数 @Override protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int count = 0; for(IntWritable ans : values) { count += ans.get(); } context.write(key, new Text(" " + count)); } }
注意:初学者可能会有疑惑为什么我们自己定义的分区器感觉没什么作用呢?不是没作用,其实作用已经体现了在进入reducer端的时候已经体现了。发挥作用的其实是在driver驱动器中的设置。
- 书写driver驱动器
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 org.qianfeng.bigdata.mapreduce.outtomanyfiles.mapper.SplitWordMapper; import org.qianfeng.bigdata.mapreduce.outtomanyfiles.partitioner.MyPartitionar; import org.qianfeng.bigdata.mapreduce.outtomanyfiles.reducer.SplitWordReducer; import java.io.IOException; /** * @description * @author: LuoDeSong 694118297@qq.com * @create: 2018-09-27 15:52:46 **/ public class SplitWordDriver { public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { Configuration conf = new Configuration(); //构建Job类的对象 Job job = Job.getInstance(conf); //给当前job类的对象设置job名称 //设置运行主类 job.setJarByClass(SplitWordDriver.class); //设置自定义的分区器 job.setPartitionerClass(MyPartitionar.class); //设置分区文件的个数 job.setNumReduceTasks(4); //设置job的Mapper及其输出K,V的类型 job.setMapperClass(SplitWordMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); //本程序不要自定义实现Reducer //设置job的输出K,V的类型,也可以说是Reducer输出的K,V的类型 job.setReducerClass(SplitWordReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); //设置要处理的HDFS上的文件的路径 FileInputFormat.addInputPath(job,new Path("E:\\Test-workspace\\test\\input\\nine.txt")); //设置最终输出结果的路径 FileOutputFormat.setOutputPath(job,new Path("E:\\Test-workspace\\test\\output\\nine")); //等待程序完成后自动结束程序 System.exit(job.waitForCompletion(true)?0:1); } }
- 说明:MapReduce的驱动器中设置了不再使用默认的partitioner而是使用自己定义的,代码为
//设置自定义的分区器 job.setPartitionerClass(MyPartitionar.class); //设置分区文件的个数 job.setNumReduceTasks(4);
- 结果
part-r-00000 ------------ 统计结果如下: (123) 1 @qq.com 1 ---------- part-r-00001 ------------ 统计结果如下: hello 1 hi 1 qianfeng 2 world 1 ----------- part-r-00002 ------------ 统计结果如下: Hello 1 Hi 1 QQ 1 Qianfeng 1 ------------ part-r-00003 ------------ 统计结果如下: 123 2 1603 1 163.com 1 -----------
-
end
- 以上就是实现自定义的分区器的案例代码,看懂以上的代码后那么自定义分区也已经有了一定的概念了。