MapReduce--自定义分组及TopN的实现

1、分组:默认分组按照整个Key进行分组,Key相同,就是同一组

  • 本质:类似于排序,也是一种比较如果相同就是同一组,如果不同就不是一组
  • 自定义分组比较器的规则
    • Class extends WritableComparator
    • 重写compare方法
      • 如果返回0,就是同一组
      • 如果返回非0,就不是一组

2、需求

  • 数据
订单id          商品id          商品价格
Order_0000001   Pdt_01       	222.8
Order_0000001   Pdt_05  		25.8
Order_0000002   Pdt_03  		522.8
Order_0000002   Pdt_04  		122.4
Order_0000002   Pdt_05  		722.4
Order_0000003   Pdt_01  		222.8
  • 需求
    • 统计得到每个订单价格最高的商品信息

3、分析

Step1:分析结果

Order_0000001   Pdt_01  222.8
Order_0000002   Pdt_05  722.4
Order_0000003   Pdt_01  222.8

只保留每个订单中商品价格最高的那条

Step2:有没有分组或者排序,用于决定Map输出的Key是什么

  • 分组:每个关键字
    • 要按照订单id分组
  • 排序:最高
    • 要按照价格排序
  • 问题来了:到底谁作为Key?
    • 两个需求都有,而且不是同一字段,怎么办?
    • 将整个数据三列作为一个整体,共同作为key,自定义数据类型
      • 实现比较器排序方法:如果订单相同,就按照价格降序排序

Step3:Value:NullWritable

Step4:

  • Input:读取这个文件
  • Map
    • 将这个文件的每一行的三列构建成一个 JavaBean
    • 用三列作为Key,Null作为Value
  • Shuffle
    • 分区:默认按照key进行分区,相同key进入同一个分区
      • 问题来了:当前的key是三个字段的整体,如果有多个分区,按照默认分区,相同的订单可能会进入不同的分区不同的Reduce中
      • 如果使用默认排序,相同订单的数据进入了不同的Reduce,无法实现按照订单分组,取每个订单最高的价格
      • 必须要让相同的订单进入同一个Reduce,按照订单id分组,取最大的那个价格
      • 不能使用默认分区,需要自定义分区,不能按照整个key进行分区,要按照key里面的订单id进行分区,只要订单id相同 ,就进入同一个分区
    • 排序:自定义数据类,封装三列,作为一个整体
      • 根据需求,如果订单id相同,按照价格降序,才能取到最高的价格
      • 如果订单id不相同,按照订单id排序
    • 分组:默认按照key进行分组,key相同作为同一组
      • key:三个字段的整体,每一条数据都不一样,每一条数据都是一组
      • 如果每一条数据都是一组,怎么取组内最高的那条?
      • 需要:自定义分组,将相同订单id的数据放入同一组,根据排序得到最高的那条
  • Reduce
    • 分组以后,每个订单的第一条就是最高价格 的那一条,直接输出即可
  • Output

4、代码实现

自定义数据类型

package bigdata.hanjiaxiaozhi.cn.mr.orders;import org.apache.hadoop.io.WritableComparable;import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;/**
 * @ClassName UserOrderBean
 * @Description TODO 自定义数据类型,用于封装订单id、商品id、商品price
 * @Date 2020/6/2 11:40
 * @Create By     hanjiaxiaozhi
 */
public class UserOrderBean implements WritableComparable<UserOrderBean> {private String orderId;
    private String productId;
    private double price;public UserOrderBean(){}public String getOrderId() {
        return orderId;
    }public void setOrderId(String orderId) {
        this.orderId = orderId;
    }public String getProductId() {
        return productId;
    }public void setProductId(String productId) {
        this.productId = productId;
    }public double getPrice() {
        return price;
    }public void setPrice(double price) {
        this.price = price;
    }@Override
    public String toString() {
        return orderId+"\t"+productId+"\t"+price;
    }/**
     * 先比较订单id,订单id不相同,直接默认升序
     * 如果订单id相同,就比较价格,降序
     * @param o
     * @return
     */
    @Override
    public int compareTo(UserOrderBean o) {
        //先比较订单是否相同
        int comp = this.getOrderId().compareTo(o.getOrderId());
        //如果订单id相同
        if(comp == 0){
            //相同订单id按照价格降序排序
            return -Double.valueOf(this.getPrice()).compareTo(Double.valueOf(o.getPrice()));
        }
        //如果订单id不相同,按照订单id升序排序
        return comp;
    }@Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(this.orderId);
        out.writeUTF(this.productId);
        out.writeDouble(this.price);
    }@Override
    public void readFields(DataInput in) throws IOException {
        this.orderId = in.readUTF();
        this.productId = in.readUTF();
        this.price = in.readDouble();
    }
}

自定义分区

package bigdata.hanjiaxiaozhi.cn.mr.orders;import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Partitioner;/**
 * @ClassName UserOrderPartition
 * @Description TODO 自定义分区,让相同订单的进入同一个分区
 * @Date 2020/6/2 11:49
 * @Create By     hanjiaxiaozhi
 */
public class UserOrderPartition extends Partitioner<UserOrderBean, NullWritable> {
    @Override
    public int getPartition(UserOrderBean key, NullWritable value, int numPartitions) {
        //用key中的订单id作为分区的条件
        return (key.getOrderId().hashCode() & Integer.MAX_VALUE) % numPartitions;
    }
}
​
自定义分组
package bigdata.hanjiaxiaozhi.cn.mr.orders;import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;/**
 * @ClassName UserOrderGroup
 * @Description TODO 自定义分组比较器,按照订单id进行分组
 * @Date 2020/6/2 11:52
 * @Create By     Frank
 */
