hadoop 的排序:key排序和分组排序

4 篇文章 0 订阅
1 篇文章 0 订阅

引入和说明

熟悉mapreduce执行流程的都知道,mapreduce流程中,一共有两类排序,第一种是对于key的排序,默认是是根据key的递增排序。第二种是对于reduce的组排序,将两条记录的key带入到 分组函数中,如果返回0,则说明两个记录是一组的,所以就把他们的value合到一个迭代器中,也就是reduce函数的第二个参数。

最下面那个代码(小标题是总体代码)包括了 map ,reduce,两个key排序类,三个分组排序类,一个分区类,和一个主函数。也就是整个文章的所有代码。下面小片代码都是从里面提取出来的,建议先看一下最后那个代码,对整个代码有一个完整性认识。
最后那个代码段是整个程序用的实验数据,其实就是A-Z 和1-9顺序的穿插在一起。

分区

分区的代码如下

    static public class myPartition extends Partitioner<Text, IntWritable> {

        @Override
        public int getPartition(Text key, IntWritable value, int numPartitions) {
            char b = key.toString().charAt(0);
            if (b >= 'A' && b <= 'Z') {
                return 0;
            }
            if (b >= '0' && b <= '9')
                return 1;
            return 2;
        }
    }

这个分区函数很简单,就是将记录按照字母,数字和其他三种类型分别分到不同的是分区,即每次结果文件中会包括四个文件,分别是_SUCCESS、part-r-00000、part-r-00001、part-r-00002。part-r-00000储存key是字母的记录,part-r-00001存储key是数字的记录,part-r-00002储存key是其他的记录,因为数据都是我们自己写的,不会出现其他这种情况,所以以下文章都值看part-r-00000和part-r-00001两个文件。具体的分区效果在下面展示。

第0种组合—默认排序

运行jar时,输入第三个参数为0,即两个排序算法都不设置,用默认的即可,则结果文件如下

#  part-r-00000文件
A   1
B   1
C   1
D   1
E   1
F   1
G   1
H   1
I   1
J   1
K   1
L   1
M   1
N   1
O   1
P   1
Q   1
R   1
S   1
T   1
U   1
V   1
W   1
X   1
Y   1
Z   1

#  part-r-00001文件
1   1
2   1
3   1
4   1
5   1
6   1
7   1
8   1
9   1

从上面看出,分区的效果,即把字母和数字分开到两个结果文件中。
而且,从结果文件可以看出,默认的排序算法就是按字典顺序升序排列。而分组排序显示不出来,但是,源代码显示,分组排序跟key排序是一样的。

第一种组合

设置代码如下

    if (Integer.valueOf(args[2]) == 1) {
                System.out.println("==============1=================");
                job.setSortComparatorClass(MySort.class);
            }

MySort类的作用按照 key的第一个字符与3的余数升序排列,具体代码可以看倒数第二个代码段。
实验结果如下

#  part-r-00000
N   9
L   8
D   9

#  part-r-00001
3   3
4   3
8   3

是不是很奇怪是为什么是这种结果,其实原因是如果只设置了key排序算法而没有设置分组排序,那么分组排序也使用key排序的算法。如果我们只想设置key排序算法,那么我们还要设置把分组排序设置成默认排序。但是我不知道hadoop有没有默认排序的实现,所以我自己完成了一个默认排序,即按照字典排序升序排列(MyGroupSort2类)。这种实现是第五种组合,可以先去第五种组合那看,本文章本来就是穿插着写的,所以也应该穿插着看。

第二种组合

if (Integer.valueOf(args[2]) == 2) {
                System.out.println("======================2=============");
                job.setSortComparatorClass(MySort.class);
                job.setGroupingComparatorClass(MyGroupSort.class);
            }

这个组合设置了分组函数,设置的分组函数作用是 key的第一个字符与3的余数相同的key分为一组,其实,这个函数与第1种组合的key排序实现一样,我只不过是为了验证,当设置了key排序而没有设置分组排序是,分组排序使用key排序的函数。也就是说,如果这种组合的结果与第1种的结果一模一样,这么这个结论就可以完全证实了。
下面是这种组合的结果

#  part-r-00000
N   9
L   8
D   9

#  part-r-00001
3   3
4   3
8   3

可以看出,第2种组合和第1种组合的结果一模一样,当设置了key排序而没有设置分组排序是,分组排序使用key排序的函数。这个结论验证。

下面我们还要验证另一个结论,就是如果只设置分组排序,而不设置key排序,那么key排序会不会使用分组排序的函数。这个验证可以看第三种组合

