MapReduce 二次排序详解

1 首先说一下工作原理:

在map阶段,使用job.setInputFormatClass定义的InputFormat将输入的数据集分割成小数据块splites,同时InputFormat提供一个RecordReder的实现。本例子中使用的是TextInputFormat,他提供的RecordReder会将文本的一行的行号作为key,这一行的文本作为value。这就是自定义Map的输入是<LongWritable, Text>的原因。然后调用自定义Map的map方法,将一个个<LongWritable, Text>对输入给Map的map方法。注意输出应该符合自定义Map中定义的输出<IntPair, IntWritable>。最终是生成一个List<IntPair, IntWritable>。在map阶段的最后,会先调用job.setPartitionerClass对这个List进行分区,每个分区映射到一个reducer。每个分区内又调用job.setSortComparatorClass设置的key比较函数类排序。可以看到,这本身就是一个二次排序。如果没有通过job.setSortComparatorClass设置key比较函数类,则使用key的实现的compareTo方法。在第一个例子中,使用了IntPair实现的compareTo方法,而在下一个例子中,专门定义了key比较函数类。

在reduce阶段,reducer接收到所有映射到这个reducer的map输出后,也是会调用job.setSortComparatorClass设置的key比较函数类对所有数据对排序。然后开始构造一个key对应的value迭代器。这时就要用到分组,使用jobjob.setGroupingComparatorClass设置的分组函数类。只要这个比较器比较的两个key相同,他们就属于同一个组,它们的value放在一个value迭代器,而这个迭代器的key使用属于同一个组的所有key的第一个key。最后就是进入Reducer的reduce方法,reduce方法的输入是所有的(key和它的value迭代器)。同样注意输入与输出的类型必须与自定义的Reducer中声明的一致。

2  二次排序 就是首先按照第一字段排序,然后再对第一字段相同的行按照第二字段排序,注意不能破坏第一次排序 的结果 。例如

输入文件

20 21
50 51
50 52
50 53
50 54
60 51
60 53
60 52
60 56
60 57
70 58
60 61
70 54
70 55
70 56
70 57
70 58
1 2
3 4
5 6
7 82
203 21
50 512
50 522
50 53
530 54
40 511
20 53
20 522
60 56
60 57
740 58
63 61
730 54
71 55
71 56
73 57
74 58
12 211
31 42
50 62
7 8

输出:(注意需要分割线)


------------------------------------------------
1       2
------------------------------------------------
3       4
------------------------------------------------
5       6
------------------------------------------------
7       8
7       82
------------------------------------------------
12      211
------------------------------------------------
20      21
20      53
20      522
------------------------------------------------
31      42
------------------------------------------------
40      511
------------------------------------------------
50      51
50      52
50      53
50      53
50      54
50      62
50      512
50      522
------------------------------------------------
60      51
60      52
60      53
60      56
60      56
60      57
60      57
60      61
------------------------------------------------
63      61
------------------------------------------------
70      54
70      55
70      56
70      57
70      58
70      58
------------------------------------------------
71      55
71      56
------------------------------------------------
73      57
------------------------------------------------
74      58
------------------------------------------------
203     21
------------------------------------------------
530     54
------------------------------------------------
730     54
------------------------------------------------
740     58

3  具体步骤:


1 自定义key。

在mr中,所有的key是需要被比较和排序的,并且是二次,先根据partitione,再根据大小。而本例中也是要比较两次。先按照第一字段排序,然后再对第一字段相同的按照第二字段排序。根据这一点,我们可以构造一个复合类IntPair,他有两个字段,先利用分区对第一字段排序,再利用分区内的比较对第二字段排序。
所有自定义的key应该实现接口WritableComparable,因为是可序列的并且可比较的。并重载方法
//反序列化,从流中的二进制转换成IntPair
public void readFields(DataInput in) throws IOException
       
//序列化,将IntPair转化成使用流传送的二进制
public void write(DataOutput out)

//key的比较
public int compareTo(IntPair o)
       
另外新定义的类应该重写的两个方法
//The hashCode() method is used by the HashPartitioner (the default partitioner in MapReduce)
public int hashCode()
public boolean equals(Object right)

2 由于key是自定义的,所以还需要自定义一下类:

2.1 分区函数类。这是key的第一次比较。
public static class FirstPartitioner extends Partitioner<IntPair,IntWritable>

在job中设置使用setPartitionerClasss

2.2 key比较函数类。这是key的第二次比较。这是一个比较器,需要继承WritableComparator。
public static class KeyComparator extends WritableComparator
必须有一个构造函数,并且重载 public int compare(WritableComparable w1, WritableComparable w2)
另一种方法是 实现接口RawComparator。
在job中设置使用setSortComparatorClass。

