hadoop MapReduce自定义分区partition的作用和用法

背景

在Hadoop的MapReduce过程中,每个map task处理完数据后,如果存在自定义Combiner类,会先进行一次本地的reduce操作,然后把数据发送到Partitioner,由Partitioner来决定每条记录应该送往哪个reducer节点,默认使用的是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;
  }

}

上面的getPartition函数的作用:
1、获取key的哈希值
2、使用key的哈希值对reduce任务数求模
3、这样做的目的是可以把(key,value)对均匀的分发到各个对应编号的reduce task节点上,达到reduce task节点的负载均衡。

上面的代码只是实现了(key,value)键值对的均匀分布,但是无法实现如下需求:
1、假设输入的数据文件有4个,里面包含各个部门各个季度的销售额
2、使用mapreduce程序进行统计各个部门全年销售额,同时每个部门对应一个输出文件

由于输出的文件是区分数据类型的(部门类型),所以这个时候就需要我们自定义partition,分别把各个部门的数据分发到各自的reduce task上。

自定义分区

自定义分区很简单,我们只需要继承抽象类Partitioner,重写getPartition方法即可,另外还要给任务设置分区:job.setPartitionerClass(),就可以了。
注意:
自定义分区的数量需要和reduce task的数量保持一致。

代码演示

1、准备数据

[hadoop@hadoop1 ~]$ cat jidu1.txt 
研发部门        100
测试部门        90
硬件部门        92
销售部门        200
[hadoop@hadoop1 ~]$ cat jidu2.txt 
研发部门        200
测试部门        93
硬件部门        95
销售部门        230
[hadoop@hadoop1 ~]$ cat jidu3.txt 
研发部门        202
测试部门        92
硬件部门        94
销售部门        231
[hadoop@hadoop1 ~]$ cat jidu4.txt 
研发部门        209
测试部门        98
硬件部门        99
销售部门        251
[hadoop@hadoop1 ~]$ 

2、上传到hdfs上

[hadoop@hadoop1 ~]$ hdfs dfs -put jidu1.txt /jidu/input
18/06/08 19:45:45 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
[hadoop@hadoop1 ~]$ hdfs dfs -put jidu2.txt /jidu/input
18/06/08 19:45:52 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
[hadoop@hadoop1 ~]$ hdfs dfs -put jidu3.txt /jidu/input
18/06/08 19:45:58 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
[hadoop@hadoop1 ~]$ hdfs dfs -put jidu4.txt /jidu/input
18/06/08 19:46:05 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable

[hadoop@hadoop1 ~]$ hdfs dfs -ls /jidu/input
18/06/08 19:46:43 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Found 4 items
-rw-r--r--   1 hadoop supergroup         66 2018-06-08 19:45 /jidu/input/jidu1.txt
-rw-r--r--   1 hadoop supergroup         66 2018-06-08 19:45 /jidu/input/jidu2.txt
-rw-r--r--   1 hadoop supergroup         66 2018-06-08 19:45 /jidu/input/jidu3.txt
-rw-r--r--   1 hadoop supergroup         66 2018-06-08 19:46 /jidu/input/jidu4.txt

3、编写mapreduce程序
JiduMapper.java:

package com.demo;

import java.io.IOException;

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

public class JiduMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
    @Override
    protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context)
            throws IOException, InterruptedException {
        // TODO Auto-generated method stub
        String line=value.toString();
        String[] ss=line.split("\t");

        context.write(new Text(ss[0]), new IntWritable(Integer.parseInt(ss[1])));
    }
}

JiduReducer.java:

package com.demo;

import java.io.IOException;

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

public class JiduReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values,
            Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
        // TODO Auto-generated method stub
        int sum=0;
        for(IntWritable value:values)
        {
            sum=sum+value.get();
        }
        context.write(key, new IntWritable(sum));
    }
}

JiduPartitioner.java:

package com.demo;

import org.apache.hadoop.mapreduce.Partitioner;

