hadoop排序之二次排序

默认情况下,在MapReduce中的shuffer阶段会自动进行排序,而且是根据key进行排序的。但是有时候需要对 Key 排序的同时再对 Value 进行排序,这时候就要用到二次排序了。

我们把二次排序分为以下几个阶段。

Map输出阶段:

在 Map 阶段的最后,会先调用 job.setPartitionerClass() 对这个 Mapper 的输出结果进行分区,每个分区映射到一个Reducer。每个分区内又调用
job.setSortComparatorClass() 设置的 Key 比较函数类排序。可以看到,这本身就是一个二次排序。如果没有通过 job.setSortComparatorClass() 设置 Key 比较函数类,则使用 Key 实现的 compareTo() 方法。我们既可以使用 sortBean实现的 compareTo() 方法,也可以专门定义 Key 比较函数类。

Reduce阶段:

在 Reduce 阶段,reduce() 方法接受所有映射到这个 Reduce 的 map 输出后,也是会调用 job.setSortComparatorClass()方法设置的 Key 比较函数类,对所有数据进行排序。然后开始构造一个 Key 对应的 Value 迭代器。这时就要用到分组,使用job.setGroupingComparatorClass()方法设置分组函数类。只要这个比较器比较的两个 Key相同,它们就属于同一组,它们的 Value 放在一个 Value 迭代器,而这个迭代器的 Key 使用属于同一个组的所有Key的第一个Key。最后就是进入 Reducer 的 reduce() 方法, reduce() 方法的输入是所有的 Key 和它的 Value 迭代器,同样注意输入与输出的类型必须与自定义的 Reducer 中声明的一致。

二次排序实战:
对学生的两门课数学英语成绩排序,先按照数学成绩从小到大排序,当数学成绩相同时,按照英语成绩进行从小到大排序,并且按照数学成绩的分数对输出文件进行划分,60-70在1号分区,70到80在2号分区,80以上在三号分区。

输入数据(第一列为数学成绩,第二列为英语成绩):

在这里插入图片描述

1.自定义bean对象实现WritableComparable接口,在此类中重写序列化和反序列化方法,并且给出排序的逻辑。

package com.sort;

import org.apache.hadoop.io.WritableComparable;

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

public class sortBean implements WritableComparable<sortBean> {
    private int math;
    private int english;

    public sortBean() {
    }

    public int getMath() {
        return math;
    }

    public void setMath(int math) {
        this.math = math;
    }

    public int getEnglish() {
        return english;
    }

    public void setEnglish(int english) {
        this.english = english;
    }

    @Override
    public String toString() {
        return  math +" "+english ;

    }

    //二次排序的过程,先按数学成绩从大到小排序,再按英语成绩从大到小排序
    @Override
    public int compareTo(sortBean s) {
        int result;
        if (this.math > s.math)
            result = 1;
        else if (this.math <s.math)
            result = -1;
        else {
            result = (this.english > s.english) ?1:-1;
        }
        return result;
    }

    //重新序列化方法和反序列化方法
    @Override
    public void write(DataOutput out) throws IOException {
        out.writeInt(math);
        out.writeInt(english);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        math = in.readInt();
        english = in.readInt();
    }
}

  1. Mapper端
package com.sort;

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

import java.io.IOException;

public class sortMapper extends Mapper<LongWritable, Text, sortBean, NullWritable>{
    @Override
    protected void map(LongWritable key, Text values, Context context) throws IOException, InterruptedException {
        String words = values.toString();
        String[] s = words.split(" ");

        //封装bean对象
        sortBean sortbean = new sortBean();
        sortbean.setMath(Integer.parseInt(s[0])); //注意转换一下类型
        sortbean.setEnglish(Integer.parseInt(s[1]));

        //写入上下文
        context.write(sortbean,NullWritable.get());
    }
}

  1. 自定义分区类继承Partitioner,因为Partitioner是框架默认的分区,在此类中给出分区的逻辑。
package com.sort;

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

public class sortPartitioner extends  Partitioner<sortBean, NullWritable> {
    // <sortBean, NullWritable>  注意K,V为Mapper端的输出
    @Override
    public int getPartition(sortBean sortbean, NullWritable nullWritable, int numPartitions) {

        //按照数学成绩进行分区,60-70为第一分区,70-80为第二分区,其它的为第三分区
        int result = 0;
        if (sortbean.getMath() >=60 && sortbean.getMath() <70)
           result = 1;
        else if(sortbean.getMath() >=70 && sortbean.getMath() <80)
            result = 2;
        else
            result =3;

        return result;
    }
}

  1. 自定义分组函数类。在reduce阶段,构造一个key对应的value迭代器的时候,只要数学成绩相同就属于同一个组,放在一个value迭代器。这是一个比较器,需要继承WritableComparator。
package com.sort;

import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;

public class GroupingComparator extends WritableComparator {

    // 在进行分组排序的时候一定要有一个无参的构造器
    protected GroupingComparator() {
        super(sortBean.class, true);
    }

    @Override
    public int compare(WritableComparable a, WritableComparable b) {
        //父类向下转型
        sortBean aBean = (sortBean) a;
        sortBean bBean = (sortBean) b;

        int result;
        if (aBean.getEnglish() > bBean.getEnglish()) {
            result = 1;
        } else if (aBean.getEnglish() < bBean.getEnglish()) {
            result = -1;
        } else {
            result = 0;
        }

        return result;
    }
}


  1. 自定义Reduce
package com.sort;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class sortReduce extends Reducer<sortBean, NullWritable,sortBean,NullWritable> {
    @Override
    protected void reduce(sortBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
        context.write(key,NullWritable.get());
    }
}

  1. 驱动的主类
package com.sort;

import com.MapReduce.test.WordcountDriver;
import com.MapReduce.test.WordcountMapper;
import com.MapReduce.test.WordcountReducer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class sortDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        String inputPath = "e:/input/sort.txt"; //文件的输入路径
        String outputPath = "e:/output3";   // 文件的输出路径
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);  //获取一个job的实例


        job.setJarByClass(sortDriver.class); // 通过反射给定类的来源来设置Jar
        job.setMapperClass(sortMapper.class); // 关联Map和Reduce
        job.setReducerClass(sortReduce.class);

        job.setMapOutputKeyClass(sortBean.class);  // Mapper阶段的输出的K,V类型
        job.setMapOutputValueClass(NullWritable.class);

        job.setOutputKeyClass(sortBean.class);  // 程序最终输出的K,V类型
        job.setOutputValueClass(NullWritable.class);

        FileInputFormat.setInputPaths(job,new Path(inputPath));
        FileOutputFormat.setOutputPath(job,new Path(outputPath));

        //设置分区
        job.setPartitionerClass(sortPartitioner.class);
        job.setNumReduceTasks(4);  //4个分区,所以开了4个ReduceTasks

        //设置reduce端的分组
        job.setGroupingComparatorClass(GroupingComparator.class);

        boolean flag = job.waitForCompletion(true);
        //交给yarn去执行,直到执行结束才退出本程序,true为程序运行时可见此过程
        System.exit(flag ? 0 : 1);

    }
}

运行结果:

在这里插入图片描述

分别打开part1,2,3这三个文件

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
可以看到程序的执行结果已经是我们预期所希望的。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
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分布式文件存储和处理系统来处理大规模数据集。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值