Hadoop辅助排序一例小结

来源:互联网 发布:大数据脱敏技术 编辑:程序博客网 时间:2024/05/29 14:46

前言:看的本来是《Hadoop权威指南(第三版)》中译本,结果各种翻译错误、语法错误、概念混淆,不胜枚举,只好对比着英文版第四版一起看。举个例子,key group被翻译成了码组。。你是要拜神?明明后面还有一个value group啊,竟然翻译成值,连组都没了。。。

话说看书看到了辅助排序这一段,对于其中分组以后输出第一个值百思不得其解,没说为什么,让我以为分组只能输出一个值,而且是通过分组比较器(GroupComparator)排序的值(最大或最小),结果根本不是这样。下面是参照书上写的一个小例子,因为是单机测试,就没写分区器Partitionor。

用到的IntPair类(这个类要自己写,Hadoop库里有一个同名的,但是功能完全不同的类)
import java.io.DataInput;import java.io.DataOutput;import java.io.IOException;import org.apache.hadoop.io.WritableComparable;public class IntPair implements WritableComparable<IntPair> {@Overridepublic String toString() {return "IntPair [first=" + first + ", second=" + second + "]";}private int first;private int second;public IntPair(){}public IntPair(int first, int second) {this.first = first;this.second = second;}@Overridepublic void write(DataOutput out) throws IOException {// TODO Auto-generated method stubout.writeInt(first);out.writeInt(second);}@Overridepublic void readFields(DataInput in) throws IOException {// TODO Auto-generated method stubfirst = in.readInt();second = in.readInt();}@Overridepublic int compareTo(IntPair o) {// TODO Auto-generated method stubif (o instanceof IntPair){int cmp = (first == o.first) ? 0 : ( (first>o.first) ? -1 : 1);if (cmp != 0)return cmp;}return (second == o.second) ? 0 : ( (second>o.second) ? -1 : 1);}public int getFirst() {return first;}public int getSecond() {return second;}}
IntPair是我的例子中用到的辅助排序结构,两列都是数字,最终结果是输出第一列每个同名元素中第二列最大的值。

输入:

1   7
2   8
3   11
2   20
2   1
5   2
1   0
4   3
3   4
1   3
2   4
1   8
3   1

输出:

IntPair [first=5, second=2]
IntPair [first=4, second=3]
IntPair [first=3, second=11]
IntPair [first=2, second=20]
IntPair [first=1, second=8]

说明:1、这个类一定要实现WritableComparable接口,在后续mapreducer程序中,架构会通过该接口的几个函数给类中属性传入从mapper获取的数据,数据如何赋值到属性的具体过程在readFields中,由我们实现;

2、属性的get方法可以有,外部比较时会用到,set方法不需要,因为有1);

3、无参构造函数一定要显示声明,因为框架会通过反射在内部生成该类对象,在keycomparator中会用到;

4、有参构造函数,方便外部生成该对象。

5、如果要文本输出,则tostring一定要重写。

6、compareTo是关键函数,本例中,mapper输出时,key排序就是根据这个函数。

然后是关键的mapreducer程序:

MapReducer程序
import java.io.IOException;import org.apache.hadoop.conf.Configured;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.io.NullWritable;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.Reducer;import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import org.apache.hadoop.util.Tool;import org.apache.hadoop.util.ToolRunner;/** *  *//** * @author bl * */public class SortTest extends Configured implements Tool {static class MyMapper extends Mapper<LongWritable, Text, IntPair, NullWritable>{@Overrideprotected void map(LongWritable key, Text value, Context context)throws IOException, InterruptedException {String[] strIn = value.toString().split("[ ]+");int first = Integer.parseInt(strIn[0]);int second = Integer.parseInt(strIn[1]);context.write(new IntPair(first, second), NullWritable.get());}}static class MyReducer extends Reducer<IntPair, NullWritable, IntPair, NullWritable>{@Overrideprotected void reduce(IntPair key, Iterable<NullWritable> value, Context context)throws IOException, InterruptedException {Exception e = new Exception("this is reducer");e.printStackTrace();context.write(key, NullWritable.get());}}static class KeyComparator extends WritableComparator{protected KeyComparator() {super(IntPair.class, true);}@SuppressWarnings("rawtypes")@Overridepublic int compare(WritableComparable a, WritableComparable b) {try{IntPair a1 = (IntPair)a;IntPair b1 = (IntPair)b;return a1.compareTo(b1);} finally{}}}static class GroupComparator extends WritableComparator{protected GroupComparator() {super(IntPair.class, true);Thread.currentThread().getStackTrace();}@SuppressWarnings("rawtypes")@Overridepublic int compare(WritableComparable a, WritableComparable b) {try{IntPair a1 = (IntPair)a;IntPair b1 = (IntPair)b;return Integer.compare(a1.getFirst(), b1.getFirst());} finally{}}}/* (non-Javadoc) * @see org.apache.hadoop.util.Tool#run(java.lang.String[]) */@Overridepublic int run(String[] args) throws Exception {// TODO Auto-generated method stubPath in = new Path(args[0]);Path out = new Path(args[1]);Job job = Job.getInstance(getConf(), "sort test");FileInputFormat.addInputPath(job, in);FileOutputFormat.setOutputPath(job, out);job.setMapperClass(SortTest.MyMapper.class);job.setSortComparatorClass(KeyComparator.class);//job.setGroupingComparatorClass(GroupComparator.class);job.setReducerClass(SortTest.MyReducer.class);job.setOutputKeyClass(IntPair.class);job.setOutputValueClass(NullWritable.class);job.waitForCompletion(true);return 0;}/** * @param args * @throws Exception  */public static void main(String[] args) throws Exception {// TODO Auto-generated method stubToolRunner.run(new SortTest(), args);}}
说明:1)本例中,为了输出第二列最大值,所以采用了复合主键的方式,然后用group的特性,输出最大值。

2)本例中,mapper的输入来自于文本,Hadoop默认的TextInputFormat或者FileInputFormat输入格式是key:LongWritable->value:Text,LongWritable代表行号(注意不是IntWritable,如果写错了,会抛出从LongWritable到IntWritable类型转换失败的异常),Text是该行的文本。

3)本例中,mapper中,map函数的作用,是把读入的Text进行解析,解析出每行的第一列整数值和第二列整数值,然后生成IntPair作为中间输出的key。Hadoop不允许值(value)部分为空,所以这里用NullWritable填充。