public class JiduPartitioner<K, V> extends Partitioner<K, V>{

    @Override
    //自定义partition的数量需要和reduce task数量保持一致
    public int getPartition(K key, V value, int numPartitions) {
        // TODO Auto-generated method stub
        String dname=key.toString();
        switch(dname)
        {
        case "研发部门":return 0;
        case "测试部门":return 1;
        case "硬件部门":return 2;
        case "销售部门":return 3;
        }
        return 4;
    }

}

JiduRunner.java:

package com.demo;

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 JiduRunner {
    public static void main(String[] args) throws Exception{
        // TODO Auto-generated method stub
        Configuration conf=new Configuration();
        Job job=Job.getInstance(conf);
        job.setJarByClass(JiduRunner.class);
        job.setMapperClass(JiduMapper.class);
        job.setReducerClass(JiduReducer.class);
        job.setCombinerClass(JiduReducer.class);
        job.setPartitionerClass(JiduPartitioner.class);

        job.setNumReduceTasks(4);//设置reduce task数量为4

        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

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

        FileInputFormat.setInputPaths(job, new Path("hdfs://192.168.16.2:9000/jidu/input"));

        FileOutputFormat.setOutputPath(job, new Path("hdfs://192.168.16.2:9000/jidu/output"));

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

输出结果:

[hadoop@hadoop1 ~]$ hdfs dfs -ls /jidu/output
18/06/08 20:59:35 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Found 5 items
-rw-r--r--   3 hadoop supergroup          0 2018-06-08 20:56 /jidu/output/_SUCCESS
-rw-r--r--   3 hadoop supergroup         17 2018-06-08 20:56 /jidu/output/part-r-00000
-rw-r--r--   3 hadoop supergroup         17 2018-06-08 20:56 /jidu/output/part-r-00001
-rw-r--r--   3 hadoop supergroup         17 2018-06-08 20:56 /jidu/output/part-r-00002
-rw-r--r--   3 hadoop supergroup         17 2018-06-08 20:56 /jidu/output/part-r-00003
[hadoop@hadoop1 ~]$ hdfs dfs -cat /jidu/output/part-r-00000
18/06/08 20:59:50 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
研发部门        711
[hadoop@hadoop1 ~]$ hdfs dfs -cat /jidu/output/part-r-00002
18/06/08 20:59:55 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
硬件部门        380
[hadoop@hadoop1 ~]$ hdfs dfs -cat /jidu/output/part-r-00001
18/06/08 21:00:04 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
测试部门        373
[hadoop@hadoop1 ~]$ hdfs dfs -cat /jidu/output/part-r-00003
18/06/08 21:00:09 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
销售部门        912
[hadoop@hadoop1 ~]$ 

OK,到此自定义partition就完成了。

MapReduce是一种用于处理大规模数据集的编程模型和软件框架。Hadoop是一个基于MapReduce模型的分布式文件存储和处理系统。在Hadoop中,MapReduce被广泛用于数据处理和分析任务。 自定义二次排序是MapReduce中常见的一种需求,其目的是对MapReduce的输出进行排序。下面我们来介绍一下如何在Linux上使用Hadoop实现自定义二次排序。 1. 准备数据 首先我们需要准备一个数据集,假设我们有一个文本文件,每行包含两个字段,分别为学生姓名和成绩,中间用制表符分隔。例如: ``` Tom 80 Jerry 70 Mike 90 Lucy 85 ``` 2. 编写Mapper代码 自定义二次排序需要进行两次排序,第一次按照学生姓名进行排序,第二次按照成绩进行排序。因此,我们需要在Mapper中将学生姓名和成绩作为Key-Value输出。 我们可以使用TextPair类来存储学生姓名和成绩,代码如下: ``` public class SortMapper extends Mapper<LongWritable, Text, TextPair, Text> { private TextPair pair = new TextPair(); public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] fields = value.toString().split("\t"); pair.set(fields[0], fields[1]); context.write(pair, value); } } ``` 在这段代码中,我们首先将输入的一行数据拆分成学生姓名和成绩两个字段,然后使用TextPair类将它们作为Key输出,原始数据作为Value输出。 3. 编写Partitioner代码 Partitioner用于对Mapper的输出进行分区,以确保相同Key的数据被分配到同一个Reducer中。在自定义二次排序中,我们需要按照学生姓名进行分区,因此我们可以使用HashPartitioner来进行分区,代码如下: ``` public class SortPartitioner extends Partitioner<TextPair, Text> { public int getPartition(TextPair key, Text value, int numPartitions) { return (key.getFirst().hashCode() & Integer.MAX_VALUE) % numPartitions; } } ``` 在这段代码中,我们使用HashPartitioner将学生姓名的HashCode和Partition数取模来确定数据被分配到哪个Reducer中。 4. 编写GroupComparator代码 GroupComparator用于将相同学生姓名的数据分配到同一个Reducer中,代码如下: ``` public class SortGroupComparator extends WritableComparator { protected SortGroupComparator() { super(TextPair.class, true); } public int compare(WritableComparable a, WritableComparable b) { TextPair pair1 = (TextPair) a; TextPair pair2 = (TextPair) b; return pair1.getFirst().compareTo(pair2.getFirst()); } } ``` 在这段代码中,我们重载了compare方法,用于比较两个Key的学生姓名是否相同。 5. 编写SortComparator代码 SortComparator用于对每个Reducer中的数据进行排序,按照成绩从大到小排序,代码如下: ``` public class SortComparator extends WritableComparator { protected SortComparator() { super(TextPair.class, true); } public int compare(WritableComparable a, WritableComparable b) { TextPair pair1 = (TextPair) a; TextPair pair2 = (TextPair) b; int cmp = pair1.getFirst().compareTo(pair2.getFirst()); if (cmp != 0) { return cmp; } return -pair1.getSecond().compareTo(pair2.getSecond()); } } ``` 在这段代码中,我们首先比较两个Key的学生姓名是否相同,如果相同则比较成绩,否则直接返回姓名比较结果。 6. 编写Reducer代码 Reducer用于对Mapper的输出进行聚合和处理。在自定义二次排序中,我们只需要将每个学生的成绩按照从高到低的顺序输出即可,代码如下: ``` public class SortReducer extends Reducer<TextPair, Text, Text, Text> { public void reduce(TextPair key, Iterable<Text> values, Context context) throws IOException, InterruptedException { for (Text value : values) { context.write(key.getFirst(), value); } } } ``` 在这段代码中,我们首先输出学生姓名,然后按照原始数据的顺序输出。 7. 编写Driver代码 最后,我们需要编写Driver代码来启动MapReduce作业。代码如下: ``` public class SortDriver extends Configured implements Tool { public int run(String[] args) throws Exception { Job job = Job.getInstance(getConf()); job.setJarByClass(SortDriver.class); job.setMapperClass(SortMapper.class); job.setPartitionerClass(SortPartitioner.class); job.setGroupingComparatorClass(SortGroupComparator.class); job.setSortComparatorClass(SortComparator.class); job.setReducerClass(SortReducer.class); job.setMapOutputKeyClass(TextPair.class); job.setMapOutputValueClass(Text.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); return job.waitForCompletion(true) ? 0 : 1; } public static void main(String[] args) throws Exception { int exitCode = ToolRunner.run(new SortDriver(), args); System.exit(exitCode); } } ``` 在这段代码中,我们首先创建一个Job实例,然后设置Mapper、Partitioner、GroupComparator、SortComparator和Reducer等类。最后,我们指定输入路径和输出路径,并启动作业。 以上就是在Linux上使用Hadoop实现自定义二次排序的流程。通过这个例子,您可以了解到如何在Linux系统上使用MapReduce编程模型和Hadoop分布式文件存储和处理系统来处理大规模数据集。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

历史五千年

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

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

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

打赏作者

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

抵扣说明:

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

余额充值