在学习二次排序的过程中,觉得还是很复杂的,写一篇博客分享一下。
一、什么是二次排序?
二次排序就是对value值进行排序(本身value值是不会排序的)
二、例子分析
需求:
求1920-2020年100年间每年气温的最大值。
问题分析:
1、这100年的数据每年的气温数据量十分的大
2、如果在每次在reduce里面对整个气温找最大值的话很消耗时间
方案:
为了解决上述分析中的问题,在reduce之前,将所有的气温数据按年份从小到大排列,同一年份中,温度由又大到小排列,这样的话,reduce只要拿同一组中的第一个数据,就得到了当年的最高温度。
mapreduce流程图:
详细分析整个过程:
1、map从reader中读取数据。
package cn.hbmy.hdfs.mr.secondsorttemperature;
import org.apache.hadoop.io.IntWritable;
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 SecondSortTemWCMapper extends Mapper <IntWritable,IntWritable,Combokey,NullWritable>{
@Override
protected void map(IntWritable key, IntWritable value, Context context) throws IOException, InterruptedException {
Combokey combokey = new Combokey();
combokey.setYear(key.get());
combokey.setTemp(value.get());
context.write(combokey,NullWritable.get());
}
}
2、由于MapReduce中,只对key,排序不对value排序,为了能够对value进行排序,自己定义一个combokey,成员变量包含key和value,也就是上图中的需要解决地方4 ,此时K由combokey代替,value为null。下面给出combokey的定义类:
package cn.hbmy.hdfs.mr.secondsorttemperature;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* 不仅要有可比较的接口,还要实现串行化的接口
* */
public class Combokey implements WritableComparable<Combokey> {
private int year;
private int temp;
public int getYear() {
return year;
}
public int getTemp() {
return temp;
}
public void setYear(int year) {
this.year = year;
}
public void setTemp(int temp) {
this.temp = temp;
}
/**
* 对key进行比较实现
*/
public int compareTo(Combokey o) {
int y0 = o.getYear();
int t0 = o.getTemp() ;
//年份相同(升序)
if(year == y0){
//气温降序
return -(temp - t0) ;
}
else{
return year - y0 ;
}
}
/**
* 串行化过程 将数据写到流中去
*
* 反串行化过程是将流读入
*/
public void write(DataOutput out) throws IOException {
//年份
out.writeInt(year);
//气温
out.writeInt(temp);
}
public void readFields(DataInput in) throws IOException {
year = in.readInt();
temp = in.readInt();
}
}
3、现在输入partitioner()函数的是(Combokey,NullWritable)了,此时,我们就需要自己实现partitioner()函数,进行分区,也就是上面的需要解决地方1。下面给出分区函数:
package cn.hbmy.hdfs.mr.secondsorttemperature;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
/**
* 自定义分区就是为了防止数据倾斜
* 也就是说:防止所有的数据都进入了一个reducer里面去了
* 解决数据倾斜的两种方式:
* 1、自定义分区类,
* 2、重新制定key值
* */
public class YearPartitioner extends Partitioner<Combokey,NullWritable>{
public int getPartition(Combokey combokey, NullWritable nullWritable, int part) {
/**
* 当数据特别的集中的时候,这样的分区会导致数据倾斜,这里只是为了做二次排序,简单处理
* */
int year = combokey.getYear();
return year % part;
}
}
4、map端现在完成了,到了reduce端,对现在的数据需要排序,将所有的气温数据按年份从小到大排列,同一年份中,温度由又大到小排列,定义一个类继承WritableComparator类即可,也就是上图中需要解决地方2。如下代码
package cn.hbmy.hdfs.mr.secondsorttemperature;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
/**
* comparator
* */
public class ComboKeyComparator extends WritableComparator {
protected ComboKeyComparator() {
super(Combokey.class, true);
}
public int compare(WritableComparable a, WritableComparable b) {k
Combokey k1 = (Combokey) a;
Combokey k2 = (Combokey) b;
/**
* 调用combokey重定义好了的方法,实现key升序,value降序排列
* */
return k1.compareTo(k2);
}
}
5、数据排序完了,对数据还需要分组,因为key相等的数据会进入同一个reduce进行化简,每一组key会分配一个reduce函数。如上图解决地方3,黄色的框框中,一个框框内的东西就是一个组。具体实现如下:
package cn.hbmy.hdfs.mr.secondsorttemperature;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
/**
* 自定义分区对比器,使得相同的一组key聚合在一起去。
* 例如我们传进来的数据是
* key value
* 1920-31 null
* 1921-32 null
* 1920-33 null
* 1921-33 null
* 默认的是相等的key会聚集在一起,但是我们吧年份和气温聚集在了一起,
* 所以使得相同的年份,默认的方法不能聚合在一起了,我们自己要重新分组
* */
public class YearGroupComparator extends WritableComparator {
protected YearGroupComparator() {
super(Combokey.class,true);
}
public int compare(WritableComparable a, WritableComparable b) {
Combokey k1 = (Combokey)a;
Combokey k2 = (Combokey)b;
/**
* 这个到底是怎么分组的,需要单步的调试,看这个返回的结果是什么
* */
System.out.println("k1.getYear() - k2.getYear()"+k1.getYear() +" "+k2.getYear());
System.out.println(k1.getYear() - k2.getYear());
return k1.getYear() - k2.getYear();
}
}
6、分完组之后,进入reduce,取每一组中的第一个数据,这个数据就是这一年中气温的最大值。如上图每一个黄色框框中的第一条数据就是该年度的最大值。
package cn.hbmy.hdfs.mr.secondsorttemperature;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* Reducer
*
* 每一组会分配一个reduce
* */
public class SecondSortTemWCReduce extends Reducer<Combokey,NullWritable,IntWritable,IntWritable> {
@Override
protected void reduce(Combokey key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
//进来第一个数据,我们就把这个数据写出去
int year = key.getYear();
int temp = key.getTemp();
//也可以通过for循环将数据全部的遍历出来
context.write(new IntWritable(year),new IntWritable(temp));
}
}
7、APP类中的代码实现。
package cn.hbmy.hdfs.mr.secondsorttemperature;
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.input.SequenceFileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
/**
* 项目的参数
* /upload/wangwei /upload/wangwei/out\
*
* 在集群上面运行的命令
* hadoop jar hdfs-1.0-SNAPSHOT.jar cn.hbmy.hdfs.mr.AllSortTemWCApp /upload/in /upload/wangwei/out
hadoop jar hdfs-1.0-SNAPSHOT.jar cn.hbmy.hdfs.mr.AllSortTemWCApp /upload//out File System Counters
FILE: Number of bytes read=137097
FILE: Number of bytes written=1491213
FILE: Number of read operations=0
FILE: Number of large read operations=0
FILE: Number of write operations=0
HDFS: Number of bytes read=510
HDFS: Number of bytes written=113
HDFS: Number of read operations=72
HDFS: Number of large read operations=0
HDFS: Number of write operations=18
Map-Reduce Framework
Map input records=9
Map output records=18
Map output bytes=167
Map output materialized bytes=173
Input split bytes=318
Combine input records=18
Combine output records=10
Reduce input groups=8
Reduce shuffle bytes=173
Reduce input records=10
Reduce output records=8
Spilled Records=20
Shuffled Maps =9
Failed Shuffles=0
Merged Map outputs=9
GC time elapsed (ms)=94
CPU time spent (ms)=0
Physical memory (bytes) snapshot=0
Virtual memory (bytes) snapshot=0
Total committed heap usage (bytes)=1325260800
Shuffle Errors
BAD_ID=0
CONNECTION=0
IO_ERROR=0
WRONG_LENGTH=0
WRONG_MAP=0
WRONG_REDUCE=0
m
mini1:7167:LocalJobRunner Map Task Executor #0:AllSortTemWCMapper@1049875=10
mini1:7167:LocalJobRunner Map Task Executor #0:AllSortTemWCMapper@1619279=4
mini1:7167:LocalJobRunner Map Task Executor #0:AllSortTemWCMapper@2772633=4
File Input Format Counters
Bytes Read=95
File Output Format Counters
Bytes Written=61
r
mini1:7167:LocalJobRunner Map Task Executor #0:AllSortTemWCReduce@1607603=2
mini1:7167:LocalJobRunner Map Task Executor #0:AllSortTemWCReduce@1903189=3
mini1:7167:LocalJobRunner Map Task Executor #0:AllSortTemWCReduce@2697133=2
mini1:7167:LocalJobRunner Map Task Executor #0:AllSortTemWCReduce@2892504=2
mini1:7167:LocalJobRunner Map Task Executor #0:AllSortTemWCReduce@8917400=1
mini1:7167:pool-6-thread-1:AllSortTemWCReduce@12779848:reduce=3
mini1:7167:pool-6-thread-1:AllSortTemWCReduce@22463614:reduce=2
mini1:7167:pool-6-thread-1:AllSortTemWCReduce@23423693:reduce=3
* */
public class SecondSortTemWCApp {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
// conf.set("fs.defaultFS","file:///");
Job job = Job.getInstance(conf);
//设置job的各种属性
job.setJobName("AllSortTemWCApp"); //作业名称
job.setJarByClass(SecondSortTemWCApp.class); //搜索类
job.setInputFormatClass(SequenceFileInputFormat.class); //设置序列文件的输入格式
FileInputFormat.addInputPath(job,new Path(args[0])); //添加输入路径
FileOutputFormat.setOutputPath(job,new Path(args[1])); //设置输出路径
//设置最大切片数 根据系统的切片法则,一般都是128M,也就是一个block
// FileInputFormat.setMaxInputSplitSize(job,13);
//设置最小切片数
// FileInputFormat.setMinInputSplitSize(job,1L);
job.setMapperClass(SecondSortTemWCMapper.class);
// 设置自定义分区,根据年份分区
job.setPartitionerClass(YearPartitioner.class);
// 设置排序器
job.setSortComparatorClass(ComboKeyComparator.class);
// 设置分组对比器
job.setGroupingComparatorClass(YearGroupComparator.class);
job.setReducerClass(SecondSortTemWCReduce.class);
//map的个数取决于切片数
job.setNumReduceTasks(3); //reduce的个数,一个输出结果 设置三个reduce个数
job.setMapOutputKeyClass(Combokey.class);
job.setMapOutputValueClass(NullWritable.class);
job.setOutputKeyClass(IntWritable.class);
job.setOutputValueClass(IntWritable.class);
job.waitForCompletion(true);
}
}
三、总结:
本人也是开始接触hadoop,对这个里面的class类文件还不是很熟悉,因此贴出来了代码,代码中也有注释,如果有异议的地方,希望大家多多交流。