第三种组合

            if (Integer.valueOf(args[2]) == 3) {
                System.out.println("======================3===========");
                job.setGroupingComparatorClass(MyGroupSort.class);
            }

运行结果如下

#  part-r-00000
A   1
B   1
C   1
D   1
E   1
F   1
G   1
H   1
I   1
J   1
K   1
L   1
M   1
N   1
O   1
P   1
Q   1
R   1
S   1
T   1
U   1
V   1
W   1
X   1
Y   1
Z   1

#  part-r-00001
1   1
2   1
3   1
4   1
5   1
6   1
7   1
8   1
9   1

这个结果跟第0种组合(也就是默认设置)的结果一样而不与第2种组合的结果相同,也就是说如果只设置分组排序,而不设置key排序,那么key排序是不会使用分组排序的函数的,而且还可以推出一个更重要的结论。具体论述如下。
因为我们设置了分组排序,但是从结果来看,分组排序显然没有起到作用,因为那些ASCII码和3的余数相同的key并没有合并到一起,这是为什么呢,其实并不是分组排序没有起到作用,而是分组排序的一个特性,这个特性就是,分组排序时,某一个key先与下一个key做分组排序,如果返回0,则说明这两个key是相同的,就合并到一个迭代器中,然后用下一个key与下下个key做分组排序;如果返回的不是0,则说明这两个key不一样,也就不合并到同一个迭代器中,而且,这个key也不再和下下个key做分组排序了。例如,1的ASCII码是49,与3的余数为1,而2的ASCII码是50,与3的余数为2,这两个key不一样,而且,1也不再和3 4 5 …继续比下去,也就是把 1 单独列出来,作为一个迭代器,而且,key排序之后,key输入的顺序是 1 2 3 4 5 6 7 8 9 ,他们的ASCII码与3的余数与下一个key的余数都是不一样的,所以也就导致了part-r-00001文件中显示的那样。hadoop之所以使用这种机制,是因为本来已经有了key排序,如果你想要把他们分到一组去,你就可以使用key排序把他们分到一起去,再使用分组函数把他们分到一个迭代器中。完全不用让分组排序使用一 一对比的方式来分组,因为这样很浪费资源。
也许你也会想,也许就是分组排序没起到作用呢,所以我做了另一个实验,来证明,如果只设置分组排序,而不设置key排序,那么分组排序还是会起作用的,请看第4中组合。

第四种组合

    if (Integer.valueOf(args[2]) == 4) {
                System.out.println("======================4=============");
                job.setGroupingComparatorClass(MyGroupSort1.class);
            }

MyGroupSort1函数只返回0,所以不管传入什么,得到的结果都是,这两个key是一样的,应该分到一个组,加入到一个迭代器中,
运行结果如下:

#  part-r-00000
Z   26

#  part-r-00001
9   9

看到了把,如果只设置分组排序,不设置key排序,那么分组排序还是会起到作用的。就像这个组合,因为分组排序只返回0,所以会把所有记录分到一个迭代器中,而且 z 和 9都是每组的最后一个,记录个数分别是 26 和 9。所以就产生了以上这种运行结果。
下面可以去看最后一个组合,第6种组合,

第五种组合

            if (Integer.valueOf(args[2]) == 5) {
                System.out.println("==============5=================");
                job.setSortComparatorClass(MySort.class);
                job.setGroupingComparatorClass(MyGroupSort2.class);
            }

运行结果如下:

#  part-r-00000
Z   1
E   1
K   1
H   1
B   1
T   1
Q   1
W   1
N   1
X   1
F   1
C   1
R   1
U   1
I   1
O   1
L   1
A   1
Y   1
V   1
S   1
P   1
M   1
J   1
G   1
D   1

#  part-r-00001
6   1
9   1
3   1
1   1
7   1
4   1
5   1
2   1
8   1

