【hadoop】22.MapReduce-shuffle之分区

简介

分区(partition),属于Mapper阶段的流程。前面提到,线程首先根据数据最终要传的reducer把数据分成相应的分区(partition)。

默认情况下,采取的分区类是HashPartitioner

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;
  }
}

显然,默认分区是根据key的hashCode对reduceTasks个数取模得到的。用户没法控制哪个key存储到哪个分区,因此我们常常需要自定义分区类。

编写自定义分区并应用的流程大致分为3步。

  1. 自定义类继承Partitioner,重写getPartition()方法;
  2. 在job驱动类中,设置自定义partitioner;
  3. 自定义partition后,要根据自定义partitioner的逻辑设置相应数量的reduce task。

为什么需要设置reduce task的数量?

  • 如果reduceTask的数量 > getPartition的结果数,则会多产生几个空的输出文件part-r-000xx;
  • 如果1 < reduceTask的数量 < getPartition的结果数,则有一部分分区数据无处安放,会Exception;
  • 如果reduceTask的数量 = 1,则不管mapTask端输出多少个分区文件,最终结果都交给这一个reduceTask,最终也就只会产生一个结果文件 part-r-00000;

例如:假设自定义分区数为5,则

  • job.setNumReduceTasks(1);会正常运行,只不过会产生一个输出文件
  • job.setNumReduceTasks(2);会报错
  • job.setNumReduceTasks(6);大于5,程序会正常运行,会产生空文件;

1、分区案例——归属地分区电话号码流量统计

接下来我们完善之前的电话号码手机流量统计项目。

完善需求:将统计结果按照手机归属地不同省份输出到不同文件。

根据手机号的前三位可以区分不同归宿地的电话号码。

1、数据准备:依然是之前的输入文件phone_data.txt;

2、分析

Mapreduce中会将map输出的kv对,按照相同key分组,然后分发给不同的reducetask。默认的分发规则为:根据key的hashcode%reducetask数来分发。

如果要按照我们自己的需求进行分组,则需要改写数据分发(分组)组件Partitioner 自定义一个CustomPartitioner继承抽象类:Partitioner

最后再从job驱动中,设置自定义partitioner(之前我们使用默认的): job.setPartitionerClass(CustomPartitioner.class)

3、编写代码

(1)、在之前的项目中添加分区类

package com.zhaoyi.phoneflow;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;

public class FlowPartition extends Partitioner<Text, FlowBean> {
    public static final String PARTITION_136 = "136";
    public static final String PARTITION_137 = "137";
    public static final String PARTITION_138 = "138";
    public static final String PARTITION_139 = "139";
    @Override
    public int getPartition(Text text, FlowBean flowBean, int i) {
        // default partition is 0.
        int partition = 0;

        // get the phone left 3 number.
        String phonePrefix = text.toString().substring(0,3);

        // get partition.
        if(PARTITION_136.equals(phonePrefix)){
            partition = 1;
        }else if(PARTITION_137.equals(phonePrefix)){
            partition = 2;
        }else if(PARTITION_138.equals(phonePrefix)) {
            partition = 3;
        }else if(PARTITION_139.equals(phonePrefix)) {
            partition = 4;
        }
        return partition;
    }
}

(2)、在驱动类中指定自定义分区类

package com.zhaoyi.phoneflow;

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;

public class FlowDriver {
    public static void main(String[] args) throws Exception {
        if(args.length != 2){
            System.out.println("Please enter the parameter: data input and output paths...");
            System.exit(-1);
        }
        Job job = Job.getInstance(new Configuration());
        job.setJarByClass(FlowDriver.class);

        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReducer.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);

        // 设置分区类
        job.setPartitionerClass(FlowPartition.class);
        // 我们设置了5个分区,对应上。
        job.setNumReduceTasks(5);

        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        System.exit(job.waitForCompletion(true)? 1:0);
    }
}

注意分区返回数字是从0开始计数。

之前我们未指定分区类的时候,生成一个分区结果文件。这一次就会生成5个分区文件了,分别对应不同地区的电话号码记录。

本案例的具体代码参见github项目phoneflow模块。

2、分区案例 —— wordcount大小写分区

既然改造了电话号码案例,我们也来改造一下wordcount案例,将单词统计根据首字母大小写不同输出为2个文件。即大写字母开头的输出为一个文件,小写字母开头的输出为1个文件。

1、分析

只需在原有项目的基础上,添加分区类:获取每个单词的首字母,直接使用JAVA Character类API判断是不是小写字母,若不是,则统一判定为大写字母。

2、编写代码,添加分区类

package com.zhaoyi.wordcount;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;

public class WordCountPartition extends Partitioner<Text, IntWritable> {
    @Override
    public int getPartition(Text text, IntWritable intWritable, int i) {
        char firstLetter = text.toString().charAt(0);
        if(Character.isLowerCase(firstLetter)){
            return 1;
        }
        return 0;
    }
}

别忘了在驱动类中设置自定义分区类。

3、查看输出结果

分区文件有2个,分别代表大小写字母的统计结果

#### part-r-00000
Alice	2
So	1
`and	1
`without	1

#### part-r-00001
a	3
and	1
bank	1
......
......
the	3
this	1
......
......
was	3
what	1

注意省略号代表我删除掉的一些记录,只是为了限制篇幅,不属于文件内容。

本案例见github项目的word-count模块。

转载于:https://my.oschina.net/u/3091870/blog/3000621

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值