MapTask并行度决定机制
maptask的并行度决定map阶段的任务处理并发度,进而影响到整个job的处理速度。那么,mapTask并行实例是否越多越好呢?其并行度又是如何决定呢?
-
一个job的map阶段并行度由客户端在提交job时决定,而客户端对map阶段并行度的规划的基本逻辑为
-
将待处理数据执行逻辑切片(即按照一个特定切片大小,将待处理数据划分成逻辑上的多个split),然后每一个split分配一个mapTask并行实例处理
-
切片定义在InputFormat类中的getSplit()方法,默认的切片机制:
- 简单地按照文件的内容长度进行切片
- 切片大小,默认等于block大小
- 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片
-
比如待处理数据有两个文件:
- file1.txt 320M
- file2.txt 10M
-
经过 经过FileInputFormat的切片机制运算后,形成的切片信息如下:
- file1.txt.split1-- 0~128
- file1.txt.split2-- 128~256
- file1.txt.split3-- 256~320
- file2.txt.split1-- 0~10M
-
-
Hadoop 中FileInputFormat类中的getSplits代码如下:

-
计算切片大小逻辑代码如下:
protected long computeSplitSize(long blockSize, long minSize,
long maxSize) {
return Math.max(minSize, Math.min(maxSize, blockSize));
}
-
因此切片大小主要由这几个值来运算决定
- minsize:默认值:1
- 配置参数: mapreduce.input.fileinputformat.split.minsize
- maxsize:默认值:Long.MAXValue
- 配置参数:mapreduce.input.fileinputformat.split.maxsize
- blocksize HDFS存储的数据块大小
- minsize:默认值:1
-
故而,默认情况下,切片大小=blocksize。maxsize(切片最大值):参数如果调得比blocksize小,则会让切片变小,而且就等于配置的这个参数的值。minsize (切片最小值):参数调的比blockSize大,则可以让切片变得比blocksize还大。
ReduceTask并行度的决定机制
reducetask的并行度同样影响整个job的执行并发度和执行效率,但与maptask的并发数由切片数决定不同,Reducetask数量的决定是可以直接手动设置。
- job.setNumReduceTasks(4); //默认值是1,手动设置为4
- 设置ReduceTask时需要指相应数据分区器Partitioner
- 如果数据分布不均匀,就有可能在reduce阶段产生数据倾斜。注意: reducetask数量并不是任意设置,还要考虑业务逻辑需求,有些情况下,需要计算全局汇总结果,就只能有1个reducetask
- 尽量不要运行太多的reduce task。对大多数job来说,最好rduce的个数最多和集群中的reduce持平,或者比集群的 reduce slots小。这个对于小集群而言,尤其重要。
流量统计需求
根据归属地输出流量统计数据结果到不同文件,以便于在查询统计结果时可以定位到省级范围进行
- 现有运营商流量日志如下,其中左起第二列为手机号,倒数三列为上行流量,倒数第二列为下行浏览量
1363157985066 13726230503 00-FD-07-A4-72-B8:CMCC 120.196.100.82 i02.c.aliimg.com 24 27 2481 24681 200
1363157995033 15920133257 5C-0E-8B-C7-BA-20:CMCC 120.197.40.4 sug.so.360.cn 20 20 3156 2936 200
- Mapreduce中会将map输出的kv对,按照相同key分组,然后分发给不同的reducetask默认的分发规则为:根据key的hashcode%reducetask数来分发。所以:如果要按照我们自己的需求进行分组,则需要改写数据分发(分组)组件Partitioner自定义一个ProvincePartitioner继承抽象类:Partitioner。然后在job对象中,设置自定义partitioner job.setPartitionerClass(CustomPartitioner.class)
- reduceTask默认分发规则如下:

