MR运行程序中自定义分区器
简述分区
- 简单来说,就是根据数据的特点将其分成多个输出结果,每个分区由一个reduceTask处理,也即是reduceTask的数量等于分区数。
- mr默认使用的是hash分区器,通过对key的hash值排序进行分区。
- 如果不设置reduceTask的任务数的话,不管设置成怎样的分区器最后都只有一个输出的文件,因为只有一个reduceTask在处理数据。
自定义分区器
-
此处用我之前学习时写的处理手机山下行流量的mr程序为案例
-
需求
- 此处由于我的数据是自己随机生成的,所以我取其中四个号码的开头157、176、156、130将这四个开头的号码都分到分区0,其他的放到分区1。
-
自定义分区器:
/** * 自定义的分区器 * 继承了mr组件中的Partitioner抽象类 * 其中泛型为map阶段输出的k-v对的类型 * 实现其中的getPartition方法,输入一个键值对,返回其分区 */ public class FlowPatitioner extends Partitioner<Text, FlowBean> { public int getPartition(Text text, FlowBean flowBean, int numPartitions) { //切分作为key的手机号,取前三位作为分区判断的依据 String key = text.toString().substring(0, 3); //初始化分区partition int partition=0; //157、176、156、130 String[] suffix = {"157","176","156","130"}; if (isInString(suffix,key)){ partition = 1; } return partition; } //循环判断该元素是否在数组中 private boolean isInString(String[] suffix,String key){ for (String suf : suffix) { if (suf.equals(key)){ return true; } } return false; } }
只要知道自定义分区器的步骤后,基本就是其内部业务实现了,没什么好说的
-
在Driver类中加入如下两句:
//设置reduceTask数量 job.setNumReduceTasks(2); //设置使用我们自定义的分区器 job.setPartitionerClass(FlowPatitioner.class);
然后就只要跑就行了。
-
结果:
有俩分区:
其中part-r-00001的内容:
13053955523 Flow[upFlow:395535 downFlow:406804 sumFlow]
15662295249 Flow[upFlow:752441 downFlow:781817 sumFlow]
15665641152 Flow[upFlow:1301912 downFlow:1341331 sumFlow]
15760764736 Flow[upFlow:1763048 downFlow:1779707 sumFlow]
17671525249 Flow[upFlow:2230671 downFlow:2270839 sumFlow]
17693411016 Flow[upFlow:2653506 downFlow:2676138 sumFlow]
只有指定开头的字段,自定义分区器成功了。
更改mr程序的排序
简介MR中的排序
排序是MR框架中最重要的操作之一。
- mapTask和reduceTask钧会对数据按照key进行排序。该操作为hadoop的默认行为。任何应用程序中的数据均会被排序而不管逻辑上是否需要。
- 默认排序是按照字典顺序排序,通过快排实现。
记录几种修改排序(利用排序)的方法:
-
对值中的某个数据排序:
-
利用mr会对键进行排序的原理,将要排序的值设置为键:
-
案例,对流量处理数据按总流量排序
-
输入数据:
//对应的属性为: //手机号 上行流量 下行流量 总流量 13053955523 1 0 1 15662295249 11 0 11 15665641152 111 0 111 15760764736 1 1 2 17671525249 11 11 22 17693411016 111 111 222
为了方便观察结果改了改数据
-
期望输出,数据按总流量的大小排序
-
-
自定义实现:
mapper:
/** * 实现思路 * 将sumFlow作为键,手机号、upFlow、downFlow通过\t连接为Text输出 */ public class FlowSortMapper extends Mapper<LongWritable, Text, IntWritable,Text> { private IntWritable k_out = new IntWritable(); private Text v_out = new Text(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] words = value.toString().split("\t"); k_out.set(Integer.parseInt(words[3])); v_out.set(words[0]+"\t"+words[1]+"\t"+words[2]+"\t"); context.write(k_out,v_out); } }
reducer:
/** * 调整思路写出即可 */ public class FlowSortReducer extends Reducer<IntWritable, Text,Text, IntWritable> { @Override protected void reduce(IntWritable key, Iterable<Text> values, Context context) throws IOException, InterruptedException { for (Text value : values) { context.write(value,key); } } }
driver:
public class FlowSortDriver { public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { Path outputPath = new Path( "e:/work/test/output/flowOutput"); Path inputPath = new Path( "e:/work/test/input/compareInput"); FileSystem fs = FileSystem.get(new Configuration()); if (fs.exists(outputPath)){ fs.delete(outputPath , true); } Configuration conf = new Configuration(); Job job = Job.getInstance(conf); job.setJarByClass(FlowSortDriver.class); job.setMapperClass(FlowSortMapper.class); job.setReducerClass(FlowSortReducer.class); job.setMapOutputKeyClass(IntWritable.class); job.setMapOutputValueClass(Text.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.setInputPaths(job,inputPath); FileOutputFormat.setOutputPath(job,outputPath); boolean result = job.waitForCompletion(true); System.out.println(result?1:-1); } }
输出文件内容:
13053955523 1 0 1 15760764736 1 1 2 15662295249 11 0 11 17671525249 11 11 22 15665641152 111 0 111 17693411016 111 111 222
成功
-
-
如要修改排序的方式(升序、降序):
-
方法一:自解继承hadoop提供的封装类,覆写其compareTo方法:
public class MyIntWritable extends IntWritable { @Override public int compareTo(IntWritable o) { return -super.compareTo(o); } }
而后将所有用到的Intwritable类换成该类即可。
-
方法二:(依旧以flow问题为例,将流量的封装对象flowbean实现)
flowBean:
import org.apache.hadoop.io.WritableComparable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; /** * 实现WritableComparable接口,实现其中的compareTo等抽象方法即可 * setter getter于构造方法略 */ public class FlowSortBean implements WritableComparable<FlowSortBean> { private long upFlow; private long downFlow; private long sumFlow; /** * 根据需求修改其中的1和-1的位置即可。 * @param o * @return */ public int compareTo(FlowSortBean o) { return (this.getSumFlow()<o.getSumFlow() ? 1 : (this.getSumFlow()==o.getSumFlow() ? 0 : -1)); } /** * 打包方法 */ public void write(DataOutput out) throws IOException { out.writeLong(upFlow); out.writeLong(downFlow); out.writeLong(sumFlow); } /** * 解包方法 * 注意语句顺序要和打包方法对应 */ public void readFields(DataInput in) throws IOException { upFlow = in.readLong(); downFlow = in.readLong(); sumFlow = in.readLong(); } /** * 重写toString,便于格式化输出 */ @Override public String toString() { return ""+upFlow+"\t"+downFlow+"\t"+sumFlow; } }
Mapper:
import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; /** * 实现思路 * 将实现了WritableComparable接口的FlowBean作为key输出 * 借由mr自带的排序机制排序 */ public class FlowSortMapper extends Mapper<LongWritable, Text, FlowSortBean,Text> { private FlowSortBean k_out = new FlowSortBean(); private Text v_out = new Text(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] words = value.toString().split("\t"); k_out.setUpFlow(Long.parseLong(words[1])); k_out.setDownFlow(Long.parseLong(words[2])); k_out.setSumFlow(Long.parseLong(words[3])); v_out.set(words[0]); context.write(k_out,v_out); } }
reducer:
import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; /** * 调整思路写出即可 */ public class FlowSortReducer extends Reducer<FlowSortBean, Text,Text, FlowSortBean> { @Override protected void reduce(FlowSortBean key, Iterable<Text> values, Context context) throws IOException, InterruptedException { for (Text value : values) { context.write(value,key); } } }
-
Combiner合并
- combiner合并时mr中map和reduce之外的一种组件,其父类就是reducer
- combiner运行于每一个mapTask的节点,负责将对应节点map阶段输出的数据做简单的处理,减少网络传输的量和reducer的工作量
- combiner能够应用的前提时不能影响最终的业务逻辑,且combiner输出的k-v对应当更reducer输入的k-v对相对应
自定义Combiner
-
以自己写的WordCount中添加自定义Combiner为例
-
添加combiner前运行的输出日志中有如下语句段:
Map input records=4 Map output records=11 ... Combine input records=0 Combine output records=0 ... Reduce input records=11 Reduce output records=6
不难看出其combine阶段输入输出都为0,因为没有设置combine阶段
而reduce输入为11,输出为6
-
自定义combiner:
/** * Combiner同样继承Reducer类,输入为mapper的输出,输出即为reducer的输入 * WC中combiner所必要的业务同reducer,所以可以复制reducer的代码 */ public class MyCombiner extends Reducer<Text, IntWritable, Text, IntWritable> { //输出的值 private IntWritable v_out = new IntWritable(); /** * 业务流程同reducer阶段的reduce */ @Override protected void reduce( Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { //计数器 int count = 0; for (IntWritable value : values) { //累加即可 count++; } //封装 v_out.set(count); //写出 context.write(key,v_out); } }
Driver中设置使用combiner:
//设置使用combiner job.setCombinerClass(MyCombiner.class);
配置好之后运行的输出日志中可以看到如下语句:
Map input records=4 Map output records=11 ... Combine input records=11 Combine output records=6 ... Reduce input records=6 Reduce output records=6
很明显,这里因为只有一个mapTask,所以Combiner完成了全部的reducer业务,reducer只是简单的读取文件然后输出,工作量减小了。
如果在集群中处理大量的数据时,很明显combiner阶段还可以明显减少集群中节点间的数据传输。
MR中的GroupingComparator分组(辅助排序
- GroupingComparator即为对reduce阶段中的数据根据某一个或几个字段进行分组
GroupingComparator分组案例:
-
获取每个订单中最贵的商品价格:
-
需求:
-
有如下订单数据:
订单id 商品id 成交金额 0000001 Pdt_01 222.8 Pdt_06 25.8 0000002 Pdt_03 522.8 Pdt_04 122.4 Pdt_05 722.4 0000003 Pdt_07 232.8 Pdt_02 33.8 -
现在需要求每一个订单中最贵的商品。
-
输入数据为:
0000001 Pdt_01 222.8 0000002 Pdt_06 722.4 0000001 Pdt_05 25.8 0000003 Pdt_01 232.8 0000003 Pdt_01 33.8 0000002 Pdt_03 522.8 0000002 Pdt_04 122.4
-
期望输出为:
1 222.8 2 722.4 3 232.8
-
-
需求分析:
- 以订单和成交金额作为key,可以将map阶段读取的订单数据按照id分区,按金额排序,发送给reduce
- 在reduce段利用froupingComparator将订单id相同的k-v聚合成组,取第一个即使最大值
-
代码实现:
OrderBean(省略构造方法和setter、getter方法):
public class OrderBean implements WritableComparable<OrderBean> { //订单id private int order_id; //价格 private double price; /** * 覆写toString方法 */ @Override public String toString() { return order_id+"\t"+price; } /** * 根据订单号排序,如果相同 * 再根据价格降序排序 */ public int compareTo(OrderBean o) { if (order_id > o.getOrder_id()){ return 1; }else if (order_id < o.getOrder_id()){ return -1; }else { return price > o.getPrice() ? -1 : 1; } } //打包 public void write(DataOutput out) throws IOException { out.writeInt(order_id); out.writeDouble(price); } //解包,注意顺序 public void readFields(DataInput in) throws IOException { this.order_id = in.readInt(); this.price = in.readDouble(); } }
OrderSortMapper:
public class OrderSortMapper extends Mapper<LongWritable, Text, OrderBean, NullWritable> { private OrderBean k_out = new OrderBean(); /** * 切割并封装好输入数据即可 */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] words = value.toString().split("\t"); k_out.setOrder_id(Integer.parseInt(words[0])); k_out.setPrice(Double.parseDouble(words[2])); context.write(k_out,NullWritable.get()); } }
OrderSortGroupingComparator:
public class OrderSortGroupingComparator extends WritableComparator { /** * 此处要编辑一下他的构造方法 * 不然在其父类创建对象时会被传入一个null对象 * 导致空指针异常 */ public OrderSortGroupingComparator(){ super(OrderBean.class,true); } /** * 重写compare方法 */ @Override public int compare(WritableComparable a, WritableComparable b) { OrderBean aBean = (OrderBean) a; OrderBean bBean = (OrderBean) b; if (aBean.getOrder_id() > bBean.getOrder_id()){ return 1; }else if (aBean.getOrder_id() < bBean.getOrder_id()){ return -1; }else { return 0; } } }
OrderSortReducer:
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()); } }
driver中加入这一条设置:
//设置使用自定义的GroupingComparatorClass job.setGroupingComparatorClass(OrderSortGroupingComparator.class);
-
运行结果:
1 222.8 2 722.4 3 232.8
符合预期