MapReduce排序之GroupingComparator

注:分区排序(默认的分区规则,区内有序)

        GroupingComparator是mapreduce当中reduce端的一个功能组件,主要的作用是决定哪些数据作为一组,调用一次reduce的逻辑,默认是每个不同的key,作为多个不同的组,每个组调用一次reduce逻辑,我们可以自定义GroupingComparator实现不同的key作为同一个组,调用一次reduce逻辑。

1、需求

原始数据

订单id商品id成交金额
Order_0000001Pdt_01222.8
Order_0000001Pdt_0525.8
Order_0000002Pdt_03522.8
Order_0000002Pdt_04122.4
Order_0000002Pdt_05722.4
Order_0000003Pdt_01232.8

需要求出每一个订单中成交金额最大的一笔交易。

2、实现思路

Mapper

  • 读取一行文本数据,切分出每个字段;
  • 订单id和金额封装为一个Bean对象,Bean对象的排序规则指定为先按照订单Id排序,订单Id相等再按照金额降序排;
  • map()方法输出kv;key-->bean对象,value-->NullWritable.get();

Shuffle

  • 指定分区器,保证相同订单id的数据去往同个分区(自定义分区器)
  • 指定GroupingComparator,分组规则指定只要订单Id相等则认为属于同一组;

Reduce

  • 每个reduce()方法写出一组key的第一个

3、示例代码

(1)OrderBean

  • OrderBean定义两个字段,一个字段是orderId,第二个字段是金额(注意金额一定要使用Double或者DoubleWritable类型,否则没法按照金额顺序排序
  • 排序规则指定为先按照订单Id排序,订单Id相等再按照金额降序排!!
package com.lagou.mr.group;

import org.apache.hadoop.io.WritableComparable;

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

public class OrderBean implements WritableComparable<OrderBean> {
    private String orderId;  // 订单id
    private Double price; // 金额

    // 指定排序规则,先按照订单id比较再按照金额比较,按照金额降序排列
    @Override
    public int compareTo(OrderBean o) {
        //比较订单id的排序顺序
        int res = this.orderId.compareTo(o.getOrderId());
        if (res == 0) {
            // 如果订单id相同,则比较金额,金额大的排在前面
             res = -this.price.compareTo(o.getPrice());
        }
        return res;

    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(orderId);
        out.writeDouble(price);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        this.orderId = in.readUTF();
        this.price = in.readDouble();
    }

    public OrderBean() {
    }

    public OrderBean(String orderId, Double price) {
        this.orderId = orderId;
        this.price = price;
    }

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return orderId + "\t" + price;
    }
}

(2)自定义分区器

        保证ID相同的订单去往同个分区最终去往同一个Reduce中

package com.lagou.mr.group;

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

public class CustomPartitioner extends Partitioner<OrderBean, NullWritable> {
    @Override
    public int getPartition(OrderBean orderBean, NullWritable nullWritable, int numPartitions) {
        // 订单相同的数据进入同一个分区
        return (orderBean.getOrderId().hashCode() & Integer.MAX_VALUE) % numPartitions;
    }
}

(3)自定义CustomGroupingComparator

package com.lagou.mr.group;

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

public class CustomGroupingComparator extends WritableComparator {
    // 将我们自定义的OrderBean注册到我们自定义的CustomGroupIngCompactor当中来
    // 表示我们的分组器在分组的时候,对OrderBean这一种类型的数据进行分组
    // 传入作为key的bean的class类型,以及制定需要让框架做反射获取实例对象
    public CustomGroupingComparator() {
        super(OrderBean.class, true); // 注册自定义的GroupingComparator接受OrderBean对象
    }

    // 重写其中的compare方法,通过这个方法来让mr接受orderId相等则两个对象相等的规则,认为这个key相等
    @Override
    public int compare(WritableComparable a, WritableComparable b) { // a和b是OrderBean的对象
        // 比较两个对象的orderId
        OrderBean o1 = (OrderBean) a;
        OrderBean o2 = (OrderBean) b;
        return o1.getOrderId().compareTo(o2.getOrderId()); // 0, 1, -1
    }
}

(4)Mapper

package com.lagou.mr.group;

