mapreduce(三):自定义二次排序流程实例详解
来源:互联网 发布:查询工资的软件 编辑:程序博客网 时间:2024/04/29 10:39
<pre name="code" class="java">一、概述MapReduce框架对处理结果的输出会根据key值进行默认的排序,这个默认排序可以满足一部分需求,但是也是十分有限的。在我们实际的需求当中,往往有要对reduce输出结果进行二次排序的需求。对于二次排序的实现,网络上已经有很多人分享过了,但是对二次排序的实现的原理以及整个MapReduce框架的处理流程的分析还是有非常大的出入,而且部分分析是没有经过验证的。本文将通过一个实际的MapReduce二次排序例子,讲述二次排序的实现和其MapReduce的整个处理流程,并且通过结果和map、reduce端的日志来验证所描述的处理流程的正确性。二、需求描述1、输入数据:sort1 1sort2 3sort2 77sort2 54sort1 2sort6 22sort6 221sort6 202、目标输出sort1 1,2sort2 3,54,77sort6 20,22,221三、解决思路 1、首先,在思考解决问题思路时,我们先应该深刻的理解MapReduce处理数据的整个流程,这是最基础的,不然的话是不可能找到解决问题的思路的。我描述一下MapReduce处理数据的大概简单流程:首先,MapReduce框架通过getSplit方法实现对原始文件的切片之后,每一个切片对应着一个map task,inputSplit输入到Map函数进行处理,中间结果经过环形缓冲区的排序,然后分区、自定义二次排序(如果有的话)和合并,再通过shuffle操作将数据传输到reduce task端,reduce端也存在着缓冲区,数据也会在缓冲区和磁盘中进行合并排序等操作,然后对数据按照Key值进行分组,然后没处理完一个分组之后就会去调用一次reduce函数,最终输出结果。大概流程我画了一下,如下图:Hadoop之MapReduce自定义二次排序流程实例详解2、具体解决思路(1)Map端处理: 根据上面的需求,我们有一个非常明确的目标就是要对第一列相同的记录合并,并且对合并后的数字进行排序。我们都知道MapReduce框架不管是默认排序或者是自定义排序都只是对Key值进行排序,现在的情况是这些数据不是key值,怎么办?其实我们可以将原始数据的Key值和其对应的数据组合成一个新的Key值,然后新的Key值对应的还是之前的数字。那么我们就可以将原始数据的map输出变成类似下面的数据结构:{[sort1,1],1}{[sort2,3],3}{[sort2,77],77}{[sort2,54],54}{[sort1,2],2}{[sort6,22],22}{[sort6,221],221}{[sort6,20],20}那么我们只需要对[]里面的新key值进行排序就ok了。然后我们需要自定义一个分区处理器,因为我的目标不是想将新key相同的传到同一个reduce中,而是想将新key中的第一个字段相同的才放到同一个reduce中进行分组合并,所以我们需要根据新key值中的第一个字段来自定义一个分区处理器。通过分区操作后,得到的数据流如下:Partition1:{[sort1,1],1}、{[sort1,2],2}Partition2:{[sort2,3],3}、{[sort2,77],77}、{[sort2,54],54}Partition3:{[sort6,22],22}、{[sort6,221],221}、{[sort6,20],20}分区操作完成之后,我调用自己的自定义排序器对新的Key值进行排序。{[sort1,1],1}{[sort1,2],2}{[sort2,3],3}{[sort2,54],54}{[sort2,77],77}{[sort6,20],20}{[sort6,22],22}{[sort6,221],221}(2)Reduce端处理: 经过Shuffle处理之后,数据传输到Reducer端了。在Reducer端对按照组合键的第一个字段来进行分组,并且没处理完一次分组之后就会调用一次reduce函数来对这个分组进行处理输出。最终的各个分组的数据结构变成类似下面的数据结构:{sort1,[1,2]}{sort2,[3,54,77]}{sort6,[20,22,221]}四、具体实现1、自定义组合键package com.mr; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import org.apache.Hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.WritableComparable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 自定义组合键 * @author zenghzhaozheng */public class CombinationKey implements WritableComparable<CombinationKey>{ private static final Logger logger = LoggerFactory.getLogger(CombinationKey.class); private Text firstKey; private IntWritable secondKey; public CombinationKey() { this.firstKey = new Text(); this.secondKey = new IntWritable(); } public Text getFirstKey() { return this.firstKey; } public void setFirstKey(Text firstKey) { this.firstKey = firstKey; } public IntWritable getSecondKey() { return this.secondKey; } public void setSecondKey(IntWritable secondKey) { this.secondKey = secondKey; } @Override public void readFields(DataInput dateInput) throws IOException { // TODO Auto-generated method stub this.firstKey.readFields(dateInput); this.secondKey.readFields(dateInput); } @Override public void write(DataOutput outPut) throws IOException { this.firstKey.write(outPut); this.secondKey.write(outPut); } /** * 自定义比较策略 * 注意:该比较策略用于mapreduce的第一次默认排序,也就是发生在map阶段的sort小阶段, * 发生地点为环形缓冲区(可以通过io.sort.mb进行大小调整) */ @Override public int compareTo(CombinationKey combinationKey) { logger.info("-------CombinationKey flag-------"); return this.firstKey.compareTo(combinationKey.getFirstKey()); } }说明:在自定义组合键的时候,我们需要特别注意,一定要实现WritableComparable接口,并且实现compareTo方法的比较策略。这个用于mapreduce的第一次默认排序,也就是发生在map阶段的sort小阶段,发生地点为环形缓冲区(可以通过io.sort.mb进行大小调整),但是其对我们最终的二次排序结果是没有影响的。我们二次排序的最终结果是由我们的自定义比较器决定的。2、自定义分区器package com.mr; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.mapreduce.Partitioner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 自定义分区 * @author zengzhaozheng */public class DefinedPartition extends Partitioner<CombinationKey,IntWritable>{ private static final Logger logger = LoggerFactory.getLogger(DefinedPartition.class); /** * 数据输入来源:map输出 * @author zengzhaozheng * @param key map输出键值 * @param value map输出value值 * @param numPartitions 分区总数,即reduce task个数 */ @Override public int getPartition(CombinationKey key, IntWritable value,int numPartitions) { logger.info("--------enter DefinedPartition flag--------"); /** * 注意:这里采用默认的hash分区实现方法 * 根据组合键的第一个值作为分区 * 这里需要说明一下,如果不自定义分区的话,mapreduce框架会根据默认的hash分区方法, * 将整个组合将相等的分到一个分区中,这样的话显然不是我们要的效果 */ logger.info("--------out DefinedPartition flag--------"); return (key.getFirstKey().hashCode()&Integer.MAX_VALUE)%numPartitions; } }说明:具体说明看代码注释。3、自定义比较器package com.mr; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 自定义二次排序策略 * @author zengzhaoheng */public class DefinedComparator extends WritableComparator { private static final Logger logger = LoggerFactory.getLogger(DefinedComparator.class); public DefinedComparator() { super(CombinationKey.class,true); } @Override public int compare(WritableComparable combinationKeyOne, WritableComparable CombinationKeyOther) { logger.info("---------enter DefinedComparator flag---------"); CombinationKey c1 = (CombinationKey) combinationKeyOne; CombinationKey c2 = (CombinationKey) CombinationKeyOther; /** * 确保进行排序的数据在同一个区内,如果不在同一个区则按照组合键中第一个键排序 * 另外,这个判断是可以调整最终输出的组合键第一个值的排序 * 下面这种比较对第一个字段的排序是升序的,如果想降序这将c1和c2���过来(假设1) */ if(!c1.getFirstKey().equals(c2.getFirstKey())){ logger.info("---------out DefinedComparator flag---------"); return c1.getFirstKey().compareTo(c2.getFirstKey()); } else{//按照组合键的第二个键的升序排序,将c1和c2倒过来则是按照数字的降序排序(假设2) logger.info("---------out DefinedComparator flag---------"); return c1.getSecondKey().get()-c2.getSecondKey().get();//0,负数,正数 } /** * (1)按照上面的这种实现最终的二次排序结果为: * sort1 1,2 * sort2 3,54,77 * sort6 20,22,221 * (2)如果实现假设1,则最终的二次排序结果为: * sort6 20,22,221 * sort2 3,54,77 * sort1 1,2 * (3)如果实现假设2,则最终的二次排序结果为: * sort1 2,1 * sort2 77,54,3 * sort6 221,22,20 */ } }说明:自定义比较器决定了我们二次排序的结果。自定义比较器需要继承WritableComparator类,并且重写compare方法实现自己的比较策略。具体的排序问题请看注释。4、自定义分组策略package com.mr; import org.apache.Hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 自定义分组策略 * 将组合将中第一个值相同的分在一组 * @author zengzhaozheng */public class DefinedGroupSort extends WritableComparator{ private static final Logger logger = LoggerFactory.getLogger(DefinedGroupSort.class); public DefinedGroupSort() { super(CombinationKey.class,true); } @Override public int compare(WritableComparable a, WritableComparable b) { logger.info("-------enter DefinedGroupSort flag-------"); CombinationKey ck1 = (CombinationKey)a; CombinationKey ck2 = (CombinationKey)b; logger.info("-------Grouping result:"+ck1.getFirstKey(). compareTo(ck2.getFirstKey())+"-------"); logger.info("-------out DefinedGroupSort flag-------"); return ck1.getFirstKey().compareTo(ck2.getFirstKey()); } }5、主体程序实现package com.mr3;import java.io.File;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Iterator;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.Text;import org.apache.hadoop.mapred.JobConf;import org.apache.hadoop.mapreduce.Job;import org.apache.hadoop.mapreduce.Mapper;import org.apache.hadoop.mapreduce.Mapper.Context;import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;import org.apache.hadoop.mapreduce.lib.input.KeyValueTextInputFormat;import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;import org.apache.hadoop.mapreduce.Reducer;import com.mr1.EJob;import com.mr2.MyWordCountJob;import com.mr2.MyWordCountJob.MyMapper;import com.mr2.MyWordCountJob.MyReducer;public class SecondSortMR {public static class SortMapper extends Mapper<Text,Text,CombinationKey,IntWritable>{CombinationKey combinationKey=new CombinationKey();Text sortName=new Text();IntWritable score=new IntWritable();String[] inputString=null;@Overrideprotected void map(Text key, Text value, Context context)throws IOException, InterruptedException {System.out.println("SecondSortMR.SortMapper.map()");System.out.println("SecondSortMR.SortMapper.map()----key="+key);System.out.println("SecondSortMR.SortMapper.map()----value="+value);if(key == null || value == null || key.toString().equals("") || value.toString().equals("")){ return; } sortName.set(key.toString());score.set(Integer.parseInt(value.toString()));combinationKey.setFirstKey(sortName);combinationKey.setSecondKey(score);context.write(combinationKey, score);}}public static class SortReducer extends Reducer<CombinationKey,IntWritable,Text,Text>{StringBuffer sb=new StringBuffer();Text sore=new Text();@Overrideprotected void reduce(CombinationKey key, Iterable<IntWritable> values,Context context)throws IOException, InterruptedException {sb.delete(0, sb.length());Iterator<IntWritable> it=values.iterator();while (it.hasNext()) {sb.append(it.next()+",");}if (sb.length()>0) {sb.deleteCharAt(sb.length()-1);}sore.set(sb.toString());context.write(key.getFirstKey(),sore);System.out.println("reduce Input data:{["+key.getFirstKey()+","+ key.getSecondKey()+"],["+sore+"]}");}}public static void main(String[] args) throws Exception {File jarFile = EJob.createTempJar("bin");ClassLoader classLoader = EJob.getClassLoader();Thread.currentThread().setContextClassLoader(classLoader);Configuration conf = new Configuration(true);conf.set("fs.default.name", "hdfs://192.168.56.111:9000");conf.set("hadoop.job.user", "root");conf.set("mapreduce.framework.name", "yarn");conf.set("mapreduce.jobtracker.address", "192.168.56.111:9001");conf.set("yarn.resourcemanager.hostname", "192.168.56.111");conf.set("yarn.resourcemanager.admin.address", "192.168.56.111:8033");conf.set("yarn.resourcemanager.address", "192.168.56.111:8032");conf.set("yarn.resourcemanager.resource-tracker.address","192.168.56.111:8031");conf.set("yarn.resourcemanager.scheduler.address","192.168.56.111:8030");conf.setBoolean("mapreduce.app-submission.cross-platform", true);String[] otherArgs = new String[2];otherArgs[0] = "hdfs://192.168.56.111:9000/day0617/sort.txt";// 计算原文件目录,需提前在里面存入文件String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());otherArgs[1] = "hdfs://192.168.56.111:9000/output/output" + time;// 计算后的计算结果存储目录,每次程序执行的结果目录不能相同,所以添加时间标签Job job = new Job(conf, "SecondSortMR job");job.setJarByClass(SecondSortMR.class);((JobConf) job.getConfiguration()).setJar(jarFile.toString());// 环境变量调用,添加此句则可在eclipse中直接提交mapreduce任务,如果将该java文件打成jar包,需要将该句注释掉,否则在执行时反而找不到环境变量System.out.println("Job start!");FileInputFormat.addInputPath(job, new Path(otherArgs[0]));FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));job.setMapperClass(SortMapper.class);job.setReducerClass(SortReducer.class);job.setPartitionerClass(DefinedPartition.class); //设置自定义分区策略 job.setGroupingComparatorClass(DefinedGroupSort.class); //设置自定义分组策略job.setSortComparatorClass(DefinedComparator.class);//设置自定义二次排序策略 job.setInputFormatClass(KeyValueTextInputFormat.class);//设置文件输入格式job.setOutputFormatClass(TextOutputFormat.class);//使用默认的output格式 //设置map的输出key和value类型 job.setMapOutputKeyClass(CombinationKey.class);job.setOutputValueClass(IntWritable.class); //设置reduce的输出key和value类型 job.setOutputKeyClass(Text.class);job.setOutputValueClass(Text.class);if (job.waitForCompletion(true)) {System.out.println("ok!");} else {System.out.println("error!");System.exit(0);}}}
1 0
- mapreduce(三):自定义二次排序流程实例详解
- Hadoop之MapReduce自定义二次排序流程实例详解
- Hadoop之MapReduce自定义二次排序流程实例详解
- Hadoop之MapReduce自定义二次排序流程实例详解
- Hadoop二次排序及MapReduce处理流程实例详解
- Hadoop二次排序及MapReduce处理流程实例详解
- Hadoop二次排序及MapReduce处理流程实例详解
- Hadoop二次排序及MapReduce处理流程实例详解
- Hadoop二次排序及MapReduce处理流程实例详解
- Mapreduce二次排序实例
- MapReduce自定义二次排序
- MapReduce 二次排序详解
- hadoop二次排序流程实例详解
- MapReduce编程实例:二次排序
- MapReduce实现自定义二次排序
- MapReduce-自定义Key-二次排序
- mapreduce学习笔记-二次排序(自定义数据类型,自定义分区分组)
- mapreduce二次排序
- [leetcode] 100. Same Tree
- CardView的介绍和使用
- jQuery Mobile基础学习(2)(控件)
- mysql 语句大全
- Android基础_6.0权限请求
- mapreduce(三):自定义二次排序流程实例详解
- Java正则表达式入门
- VelocityTracker简介
- Android AIDL进程间通信介绍
- mysql 常用操作(整理)
- C++覆盖特性在android源码中的实际应用
- hive中的时间处理函数
- Swift
- iOS推送、3D touch、分享等进入APP的不同跳转方式