默认情况下,在MapReduce中的shuffer阶段会自动进行排序,而且是根据key进行排序的。但是有时候需要对 Key 排序的同时再对 Value 进行排序,这时候就要用到二次排序了。
我们把二次排序分为以下几个阶段。
Map输出阶段:
在 Map 阶段的最后,会先调用 job.setPartitionerClass() 对这个 Mapper 的输出结果进行分区,每个分区映射到一个Reducer。每个分区内又调用
job.setSortComparatorClass() 设置的 Key 比较函数类排序。可以看到,这本身就是一个二次排序。如果没有通过 job.setSortComparatorClass() 设置 Key 比较函数类,则使用 Key 实现的 compareTo() 方法。我们既可以使用 sortBean实现的 compareTo() 方法,也可以专门定义 Key 比较函数类。
Reduce阶段:
在 Reduce 阶段,reduce() 方法接受所有映射到这个 Reduce 的 map 输出后,也是会调用 job.setSortComparatorClass()方法设置的 Key 比较函数类,对所有数据进行排序。然后开始构造一个 Key 对应的 Value 迭代器。这时就要用到分组,使用job.setGroupingComparatorClass()方法设置分组函数类。只要这个比较器比较的两个 Key相同,它们就属于同一组,它们的 Value 放在一个 Value 迭代器,而这个迭代器的 Key 使用属于同一个组的所有Key的第一个Key。最后就是进入 Reducer 的 reduce() 方法, reduce() 方法的输入是所有的 Key 和它的 Value 迭代器,同样注意输入与输出的类型必须与自定义的 Reducer 中声明的一致。
二次排序实战:
对学生的两门课数学英语成绩排序,先按照数学成绩从小到大排序,当数学成绩相同时,按照英语成绩进行从小到大排序,并且按照数学成绩的分数对输出文件进行划分,60-70在1号分区,70到80在2号分区,80以上在三号分区。
输入数据(第一列为数学成绩,第二列为英语成绩):
1.自定义bean对象实现WritableComparable接口,在此类中重写序列化和反序列化方法,并且给出排序的逻辑。
package com.sort;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class sortBean implements WritableComparable<sortBean> {
private int math;
private int english;
public sortBean() {
}
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public int getEnglish() {
return english;
}
public void setEnglish(int english) {
this.english = english;
}
@Override
public String toString() {
return math +" "+english ;
}
//二次排序的过程,先按数学成绩从大到小排序,再按英语成绩从大到小排序
@Override
public int compareTo(sortBean s) {
int result;
if (this.math > s.math)
result = 1;
else if (this.math <s.math)
result = -1;
else {
result = (this.english > s.english) ?1:-1;
}
return result;
}
//重新序列化方法和反序列化方法
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(math);
out.writeInt(english);
}
@Override
public void readFields(DataInput in) throws IOException {
math = in.readInt();
english = in.readInt();
}
}
- Mapper端
package com.sort;
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 sortMapper extends Mapper<LongWritable, Text, sortBean, NullWritable>{
@Override
protected void map(LongWritable key, Text values, Context context) throws IOException, InterruptedException {
String words = values.toString();
String[] s = words.split(" ");
//封装bean对象
sortBean sortbean = new sortBean();
sortbean.setMath(Integer.parseInt(s[0])); //注意转换一下类型
sortbean.setEnglish(Integer.parseInt(s[1]));
//写入上下文
context.write(sortbean,NullWritable.get());
}
}
- 自定义分区类继承Partitioner,因为Partitioner是框架默认的分区,在此类中给出分区的逻辑。
package com.sort;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Partitioner;
public class sortPartitioner extends Partitioner<sortBean, NullWritable> {
// <sortBean, NullWritable> 注意K,V为Mapper端的输出
@Override
public int getPartition(sortBean sortbean, NullWritable nullWritable, int numPartitions) {
//按照数学成绩进行分区,60-70为第一分区,70-80为第二分区,其它的为第三分区
int result = 0;
if (sortbean.getMath() >=60 && sortbean.getMath() <70)
result = 1;
else if(sortbean.getMath() >=70 && sortbean.getMath() <80)
result = 2;
else
result =3;
return result;
}
}
- 自定义分组函数类。在reduce阶段,构造一个key对应的value迭代器的时候,只要数学成绩相同就属于同一个组,放在一个value迭代器。这是一个比较器,需要继承WritableComparator。
package com.sort;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
public class GroupingComparator extends WritableComparator {
// 在进行分组排序的时候一定要有一个无参的构造器
protected GroupingComparator() {
super(sortBean.class, true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
//父类向下转型
sortBean aBean = (sortBean) a;
sortBean bBean = (sortBean) b;
int result;
if (aBean.getEnglish() > bBean.getEnglish()) {
result = 1;
} else if (aBean.getEnglish() < bBean.getEnglish()) {
result = -1;
} else {
result = 0;
}
return result;
}
}
- 自定义Reduce
package com.sort;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class sortReduce extends Reducer<sortBean, NullWritable,sortBean,NullWritable> {
@Override
protected void reduce(sortBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
context.write(key,NullWritable.get());
}
}
- 驱动的主类
package com.sort;
import com.MapReduce.test.WordcountDriver;
import com.MapReduce.test.WordcountMapper;
import com.MapReduce.test.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.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
public class sortDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
String inputPath = "e:/input/sort.txt"; //文件的输入路径
String outputPath = "e:/output3"; // 文件的输出路径
Configuration conf = new Configuration();
Job job = Job.getInstance(conf); //获取一个job的实例
job.setJarByClass(sortDriver.class); // 通过反射给定类的来源来设置Jar
job.setMapperClass(sortMapper.class); // 关联Map和Reduce
job.setReducerClass(sortReduce.class);
job.setMapOutputKeyClass(sortBean.class); // Mapper阶段的输出的K,V类型
job.setMapOutputValueClass(NullWritable.class);
job.setOutputKeyClass(sortBean.class); // 程序最终输出的K,V类型
job.setOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job,new Path(inputPath));
FileOutputFormat.setOutputPath(job,new Path(outputPath));
//设置分区
job.setPartitionerClass(sortPartitioner.class);
job.setNumReduceTasks(4); //4个分区,所以开了4个ReduceTasks
//设置reduce端的分组
job.setGroupingComparatorClass(GroupingComparator.class);
boolean flag = job.waitForCompletion(true);
//交给yarn去执行,直到执行结束才退出本程序,true为程序运行时可见此过程
System.exit(flag ? 0 : 1);
}
}
运行结果:
分别打开part1,2,3这三个文件
可以看到程序的执行结果已经是我们预期所希望的。