public class UserOrderGroup extends WritableComparator {//将Key的类型进行注册
    public UserOrderGroup(){
        super(UserOrderBean.class,true);
    }@Override
    public int compare(WritableComparable a, WritableComparable b) {
        UserOrderBean o1 = (UserOrderBean) a;
        UserOrderBean o2 = (UserOrderBean) b;
        //比较订单id是否相等,相等就是同一组
        return o1.getOrderId().compareTo(o2.getOrderId());
    }
}

Mr

package bigdata.hanjiaxiaozhi.cn.mr.orders;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.mapreduce.lib.partition.HashPartitioner;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;import java.io.IOException;/**
 * @ClassName MRDriver
 * @Description TODO 这是MapReduce程序的Driver类的模板
 * @Date 2020/5/30 10:34
 * @Create By     hanjiaxiaozhi
 */
public class MROrdersTop1 extends Configured implements Tool {/**
     * 用于将Job的代码封装
     * @param args
     * @return
     * @throws Exception
     */
    @Override
    public int run(String[] args) throws Exception {
        //todo:1-构建一个Job
        Job job = Job.getInstance(this.getConf(),"top1");//构建Job对象,调用父类的getconf获取属性的配置
        job.setJarByClass(MROrdersTop1.class);//指定可以运行的类型
        //todo:2-配置这个Job
        //input
//        job.setInputFormatClass(TextInputFormat.class);//设置输入的类的类型,默认就是TextInputFormat
        Path inputPath = new Path("datas/orders/orders.txt");//用程序的第一个参数做为第一个输入路径
        //设置的路径可以给目录,也可以给定文件,如果给定目录,会将目录中所有文件作为输入,但是目录中不能包含子目录
        TextInputFormat.setInputPaths(job,inputPath);//为当前job设置输入的路径//map
        job.setMapperClass(MRMapper.class);//设置Mapper的类,需要调用对应的map方法
        job.setMapOutputKeyClass(UserOrderBean.class);//设置Mapper输出的key类型
        job.setMapOutputValueClass(NullWritable.class);//设置Mapper输出的value类型//shuffle
        job.setPartitionerClass(UserOrderPartition.class);//自定义分区
        job.setGroupingComparatorClass(UserOrderGroup.class);//自定义分组的方式
//        job.setSortComparatorClass(null);//自定义排序的方式//reduce
        job.setReducerClass(MRReducer.class);//设置Reduce的类,需要调用对应的reduce方法
        job.setOutputKeyClass(UserOrderBean.class);//设置Reduce输出的Key类型
        job.setOutputValueClass(NullWritable.class);//设置Reduce输出的Value类型
        job.setNumReduceTasks(1);//设置ReduceTask的个数,默认为1//output:输出目录默认不能提前存在
//        job.setOutputFormatClass(TextOutputFormat.class);//设置输出的类,默认我诶TextOutputFormat
        Path outputPath = new Path("datas/output/orders/output");//用程序的第三个参数作为输出
        //解决输出目录提前存在,不能运行的问题,提前将目前删掉
        //构建一个HDFS的文件系统
        FileSystem hdfs = FileSystem.get(this.getConf());
        //判断输出目录是否存在,如果存在就删除
        if(hdfs.exists(outputPath)){
            hdfs.delete(outputPath,true);
        }
        TextOutputFormat.setOutputPath(job,outputPath);//为当前Job设置输出的路径//todo:3-提交运行Job
        return job.waitForCompletion(true) ? 0:-1;
    }/**
     * 程序的入口,调用run方法
     * @param args
     */
    public static void main(String[] args) throws Exception {
        //构建一个Configuration对象,用于管理这个程序所有配置,工作会定义很多自己的配置
        Configuration conf = new Configuration();
        //t通过Toolruner的run方法调用当前类的run方法
        int status = ToolRunner.run(conf, new MROrdersTop1(), args);
        //退出程序
        System.exit(status);
    }
​
​
    /**
     * @ClassName MRMapper
     * @Description TODO 这是MapReduce模板的Map类
     *      输入的KV类型:由inputformat决定,默认是TextInputFormat
     *      输出的KV类型:由map方法中谁作为key,谁作为Value决定
     */
    public static class MRMapper extends Mapper<LongWritable, Text, UserOrderBean,NullWritable> {private UserOrderBean outputKey = new UserOrderBean();
        private NullWritable outputValue = NullWritable.get();@Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            //将这个文件中的每一行的三列作为key
            String[] items = value.toString().split("\t");
            //对key进行赋值
            this.outputKey.setOrderId(items[0]);
            this.outputKey.setProductId(items[1]);
            this.outputKey.setPrice(Double.parseDouble(items[2]));
            //输出
            context.write(this.outputKey,this.outputValue);
        }
    }
​
​
​
    /**
     * @ClassName MRReducer
     * @Description TODO MapReduce模板的Reducer的类
     *      输入的KV类型:由Map的输出决定,保持一致
     *      输出的KV类型:由reduce方法中谁作为key,谁作为Value决定
     */
    public static class MRReducer extends Reducer<UserOrderBean,NullWritable,UserOrderBean,NullWritable> {
        @Override
        protected void reduce(UserOrderBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
            //直接输出第一条
//            context.write(key,NullWritable.get());
            //迭代输出每一条
            for (NullWritable value : values) {
                context.write(key,value);
            }
        }
    }
​
​
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值