这样就跟我们预想的一样了,先看part-r-00001文件,因为 6 9 3的ASCII码分别是 54 57 51,他们的ASCII码与3的余数都是0,而1 7 4的ASCII码分别是 49 55 52, 与3的余数是1,而5 2 8的ASCII码分别是 53 50 56,与3的余数是2,所以就成了上面显示的那种排序方式。part-r-00000文件的排序方式也是如此。如 Z的ASCII码为90,90与3的余数为0,所以排在前面,剩下的那些以此类推。
现在反过来看一下为什么第1种组合所产生的结果。先看part-r-00001文件,他显示的是3 4 8,其中,他们的ASCII码与3的余数分别是 0 1 2。这就说明了一件事,分组排序是使用key排序的算法。例如,6 9 3的ASCII码与3的余数都是0,所以就会合并到同一个迭代器中,这也就是为什么part-r-00001文件中3后面那个是3,也就是迭代器中有三个元素。再例如。1 7 4 的ASCII码与3的余数都是1,所以把他们合并到同一个迭代器中,part-r-00001文件中 4 后面也是一个3。至于 合并之后,key是用谁的key,part-r-00001文件中,6 9 3 里的3,1 7 4里的4 ,5 2 8里的8,结论就是合并之后,用的是整个组的最后一个key。但是我在学习时。老师讲的是,用整个组的最前面的一个key,我也不知道为什么会出现矛盾,如果谁知道,请留言给我,多谢多谢。
part-r-00000文件是这个样子也就可以明白了,Z E K H B T Q W N的ASCII码与3的余数都是0,所以合并到同一个迭代器中,个数有9个,而N是整组的最后一个, part-r-00000文件的第一行为 N 9,而第二行L 8 和第三行 D 9也是这么来的,希望你看明白了,我已经尽了最大努力了。下面你可以看一下第二种组合。

第六种组合

            if (Integer.valueOf(args[2]) == 6) {
                System.out.println("======================6=============");
                job.setSortComparatorClass(MySort1.class);
                job.setGroupingComparatorClass(MyGroupSort.class);
            }

这个组合其实和第三个组合一样,只不过第三个组合的key排序使用的是程序默认的,而这个组合使用的是自己实现的key排序函数,其实,自己实现的key排序函数跟默认的排序函数一样,都是按照 key的第一个字符的字典顺序升序排列。这个组合的结果如下

#  part-r-00000
A   1
B   1
C   1
D   1
E   1
F   1
G   1
H   1
I   1
J   1
K   1
L   1
M   1
N   1
O   1
P   1
Q   1
R   1
S   1
T   1
U   1
V   1
W   1
X   1
Y   1
Z   1

#  part-r-00001
1   1
2   1
3   1
4   1
5   1
6   1
7   1
8   1
9   1

结果如第3种组合的结果一模一样,我也就不多做解释了,就是证明,默认的key排序函数就是按照 key的字典顺序升序排列。

结论总结

下面是这篇文章所有的结论
1. 默认的key排序和分组排序都是按照 key的字典顺序升序排列。(第0种和第6种组合证明)
2. 当设置了key排序而没有设置分组排序是,分组排序使用key排序的函数。(1,2)
3. 如果只设置分组排序,而不设置key排序,那么key排序是不会使用分组排序的函数的。(3)
4. 分组排序时,某一个key先与下一个key做分组排序,如果返回0,则说明这两个key是相同的,就合并到一个迭代器中,然后用下一个key与下下个key做分组排序;如果返回的不是0,则说明这两个key不一样,也就不合并到同一个迭代器中,而且,这个key也不再和下下个key做分组排序了。(3)

总体代码

public class WordCount {
    private static final Log LOG = LogFactory.getLog("MyLog");

    public static class MapImpl extends
            Mapper<LongWritable, Text, Text, IntWritable> {

        enum ERRORROW {
            error_field, error_row
        }

        private static Text k = new Text();
        private static IntWritable v = new IntWritable(1);

        @Override
        public void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {
            String valuestr = value.toString();
            String[] splits = StringUtils.split(valuestr, ' ');
            k.set(splits[0]);
            context.write(k, v);
        }
    }

    public static class ReducerImpl extends
            Reducer<Text, IntWritable, Text, IntWritable> {

        private static IntWritable v = new IntWritable();

        @Override
        protected void reduce(Text arg0, Iterable<IntWritable> arg1,
                Context arg2) throws IOException, InterruptedException {
            int sum = 0;
            for (IntWritable intWritable : arg1) {
                int i = intWritable.get();
                sum += i;
            }
            v.set(sum);
            arg2.write(arg0, v);
        }
    }

    static public class myPartition extends Partitioner<Text, IntWritable> {

        @Override
        public int getPartition(Text key, IntWritable value, int numPartitions) {
            char b = key.toString().charAt(0);
            if (b >= 'A' && b <= 'Z') {
                return 0;
            }
            if (b >= '0' && b <= '9')
                return 1;
            return 2;
        }

    }

    /**
     * key 排序算法,按照 key的第一个字符与3的余数升序排列
     */
    static public class MySort extends WritableComparator {

        public MySort() {
            super(Text.class, true);
        }

