MapReduce编程练习2.0
2.0新需求:不需要统计总流量,需要将所有数据按照总流量倒序排序。
问题点:
增加排序
解析:
这个第一眼看过去有点懵,因为之前都没有说到有排序这个功能,但是我们仔细查看一下初始需求的结果可以发现:key是正序排列的。
即:手机号是正序排列的,Hadoop有一个特性,就是会对key值进行排序,所以我们在遇到排序这个功能的时候可以想是否能将需要排序的字段作为key。
Hadoop里有一个WritableComparable接口,不仅包括了Writable接口,还包含了Comparable接口,实现这个接口后重写compareTo方法以实现倒序排序。
所以最终解决方案:在map方法中,对数据切分后,以Bean对象作为key,手机号作为value传给reducer,reducer类再次转换过来,将手机号作为key,处理完后的Bean对象最为value统计出来。
2.1 新需求解决编程
1. 先修改bean类FlowSortBean,实现WritableComparable接口
实现WritableComparable接口需要填写比较类,即FlowSortBean类本身,同时还需要重写compareTo方法。compareTo方法返回值是一个int类型的值。
// compareTo 方法用于将当前对象与方法的参数进行比较。
// 如果指定的数与参数相等返回 0。
// 如果指定的数小于参数返回 -1。
// 如果指定的数大于参数返回 1。
// 例如:o1.compareTo(o2);
// 返回正数的话,当前对象(调用 compareTo 方法的对象 o1)要排在比较对象(compareTo 传参对象 o2)后面,返回负数的话,放在前面。
package com.chinasofti.mapreducepractice.flowsum.sort2;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/*
* 自定义的数据封装类,用于封装需求数据
* Java 的序列化(Serializable)是一个重量级序列化框架,
* 一个对象被序列化后,会附带很多额外的信息(各种校验信息,header,继承体系…),不便于在网络中高效传输;
* hadoop 自己开发了一套序列化机制(**Writable**),精简,高效。
* 不用像 java 对象类一样传输多层的父子关系,需要哪个属性就传输哪个属性值,大大的减少网络传输的开销。
* 数据封装类需要实现序列化接口,Hadoop有自己的序列化接口Writable,实现Hadoop的Writable接口
*/
public class FlowSortBean implements WritableComparable<FlowSortBean> {
// 我们要保存三个数据,所以在类中定义三个参数
private long upflow = 0; // 上行流量
private long downflow = 0; // 下行流量
private long sumflow = 0; // 总流量
// 创建无参构造
// 注意:如果没有创建这个无参构造方法,再反序列化的时候会出错
public FlowSortBean(){
super();
}
// 创建有参构造
public FlowSortBean(long upflow, long downflow, long sumflow) {
super();
this.upflow = upflow;
this.downflow = downflow;
this.sumflow = sumflow;
}
//因为入参的时候有时会入下行流量和上行流量,没有入总流量,所有需要再建立一个这样的有参构造方法
public FlowSortBean(long upflow, long downflow) {
super();
this.upflow = upflow;
this.downflow = downflow;
this.sumflow = upflow+downflow;
}
// bean对象常规操作:为属性提供get和set方法
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;
}
// set数据时没有set方法,所以我们在这里手动编写一个set方法
// 主要实现:给属性复赋值
public void set(long upflow, long downflow){
this.upflow = upflow;
this.downflow = downflow;
this.sumflow = upflow+downflow;
}
//这就序列化方法
@Override
public void write(DataOutput dataOutput) throws IOException {
// 先序列化上行流量,再序列化下行流量,最后序列化总流量
// 序列化的方法在 DataOutput 类中以write开头的方法
dataOutput.writeLong(this.upflow);
dataOutput.writeLong(this.downflow);
dataOutput.writeLong(this.sumflow);
}
//这是反序列化方法
//反序列时候 注意序列化的顺序
//先序列化的先出来
@Override
public void readFields(DataInput dataInput) throws IOException {
// 注意反序列化的顺序,先反序列化上行流量,再反序列化下行流量,最后反序列化总流量
// 反序列化的方法在 DataInput 类中以read开头的方法
this.upflow = dataInput.readLong();
this.downflow = dataInput.readLong();
this.sumflow = dataInput.readLong();
}
// 重写toSting方法
@Override
public String toString() {
return upflow+"\t"+downflow+"\t"+sumflow;
}
// 实现WritableComparable需要重写compareTo方法,
// compareTo 方法用于将当前对象与方法的参数进行比较。
// 如果指定的数与参数相等返回 0。
// 如果指定的数小于参数返回 -1。
// 如果指定的数大于参数返回 1。
// 例如:o1.compareTo(o2);
// 返回正数的话,当前对象(调用 compareTo 方法的对象 o1)要排在比较对象(compareTo 传参对象 o2)后面,
// 返回负数的话,放在前面。
@Override
public int compareTo(FlowSortBean o) {
//采用三目运算符实现倒序排序。
return sumflow>o.getSumflow()?-1:1;
}
}
2. 修改Mapper继承类:FlowSumSortMapper
这里我们将kv转换一下即可。
package com.chinasofti.mapreducepractice.flowsum.sort2;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class FlowSumSortMapper extends Mapper<LongWritable, Text, FlowSortBean,Text> {
// mapper类输出的数据是两个对象,不能每运行一次map方法就创建两个对象,这势必会造成很多垃圾
// 我们可以先创建这两个对象,每次运行map方法时将值set这两个对象,从而避免创建多个对象的问题
Text v = new Text();
FlowSortBean k = new FlowSortBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 获取每一行的内容
String line = value.toString();
//将数据按 "/t" 进行切分,这个切分方式是根据数据源来决定的
String[] fields = line.split("\t");
//切分后即可获取手机号和上下行流量
// 手机号就是第二个
String phoneNum = fields[1];
// 上行流量是倒数第三个,切分完后数据类型是String,需要转换成Long
long upflow = Long.parseLong(fields[fields.length - 3]);
// 下行流量是倒数第二个,切分完后数据类型是String,需要转换成Long
long downflow = Long.parseLong(fields[fields.length - 2]);
// 为了避免重复创建对象而产生垃圾,我们先创建好key,value对象,然后通过各自的set方法去赋值
v.set(phoneNum);
// 因为数据封装类中没有set方法,所以我们自己去类中编写一个set方法
k.set(upflow,downflow);
// 通过context对象将数据传递给reduce
context.write(k,v);
}
}
3. 编写reducer继承类:FlowSumSortReducer
将数据封装类和手机号转换回去。
package com.chinasofti.mapreducepractice.flowsum.sort2;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class FlowSumSortReducer extends Reducer<FlowSortBean, Text, Text, FlowSortBean> {
@Override
protected void reduce(FlowSortBean key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
context.write(values.iterator().next(),key);
}
}
4. 编写driver类:FlowSumDriver
package com.chinasofti.mapreducepractice.flowsum.sort2;
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;
public class FlowSumSortDriver {
public static void main(String[] args) throws Exception {
//通过Job来封装本次mr的相关信息
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//指定本次mr job jar包运行主类
job.setJarByClass(FlowSumSortDriver.class);
//指定本次mr 所用的mapper reducer类分别是什么
job.setMapperClass(FlowSumSortMapper.class);
job.setReducerClass(FlowSumSortReducer.class);
//指定本次mr mapper阶段的输出 k v类型
job.setMapOutputKeyClass(FlowSortBean.class);
job.setMapOutputValueClass(Text.class);
//指定本次mr 最终输出的 k v类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowSortBean.class);
// 暂不用设置reduce数量
// job.setNumReduceTasks(3);
//如果业务有需求,就可以设置combiner组件
// job.setCombinerClass(FlowSumReducer.class);
/*
* 本地运行语句
* 注意本地运行时输出目录也不能存在,否则也会报错
*/
FileInputFormat.setInputPaths(job,new Path("D:\\Practice_File\\hadoop_practice\\MapReduce\\flowsum\\input"));
FileOutputFormat.setOutputPath(job,new Path("D:\\Practice_File\\hadoop_practice\\MapReduce\\flowsum\\outputsort"));
// job.submit();
//提交程序 并且监控打印程序执行情况
boolean b = job.waitForCompletion(true);
System.exit(b?0:1);
}
}
5. 最终结果
问题来了,倒序是OK了,但是没有数据没有合并啊。