分区排序
要点
2 分区排序(默认的分区规则,区内有序)
直白讲解:CustomGroupingComparator帮助我们实现Reduce分组的时候来制定我们的分组规则。然后通过Bean重写的compareTo再在组内进行排序,即实现分组排序。(注意!其实这里的分组的意义是将kv整理成Key:Value[]。即构造一个key对应的value迭代器。所以在后续的Reducer中我们直接输出Key就得到了订单中最大的金额,因为此时key为分组排序后第一个(最大金额)kv的key,而value为空)
例如:按照订单id分组,然后排序,得到每个订单中价格最高或最低的商品id或价格。
直白讲解:CustomPartitioner帮助我们实现map输出数据的分区规则。分区后,每个分区进入一个ReduceTask,每个ReduceTask输出一份文件,方便数据查询管理。(注意!这里才实现了物理上面额的分区,即根据订单id进入不同的ReduceTask)
例如:最终结果,每个订单id内容输出为一个文件,方便分类管理。
GroupingComparator是mapreduce当中reduce端的⼀个功能组件,主要的作⽤是决定哪些数据作为⼀组,调⽤⼀次reduce的逻辑。默认是每个不同的key,作为多个不同的组,每个组调⽤⼀次reduce逻辑,我们可以⾃定义GroupingComparator实现不同的key作为同⼀个组,调⽤⼀次reduce逻辑。
思路分析
- 需求
原始数据
- 实现思路
Mapper
读取⼀⾏⽂本数据,切分出每个字段;
订单id和⾦额封装为⼀个Bean对象,Bean对象的排序规则指定为先按照订单Id排序,订单Id相等再按照⾦额降序排;
map()⽅法输出kv;key–>bean对象,value–>NullWritable.get();
Shuffle
指定分区器,保证相同订单id的数据去往同个分区(⾃定义分区器)指定GroupingComparator,分组规则指定只要订单Id相等则认为属于同⼀组;
Reduce
每个reduce()⽅法写出⼀组key的第⼀个
代码示例
CustomGroupingComparator代码
package com.lagou.mr.group;
import com.sun.corba.se.impl.orb.ParserTable;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
public class CustomGroupingComparator extends WritableComparator {
public CustomGroupingComparator() {
super(OrderBean.class, true); //注册自定义的GroupingComparator接受OrderBean对象
}
//重写其中的compare方法,通过这个方法来让mr接受orderid相等则两个对象key相等的规则
@Override
public int compare(WritableComparable a, WritableComparable b) { //a 和b是orderbean的对象
//比较两个对象的orderid
final OrderBean o1 = (OrderBean) a;
final OrderBean o2 = (OrderBean) b;
return o1.getOrderId().compareTo(o2.getOrderId()); // 0 1 -1
}
}
CustomPartitioner代码
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) {
//希望订单id相同的数据进入同个分区
return (orderBean.getOrderId().hashCode() & Integer.MAX_VALUE) % numPartitions;
}
}
Mapper代码
package com.lagou.mr.group;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
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 {
final String[] fields = value.toString().split("\t");
//订单id与金额封装为一个orderBean
bean.setOrderId(fields[0]);
bean.setPrice(Double.parseDouble(fields[2]));
//这里按每一bean个实例为一个key方便进行排序处理
context.write(bean, NullWritable.get());
}
}
Reduce代码
package com.lagou.mr.group;
import org.apache.avro.Schema;
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是否相同,自定义对象是需要我们指定一个规则,这个规则通过Groupingcomaprator来指定
@Override
protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
//直接输出key就是金额最大的交易
context.write(key, NullWritable.get());
}
}
OrderBean代码
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;//金额
public OrderBean(String orderId, Double price) {
this.orderId = orderId;
this.price = price;
}
public OrderBean() {
}
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;
}
//指定排序规则,先按照订单id比较再按照金额比较,按照金额降序排
@Override
public int compareTo(OrderBean o) {
int res = this.orderId.compareTo(o.getOrderId()); //0 1 -1
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();
}
//重写toString()
@Override
public String toString() {
return orderId + '\t' +
price
;
}
}
Driver代码
package com.lagou.mr.group;
import com.lagou.mr.wc.WordCountDriver;
import com.lagou.mr.wc.WordCountMapper;
import com.lagou.mr.wc.WordCountReducer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
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, ClassNotFoundException, InterruptedException {
/*
1. 获取配置文件对象,获取job对象实例
2. 指定程序jar的本地路径
3. 指定Mapper/Reducer类
4. 指定Mapper输出的kv数据类型
5. 指定最终输出的kv数据类型
6. 指定job处理的原始数据路径
7. 指定job输出结果路径
8. 提交作业
*/
// 1. 获取配置文件对象,获取job对象实例
final Configuration conf = new Configuration();
final Job job = Job.getInstance(conf, "GroupDriver");
// 2. 指定程序jar的本地路径
job.setJarByClass(GroupDriver.class);
// 3. 指定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);
//指定分区器
job.setPartitionerClass(CustomPartitioner.class);
//指定使用groupingcomparator
job.setGroupingComparatorClass(CustomGroupingComparator.class);
FileInputFormat.setInputPaths(job, new Path("E:\\teach\\hadoop框架\\资料\\data\\GroupingComparator")); //指定读取数据的原始路径
// 7. 指定job输出结果路径
FileOutputFormat.setOutputPath(job, new Path("E:\\group\\out")); //指定结果数据输出路径
//指定reducetask的数量,不要使用默认的一个,分区效果不明显
job.setNumReduceTasks(3);
// 8. 提交作业
final boolean flag = job.waitForCompletion(true);
//jvm退出:正常退出0,非0值则是错误退出
System.exit(flag ? 0 : 1);
}
}
总结
Reduce中的注释
key:reduce方法的key注意是一组相同key的kv的第一个key作为传入reduce方法的key,因为我们已经指定了排序的规则按照金额降序排列,则第一个key就是金额最大的交易数据
value:一组相同key的kv对中v的集合,对于如何判断key是否相同,自定义对象是需要我们指定一个规则,这个规则通过Groupingcomaprator来指定
相比全区排序,主要内容为CustomGroupingComparator:重新指定分组。OrderBean:中使用二次排序。
流程示意
注意:上图排版暂时不确定分组与排序的先后顺序,仅作为个人理解,不代表实际情况。