9.2.3 hadoop辅助排序(二次排序)实例详解

1.1.1       

1)二次排序定义

通常情况下我们只对键进行排序,例如(年份,温度)组成的键值对,我们通常只对key年份进行排序,如果先按照年份排好序,还要求年份相同的再按照温度进行进行逆序排列,像这样先按照第一字段进行排序,然后再对第一字段相同的行按照第二字段排序,我们称为二次排序

2)组合键定义

因为排序都是针对键的排序,现在要求按照两个字段进行排序,那么可以定义一个对象,包含两个字段,并且把这个对象作为map的输出键,就可以实现组合键的排序。如果map输出值的话不重要,就设置为NullWritable对象。因为这个对象要作为map的键,而且还要能够进行比较,所以对象要实现WritableComparable接口。定义一个两个字段的Class如下,first保存年份,second保存温度。实现序列化、反序列化接口、对比接口。对比接口是比较两个字段,按照第一字段升序排列,第一字段相同的按第二字段逆序排列。

package Temperature;


import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.WritableComparable;

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

public class FirstSecondPair implements WritableComparable<FirstSecondPair>{
    LongWritable first;//保存年份
    DoubleWritable second;//保存温度

    public FirstSecondPair(LongWritable first, DoubleWritable second) {
        this.first = first;
        this.second = second;
    }

    /**
     *
对比接口
     * @param
o
    
* @return
    
*/
   
public int compareTo(FirstSecondPair o) {
        int cmp=first.compareTo(o.getFirst());
        if (cmp!=0)//第一个字段不同,按第一个字段顺序排列
        {
            return cmp;
        }
        //第一个字段相同的情况,按照第二个字段逆序排列,-号是逆序
        return -second.compareTo(o.getSecond());

    }

    /**
     *
序列化
     * @param
dataOutput
    
* @throws IOException
     */
   
public void write(DataOutput dataOutput) throws IOException {
        first.write(dataOutput);
        second.write(dataOutput);
    }

    /**
     *
反序列化
     * @param
dataInput
    
* @throws IOException
     */
   
public void readFields(DataInput dataInput) throws IOException {
        first.readFields(dataInput);
        second.readFields(dataInput);
    }

    public LongWritable getFirst() {
        return first;
    }

    public void setFirst(LongWritable first) {
        this.first = first;
    }

    public DoubleWritable getSecond() {
        return second;
    }

    public void setSecond(DoubleWritable second) {
        this.second = second;
    }

    public void setFirstSecond(LongWritable first, DoubleWritable second) {
        this.first = first;
        this.second = second;
    }
}

3)二次排序步骤

需求:求出下列数据中气象站每年的最高气温,下面只是为了说明,实际肯定不只6行数据。6行数据保存在三个不同文件中。

文件1

1900 ~34

1901 ~17

1900 ~27

扫描二维码关注公众号,回复: 9180296 查看本文章

文件2

1901 ~11

1900 ~40

文件3

1902~13

1902 ~26

实现步骤:

1)定义map类,读取三个文件中的数据,定义组合键类FirstSecondPair,将年份写入第一个字段,温度写入第二个字段,实现CompareTo函数,按照年份升序,按照温度降序排列。

public static class SecondSortMaper extends Mapper<LongWritable, Text, FirstSecondPair, NullWritable>
{
    @Override
    //map的作用是解析记录,得到年和 温度,组成组合键输出
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        super.map(key, value, context);
        String strValue=value.toString();
        String values[]=strValue.split("~");
        int year=Integer.parseInt(values[0]);
        double temperature=Double.parseDouble(values[1]);
        //map输出默认按照键排序,也就是按照FirstSecondPair的CompareTo定义的排序规则排序
        context.write(new FirstSecondPair(new LongWritable(year),new DoubleWritable(temperature)),NullWritable.get());
    }
}

map处理后输出数据应该是如下这样。

1900 40        NULL

1900 34       NULL

1900 27       NULL

1901 17       NULL

1901 11       NULL

1902 26       NULL

1902 13       NULL

