mapreduce的任务切片规划机制、job提交流程、Mapreduce中的分区Partitioner与流量汇总程序开发

来源:互联网 发布:mac photoshop 破解版 编辑:程序博客网 时间:2024/05/16 06:29

1、mapreduce的任务切片规划机制
一个job的map阶段并行度由客户端在提交job时决定
而客户端对map阶段并行度的规划的基本逻辑为:
将待处理数据执行逻辑切片(即按照一个特定切片大小,将待处理数据划分成逻辑上的多个split),然后每一个split分配一个mapTask并行实例处理,这段逻辑及形成的切片规划描述文件,由FileInputFormat实现类的getSplits()方法完成,其过程如下图:
这里写图片描述
分析:图中已经写的很清楚,拿到一个文件a.txt后会根据其大小进行划分。假设a.txt是300M的话,由于hdfs默认block大小为128M,所以三个block为0-128M,128M-256M,256M-300M,但是FileInputFormat是怎么根据文件大小来切片呢?会不会是三个100M呢,如下图右上角
这里写图片描述
如果是这样的话,那么三个maptask在三个datanode所在机器上运行处理即是,第一个maptask读取0-100M,第二个maptask在第一个机器上100M开始读,读到第二个机器的200M,由于第二个maptask在第二台机器上,要读第一台机器上的数据就需要进行网络传输,这显然会降低效率。所以根本不会这样划分切片的,默认就是根据blocksize大小来划分切片的,即0-128M,128-256M,256-300M划分出三个切片,每个切片对应一个maptask实例。刚好去处理三个对应的block。

注:切片是定义在FileInputFormat的 getSplit()方法中的,默认切片机制如下
a)简单地按照文件的内容长度进行切片
b)切片大小,默认等于block大小
c)切片时不考虑数据集整体,而是逐个针对每一个文件单独切片

reducetask的并行度同样影响整个job的执行并发度和执行效率,但与maptask的并发数由切片数决定不同,Reducetask数量的决定是可以直接手动设置,默认个数是1,手动设置4可以使用//默认值是1,手动设置为4 job.setNumReduceTasks(4);

2、流量汇总程序开发
流量汇总程序开发这篇文章已经写过了,这里添加了新需求,要求流量汇总统计并按省份区分。也就是说不但计算每个用户(手机号)的上行流量,下行流量,总流量外,还要按照每个手机号所属不同的省份来将计算结果写到不同的文件中(假如共4个省份,那么需要将输出结果写到4个文件中,也就是说有4个分区每个分区对应一个reduce task)。

