Hadoop(十一) MapReduce Partitioner分区

Partitioner :是用来对 mapper 输出的数据进行分区的,partitioner通过哈希函数对Key 或者 Key 的子集求哈希值,哈希值相同的键值对将在同一个分区里面。在MapReduce中,默认的分区机制是基于键(key)的哈希分区(Hash Partitioning)。MapReduce框架将Map阶段产生的键值对按照键进行哈希运算,然后将结果对分区数取模,从而确定每个键值对应的分区。这样可以确保具有相同键的键值对会被分配到相同的分区,方便后续的Reduce阶段进行处理

当MapReduce中有多个reduce task执行的时候,此时map task的输出就会面临一个问题:究竟将自己的输出数据交给哪一个reducetask来处理,这就是数据分区(partition)

 

默认情况下,MapReduce是只有一个reducetask来进行数据的处理。这就使得不管输入的数据量多大,最终的结果都是输出到一个文件中
当改变reducetask个数的时候,作为maptask就会涉及到分区的问题,即:MapTask输出的结果如何分配给各个ReduceTask来处理
Map的数量由数据文件大小决定,即map的数量=数据文件大小(M)/128M。

在MapReduce中,通过我们指定分区,会将同一个分区的数据发送到同一个Reduce当中进行处理。例如:为了数据的统计,可以把一批类似的数据发送到同一个Reduce当中,在同一个Reduce当中统计相同类型的数据,就可以实现类似的数据分区和统计等。其实就是相同类型的数据,有共性的数据,送到一起去处理。MapReduce默认是只有一个分区(一个分区就是对应一个输出文件)

总结一下

分区的作用是对数据进行分类处理
在分区的时候,需要对分区进行编号,编号从0开始
在MapReduce中,默认只有1个分区,也只有一个1个ReduceTask来处理数据,最后只会产生一个结果文件
分区的数量决定ReduceTask的数量
在MapReduce中,如果不指定Partitioner,那么默认使用的是HashPartitioner,

在MapReduce中,需要对分区进行编号,编号从0开始依次往上递增 在MapReduce中,如果不指定,那么默认只有1个ReduceTask,每一个ReduceTask会对应一个结果文件。也因此,如果设置了Partitioner,那么需要给定对应数量的ReduceTask - 分区数决定了ReduceTask的数量

案例1:

数据,partion.txt,按第二列地名分类,统计各地区每个人的销售金额 ,先将上传到hdfs中

202412 sh zhangsan 10000
202412 js lisi 10000
202412 sh liming 20000
202401 sh zhangsan 3000
202401 js lisi 10000

代码:

Mapper

package partion;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

// 用于完成Map阶段
// 再MapReduce中,要求被处理的数据能够被序列化
// MApReduce提供了一套单独的序列化机制
// KEYIN-输入的键的类型。如果不指定,那么默认情况下,表示行的字节偏移量
// VALUEIN-输入值得类型。如果不指定,那么默认情况下,表示的读取到的一行数据
// KEYOUT-输出的键的类型。当前案例中,输出的键表示的是姓名
// VALUEOUT-输出的值的类型。当前案例,输出的值表示的是money
public class CityFlowMapper extends Mapper<LongWritable, Text, Text, CityPeopleInfo> {

    // 覆盖map方法,将处理逻辑写到这个方法中
    // key:键。表示的是行的字节偏移量
    // value:值。表示读取到的一行数据
    // context:配置参数

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] array = value.toString().split(" ");
        CityPeopleInfo p = new CityPeopleInfo();
        //202412 sh zhangsan 10000
        p.setCity(array[1]);//sh
        p.setMoney(Integer.parseInt(array[3]));//10000
        //name zhangsan
        context.write(new Text(array[2]),p);

    }
}

Reducer

package partion;

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

import java.io.IOException;

// KEYIN、VALUEIN输入的键的类型。
// Reducer的数据从Mapper来的
// 所以Mapper的输出就是Reducer的输入
// KEYOUT、VALUEOUT-输出的值的类型。当前案例中,要输出每一个人的金额
public class CityFlowReducer extends Reducer<Text, CityPeopleInfo,Text, IntWritable> {

    protected void reduce(Text key, Iterable<CityPeopleInfo> values, Context context) throws IOException, InterruptedException {
int sum = 0;
        for (CityPeopleInfo p : values) {
             sum+= p.getMoney();
        }
        //3、写入上下文
        context.write(key,new IntWritable(sum));
    }
}

 Partion

package partion;

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

public class CityPartion extends Partitioner<Text,CityPeopleInfo> {

     public int getPartition(Text text,CityPeopleInfo flow,int i){
        String city=flow.getCity();
        if(city.equals("sh"))return 0;
        else if(city.equals("sx"))return 1;
        else return 2;
        }
}

实体类

package partion;

import org.apache.hadoop.io.Writable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