        @Override
        public int compare(WritableComparable a, WritableComparable b) {
            if (a == null || b == null)
                return 0;
            char c1 = a.toString().charAt(0);
            char c2 = b.toString().charAt(0);

            return c1 % 3 - c2 % 3;
        }

    }

    /**
     * key 排序算法,按照 key的第一个字符的字典顺序升序排列,即默认排序
     */
    static public class MySort1 extends WritableComparator {

        public MySort1() {
            super(Text.class, true);
        }

        @Override
        public int compare(WritableComparable a, WritableComparable b) {
            if (a == null || b == null)
                return 0;
            char c1 = a.toString().charAt(0);
            char c2 = b.toString().charAt(0);

            return c1 - c2;
        }

    }

    /**
     * 分组 排序算法,key的第一个字符与3的余数相同的key分为一组
     */
    static public class MyGroupSort extends WritableComparator {
        public MyGroupSort() {
            super(Text.class, true);
        }

        @Override
        public int compare(WritableComparable a, WritableComparable b) {
            char c1 = a.toString().charAt(0);
            char c2 = b.toString().charAt(0);
            return (c1 % 3) - (c2 % 3);
        }

    }


    /**
     * 分组 排序算法,直接返回0,即 把所有 记录整合成一个
     */
    static public class MyGroupSort1 extends WritableComparator {

        public MyGroupSort1() {
            super(Text.class, true);
        }

        @Override
        public int compare(WritableComparable a, WritableComparable b) {
            return 0;
        }

    }

    /**
     * 分组 排序算法, 按照key的第一个字符是否相等进行分组,即默认分组
     */
    static public class MyGroupSort2 extends WritableComparator {

        public MyGroupSort2() {
            super(Text.class, true);
        }

        @Override
        public int compare(WritableComparable a, WritableComparable b) {
            char c1 = a.toString().charAt(0);
            char c2 = b.toString().charAt(0);

            return c1 - c2;
        }

    }

    public static void main(String[] args) {

        Configuration conf = new Configuration();

        try {
            Job job = new Job(conf, "Word Count");

            job.setJarByClass(WordCount.class);

            job.setMapperClass(MapImpl.class);
            job.setReducerClass(ReducerImpl.class);

            job.setInputFormatClass(TextInputFormat.class);
            job.setOutputFormatClass(TextOutputFormat.class);

            Path inpath = new Path(args[0]);
            Path outpath = new Path(args[1]);

            FileSystem fs = FileSystem.get(conf);

            if (!fs.exists(inpath)) {
                System.out.println("输入路径 " + args[0] + " 不存在");
            }
            if (fs.exists(outpath)) {
                fs.delete(outpath, true);
            }

            /*
             * 排序算法,有7个不同的搭配组合,通过这些组合可以理解                         
             * mapreduce的排序。
             */ 
            if (Integer.valueOf(args[2]) == 1) {
                System.out.println("==============1=================");
                job.setSortComparatorClass(MySort.class);
            }

            if (Integer.valueOf(args[2]) == 2) {
                System.out.println("======================2=============");
                job.setSortComparatorClass(MySort.class);
                job.setGroupingComparatorClass(MyGroupSort.class);
            }
            if (Integer.valueOf(args[2]) == 3) {
                System.out.println("======================3===========");
                job.setGroupingComparatorClass(MyGroupSort.class);
            }
            if (Integer.valueOf(args[2]) == 4) {
                System.out.println("======================4=============");
                // job.setSortComparatorClass(MySort.class);
                job.setGroupingComparatorClass(MyGroupSort1.class);
            }
            if (Integer.valueOf(args[2]) == 5) {
                System.out.println("==============5=================");
                job.setSortComparatorClass(MySort.class);
                job.setGroupingComparatorClass(MyGroupSort2.class);
            }
            if (Integer.valueOf(args[2]) == 6) {
                System.out.println("======================6=============");
                job.setSortComparatorClass(MySort1.class);
                job.setGroupingComparatorClass(MyGroupSort.class);
            }

            /**
             * 分区
             */
            job.setPartitionerClass(myPartition.class);
            job.setNumReduceTasks(3);

            FileInputFormat.addInputPath(job, inpath);
            FileOutputFormat.setOutputPath(job, outpath);

            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(IntWritable.class);
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(IntWritable.class);

            System.exit(job.waitForCompletion(true) ? 0 : 1);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

实验数据

A
1
B
C
D
2
E
F
G
H
3
I
J
4
K
5
6
L
M
N
O
P
Q
R
7
S
T
8
U
V
W
X
Y
Z
9
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值