public class Flowcount {    /**     * KEYIN:默认情况下,是mr框架所读到的一行文本的起始偏移量,Long,但是在hadoop中有自己的     * 更精简的序列化接口(Seria会将类结构都序列化,而实际我们只需要序列化数据),所以不直接用Long,而用LongWritable     * VALUEIN:默认情况下,是mr框架所读到的一行文本的内容,String,同上,用Text     * KEYOUT:是用户自定义逻辑处理完成之后输出数据中的key     * VALUEOUT:是用户自定义逻辑处理完成之后输出数据中的value     * @author 12706     *     */    static class FlowcountMapper extends Mapper<LongWritable, Text, Text, FlowBean>{        @Override        protected void map(LongWritable key, Text value, Context context)                throws IOException, InterruptedException {            //输入为1234    23455   33333   33333(中间是制表符)            //第二列为手机号,倒数第二列为下行流量,倒数第三列为上行流量            String line = value.toString();            String[] values = line.split("\t");            //获取手机号            String phoneNum = values[1];            //获取上行流量下行流量            long upFlow = new Long(values[values.length-3]);            long downFlow = new Long(values[values.length-2]);            //封装好后写出到输出收集器            context.write(new Text(phoneNum), new FlowBean(upFlow,downFlow));        }    }    /**     * KEYIN VALUEIN对应mapper输出的KEYOUT KEYOUT类型对应     * KEYOUT,VALUEOUT:是自定义reduce逻辑处理结果的输出数据类型     * KEYOUT     * VALUEOUT     * @author 12706     *     */    static class FlowcountReducer extends Reducer<Text, FlowBean, Text, FlowBean>{        @Override        protected void reduce(Text key, Iterable<FlowBean> beans,Context context)                throws IOException, InterruptedException {            //传进来的实例<13345677654,beans>,即多个该电话的键值对            //取出values获得上下行和总流量求和            long upFlow = 0;            long downFlow = 0;            for (FlowBean flowBean : beans) {                upFlow += flowBean.getUpFlow();                downFlow += flowBean.getDownFlow();            }            context.write(key, new FlowBean(upFlow,downFlow));        }    }    /**     * 相当于一个yarn集群的客户端     * 需要在此封装mr程序的相关运行参数,指定jar包     * 最后提交给yarn     * @author 12706     * @param args     * @throws Exception     *     */    public static void main(String[] args) throws Exception {        Configuration conf = new Configuration();        Job job = Job.getInstance(conf);        //指定Partitioner        job.setPartitionerClass(FlowPartitioner.class);        //设置reduce task数量        //可以设置1或者不设置或>5,但是不能设置2,3,4        job.setNumReduceTasks(5);        job.setJarByClass(Flowcount.class);        //指定本业务job要使用的mapper,reducer业务类        job.setMapperClass(FlowcountMapper.class);        job.setReducerClass(FlowcountReducer.class);        //虽然指定了泛型,以防框架使用第三方的类型                //指定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 b = job.waitForCompletion(true);        System.exit(b?0:1);    }}
public class FlowBean implements Writable{    private long upFlow;//上行流量    private long downFlow;//下行流量    private long totalFlow;//总流量    //序列化时需要无参构造方法    public FlowBean() {    }    public FlowBean(long upFlow, long downFlow) {        this.upFlow = upFlow;        this.downFlow = downFlow;        this.totalFlow = upFlow + downFlow;    }    //序列化方法 hadoop的序列化很简单,要传递的数据写出去即可    public void write(DataOutput out) throws IOException {        out.writeLong(upFlow);        out.writeLong(downFlow);        out.writeLong(totalFlow);    }    //反序列化方法 注意:反序列化的顺序跟序列化的顺序完全一致    public void readFields(DataInput in) throws IOException {        this.upFlow = in.readLong();        this.downFlow = in.readLong();        this.totalFlow = in.readLong();    }    //重写toString以便展示    @Override    public String toString() {        return upFlow + "\t" + downFlow + "\t" + totalFlow;    }    get,set方法}
/** * Mapreduce中会将map输出的kv对,按照相同key分组,然后分发给不同的reducetask *默认的分发规则为:根据key的hashcode%reducetask数来分发 *所以:如果要按照我们自己的需求进行分组,则需要改写数据分发(分组)组件Partitioner *自定义一个CustomPartitioner继承抽象类:Partitioner *然后在job对象中,设置自定义partitioner: job.setPartitionerClass(CustomPartitioner.class) * @author 12706 * */public class FlowPartitioner extends Partitioner<Text, FlowBean>{    private static HashMap<String, Integer> map = new HashMap<String, Integer>();    static {        //模拟手机号归属地 0:北京,1:上海,2:广州,3:深圳,4:其它        map.put("135", 0);        map.put("136", 1);        map.put("137", 2);        map.put("138", 3);    }    //返回分区号    @Override    public int getPartition(Text key, FlowBean value, int numPartitions) {        //进来的数据是<13567898766,flowbean1>,flowbean1中封装了上下行流量,总流量        String phoneNum = key.toString();        //截取手机号前3位        String num = phoneNum.substring(0, 3);        //获取对应的省        Integer provinceId = map.get(num);        return provinceId==null?4:provinceId;    }}

测试程序
将工程打jar包到本地,上传到linux,启动hadoop集群
数据以及在hdfs下的文件均使用流量汇总程序中的。使用以下命令

[root@mini2 ~]# hadoop jar flowcount.jar com.scu.hadoop.partitioner.Flowcount /flowcount/input /flowcount/output17/10/09 10:47:49 INFO mapreduce.JobSubmitter: number of splits:117/10/09 10:47:49 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1507516839481_000117/10/09 10:47:50 INFO impl.YarnClientImpl: Submitted application application_1507516839481_000117/10/09 10:47:50 INFO mapreduce.Job: The url to track the job: http://mini1:8088/proxy/application_1507516839481_0001/17/10/09 10:47:50 INFO mapreduce.Job: Running job: job_1507516839481_000117/10/09 10:47:58 INFO mapreduce.Job: Job job_1507516839481_0001 running in uber mode : false17/10/09 10:47:58 INFO mapreduce.Job:  map 0% reduce 0%17/10/09 10:48:03 INFO mapreduce.Job:  map 100% reduce 0%17/10/09 10:48:13 INFO mapreduce.Job:  map 100% reduce 20%17/10/09 10:48:14 INFO mapreduce.Job:  map 100% reduce 40%17/10/09 10:48:19 INFO mapreduce.Job:  map 100% reduce 100%17/10/09 10:48:20 INFO mapreduce.Job: Job job_1507516839481_0001 completed successfully17/10/09 10:48:21 INFO mapreduce.Job: Counters: 50        File System Counters                FILE: Number of bytes read=863                FILE: Number of bytes written=642893                FILE: Number of read operations=0                FILE: Number of large read operations=0                FILE: Number of write operations=0                HDFS: Number of bytes read=2278                HDFS: Number of bytes written=551                HDFS: Number of read operations=18                HDFS: Number of large read operations=0                HDFS: Number of write operations=10        Job Counters                 Killed reduce tasks=1                Launched map tasks=1                Launched reduce tasks=5                Data-local map tasks=1    ...

从打印信息可以看到切片splits为1,即一个maptask从Job Counters可以看出map tasks=1,reduce tasks=5所以输出文件应该也有5个。
查看输出

[root@mini2 ~]# hadoop fs -ls /flowcount/output-rw-r--r--   2 root supergroup          0 2017-10-09 10:48 /flowcount/output/_SUCCESS-rw-r--r--   2 root supergroup         84 2017-10-09 10:48 /flowcount/output/part-r-00000-rw-r--r--   2 root supergroup         53 2017-10-09 10:48 /flowcount/output/part-r-00001-rw-r--r--   2 root supergroup        104 2017-10-09 10:48 /flowcount/output/part-r-00002-rw-r--r--   2 root supergroup         22 2017-10-09 10:48 /flowcount/output/part-r-00003-rw-r--r--   2 root supergroup        288 2017-10-09 10:48 /flowcount/output/part-r-00004

确实是5个文件
查看每个文件内容

[root@mini2 ~]# hadoop fs -cat /flowcount/output/part-r-0000013502468823     7335    110349  11768413560436666     1116    954     207013560439658     2034    5892    7926[root@mini2 ~]# hadoop fs -cat /flowcount/output/part-r-0000113602846565     1938    2910    484813660577991     6960    690     7650...

按照省份划分了5个文件,每个文件里面有对应省份手机号与计算出的上行流量,下行流量,总流量。

3、job提交流量
跟踪源码看了挺晕,大体流程如下
这里写图片描述
通过上面的分析与开发也不难从大体上理解job提交流程了。