public class CityPeopleInfo implements Writable {
    private int money;

    private String city="";

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }



    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    private int downFlow;

    // 需要将有必要的属性依次序列化写出即可
    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(getCity());
        out.writeInt(getMoney());
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        setCity(in.readUTF());
        setMoney(in.readInt());
    }

}

main

package partion;


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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;

import java.io.IOException;

public class CityJobMain {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //一、初始化Job
        Configuration configuration = new Configuration();

        //获取运行命令的参数,参数一:输入文件路径,参数二:输出文件路径
        //如果输入路径是一个文件,那么只处理这个文件,如果指定的路径是目录,则处理这个目录下的所有文件
        //输出路径只能是不存在的目录名
        String [] otherArgs = new GenericOptionsParser(configuration,args).getRemainingArgs();
        if(otherArgs.length < 2){
            System.err.println("必须提供输入文件路径和输出文件路径");
            System.exit(2);
        }
        Job job = Job.getInstance(configuration, "mr");
        job.setJarByClass(CityJobMain.class);

        //二、设置Job的相关信息 8个小步骤
        //1、设置输入路径
        job.setInputFormatClass(TextInputFormat.class);
        //本地运行
        //TextInputFormat.addInputPath(job,new Path("/tmp/input/mr1.txt"));
        TextInputFormat.addInputPath(job,new Path(args[0]));
//特别注意,有几个分区要写到这
        job.setNumReduceTasks(3);
        job.setPartitionerClass(CityPartion.class);

        //2、设置Mapper类型,并设置输出键和输出值
        job.setMapperClass(CityFlowMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(CityPeopleInfo.class);

        //shuffle阶段,使用默认的
        //3、设置Reducer类型,并设置输出键和输出值
        job.setReducerClass(CityFlowReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);

        //4、设置输出路径
        job.setOutputFormatClass(TextOutputFormat.class);
        //本地运行
        //TextOutputFormat.setOutputPath(job,new Path("/tmp/output/mr"));
        TextOutputFormat.setOutputPath(job,new Path(args[1]));
        //三、等待完成
        boolean b = job.waitForCompletion(true);
        System.out.println(b==true?"MapReduce 任务执行成功!":"MapReduce 任务执行失败!");
        System.exit(b ? 0 : 1);
    }
}

打成jar包上传到linux上

执行

hadoop jar /hadoopmapreduce-1.0-SNAPSHOT.jar partion.CityJobMain /partion.txt /mypeopleinfo2

 使用hdfs查看,生成的结果文件有三个,三个分区

分别查看三个文件的计算结果

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要按年份分区,可以使用自定义Partitioner类来实现。以下是一个例子,假设您有一个输入文件,其中每行包含一个日期和一些数据: ``` 2019-01-01 10 2018-12-31 20 2019-02-01 30 ``` 您需要将此数据按照年份分区,并且每个分区的数据都应该按照日期排序。为此,您可以编写以下Partitioner类: ```java import org.apache.hadoop.mapreduce.Partitioner; public class YearPartitioner extends Partitioner<Text, IntWritable> { @Override public int getPartition(Text key, IntWritable value, int numPartitions) { String year = key.toString().substring(0, 4); return Integer.parseInt(year) % numPartitions; } } ``` 这个Partitioner类会从键中提取年份,并将其用作分区键。这样,相同年份的数据都会被分配到相同的分区中。在这里,我们使用了numPartitions参数,该参数由Hadoop运行时环境传递,表示总分区数。这意味着,如果您的集群有10个节点,那么您会得到10个分区。 为了确保每个分区内的数据按照日期排序,您可以在Mapper中对键进行排序: ```java import java.io.IOException; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; public class YearMapper extends Mapper<Object, Text, Text, IntWritable> { private Text year = new Text(); private IntWritable data = new IntWritable(); @Override public void map(Object key, Text value, Context context) throws IOException, InterruptedException { String[] parts = value.toString().split(" "); year.set(parts[0]); data.set(Integer.parseInt(parts[1])); context.write(year, data); } } ``` 在这个Mapper中,我们将键设置为日期,值设置为数据。这样,对于相同年份的数据,它们会按照日期排序。最后,我们将分区键和数据写入输出。 接下来,您需要在Job中设置Partitioner类: ```java 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; public class YearJob { public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); Job job = Job.getInstance(conf, "year partitioning"); job.setJarByClass(YearJob.class); job.setMapperClass(YearMapper.class); job.setPartitionerClass(YearPartitioner.class); job.setNumReduceTasks(4); // set the number of reducers job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } } ``` 在这个Job中,我们设置了Partitioner类,并将其设置为使用4个reduce任务。这意味着,我们将得到4个输出文件,每个文件包含一个分区的数据。最后,我们运行Job并等待它完成。 以上就是按年份分区MapReduce程序的实现方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Allen019

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值