先 定义需求,给出如下2个字段,要求先按第1个字段升序,若第1字段相同则按第2字段升序(二次排序):
20 21 50 51 1 2 3 4 5 6 7 82 63 70 50 522 40 511 20 53 12 98 20 522 63 61 12 211 31 42 7 8
结果:
1 2 3 4 5 6 7 8 7 82 12 98 12 211 20 21 20 53 20 522 31 42 40 511 50 51 50 522 63 61 63 70
map阶段
mapreduce使用job.setInputFormatClass定义的InputFormat将输入的数据集分割成小数据块splites,默认情况下一个block恰为一个splite,而一个splite恰好对应一个map任务。map的输出不是写入到hdfs,而是输入到1个容量为100M的环形缓冲区中,当缓冲区的已用空间超过容量的80%,Partitioner会根据key的确定其分配到哪个分区从而决定它会分配到哪个reduce上,HashPartitioner是mapreduce的默认partitioner。其源代码如下
public class HashPartitioner<K, V> extends Partitioner<K, V> { public int getPartition(K key, V value,int numReduceTasks) { return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks; } }如图Figure6-4,其定义了3个分区(图中partions所示),每当达到环形缓冲区的阈值80%,就会把数据写入到很多的小文件中(溢出写文件),并把里面的数据按key排序。最后会把诸多的小文件合并成一个大的文件并也按key排序就即最终的分区(图中fefch下方)。如果map之后接有一个Combiner,它会在排序后执行。(combiner是一个本地的reduce,一般为了减少map到rudece端的网络数据传输,我们通常在map段进行一次本地的归并操作)
reduce阶段
便于说明,我们把上图的reduce定义为'r1',其分区称为'p1'。reduce会把不同map传递过来的小'p1'合并成一个大的‘p1’并按key排序(图中sort phase下部),然后对数据进行分组,相同的key会分到同一组,它们的value放在一个value迭代器,而这个迭代器的key使用属于同一个组的所有key的第一个key。我们也可以定义自己的分组策略,之后我会在例子中说明。
具体代码实现:
import org.apache.hadoop.io.*; import org.apache.hadoop.fs.Path; 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 java.io.DataInput; import java.io.DataOutput; import java.io.IOException; public class SecondSort { //自定义的Key类型,其继承WritableComparable可以实现排序 public static class DataBean implements WritableComparable<DataBean> { private int first; private int second; public int getFirst() { return first; } public int getSecond() { return second; } public void set(int first, int second) { this.first = first; this.second = second; } @Override //我们使用默认的HashPartitioner,让其按照first分区 public int hashCode() { return first; } //排序的函数 public int compareTo(DataBean db) { int r = this.first - db.first; return r == 0 ? this.second - db.second : r; } //序列化 public void write(DataOutput out) throws IOException { int r1 = 0; out.writeInt(first); out.writeInt(second); } //反序列化 public void readFields(DataInput in) throws IOException { first = in.readInt(); second = in.readInt(); } } //自定义mapper public static class SsMapper extends Mapper<LongWritable, Text, DataBean, IntWritable> { private DataBean db = new DataBean(); private IntWritable intValue = new IntWritable(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] arrs = value.toString().split(" "); int first = Integer.parseInt(arrs[0]); //获得第1个字段值 int second = Integer.parseInt(arrs[1]); //获得第2个字段值 db.set(first, second); //设置自定义类型的值 intValue.set(second); context.write(db, intValue);// key保存复合键,value为null } } //自定义reducer public static class SsRedeucer extends Reducer<DataBean, IntWritable, Text, IntWritable> { private Text txtKey = new Text(); @Override protected void reduce(DataBean key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { //简单地输出first 和 second 字段 for (IntWritable v : values) { txtKey.set(String.valueOf(key.getFirst())); context.write(txtKey, v); } } } //因为key是复合键,我们使用自己的分组策略:按key分组 //注意在main方法中添加 job.setGroupingComparatorClass(GroupingComparator.class); public static class GroupingComparator extends WritableComparator { //这个构造函数必须使用,否则会报空异常 protected GroupingComparator() { super(DataBean.class, true); } @Override //重写比较函数 public int compare(WritableComparable a, WritableComparable b) { DataBean db1 = (DataBean) a; DataBean db2 = (DataBean) b; return db1.getFirst() - db2.getFirst(); } } public static void main(String[] args) throws Exception { Job job = Job.getInstance(); job.setJarByClass(SecondSort.class); job.setMapperClass(SsMapper.class); job.setMapOutputKeyClass(DataBean.class); job.setMapOutputValueClass(IntWritable.class); job.setReducerClass(SsRedeucer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); job.setGroupingComparatorClass(GroupingComparator.class); FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); job.waitForCompletion(true); } }
上面的代码只使用了一个分区和reduce(单节点可做测试),为了进一步说明自定义的Patitioner,我们定义自己的Patitioner
/*自定义分区策略:使用3个分区,按first的值分配(注意在main函数中添加) * job.setPartitionerClass(SsPartioner.class); * job.setNumReduceTasks(3); */ public static class SsPartioner extends Partitioner<DataBean, IntWritable> { @Override public int getPartition(DataBean dataBean, IntWritable intWritable, int numPartitions) { return dataBean.first % 3; } }