11.shuffle的机制、排序和分区
shuffle英文翻译:洗牌。
在mapreduce中间阶段,作用有缓存,排序和分区。缓存的大小可以更改,在mapreduce-site.xml配置:
<name>io.sort</name><value>1000</value>,单位是M,默认的缓存大小是100M。下面根据shuffle的图形详细说一下shuffle的作用。
Map阶段将结果输出到shuffle缓存中,如果缓存不够,则暂时存于本机的磁盘上(分区和排序存储),根据数据量的大小可能存在多个文件,当Map完成后,shuffle将这些文件的数据汇总到总的文件中(分区和排序,有几个分区就有几个文件,文件带索引),磁盘文件汇总阶段是combiner在管理。
Reduce阶段将这些分区和排序好的数据根据reduceTask处理的分区编号拿到集群上的属于自己应该处理的文件归并并排序后进行处理。
接下来就是怎么用代码实现分区和排序?
排序:Map阶段处理数据的时候,shuffle默认是根据key的hashcode值来排序,如果我们要自定义的话就需要去实现hadoop的排序接口WritableComparable<FlowSortBean>,会让你重写方法:
@Override public int compareTo(FlowSortBean o) { //按照从大到小排序,如果本对象大,则返回-1,本对象小,则返回1. //默认1为升序排列,-1为降。 return this.getSumFlow()>o.getSumFlow()?-1:1; }
注意在map阶段输出的时候,你如果要根据这个类的某个属性来排序的话,map中的输出的key必须是此对象,shuffle是根据map输出的key值排序的。
分区:自定义一个类来集成父类Partitioner,重写getPartition方法,根据map输出的key值或者value值return分区号对应不同的reduceTask。
package com.xws.flowCountSortAndPartitionerMapreduce; import java.util.HashMap; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Partitioner; public class PhonePartitioner extends Partitioner<FlowSortBean, Text>{ private static HashMap<String,Integer> phoneMap = new HashMap<String,Integer>(); /** * 获得分类,如果是135开头的,则为分区1的数据 */ static{ phoneMap.put("135", 0); phoneMap.put("136", 1); phoneMap.put("137", 2); phoneMap.put("138", 3); } @Override public int getPartition(FlowSortBean fsb, Text phone, int arg2) { String key = phone.toString().substring(0, 3); Integer value = phoneMap.get(key); if(value==null){ value=4; } return value; } }
另外,还需要在job调用阶段设置自定义的排序类以及分区个数。
//指定自定义的partitioner以及partitioner的个数 job.setPartitionerClass(PhonePartitioner.class); job.setNumReduceTasks(5);
Map和reuduce的代码如下:在流量统计的基础上既实现了排序,也实现了分区。
package com.xws.flowCountSortAndPartitionerMapreduce; import java.io.IOException; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.LongWritable; 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.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; //com.xws.flowCountSortAndPartitionerMapreduce.FlowSortPartitionerCount public class FlowSortPartitionerCount { /** * 我们将数据已经汇总之后进行排序 * * @author root * */ public static class FlowCountMapper extends Mapper<LongWritable, Text, FlowSortBean, Text> { @Override protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, FlowSortBean, Text>.Context context) throws IOException, InterruptedException { // 获取本行数据 String line = value.toString(); // 将数据拆分 String[] fileds = StringUtils.split(line, "\t"); String phone = fileds[0]; long upFlow = Long.parseLong(fileds[1]); long dFlow = Long.parseLong(fileds[2]); context.write(new FlowSortBean(upFlow, dFlow),new Text(phone)); } } /** * reduce将shuffle阶段排序好的数据直接输出 * @author root * */ public static class FlowCountReducer extends Reducer<FlowSortBean, Text, Text, FlowSortBean> { @Override protected void reduce(FlowSortBean key, Iterable<Text> values, Reducer<FlowSortBean, Text, Text, FlowSortBean>.Context context) throws IOException, InterruptedException { context.write(values.iterator().next(), key); } } public static void main(String[] args) throws Exception { Job job = Job.getInstance(); // 设置工作类 job.setJarByClass(FlowSortPartitionerCount.class); // 设置map和reduce类 job.setMapperClass(FlowCountMapper.class); job.setReducerClass(FlowCountReducer.class); // 设置map输出结果类型 job.setMapOutputKeyClass(FlowSortBean.class); job.setMapOutputValueClass(Text.class); // 设置输出结果类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(FlowSortBean.class); //指定自定义的partitioner以及partitioner的个数 job.setPartitionerClass(PhonePartitioner.class); job.setNumReduceTasks(5); // 设置输入输出文件路径 FileInputFormat.setInputPaths(job, args[0]); FileOutputFormat.setOutputPath(job, new Path(args[1])); // 打印过程信息 boolean flag = job.waitForCompletion(true); System.exit(flag ? 0 : 1); } }
结果文件如下:
part-r-00000
part-r-00000
part-r-00000
part-r-00000
part-r-00000