import org.apache.hadoop.io.*;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

public class GroupMapper extends Mapper<LongWritable, Text, OrderBean, NullWritable> {
    OrderBean bean = new OrderBean();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] fields = value.toString().split("\t");
        // 订单id与金额封装作为一个OrderBean
        bean.setOrderId(fields[0]);
        bean.setPrice(Double.parseDouble(fields[2]));
        context.write(bean, NullWritable.get());
    }
}

(5)Reducer

package com.lagou.mr.group;

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

import java.io.IOException;

public class GroupReducer extends Reducer<OrderBean, NullWritable, OrderBean, NullWritable> {
    // key:reduce方法的key,注意是一组相同key的kv的第一个key作为传入reduce方法的key,因为我们已经指定了排序的规则
    // 按照金额降序排列,则第一个key就是金额最大的交易数据
    // value:一组相同key的kv对中v的集合
    // 对于如何判断key是否相同,自定义对象是需要我们指定一个规则,这个规则通过GroupingComparator来指定
    @Override
    protected void reduce(OrderBean key, Iterable<NullWritable> values, Reducer<OrderBean, NullWritable, OrderBean, NullWritable>.Context context) throws IOException, InterruptedException {
        // 直接输出key就是金额最大的交易
        context.write(key, NullWritable.get());
    }
}

(6)Driver

package com.lagou.mr.group;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class GroupDriver {
    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
        // 1 获取配置信息,或者job对象实例
        Configuration configuration = new Configuration();
        Job job = Job.getInstance(configuration);

        // 2 指定本程序的jar包所在的本地路径
        job.setJarByClass(GroupDriver.class);

        // 3 指定本业务job要使用的mapper/Reducer业务类
        job.setMapperClass(GroupMapper.class);
        job.setReducerClass(GroupReducer.class);

        // 4 指定mapper输出数据的kv类型
        job.setMapOutputKeyClass(OrderBean.class);
        job.setMapOutputValueClass(NullWritable.class);

        // 5 指定最终输出的数据的kv类型
        job.setOutputKeyClass(OrderBean.class);
        job.setOutputValueClass(NullWritable.class);

        // 6 指定job的输入原始文件所在目录
        FileInputFormat.setInputPaths(job, new Path("D:\\data\\GroupingComparator"));
        FileOutputFormat.setOutputPath(job, new Path("D:\\group\\out"));

        // 7 指定分区器,指定分组比较器,设置reducetask数量
        job.setPartitionerClass(CustomPartitioner.class);
        job.setGroupingComparatorClass(CustomGroupingComparator.class);
        job.setNumReduceTasks(2);

        // 8 运行
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
        boolean b = job.waitForCompletion(true);
        System.exit(b ? 0 : 1);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MapReduce中,排序是非常重要的。MapReduce在Map和Reduce的两个阶段中都会执行排序操作。全局排序是指在一个MapReduce程序产生的输出文件中,所有的结果都是按照某个策略进行排序的,例如降序还是升序。在全局排序中,只有一个reduce任务可以保证数据的全局有序,但这样无法充分利用Hadoop集群的优势。 在MapReduce的shuffle过程中,通常会执行多次排序。首先是在Map输出阶段,根据分区和key进行快速排序。然后,在Map的合并溢写文件阶段,将同一个分区的多个溢写文件进行归并排序,合成一个大的溢写文件。最后,在Reduce输入阶段,将同一分区来自不同Map任务的数据文件进行归并排序。最后阶段使用了堆排作为最后的合并过程。 在MapReduce中,有两种排序方式,即快速排序和归并排序。快速排序是通过一趟排序将要排序的数据分割成独立的两部分,然后对这两部分数据分别进行快速排序,最终达到整个数据变成有序序列的目的。归并排序是建立在归并操作上的一种排序算法,通过将已有序的子序列合并,得到完全有序的序列。归并排序可以采用分治法的方式进行,将子序列逐步合并,最终得到整个序列的有序结果。 因此,MapReduce中的排序操作是通过多次排序和归并的方式来实现的,以确保数据的有序性。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

悠然予夏

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

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

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

打赏作者

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

抵扣说明:

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

余额充值