默认情况下,Map输出的结果会按照key进行排序,但在实际的应用中,有时间我们不仅要对key进行排序,同时还要对value进行排序,这时候就要用到mapreduce中的二次排序。
一、 二次排序原理分析
我们对二次排序分为以下几个阶段:
(1)Map起始阶段
在Map阶段,通过job.setInputFormatClass()定义的InputFormat,将输入的数据集切分成小的split,同时InputFormat提供一个RecordReader的实现。RecordReader会将文本的行号最为Key,该行的内容作为Value,这也是Map的输入数据类型为<LongWritable, Text>的原因。接着会调用自定义的map方法,将读取到的<key, value>键值对输入给map方法。
(2)Map的最后阶段
在Map的最后阶段,会先调用job.setPartitionerClass()对Mapper的输出结果进行分区,每个分区会映射到一个Reducer。在每个分区内,又会调用job.setSortComparatorClass()设置的key的比较函数类排序。这本身就是一个二次排序,如果没有通过job.setSortComparatorClass()设置key的比较函数类,则会使用key实现的compareTo()方法。
(3)Reducer阶段
在Reducer阶段,自定义的reduce方法接收所有映射到这个reduce的map的输出,之后调用job.setSortComparatorClass()方法设置的key比较函数类,对所有数据进行排序。然后开始构造一个key对应的Value迭代器,这里就要用到分组,调用job.setGroupingComparatorClass()方法设置分组函数类,只要这个比较器比较的两个key相同,他们就属于同一个组,他们的value放在一个value迭代器中,而这个迭代器的key使用属于同一个的所有key的第一个key。最后进入Reducer的reduce方法,该方法的输入是所有的key和它的value迭代器。
二、二次排序样例
假设要对如下文件进行排序,首先按照第一列字母进行排序,然后对每个字母所对应的第二列数字按大小进行排序。
a 5
a 3
b 2
c 9
b 10
a 1
c 8
b 6
排序结果如下:
a 1
a 3
a 5
b 2
b 6
b 10
c 8
c 9
(1)自定义key
所有自定义的key应该实现接口WritableComparable,因为它是可序列化的且可比较。实现该接口必须要实现如下函数:
// 序列化,将 IntPair 序列化成二进制流
public void write(DataOutput dataOutput) throws IOException
// 反序列化,将读取的二进制流转换成 IntPair
public void readFields(DataInput dataInput) throws IOException
// key 的比较
public int compareTo(IntPair o)
// 默认的分区类 HashPartitioner, 使用此方法
public int hashCode()
(2)自定义分区
自定义分区类FirstPartitioner,是key的第一次比较,完成对所有的key排序