MapReduce练习之二次排序

MapReduce练习之二次排序

(所有文章均作为本人笔记使用,可以指点,但是不要喷我,谢谢。)
  本次练习的数据为历年世界气候数据,其数据格式为:
0010999999999991992010100004-24000-045700FM-13+9999KNFG V02099999999999999999N9999999N9+99999+99999999999ADDAG12000
0016999999999991992010100004+55700+004700FM-13+9999OU24 V0202301N012319999999N0040001N9+00971+99999102141ADDAG12000MW1101
0024999999999991992010100004+15600+041200FM-13+9999PIGM V0201501N004619999999N0200001N9+02511+02341101231ADDAG12000REMSYN00522234
0036999999999991992010100004+02100-038300FM-13+9999WXVH V0201101N006219999999N0100001N9+99999+99999999999ADDAG12000GF106991999999002501999999
  本次练习的目的是将其中数据提取出来,格式为:气象站编号(sid) 年份(year)\t温度(temp)。然后进行二次排序,首先按照气象站编号进行排序,当气象站编号相同时再按照温度进行排序。
  由于要进行两次排序,所以需要定义两个标准。即先按照某个标准排序,当该标准无法比较出大小(相等)时,则需要根据第二个标准进行排序。主要涉及map之后的shuffle阶段,包括分区、分组、排序过程。
  1.自定义标准。根据标准定义一个数据类型,其中包含这两个排序标准。其中先比较的标准称为自然键,后比较的标准称为自然值,这个自定义的数据类型称为复合键
  2.在分区阶段,需要控制数据流向,所以要自定义分区器。分区器的分区规则只和自然键有关系。
  3.在分组阶段,同样需要控制数据流向,所以需要自定义分组器。分组器的分组规则只和自然键有关系。
  4.在排序阶段,即二次排序的核心阶段。所以要自定义一个与整个复合键相关的比较器。

0.数据提取

package com.briup.bigdata.BD1805.hadoop.mr;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

import java.io.IOException;

public class ExtractSidYearTemp extends Configured implements Tool {

    static class ExtractSidYearTempMapper extends Mapper<LongWritable, Text,Text,Text>{

        private Text key = new Text();
        private Text value = new Text();

        private WeatherParser parser = new WeatherParser();
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            if (parser.parse(value)){
                this.key.set(parser.getSid());
                this.value.set(parser.getYear()+"\t"+parser.getTemp());
                context.write(this.key,this.value);
            }
        }
    }

    @Override
    public int run(String[] strings) throws Exception {

        Configuration conf = this.getConf();
        Path in = new Path(conf.get("in"));
        Path out = new Path(conf.get("out"));
        String type = conf.get("type");

        Job job = Job.getInstance(conf, this.getClass().getSimpleName());

        job.setJarByClass(this.getClass());

        job.setMapperClass(ExtractSidYearTempMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);
        job.setInputFormatClass(TextInputFormat.class);
        FileInputFormat.addInputPath(job,in);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);
        if ("txt".equals(type)) job.setOutputFormatClass(TextOutputFormat.class);
        if ("seq".equals(type)) job.setOutputFormatClass(SequenceFileOutputFormat.class);
        FileOutputFormat.setOutputPath(job,out);

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

    public static void main(String[] args) throws Exception {
        System.exit(ToolRunner.run(new ExtractSidYearTemp(),args));
    }
}

上面的代码将初始数据进行处理,处理后结果为:

sid					year	temp
001099999999999	1992	271.0
001099999999999	1992	254.0
001099999999999	1992	261.0
001099999999999	1992	253.0
001099999999999	1992	280.0
001099999999999	1992	271.0
001099999999999	1992	267.0
001099999999999	1992	265.0
001099999999999	1992	265.0
001099999999999	1992	77.0

代码可以通过指定type可以生成相应类型的文件,如在命令行加入 -Dtype=txt,则会生成TextOutPutFotmat类型。


1.自定义复合键


package com.briup.bigdata.BD1805.hadoop.mr;

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

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

public class SidTemp implements WritableComparable<SidTemp> {

    private Text sid = new Text();
    private DoubleWritable temp = new DoubleWritable();

    //在默认的情况下,该比较方法被用于分组阶段,排序阶段
    @Override
    public int compareTo(SidTemp o) {
        int sidComp = this.sid.compareTo(o.sid);
        int tempComp = this.temp.compareTo(o.temp);

        return sidComp==0?tempComp:sidComp;
    }

    @Override
    public void write(DataOutput dataOutput) throws IOException {
        this.sid.write(dataOutput);
        this.temp.write(dataOutput);

    }

