文章目录
一、Shuffle机制
Map
方法之后,Reduce
方法之前的数据处理过程称之为Shuffle
(多称为洗牌)
二、Partition分区
(一)Partition分区详解
-
默认
Partition
分区为HashPartitioner
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; } }
**注意:**默认分区是根据
key
的hashCode
对ReduceTasks
个数取模得到的。用户没法控制哪个key
存储到哪个分区 -
自定义
Partition
分区步骤(1)自定义类继承
Partitioner
,重写getPartition()
方法public class CustomPartitioner extends Partitioner<Text, FlowBean> { @Override public int getPartition(Text key, FlowBean value, int numPartitions) { // 控制分区代码逻辑 … … return partition; } }
(2)在Job驱动中,设置自定义
Partitioner
job.setPartitionerClass(CustomPartitioner.class);
(3)自定义
Partition
后,要根据自定义Partitioner
的逻辑设置相应数量的ReduceTask
job.setNumReduceTasks(5);
-
分区总结
(1)如果ReduceTask的数量> getPartition的结果数,则会多产生几个空的输出文件part-r-000xx;
(2)如果1<ReduceTask的数量<getPartition的结果数,则有一部分分区数据无处安放,会Exception;
(3)如果ReduceTask的数量=1,则不管MapTask端输出多少个分区文件,最终结果都交给这一个ReduceTask,最终也就只会产生一个结果文件 part-r-00000;
(4)分区号必须从零开始,逐一累加。
-
示例分析
假设自定义分区数为5,则
(1)job.setNumReduceTasks(1); 会正常运行,只不过会产生一个输出文件
(2)job.setNumReduceTasks(2); 会报错
(3)job.setNumReduceTasks(6); 大于5,程序会正常运行,会产生空文件
(二)Partition分区案例
-
需求
将统计结果按照手机归属地不同省份输出到不同文件中(分区)。
-
输入数据
电话号码。
-
期望输出
手机号136、137、138、139开头都分别放到一个独立的4个文件中,其他开头的放到一个文件中。
-
在序列化案例的基础上进行如下操作
(1)创建
ProvincePartitioner
分区类:package com.easysir.flowsum; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Partitioner; public class ProvincePartitioner extends Partitioner<Text, FlowBean> { @Override public int getPartition(Text key, FlowBean value, int numPartitions) { // 1 获取电话号码的前三位 String preNum = key.toString().substring(0, 3); int partition = 4; // 2 判断是哪个省 switch (preNum) { case "136": partition = 0; break; case "137": partition = 1; break; case "138": partition = 2; break; case "139": partition = 3; break; } return partition; } }
(2)在驱动类
FlowsumDriver
中增加自定义数据分区设置和ReduceTask
设置:// 指定自定义数据分区 job.setPartitionerClass(ProvincePartitioner.class); // 同时指定相应数量的reduce task job.setNumReduceTasks(5);
三、WritableComparable排序
(一)WritableComparable排序详解
排序是MapReduce框架中最重要的操作之一
-
排序概述
MapTask
和ReduceTask
均会对数据按照key
进行排序,该操作属于Hadoop
的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。默认排序是按照字典顺序排序,且实现该排序的方法是快速排序。 对于
MapTask
,它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序。 对于
ReduceTask
,它从每个MapTask
上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则进行一次归并排序以生成一个更大文件;如果内存文件中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask
同意对内存和磁盘上的所有数据进行一次归并排序。 -
排序分类
(1)部分排序
MapReduce根据输入记录的键对数据集排序,保证输出的每个文件内部有序。
(2)全排序
最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个
ReduceTask
。但该方法在处理大型文件时效率极低,因为一台机器处理所有文件,完全丧失了MapReduce
所提供的并行架构。(3)辅助排序(
GroupingComparator
分组) 在
Reduce
端对key
进行分组。应用于:在接收的key
为bean
对象时,想让一个或几个字段相同(全部字段比较不相同)的key
进入到同一个reduce
方法时,可以采用分组排序。(4)二次排序
在自定义排序过程中,如果
compareTo
中的判断条件为两个即为二次排序。 -
自定义排序
WritableComparable
bean
对象做为key
传输,需要实现WritableComparable
接口重写compareTo
方法,就可以实现排序。
(二)WritableComparable排序案例之全排序
-
需求
在序列化案例的基础上,将结果按照总流量降序排列。
-
创建包名:
com.easysir.sort
-
创建
FlowBean
类,实现WritableComparable
接口,重写compareTo
方法:package com.easysir.sort; import org.apache.hadoop.io.WritableComparable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; public class FlowBean implements WritableComparable<FlowBean> { private long upFlow; // 上行流量 private long downFlow; // 下行流量 private long sumFlow; // 总流量 public FlowBean() { super(); } public FlowBean(long upFlow, long downFlow) { super(); this.upFlow = upFlow; this.downFlow = downFlow; this.sumFlow = upFlow + downFlow; } // 比较方法 @Override public int compareTo(FlowBean bean) { int result; // 核心比较方法 if (sumFlow > bean.getSumFlow()){ result = -1; }else if (sumFlow < bean.getSumFlow()) { result = 1; }else { result = 0; } return result; } // 序列化方法 @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 { upFlow = in.readLong(); downFlow = in.readLong(); sumFlow = in.readLong(); } 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; } @Override public String toString() { return upFlow + "\t" + downFlow + "\t" + sumFlow; } }
-
创建
FlowCountSortMapper
类:package com.easysir.sort; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; import java.io.IOException; /** * description * * @author Hu.Wang 2020/02/10 13:23 */ public class FlowCountSortMapper extends Mapper<LongWritable, Text, FlowBean, Text> { FlowBean k = new FlowBean(); Text v = new Text(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 1 获取一行 String line = value.toString(); // 2 按\t切割 String[] fields = line.split("\t"); // 3 封装对象 String phoneNum = fields[0]; long upFlow = Long.parseLong(fields[1]); long downFlow = Long.parseLong(fields[2]); long sumFlow = Long.parseLong(fields[3]); k.setUpFlow(upFlow); k.setDownFlow(downFlow); k.setSumFlow(sumFlow); v.set(phoneNum); // 4 写出 context.write(k, v); } }
-
创建
FlowCountSortReducer
类:package com.easysir.sort; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException; public class FlowCountSortReducer 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); } } }
-
创建
FlowCountSortDriver
类:package com.easysir.sort; import com.easysir.flowsum.FlowsumDriver; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import java.io.IOException; public class FlowCountSortDriver { public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { // 输入输出路径需要根据自己电脑上实际的输入输出路径设置 args = new String[] { "E:\\idea-workspace\\mrWordCount\\output", "E:\\idea-workspace\\mrWordCount\\output1" }; // 1 获取配置信息 Configuration conf = new Configuration(); Job job = Job.getInstance(conf); // 2 指定jar包所在本地路径 job.setJarByClass(FlowsumDriver.class); // 3 关联mapper和reducer job.setMapperClass(FlowCountSortMapper.class); job.setReducerClass(FlowCountSortReducer.class); // 4 指定map输出kv类型 job.setMapOutputKeyClass(FlowBean.class); job.setMapOutputValueClass(Text.class); // 5 指定最终输出类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(FlowBean.class); // 6 指定输入输出路径 FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); // 7 提交job job.waitForCompletion(true); } }
(三)WritableComparable排序案例之区内排序
-
需求
在全排序案例的基础上将手机号分区。
-
在全排序案例的基础上增加自定义分区类
ProvincePartitioner
:package com.easysir.sort; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Partitioner; /** * description * * @author Hu.Wang 2020/02/10 14:11 */ public class ProvincePartitioner extends Partitioner<FlowBean, Text> { @Override public int getPartition(FlowBean bean, Text text, int numPartitions) { // 1 获取手机号前三位 String preNum = text.toString().substring(0, 3); int partition = 4; // 2 根据手机号前三位判断分区 switch (preNum) { case "136": partition = 0; break; case "137": partition = 1; break; case "138": partition = 2; break; case "139": partition = 3; break; } return partition; } }
-
在驱动类
FlowCountSortDriver
中添加分区类配置:// 加载自定义分区类 job.setPartitionerClass(ProvincePartitioner.class); // 设置Reducetask个数 job.setNumReduceTasks(5);
四、Combiner合并
(一)Combiner合并详解
-
Combiner
是MR
程序中Mapper
和Reducer
之外的一种组件。 -
Combiner
组件的父类就是Reducer
。 -
Combiner
和Reducer
的区别在于运行的位置(1)
Combiner
是在每一个MapTask
所在的节点运行;(2)
Reducer
是接收全局所有Mapper
的输出结果。 -
Combiner
的意义就是对每一个MapTask
的输出进行局部汇总,以减小网络传输量。 -
Combiner
能够应用的前提是不影响最终的业务逻辑,而且,Combiner
的输出kv
应该跟Reducer
的输入kv
类型对应起来。 -
自定义
Combiner
实现步骤:(1)自定义一个Combiner继承Reducer,重写Reduce方法
public class WordcountCombiner extends Reducer<Text, IntWritable, Text,IntWritable>{ @Override protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException { // 1 汇总操作 int count = 0; for(IntWritable v :values){ count += v.get(); } // 2 写出 context.write(key, new IntWritable(count)); } }
(2)在驱动类中设置:
job.setCombinerClass(WordcountCombiner.class);
(二)Combiner合并案例
-
需求
统计过程中对每一个
MapTask
的输出进行局部汇总,以减小网络传输量即采用Combiner
功能。 -
输入数据
banzhang ni hao xihuan hadoop banzhang banzhang ni hao xihuan hadoop banzhang
-
期望输出
Combine输入数据多,输出时经过合并,输出数据降低。
Combine input records=12 Combine output records=5
-
在WordCount案例基础上进行如下操作
方案一:
(1)创建
WordcountCombiner
类继承Reducer
:package com.easysir.wordcount; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException; public class WordcountCombiner extends Reducer<Text, IntWritable, Text, IntWritable>{ IntWritable v = new IntWritable(); @Override protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { // 1 汇总 int sum = 0; for(IntWritable value :values){ sum += value.get(); } v.set(sum); // 2 写出 context.write(key, v); } }
(2)在
WordcountDriver
驱动类中指定Combiner
// 指定需要使用combiner,以及用哪个类作为combiner的逻辑 job.setCombinerClass(WordcountCombiner.class);
方案二:
将WordcountReducer作为Combiner在WordcountDriver驱动类中指定:
// 指定需要使用Combiner,以及用哪个类作为Combiner的逻辑 job.setCombinerClass(WordcountReducer.class);
五、GroupingComparator分组(辅助)排序
(一)GroupingComparator详解
GroupingComparator分组(辅助)排序:对Reduce阶段的数据根据一个或几个字段进行分组
分组排序的步骤:
-
自定义类继承
WritableComparator
-
重写
compare()
方法@Override public int compare(WritableComparable a, WritableComparable b) { // 比较的业务逻辑 return result; }
-
创建一个构造将比较对象的类传给父类
protected OrderGroupingComparator() { super(OrderBean.class, true); }
(二)GroupingComparator分组排序案例
-
需求
输出每一个订单中最贵的商品。
-
输入数据
0000001 Pdt_01 222.8 0000002 Pdt_05 722.4 0000001 Pdt_02 33.8 0000003 Pdt_06 232.8 0000003 Pdt_02 33.8 0000002 Pdt_03 522.8 0000002 Pdt_04 122.4
-
期望输出数据
1 222.8 2 722.4 3 232.8
-
创建包:
com.easysir.groupingcomparator
-
创建订单信息类
OrderBean
:package com.easysir.groupingcomparator; import org.apache.hadoop.io.WritableComparable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; public class OrderBean implements WritableComparable<OrderBean> { private int order_id; // 订单id private double price; // 价格 public OrderBean() { super(); } public OrderBean(int order_id, double price) { super(); this.order_id = order_id; this.price = price; } @Override public int compareTo(OrderBean bean) { // 先按照订单id升序排序,若订单相同则按价格降序排序 int result; if (order_id > bean.getOrder_id()) { result = 1; }else if (order_id < bean.getOrder_id()) { result = -1; }else { if (price > bean.getPrice()){ result = -1; }else if (price < bean.getPrice()){ result = 1; }else { result = 0; } } return 0; } @Override public void write(DataOutput out) throws IOException { out.writeInt(order_id); out.writeDouble(price); } @Override public void readFields(DataInput in) throws IOException { order_id = in.readInt(); price = in.readDouble(); } public int getOrder_id() { return order_id; } public void setOrder_id(int order_id) { this.order_id = order_id; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } @Override public String toString() { return order_id + "\t" + price; } }
-
创建
OrderSortMapper
类:package com.easysir.groupingcomparator; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; import java.io.IOException; public class OrderSortMapper extends Mapper<LongWritable, Text, OrderBean, NullWritable> { OrderBean k = new OrderBean(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 1 获取一行 String line = value.toString(); // 2 切割 String[] fields = line.split(" "); // 3 将订单id和价格封装至bean对象 k.setOrder_id(Integer.parseInt(fields[0])); k.setPrice(Double.parseDouble(fields[1])); // 4 写出 context.write(k, NullWritable.get()); } }
-
创建
OrderSortGroupingComparator
类package com.easysir.groupingcomparator; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; public class OrderSortGroupingComparator extends WritableComparator { public OrderSortGroupingComparator() { // 注意这里要传入两个参数,第一个参数为比较类,第二个参数若置false则将所有key置空,会报空指针异常 super(OrderBean.class, true); } @Override public int compare(WritableComparable a, WritableComparable b) { // 只要id相同,则认定为相同的key OrderBean aBean = (OrderBean) a; OrderBean bBean = (OrderBean) b; int result; if (aBean.getOrder_id() > bBean.getOrder_id()){ result = 1; }else if (aBean.getOrder_id() < bBean.getOrder_id()){ result = -1; }else { result = 0; } return result; } }
-
创建
OrderSortReducer
类:package com.easysir.groupingcomparator; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException; public class OrderSortReducer extends Reducer<OrderBean, NullWritable, OrderBean, NullWritable> { @Override protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException { context.write(key, NullWritable.get()); } }
-
创建
OrderSortDriver
类:package com.easysir.groupingcomparator; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import java.io.IOException; public class OrderSortDriver { public static void main(String[] args) throws Exception, IOException { // 输入输出路径需要根据自己电脑上实际的输入输出路径设置 args = new String[] { "E:\\idea-workspace\\mrWordCount\\input\\grouping_data.txt", "E:\\idea-workspace\\mrWordCount\\output" }; // 1 获取配置信息 Configuration conf = new Configuration(); Job job = Job.getInstance(conf); // 2 设置jar包加载路径 job.setJarByClass(OrderSortDriver.class); // 3 加载map/reduce类 job.setMapperClass(OrderSortMapper.class); job.setReducerClass(OrderSortReducer.class); // 4 设置map输出数据key和value类型 job.setMapOutputKeyClass(OrderBean.class); job.setMapOutputValueClass(NullWritable.class); // 5 设置最终输出数据的key和value类型 job.setOutputKeyClass(OrderBean.class); job.setOutputValueClass(NullWritable.class); // 6 设置输入数据和输出数据路径 FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); // 8 设置reduce端的分组 job.setGroupingComparatorClass(OrderSortGroupingComparator.class); // 7 提交 boolean result = job.waitForCompletion(true); System.exit(result ? 0 : 1); } }