「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战」。
一、Shuffle 机制
Map方法之后,Reduce方法之前的数据处理过程称之为Shuffle。
二、Partition 分区
三、Partition 分区实操案例
-
需求
将统计结果按照手机归属地不同省份输出到不同文件中(分区)
-
需求分析
-
增加分区类
public class ProvincePartitioner extends Partitioner<Text, FlowBean> { @Override public int getPartition(Text text, FlowBean flowBean, int numPartitions) { // text 是手机号 String phone = text.toString(); String prePhone = phone.substring(0, 3); int partition; if ("136".equals(prePhone)) { partition = 0; } else if ("137".equals(prePhone)) { partition = 1; } else if ("138".equals(prePhone)) { partition = 2; } else if ("139".equals(prePhone)) { partition = 3; } else { partition = 4; } return partition; } }
-
在驱动函数中增加自定义数据分区设置和ReduceTask设置
job.setPartitionerClass(ProvincePartitioner.class); job.setNumReduceTasks(5);
四、WritableComparable 排序
1、排序概述
排序是MapReduce框架中最重要的操作之一。
MapTask和ReduceTask均会对数据按照key进行排序。该操作属于Hadoop的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。
默认排序是按照字典顺序排序,且实现该排序的方法是快速排序。
对于MapTask,它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序。
对于ReduceTask,它从每个MapTask上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask统一对内存和磁盘上的所有数据进行一次归并排序。
2、排序分类
2.1、部分排序
MapReduce根据输入记录的键对数据集排序。保证输出的每个文件内部有序。
2.2、全排序
最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个ReduceTask。但该方法在处理大型文件时效率极低,因为一台机器处理所有文件,完全丧失了MapReduce所提供的并行架构。
2.3、辅助排序
GroupingComparator分组
在Reduce端对key进行分组。应用于:在接收的key为bean对象时,想让一个或几个字段相同(全部字段比较不相同)的key进入到同一个reduce方法时,可以采用分组排序。
2.4、二次排序
在自定义排序过程中,如果compareTo中的判断条件为两个即为二次排序。
3、自定义排序
bean对象做为key传输,需要实现WritableComparable接口重写compareTo方法,就可以实现排序。
五、WritableComparable 排序案例实操(全排序)
-
需求 根据手机号分区案例产生的结果,再次对总流量进行倒序排序。
期望输出数据
-
需求分析
-
代码实现
- FlowBean对象在在需求基础上增加了比较功能
public class FlowBean implements WritableComparable<FlowBean> { private long upFlow; // 上行流量 private long downFlow; // 下行流量 private long sumFlow; // 总流量 // 空参构造 public FlowBean() { } public long getUpFlow() { return upFlow; } public void setUpFlow(long upFlow) { this.upFlow = upFlow; } public long getDownFlow() { return downFlow; } public void setDownFlow(long downFlow) { this.downFlow = downFlow; } public long getSumFlow() { return sumFlow; } public void setSumFlow(long sumFlow) { this.sumFlow = sumFlow; } public void setSumFlow() { this.sumFlow = this.upFlow + this.downFlow; } @Override public void write(DataOutput out) throws IOException { out.writeLong(upFlow); out.writeLong(downFlow); out.writeLong(sumFlow); } @Override public void readFields(DataInput in) throws IOException { this.upFlow = in.readLong(); this.downFlow = in.readLong(); this.sumFlow = in.readLong(); } @Override public String toString() { return upFlow + "\t" + downFlow + "\t" + sumFlow; } @Override public int compareTo(FlowBean o) { // 总流量的倒序排序 return Long.compare(this.sumFlow, o.sumFlow); } }
- 编写Mapper类
public class FlowMapper extends Mapper<LongWritable, Text, FlowBean, Text> { private FlowBean outK = new FlowBean(); private Text outV = new Text(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 获取一行 String line = value.toString(); // 切割 String[] split = line.split("\t"); // 封装 outV.set(split[0]); outK.setUpFlow(Long.parseLong(split[1])); outK.setDownFlow(Long.parseLong(split[2])); outK.setSumFlow(); // 写出 context.write(outK, outV); } }
- 编写Reducer类
public class FlowReducer extends Reducer<FlowBean, Text, Text, FlowBean> { @Override protected void reduce(FlowBean key, Iterable<Text> values, Context context) throws IOException, InterruptedException { for (Text value : values) { context.write(value, key); } } }
- 编写Driver类
public class FlowDriver { public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { // 1 获取job Configuration conf = new Configuration(); Job job = Job.getInstance(conf); // 2 设置jar job.setJarByClass(FlowDriver.class); // 3 关联mapper 和Reducer job.setMapperClass(FlowMapper.class); job.setReducerClass(FlowReducer.class); // 4 设置mapper 输出的key和value类型 job.setMapOutputKeyClass(FlowBean.class); job.setMapOutputValueClass(Text.class); // 5 设置最终数据输出的key和value类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(FlowBean.class); // 6 设置数据的输入路径和输出路径 FileInputFormat.setInputPaths(job, new Path("D:\hadoop\output4")); FileOutputFormat.setOutputPath(job, new Path("D:\hadoop\output6")); // 7 提交job boolean result = job.waitForCompletion(true); System.exit(result ? 0 : 1); } }
六、WritableComparable 排序案例实操(区内排序)
-
需求
要求每个省份手机号输出的文件中按照总流量内部排序。
-
需求分析
基于前一个需求,增加自定义分区类,分区按照省份手机号设置。
-
案例实操
- 增加自定义分区类
public class ProvincePartitioner2 extends Partitioner<FlowBean, Text> { @Override public int getPartition(FlowBean flowBean, Text text, int numPartitions) { String phone = text.toString(); String prePhone = phone.substring(0, 3); int partition; if ("136".equals(prePhone)) { partition = 0; } else if ("137".equals(prePhone)) { partition = 1; } else if ("138".equals(prePhone)) { partition = 2; } else if ("139".equals(prePhone)) { partition = 3; } else { partition = 4; } return partition; } }
- 在驱动类中添加分区类
job.setPartitionerClass(ProvincePartitioner2.class); job.setNumReduceTasks(5);
七、Combiner 合并
- Combiner是MR程序中Mapper和Reducer之外的一种组件。
- Combiner组件的父类就是Reducer。
- Combiner和Reducer的区别在于运行的位置。
- Combiner是在每一个MapTask所在的节点运行;
- Reducer是接收全局所有Mapper的输出结果;
- Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减小网络传输量。
- Combiner能够应用的前提是不能影响最终的业务逻辑,而且,Combiner的输出kv应该跟Reducer的输入kv类型要对应起来。
- 自定义Combiner实现步骤
- 自定义一个Combiner继承Reducer,重写Reduce方法
public class WordCountCombiner extends Reducer<Text, IntWritable, Text, IntWritable> { private IntWritable outV = new IntWritable(); @Override protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable value : values) { sum += value.get(); } outV.set(sum); context.write(key,outV); } }
- 在Job驱动类中设置
job.setCombinerClass(WordCountCombiner.class);
八、Combiner 合并案例实操
-
需求
统计过程中对每一个MapTask的输出进行局部汇总,以减小网络传输量即采用Combiner功能。
期望:Combine输入数据多,输出时经过合并,输出数据降低。
-
需求分析
-
案例实操-方案一
- 增加一个WordCountCombiner类继承Reducer
public class WordCountCombiner extends Reducer<Text, IntWritable, Text, IntWritable> { private IntWritable outV = new IntWritable(); @Override protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable value : values) { sum += value.get(); } outV.set(sum); context.write(key, outV); } }
- 在WordcountDriver驱动类中指定Combiner
job.setCombinerClass(WordCountCombiner.class);
-
案例实操-方案二
将WordcountReducer作为Combiner在WordcountDriver驱动类中指定
// 指定需要使用Combiner,以及用哪个类作为Combiner的逻辑 job.setCombinerClass(WordCountReducer.class);