[toc]
MapReduce程序之数据排序
需求
下面有三个文件:
yeyonghao@yeyonghaodeMacBook-Pro:~/data/input/sort$ cat file1.csv
2
32
654
32
15
756
65223
yeyonghao@yeyonghaodeMacBook-Pro:~/data/input/sort$ cat file2.csv
5956
22
650
92
yeyonghao@yeyonghaodeMacBook-Pro:~/data/input/sort$ cat file3.csv
26
54
6
使用MapReduce对其进行排序并输出。
分析思路
Map阶段分析:
/**
* 数据在Map之后会做sort(从内存缓冲区到磁盘的时候会做sort),所以Map操作只需要把数据直接写出即可,最后在本地做数据
* 合并的时候也是会有排序的,详细可以参考MapReduce的过程,但是需要注意的是,因为我们需要进行的是数字的排序,
* 所以在Map输出时,key的类型应该是Int类型才能按照数字的方式进行排序,如果是Text文本的话,那么是按照字典顺序
* 来进行排序的(也就是先比较字符串中的第一个字符,如果相同再比较第二个字符,以此类推),而不是按照数字进行排序
*/
Reduce阶段分析:
/**
* 需要注意的是,排序与其它MapReduce程序有所不同,最后在驱动程序设置ReduceTask时,必须要设置为1
* 这样才能把数据都汇总到一起,另外一点,数据在shuffle到达reducer的时候,从内存缓冲区写到磁盘时
* 也会进行排序操作,所以即便是从不同节点上的Map上shuffle来的数据,到输入到reducer时,数据也是有序的,
* 所以Reducer需要做的是把数据直接写到context中就可以了
*/
MapReduce程序
关于如何进行数据的排序,思路已经在代码注释中有说明,不过需要注意的是,这里使用了前面开发的Job工具类来开发驱动程序,程序代码如下:
package com.uplooking.bigdata.mr.sort;
import com.uplooking.bigdata.common.utils.MapReduceJobUtil;
import com.uplooking.bigdata.mr.duplication.DuplicationJob;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import java.io.IOException;
public class SortJob {
/**
* 驱动程序,使用工具类来生成job
*
* @param args
*/
public static void main(String[] args) throws Exception {
if (args == null || args.length < 2) {
System.err.println("Parameter Errors! Usages: ");
System.exit(-1);
}
Job job = MapReduceJobUtil.buildJob(new Configuration(),
SortJob.class,
args[0],
TextInputFormat.class,
SortMapper.class,
IntWritable.class,
NullWritable.class,
new Path(args[1]),
TextOutputFormat.class,
SortReducer.class,
IntWritable.class,
NullWritable.class);
// ReduceTask必须设置为1
job.setNumReduceTasks(1);
job.waitForCompletion(true);
}
/**
* 数据在Map之后会做sort(从内存缓冲区到磁盘的时候会做sort),所以Map操作只需要把数据直接写出即可,最后在本地做数据
* 合并的时候也是会有排序的,详细可以参考MapReduce的过程,但是需要注意的是,因为我们需要进行的是数字的排序,
* 所以在Map输出时,key的类型应该是Int类型才能按照数字的方式进行排序,如果是Text文本的话,那么是按照字典顺序
* 来进行排序的(也就是先比较字符串中的第一个字符,如果相同再比较第二个字符,以此类推),而不是按照数字进行排序
*/
public static class SortMapper extends Mapper {
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
// 先将value转换为数字
int num = Integer.valueOf(value.toString());
// 直接写出数据到context中
context.write(new IntWritable(num), NullWritable.get());
}
}
/**
* 需要注意的是,排序与其它MapReduce程序有所不同,最后在驱动程序设置ReduceTask时,必须要设置为1
* 这样才能把数据都汇总到一起,另外一点,数据在shuffle到达reducer的时候,从内存缓冲区写到磁盘时
* 也会进行排序操作,所以即便是从不同节点上的Map上shuffle来的数据,到输入到reducer时,数据也是有序的,
* 所以Reducer需要做的是把数据直接写到context中就可以了
*/
public static class SortReducer extends Reducer {
@Override
protected void reduce(IntWritable key, Iterable values, Context context)
throws IOException, InterruptedException {
// 直接将数据写入到context中
context.write(key, NullWritable.get());
}
}
/**
* 仍然需要说明的是,因为reduce端在shuffle数据写入到磁盘的时候已经完成了排序,
* 而这个排序的操作不是在reducer的输出中完成的,这也就意味着,reducer的输出数据中的key数据类型,
* 可以是IntWritable,显然也可以设置为Text的,说明这个问题主要是要理清map-shuffle-reduce的过程
*/
}
测试
这里使用本地环境来运行MapReduce程序,输入的参数如下:
/Users/yeyonghao/data/input/sort /Users/yeyonghao/data/output/mr/sort
也可以将其打包成jar包,然后上传到Hadoop环境中运行。
运行程序后,查看输出结果如下:
yeyonghao@yeyonghaodeMacBook-Pro:~/data/output/mr/sort$ cat part-r-00000
2
6
15
22
26
32
54
92
650
654
756
5956
65223
可以看到,我们的MapReduce已经完成了数据排序的操作。
注意事项
因为在map输出后,相同的key会被shuffle到同一个reducer中,所以这个过程其实也完成了去重的操作,这也就意味着,按照上面的MapReduce程序的思路,重复的数据也会被删除,那么如何解决这个问题呢?大家可以思考一下。
思路也比较简单,可以这样做,map输出的时候,key还是原来的key,而value不再是NullWritalbe,而是跟key一样的,这样到了reducer的时候,如果有相同的数据,输入的数据就类似于<32, [32, 32, 32]>,那么在reducer输出数据的时候,就可以迭代[32, 32, 32]数据进行输出,这样就可以避免shuffle阶段key去重所带来去除了相同数字的问题。
注意,前面一些文章,很多操作多次提到是在shuffle,其实有些是不准备的,包括这里的。在Map端,其实key的去重是在merge on disk的过程中完成了。