2)如果是大量数据的话,需要对多个reduce任务来处理,为了让年份相同的数据被同一个reduce处理,这样才能找出每一年的最高气温,需要定义分区类Partitioner,让年份相同数据进入同一个reduce分区。按组合键第一字段分区的类定义如下:

public static class FirstPartitioner extends Partitioner<FirstSecondPair,NullWritable>{
    @Override
    public int getPartition(FirstSecondPair firstSecondPair, NullWritable nullWritable, int numPartition) {
       //numPartition是分区数,根据年份取余得到分区编号,可以保证年份相同记录进入通一个分区,但一个分区内有多个年份的记录。
        //例如1900~2000年的数据,numPartition为10,则1900,1910,1930……2000这些取余后都为0,这些年份的记录都会放入分区0,
        //需要对分区0中的记录按照年份分组,setGroupingComparatorClass就起到了这个分组的作用,分组后再按温度逆序排序
        return (int) (firstSecondPair.getFirst().get()/numPartition);
    }
}

例如采用两个reduce任务,就会有两个分区,分区函数采用年份取余2。那么1900和1902年的数据都会被分到第一个reduce,1901年的数据被分到第二个reduce。shuftle分区后的数据如下所示:

分区1

1900 40       NULL

1900 34       NULL

1900 27       NULL

1902 26       NULL

1902 13       NULL

分区2

1901 17       NULL

1901 11       NULL

3)GroupingComparator的作用。在分区1中有1900和1902两个年份的数据,为了得到每一年的最高气温,需要对同一个分区的数据,按照年份进行分组,每一组的第一条数据就是我们想要的每年最高的气温。这时候就需要用GroupingComparator来实现分组,就是把年份相同的分为一组。分组类定义如下:

//一个reduce中有多个年份的数据,按照年份进行分组
public static class ReducerGroupingComparator extends WritableComparator
{
    @Override
    public int compare(WritableComparable a, WritableComparable b) {
        FirstSecondPair pairA=(FirstSecondPair)a;
        FirstSecondPair pairB=(FirstSecondPair)b;
        //年份相同,返回值为0的分为一组
        return pairA.getFirst().compareTo(pairB.getFirst());
    }
}

分区1中的数据分组之后变为

组1

1900 40        NULL

1900 34        NULL

1900 27        NULL

组2

1902 26        NULL

1902 13        NULL

分组并不是分为一组那么简单,还有按键聚合的功能。组1中三条记录虽然三个键都不相同<1900 40>、<1900 34>、<1900 27>,但是ReducerGroupingComparator进行键的比较只是按照第一字段年份进行比较,所以认为是相同的键,所以年份相同的键会被合并为第一个键<1900 40>,三条记录的值也会合并,变为<NULL,NULL,NULL>。这里的值并不重要。所以按键聚合之后数据为

组1

1900 40        <NULL,NULL,NULL>

组2

1902 26        <NULL,NULL >

4)分组之后再按键聚合的数据就是我们要获取的的每年的最高气温值,还要通过reduce函数按年排个序。在将键传给reduce函数的key,值组成的values的迭代器iterator传给reduce方法的入参values,这里的值其实不重要,直接将key写入输出文件,默认就会按照键FirstSecondPair排序(年份升序,没有年份相同的气温)。

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

reduce处理之后的的数据结果如下,这就是我们想要的每年的最高气温的数据。

1900 40

1901 17

1902 26

(4)详细的二次排序代码实例

package Temperature;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Partitioner;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.Tool;

import java.io.IOException;

public class MaxTemperatureUsingSecondSort extends Configured implements Tool {