    @Override
    public void readFields(DataInput dataInput) throws IOException {
        this.sid.readFields(dataInput);
        this.temp.readFields(dataInput);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        SidTemp sidTemp = (SidTemp) o;
        return Objects.equals(sid, sidTemp.sid) &&
                Objects.equals(temp, sidTemp.temp);
    }

    @Override
    public int hashCode() {
        return Objects.hash(sid, temp);
    }

    public Text getSid() {
        return sid;
    }

    public void setSid(Text sid) {
        this.sid = new Text(sid.toString());
    }

    public DoubleWritable getTemp() {
        return temp;
    }

    public void setTemp(DoubleWritable temp) {
        this.temp = new DoubleWritable(temp.get());
    }

    @Override
    public String toString() {
        return sid+"\t"+temp;
    }
}


2.自定义分区器

package com.briup.bigdata.BD1805.hadoop.mr;

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

public class SidTempPartitioner extends Partitioner<SidTemp, IntWritable> {
    /**
     *
     * @param sidTemp		Map端输出的key的数据类型
     * @param intWritable	Map端输出的value的数据类型
     * @param i 			整个MR程序中所指定的Reduce的个数
     * @return 			从Map端出来的数据应该进入的Reduce的编号
     */
    @Override
    public int getPartition(SidTemp sidTemp, IntWritable intWritable, int i) {
        return Math.abs(sidTemp.getSid().hashCode()%i);
    }
}

使用取绝对值方法的原因是:
  防止获取的哈希值为负值,导致结果为负。当结果为负值时,将找不到对应的Reduce,程序会报错。


3.自定义分组器

package com.briup.bigdata.BD1805.hadoop.mr;

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

public class SidTempGroupComprator extends WritableComparator {

    //由于WritableComparator不是泛型类,所以需要告诉MR程序该比较器是为哪个数据类型所编写的比较器
    //第二个参数的作用是当可以自动创建对象。用于第一个数据进行排序时,没有与之相比较的对象,所以需要可以自动创建。
    public SidTempGroupComprator() {
        super(SidTemp.class,true);
    }

    @Override
    public int compare(WritableComparable a, WritableComparable b) {
        SidTemp sta = (SidTemp) a;
        SidTemp stb = (SidTemp) b;
        //分组阶段只和复合键中的自然键有关系
        return sta.getSid().compareTo(stb.getSid());
    }
}


4.排序阶段

package com.briup.bigdata.BD1805.hadoop.mr;

import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.SequenceFileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

public class SecondarySortForSidTempYear extends Configured implements Tool{
    static class SecondarySortForSidTempYearMapper extends Mapper<Text,Text,SidTemp,IntWritable>{
        private SidTemp key=new SidTemp();
        private IntWritable value=new IntWritable();

        @Override
        protected void map(Text key,Text value,Context context) throws IOException, InterruptedException{
            String[] strs=value.toString().split("[\t]");

            this.key.setSid(key);
            this.key.setTemp(new DoubleWritable(Double.parseDouble(strs[1])));

            this.value.set(Integer.parseInt(strs[0]));

            context.write(this.key,this.value);
        }
    }

    @Override
    public int run(String[] strings) throws Exception{
        Configuration conf=this.getConf();
        Path in=new Path(conf.get("in"));
        Path out=new Path(conf.get("out"));

        Job job=Job.getInstance(conf,this.getClass().getSimpleName());
        job.setJarByClass(this.getClass());

        job.setMapperClass(SecondarySortForSidTempYearMapper.class);
        job.setMapOutputKeyClass(SidTemp.class);
        job.setMapOutputValueClass(IntWritable.class);
        job.setInputFormatClass(SequenceFileInputFormat.class);
        FileInputFormat.addInputPath(job,in);

        job.setOutputKeyClass(SidTemp.class);
        job.setOutputValueClass(IntWritable.class);
        job.setOutputFormatClass(TextOutputFormat.class);
        FileOutputFormat.setOutputPath(job,out);

        // 二次排序的相关设置
        job.setNumReduceTasks(5);

        job.setPartitionerClass(SidTempPartitioner.class);

        job.setGroupingComparatorClass(SidTempGroupComprator.class);

        job.setSortComparatorClass(SidTempSortComparator.class);

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

    public static void main(String[] args) throws Exception{
        System.exit(ToolRunner.run(new SecondarySortForSidTempYear(),args));
    }
}

  由于本次练习只需要排序,不需对数据做其他处理,所以不需要写Reducer,只要写出Mapper即可。


出现的错误

运行时出现了InstantiationException
  这个错误让我找了一个小时,结果发现只是因为我将分组比较器定义为类抽象类。编程还需要再认真一点啊。特地跟老师代码进行对比,发现一直也没发现错误,最后通过用老师代码代替我的代码,然后,再一个个将自己的代码放回去,多次执行,发现出错的类,最终才找到错误。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值