2.3 分组函数类。在reduce阶段,构造一个key对应的value迭代器的时候,只要first相同就属于同一个组,放在一个value迭代器。这是一个比较器,需要继承WritableComparator。
public static class GroupingComparator extends WritableComparator
同key比较函数类,必须有一个构造函数,并且重载 public int compare(WritableComparable w1, WritableComparable w2)
同key比较函数类,分组函数类另一种方法是实现接口RawComparator。
在job中设置使用setGroupingComparatorClass。

另外注意的是,如果reduce的输入与输出不是同一种类型,则不要定义Combiner也使用reduce,因为Combiner的输出是reduce的输入。除非重新定义一个Combiner。

4 代码。这个例子中没有使用key比较函数类,而是使用key的实现的compareTo方法

package sorttwice;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.StringTokenizer;

import org.apache.hadoop.conf.*;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Partitioner;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;

public class sorttwice {

	//自己定义的key类实现WritableComparable接口
	public static class IntPair implements WritableComparable{

		int first,second;
		/**
		 * 设置 left,right
		 */
		public void set(int left,int right){
			first=left;
			second=right;
		}
		public int getFirst(){
			return first;
		}
		public int getSecond(){
			return second;
		}
		//反序列化,从流中读进二进制转换成IntPair
		@Override
		public void readFields(DataInput in) throws IOException {
			// TODO Auto-generated method stub
			first = in.readInt();
			second = in.readInt();
		}
		//序列化,将IntPair转换成二进制输出
		@Override
		public void write(DataOutput out) throws IOException {
			// TODO Auto-generated method stub
			out.writeInt(first);
			out.writeInt(second);
		}
		//key的比较
		@Override
		public int compareTo(IntPair o) {
			// TODO Auto-generated method stub
			if(first!=o.first){
				return first{

		@Override
		public int getPartition(IntPair key, IntWritable value, int numParatitions) {
			// TODO Auto-generated method stub
			return Math.abs(key.getFirst()*127) % numParatitions;
		}
	}
	/**
     * 分组函数类。只要first相同就属于同一个组。
     */
    //第一种方法,实现接口RawComparator
    public static class GroupingComparator implements RawComparator {
        @Override
        public int compare(IntPair o1, IntPair o2) {
            int l = o1.getFirst();
            int r = o2.getFirst();
            return l == r ? 0 : (l {
		private final IntPair intkey = new IntPair();
		private final IntWritable intvalue = new IntWritable();
		public void map(Object key,Text value,Context context) throws IOException, InterruptedException{
			StringTokenizer itr = new StringTokenizer(value.toString());
			int left =0;
			int right =0;
			while(itr.hasMoreTokens()){
				left = Integer.parseInt(itr.nextToken());
				if(itr.hasMoreTokens())
					right = Integer.parseInt(itr.nextToken());
				intkey.set(left, right);
				intvalue.set(right);
				context.write(intkey, intvalue);
				
			}
		}
	}
	
	public static class Reduce extends Reducer{
		private final Text left = new Text();
		private static final Text SEPAPATOR= new Text("================================");
		public void reduce(IntPair key,Iterablevalues,Context context) throws IOException, InterruptedException{
			context.write(SEPAPATOR, null);
			left.set(Integer.toString(key.getFirst()));
			for(IntWritable val:values){
				context.write(left, val);
			}
		}
	}
	
	public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
		// TODO Auto-generated method stub

		//读取hadoop配置
		Configuration conf = new Configuration();
		
		//初始化作业
		Job job =new Job(conf,"Secondray Sort");
		job.setJarByClass(sorttwice.class);
		
		job.setNumReduceTasks(1);
		
		//设置map和reduce类
		job.setMapperClass(Map.class);
		job.setReducerClass(Reduce.class);
		
		//设置map的输出key类,value类
		job.setMapOutputKeyClass(IntPair.class);
		job.setMapOutputValueClass(IntWritable.class);
		
		//设置输出Reduce的key,value
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(IntWritable.class);
		
		//将输入的数据分割成小数块,并使用RecordReader类实现
		job.setInputFormatClass(TextInputFormat.class);
		
		//提供一个RecordReader实现数据的输出
		job.setOutputFormatClass(TextOutputFormat.class);
		
		//重写分区函数
		job.setPartitionerClass(FirstPartition.class);
		
		//重写分组函数
		job.setGroupingComparatorClass(GroupingComparator.class);
		
		//设置路径
		FileInputFormat.addInputPath(job, new Path("/thinkgamer/input"));
		FileOutputFormat.setOutputPath(job,new Path("/thinkgamer/output"));
		
		//提交作业
		System.exit(job.waitForCompletion(true)?0:1);
	}
}

上一篇:监控视频须严加规范


下一篇:Facebook Messenger拟对聊天进行用户可选加密