- 自定义ProvincePartitioner:
import java.util.HashMap;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
/**
* K2 V2 对应的是map输出的KV类型
* @author potter
*
*/
public class ProvincePartitioner extends Partitioner<Text,FlowBean>{
private static HashMap<String , Integer> provinceDict = new HashMap<String , Integer>();
static {
provinceDict.put("136", 0);
provinceDict.put("137", 1);
provinceDict.put("138", 2);
provinceDict.put("139", 3);
}
@Override
public int getPartition(Text key, FlowBean value, int numPartitions) {
String prefix = key.toString().substring(0,3);
Integer provinceId = provinceDict.get(prefix);
return provinceId == null ? 4:provinceId;
}
}
- 自定义流量FlowBean实体类如下:
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.WritableComparable;
public class FlowBean implements WritableComparable<FlowBean>{
private long upFlow;
private long dFlow;
private long sumFlow;
public long getUpFlow() {
return upFlow;
}
public void setUpFlow(long upFlow) {
this.upFlow = upFlow;
}
public long getdFlow() {
return dFlow;
}
public void setdFlow(long dFlow) {
this.dFlow = dFlow;
}
public long getSumFlow() {
return sumFlow;
}
public void setSumFlow(long sumFlow) {
this.sumFlow = sumFlow;
}
public FlowBean(long upFlow, long dFlow) {
super();
this.upFlow = upFlow;
this.dFlow = dFlow;
this.sumFlow = upFlow+dFlow;
}
//反序列化时需要使用空参构造方法
public FlowBean() {
}
public void setBean(long upFlow,long dFlow){
this.upFlow = upFlow;
this.dFlow = dFlow;
this.sumFlow = upFlow + dFlow;
}
/**
* 序列化方法
*/
@Override
public void write(DataOutput out) throws IOException {
// TODO Auto-generated method stub
out.writeLong(upFlow);
out.writeLong(dFlow);
out.writeLong(sumFlow);
}
/**
* 反序列化方法
*/
@Override
public void readFields(DataInput in) throws IOException {
// TODO Auto-generated method stub
this.upFlow = in.readLong();
this.dFlow = in.readLong();
this.sumFlow = in.readLong();
}
@Override
public String toString() {
return upFlow + "\t" +dFlow + "\t" + sumFlow;
}
@Override
public int compareTo(FlowBean o) {
return this.sumFlow > o.getSumFlow() ? -1 : 1;
}
}
- Map 、Reduce以及相应的Job 代码如下:
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class FlowCount {
static class FlowCountMapper extends Mapper<LongWritable,Text,Text,FlowBean>{
protected void map(LongWritable key,Text value , Context context) throws IOException, InterruptedException{
//将一行内容转化成String
String line = value.toString();
//切分字段
String[] fields = line.split("\t");
//取出手机号
String phoneNbr = fields[1];
//取出上行、下行流量
long upFlow = Long.parseLong(fields[fields.length-3]);
long dFlow = Long.parseLong(fields[fields.length-2]);
context.write(new Text(phoneNbr),new FlowBean(upFlow,dFlow));
}
}
/**
* 接收的数据格式:<183532421,bean1><183532421,bean2><183532421,bean3><183532421,bean4>
* @author potter
*
*/
static class FlowCountReducer extends Reducer<Text,FlowBean,Text,FlowBean>{
protected void reduce(Text key,Iterable<FlowBean> values , Context context) throws IOException, InterruptedException{
long sum_upFlow = 0;
long sum_dFlow = 0;
//遍历所有bean,将其中的上行流量,下行流量分别累加
for(FlowBean bean : values){
sum_upFlow += bean.getUpFlow();
sum_dFlow += bean.getdFlow();
}
FlowBean resultBean = new FlowBean(sum_upFlow,sum_dFlow);
context.write(key, resultBean);
}
}
public static void main(String args[]) throws IOException, Exception{
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//指定本程序的jar包所在的本地路径
job.setJarByClass(FlowCount.class);
//指定本业务job要使用的mapper/Reducer业务类
job.setMapperClass(FlowCountMapper.class);
job.setReducerClass(FlowCountReducer.class);
//指定自定义的数据分区器
job.setPartitionerClass(ProvincePartitioner.class);
//指定相应分区数量的reducetask
job.setNumReduceTasks(5);
//指定mapper输出数据的KV类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
//指定最终输出的数据的KV类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
//指定job输出的原始文件所在目录
FileInputFormat.setInputPaths(job, new Path(args[0]));
//指定job输出结果所在目录
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//将job中配置的相关参数,以及job所用的java类所在的jar包,提交给yarn运行
boolean res = job.waitForCompletion(true);
System.exit(res?0:1);
}
}
- 打jar包放入集群运行,同时在HDFS的 /flowcount/input/ 目录下放入指定的流量日志文件 通过hadoop jar flowcount.jar cn.itcast.bigdata.mr.provinceflow.FlowCount /flowsum/input /flowsum/output2 命令运行jar包,并指定输入输出目录。运行结果如下,产生了5个文件,对应了5个ReduceTask。

- 查看其中的一个结果文件,可以看出手机号以136开头的被统计进了同一文件,即省代号为“0”的,用户流量被统计归入了同一文件。

本文探讨了Hadoop中MapTask和ReduceTask的并行度决定机制,详细解析了切片大小的计算逻辑,以及如何通过自定义Partitioner实现数据按需分组,最后通过一个流量统计案例展示了自定义Partitioner的具体应用。

8172

被折叠的 条评论
为什么被折叠?