4)本例中,reducer只是负责把数据直接输出,没有对中间数据进行处理,但是如果没有reducer,则中间输出会直接写到最终结果,就没有group的过程了。

5)KeyComparator 的作用:中间输出按照该比较器进行排序,这里是按照IntPair排序,最终调用IntPair的排序规则,本例中生成的中间输出是按照第一、第二列升序排列。该函数在mapper的最终阶段调用,之后启动reducer的run,进入reducer工作。

6)GroupComparator 的作用:中间输出在reducer的context中(实际是ReducerContextImpl类),通过该比较器进行判断。中间输出经过上一步,已经是按序排列(Hadoop默认是升序),所以本利中输入中第一列值相同的IntPair会被排列在一起。而这个比较器,判断的就是当前中间输入的key(本例是IntPair对象)是否和下一个key相同。如果比较器返回0(代表相同,非0代表不同),则reducer的run会一直在context的nextkey->nextkeyvalue方法中调用该比较器,直到比较器返回非0。而每次nextkeyvalue的调用,都会更新context的当前key->value对的值,然后在run中,调用reduce方法,传入context的当前key->value对。所以如本例中,中间输出中两列都是升序排列,则context会一直调用groupcomparator并更新当前key->value,直到非0时调用reduce方法传入当前key->value。后台实际还进行了输入队列是否为空(没有下一个key)的判断,不过这不是关键。

7)经过group以后,调用reduce方法,所以group过程其实是在reduce程序中执行,而且是先于reduce方法调用。此时传入reduce的,已经是书中所说的“各组首条记录”。

8)因此,key比较器决定了key 的排序方式,group比较器决定了同一组中哪个数据最终可以被输出。

9)另一个和group比较器类似的,是常用的格式为<key, list<value>>的中间输出,可以认为list也是一个分组,所以实际上辅助排序例子也可以用常用方式得到同样的结果。

10)key比较器还有一个重要功能,就是如果job没有设定group比较器,则key比较器将充当group比较器的角色(如果设置了key比较器的话),也即mapper过程和reducer过程都会用key比较器。这可能就是为什么新API中把他改名为setSortComparatorClass。

11)中间数据类型如果是自定义的,最好是属性是基本类型,因为reducer在获取key->value时,是通过读取内存字节,反序列化后,生成对象的。我开始用的是IntWritable做为IntPair的属性类型,结果反序列化失败,运行报空指针错误。

12)这点也很重要,那就是在实现比较器时,在构造函数中,一定要调用super(XXX.class,true),XXX是你用来比较的类的名称,true是表示生成该类对象(在比较器内部)。之后比较时,比较器会用输入数据的字节值进行反序列化来给内部XXX类对象赋值,之后该属性对象被传递到比较器的compareTo函数。如果没有以上步骤,运行时会抛出空指针异常。

小结:这个例子最开始,最困扰我的就是为什么分组只输出一个结果?结果选择的依据是什么?经过跟踪源代码,终于发现了group的实际作用,就是判断中间输出的两个相邻key是否有某种关联,如果有设定的管理,则返回0,没有则返回非0。本例中的关联就是IntPair的第一列值相同。

另外,通过这个例子,也对mapper和reducer的上层调用过程有了初步的了解,至于最终结果的输出、备份,中间输出的合并等等内容,等以后再分析。



0 0
原创粉丝点击