    public static class SecondSortMaper extends Mapper<LongWritable, Text, FirstSecondPair, NullWritable>
    {
        @Override
        //map的作用是解析记录,得到年和 温度,组成组合键输出
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            super.map(key, value, context);
            String strValue=value.toString();
            String values[]=strValue.split("~");
            int year=Integer.parseInt(values[0]);
            double temperature=Double.parseDouble(values[1]);
            //map输出默认按照键排序,也就是按照FirstSecondPair的CompareTo定义的排序规则排序
            context.write(new FirstSecondPair(new LongWritable(year),new DoubleWritable(temperature)),NullWritable.get());
        }
    }
    //按照组合键的第一个字段年份进行分区,让相同年份的数据被同一个分区处理,才能比较出每一年的最高气温
    public static class FirstPartitioner extends Partitioner<FirstSecondPair,NullWritable>{
        @Override
        public int getPartition(FirstSecondPair firstSecondPair, NullWritable nullWritable, int numPartition) {
           //numPartition是分区数,根据年份取余得到分区编号,可以保证年份相同记录进入通一个分区,但一个分区内有多个年份的记录。
            //例如1900~2000年的数据,numPartition为10,则1900,1910,1930……2000这些取余后都为0,这些年份的记录都会放入分区0,
            //需要对分区0中的记录按照年份分组,setGroupingComparatorClass就起到了这个分组的作用,分组后再按温度逆序排序
            return (int) (firstSecondPair.getFirst().get()/numPartition);
        }
    }
    //一个reduce中有多个年份的数据,按照年份进行分组,分组后在按键进行聚合,年份相同的键都被认为是同一个键,聚合为第一个键,值都是NULL
    public static class ReducerGroupingComparator extends WritableComparator
    {
        @Override
        public int compare(WritableComparable a, WritableComparable b) {
            FirstSecondPair pairA=(FirstSecondPair)a;
            FirstSecondPair pairB=(FirstSecondPair)b;
            //年份相同,返回值为0的分为一组
            return pairA.getFirst().compareTo(pairB.getFirst());
        }
    }
    //与FirstSecondComparator中定义的默认对比函数功能相同,这个用于显示设置对比类
    public  static class FirstSecondComparator extends WritableComparator
    {
        @Override
        public int compare(WritableComparable a, WritableComparable b) {
            FirstSecondPair pairA=(FirstSecondPair)a;
            FirstSecondPair pairB=(FirstSecondPair)b;
            int cmp=pairA.getFirst().compareTo(pairA.getFirst());
            if (cmp!=0)
            {
                return cmp;
            }
            return -pairA.getSecond().compareTo(pairB.getSecond());
        }
    }
    //将已经排序、分组、聚合后的数据写入文件,默认按照FirstSecondPair进行排序。
    public static class SecondSortReducer extends Reducer<FirstSecondPair,NullWritable, FirstSecondPair,NullWritable>
    {
        @Override
        protected void reduce(FirstSecondPair key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
            super.reduce(key, values, context);
            context.write(key,NullWritable.get());
        }
    }
    public static class JobBuilder {
        public static Job parseInputAndOutput(Tool tool, Configuration conf, String[] args) throws IOException {
            if (args.length != 2) {
                return null;
            }
            Job job = null;
            try {
                job = new Job(conf, tool.getClass().getName());
            } catch (IOException e) {
                e.printStackTrace();
            }
            FileInputFormat.addInputPath(job, new Path(args[0]));
            FileOutputFormat.setOutputPath(job, new Path(args[1]));
            return job;
        }
    }
    public int run(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Job job =JobBuilder.parseInputAndOutput(this,getConf(),args);
        if (job==null)
        {
            return -1;
        }
        //设置map和reduce
        job.setMapperClass(SecondSortMaper.class);
        job.setReducerClass(SecondSortReducer.class);
        //显示设置排序类,先按照第一字段年份升序排列,年份相同的按照温度逆序排列。
        job.setSortComparatorClass(FirstSecondComparator.class);
        //设置分区类,根据年份分区,1990~2000分到10个分区,每个分区10年的数据
        job.setPartitionerClass(FirstPartitioner.class);
        //需要将每个分区中的10年的数据按照年份进行分组,每组的第一个值就是这一年的最高气温
        job.setGroupingComparatorClass(ReducerGroupingComparator.class);
        job.setOutputKeyClass(FirstSecondPair.class);
        job.setOutputValueClass(NullWritable.class);
        return job.waitForCompletion(true)? 0:1;
    }

}

参考文献:

https://blog.csdn.net/sinat_32329183/article/details/73741880

自己开发了一个股票智能分析软件,功能很强大,需要的点击下面的链接获取:

https://www.cnblogs.com/bclshuai/p/11380657.html

猜你喜欢

转载自www.cnblogs.com/bclshuai/p/12314002.html
今日推荐