持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情
上文我们说了在Reduce阶段可以自定义分区来防止数据倾斜情况的发生,那我们这篇文章呢,就详细的讲解下Partition分区,并且实现一个自定义分区的具体需求。
Partition分区
对于分区来说,我们首先需要知道 MR程序中的 ReduceTask实例进程 和 Partition 之间的关系。
-
如果 ReduceTask的数量 > getPatition的结果数,则会多产生几个空的输出文件part-r-000xx;
-
如果 1 < ReduceTask的数量 < getPatition的结果数,则有一部分分区数据无处安放,程序会报错Exception;
-
如果 ReduceTask的数量 = 1,则不管MapTask端输出多少个分区文件,最终结果都交给这一个RecceTask,最终也就只会产生─个结果文件part-r-00000 ;
这里还有两点我们需要注意:
- 如果分区数不是1,但是ReduceTask为1,程序是否会执行分区过程呢? 那么答案是: 程序不执行分区过程。因为在MapTask的源码中,执行分区的前提是先判断ReduceNum个数是否大于1,不大于1肯定不执行。
- 如果 ReduceTask = 0,表示程序中没有Reduce阶段,输出文件个数与 Map 个数一致;
自定义Partition步骤
(1) 自定义类继承Partitioner,重写getPartition()方法
public class CustomPartitioner extends Partitioner<Text,FlowBean> {
@override
public int getPartition(Text key,FlowBean value, int numPartiti ons){
// 控制分区代码逻辑
......
return partition ;
}
}
(2) 在Job驱动中,设置自定义Partitioner
job.setPartitionerClass(CustomPartitioner.class);
(3)自定义Partition后,根据自定义Partitioner的逻辑设置相应数量的ReduceTask
job.setNumR educeTasks(5);
Partition自定义分区demo
(1)需求
将统计结果按照手机归属地不同省份输出到不同文件中(分区)。
以 136、137、138、139开头的手机号都单独存放到4个文件中,剩余的手机号放到一个文件中。输入数据如下。
(2)自定义分区类
package com.mapreduce.partitioner;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
public class ProvincePartitioner extends Partitioner<Text, FlowBean> {
@Override
public int getPartition(Text text, FlowBean flowBean, int numPartitions) {
//获取手机号前三位prePhone
String phone = text.toString();
String prePhone = phone.substring(0, 3);
//定义一个分区号变量partition,根据prePhone设置分区号
int partition;
if("136".equals(prePhone)){
partition = 0;
}else if("137".equals(prePhone)){
partition = 1;
}else if("138".equals(prePhone)){
partition = 2;
}else if("139".equals(prePhone)){
partition = 3;
}else {
partition = 4;
}
//最后返回分区号partition
return partition;
}
}
(3)Mapper类
package com.mapreduce.writable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class FlowMapper extends Mapper<LongWritable, Text, Text, FlowBean> {
private Text outK = new Text();
private FlowBean outV = new FlowBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//1 获取一行数据,转成字符串
String line = value.toString();
//2 切割数据
String[] split = line.split("\t");
//3 抓取我们需要的数据
String phone = split[1];
String up = split[split.length - 3];
String down = split[split.length - 2];
//4 封装outK outV
outK.set(phone);
outV.setUpFlow(Long.parseLong(up));
outV.setDownFlow(Long.parseLong(down));
outV.setSumFlow();
//5 写出outK outV
context.write(outK, outV);
}
}
(3)Reducer类
package com.mapreduce.writable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class FlowReducer extends Reducer<Text, FlowBean, Text, FlowBean> {
private FlowBean outV = new FlowBean();
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
long totalUp = 0;
long totalDown = 0;
//1 遍历values
for (FlowBean flowBean : values) {
totalUp += flowBean.getUpFlow();
totalDown += flowBean.getDownFlow();
}
//2 封装outKV
outV.setUpFlow(totalUp);
outV.setDownFlow(totalDown);
outV.setSumFlow();
//3 写出outK outV
context.write(key,outV);
}
}
(4)Driver驱动类
package com.mapreduce.partitioner;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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 FlowDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//1 获取job对象
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//2 关联本Driver类
job.setJarByClass(FlowDriver.class);
//3 关联Mapper和Reducer
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
//4 设置Map端输出数据的KV类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
//5 设置程序最终输出的KV类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
// 指定自定义分区器
job.setPartitionerClass(ProvincePartitioner.class);
// 同时指定相应数量的ReduceTask
job.setNumReduceTasks(5);
//6 设置输入输出路径
FileInputFormat.setInputPaths(job, new Path("D:\\inputflow"));
FileOutputFormat.setOutputPath(job, new Path("D\\partitionout"));
//7 提交Job
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
总结
通过以上案例,我们可以看出,MapReduce代码的编写有基本的三个类,Map类、Reducer类以及Driver类,其中Driver驱动类中有固定7个步骤代码,
- 获取Job对象
- 关联本Drive类
- 关联Mapper和Reducer
- 设置Map端输出数据的KV类型
- 设置程序最终输出的KV类型
- 设置输入输出路径
- 提交Job