小知识: Map Reduce排序实践

337 阅读3分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

Map Reduce 之与spark RDD 的一个区别🧐就在于groupby这个操作的实现。

MapReduce 一直以来都宣称它把整个数据的批处理操作高度抽象成了Map和Reduce两个操作(虽然高度抽象这几个字不是很能让人认同😑)。

但是显然,只了解这两个操作是不够的,mapreduce的真正的核心操作其实应该是隐藏在Map和Reduce 过程之间的分组groupby操作,正是这个操作为Map Reduce框架提供了真正意义上的数据处理能力。

与Spark 不同的是,Map Reduce 在分组时会通过先排序再将排序后的数据分组交给Reduce处理(Spark 可以操纵哈希表的方式)。

借助于这个特性,我们可以利用它实现大批量数据的排序。

具体来讲是这样的:Map把我们需要用于排序的字段放置在key处,这样在Map和Reduce的中间阶段,会根据key来排序,然后按key分组。而我们在Reduce阶段只需要把key和相应的每个值都写入上下文即可。

我们来尝试如下示例:

假设文件内容是这些:

33 
37 
12 
40
4
16 
39 
5
1
45
25

也就是每行由一个数字组成,没有额外的部分,也就是最简单的一种情况。这样的情况下,在Map中我们输入的值就是用来排序的key,而value由于是不存在的,我们可以使用一个空值也就是NullWritable

Map的实现可以像下面这样

 public static class Map extends Mapper<Object, Text, IntWritable, NullWritable> {
        private static final IntWritable k = new IntWritable();
        @Override
        public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
            String line = value.toString();
            k.set(Integer.parseInt(line));
            context.write(k, NullWritable.get());
        }
 }

这个Map的实现其实有点丑,因为我们没有直接在输入的时候将每行的数据以整数的形式读取,而是当作了文本先读取,于是需要一个整数解析操作。

而在Reduce阶段,我们得到的列表长度实际上就是我们需要输出的次数,而由于我们没有额外的字段,我们可以只输出键或者值的部分即可。不输出的部分使用NullWritable

Reduce 阶段的代码可以像这样

public static class Reduce extends Reducer<IntWritable, NullWritable, IntWritable, NullWritable> {
        @Override
        public void reduce(IntWritable key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
            for (NullWritable ignored : values) {
                context.write(key, NullWritable.get());
            }
        }
    }

然后我们设定下相关的Job参数

job.setMapperClass(Map.class);
job.setReducerClass(Reduce.class);
job.setOutputKeyClass(IntWritable.class);
job.setOutputValueClass(NullWritable.class);
FileInputFormat.addInputPath(job, inDir); //输入目录
FileOutputFormat.setOutputPath(job, outDir);//输出目录

在运行本次任务之后,我们可以查看输出的运行结果如下:

1
4
5
12
16
25
33
37
39
40
45

可以看到的是,我们的排序效果已经实现了。

小结:

Map Reduce的 groupby 自带有排序效果,而利用这个特性,我们可以实现对批量数据的排序,而不用关注细节。

这个代码还有相应的改进空间,比如我们可以尝试重写框架的数据读取机制,然后用来适应更多的情况,同时减轻map的负担(例如:map中的解析整数可以去掉),只是它牵扯到了MapReduce的序列化机制,为了不加重我们的心智负担,姑且先以这样的形式完成代码的书写,日后我们做相应的改进。