注:分区排序(默认的分区规则,区内有序)
GroupingComparator是mapreduce当中reduce端的一个功能组件,主要的作用是决定哪些数据作为一组,调用一次reduce的逻辑,默认是每个不同的key,作为多个不同的组,每个组调用一次reduce逻辑,我们可以自定义GroupingComparator实现不同的key作为同一个组,调用一次reduce逻辑。
1、需求
原始数据
订单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 | 232.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);
}
}