统计每一个手机号耗费的总上行流量、总下行流量、总流量
前言
1.说明:
什么是序列化、反序列化?
在操作系统中,当两个进程在进行远程通信时,彼此可以发送各种类型的数据,包括文本、图片、视频、音频等。两者之间传输的数据其形式就是二进制序列。那么如果我们需要将Java对象进行传输的时候,是不是也应该先将对象进行序列化?答案是肯定的,我们需要先将Java对象进行序列化,然后通过网络,IO进行传输,当到达目的地之后,再进行反序列化获取到我们想要的对象,最后完成通信。(就比如我们到街上去买菜;为了方便,我们会将菜放入塑料袋,而后到家则从塑料袋中将菜拿出来,这样能更好的运输。这就是一个简单的序列化例子)
序列化:将对象转换为二进制序列在网络中传输或保存到磁盘 反序列化:从网络或磁盘中将二进制序列转换为对象
2. 为什么要序列化
一般来说,“活的”对象只生存在内存里,关机断电就没有了。而且“活的”对象只能由本地的进程使用,不能被发送到网络上的另外一台计算机。 然而 序列化可以存储“活的”对象,可以将“活的”对象发送到远程计算机。
3. 为什么不用 Java的序列化
Java的序列化是一个重量级序列化框架( Serializable),一个对象被序列化后,会附带很多额外的信息(各种校验信息, Header,继承体系等),不便于在网络中高效传输。所以Hadoop自己开发了一套序列化机制( Writable)。
4. Hadoop序列化特点:
紧凑 :高效使用存储空间
快速 :读写数据的额外开销小
互操作 :支持多语言的交互
序列化实例:
统计每一个手机号耗费的总上行流量、总下行流量、总流量。
(1)创建一个文本文档输入数据:
格式:id 手机号 网络ip 域名 上行流量 下行流量 网络状态码
1 13736230513 192.196.100.1 www.google.com 2481 24681 200
2 13846544121 192.196.100.2 www.xiaosh.com 264 0 200
3 13956435636 192.196.100.3 132 1512 200
4 13966251146 192.168.100.1 240 0 404
5 18271575951 192.168.100.2 www.google.com 1527 2106 200
基本思路(基于mapreduce计算框架):
Map阶段: (1)读取一行数据,切分字段 (2)抽取手机号、上行流量、下行流量 (3)以手机号为key,bean对象为value输出,即context.write(手机号,bean);
Reduce阶段:
(1)累加上行流量和下行流量得到总流量。 (2)实现自定义的bean来封装流量信息,并将bean作为map输出的key来传输 (3)MR程序在处理数据的过程中会对数据排序(map输出的kv对传输到reduce之前,会排序),排序的依据是map输出的key 所以,我们如果要实现自己需要的排序规则,则可以考虑将排序因素放到key中,让key实现接口:WritableComparable
分析:
分析图:
代码
1.FlowBean类代码:
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.Writable;
public class FlowBean implements Writable {
/* 在企业开发中往往常用的基本序列化类型不能满足所有需求 比如在
hadoop框架内部传递一个 bean对象,那么该对象就需要实现序列化接口。
bean对象序列化 必须实现 Writable接口
反序列化时,需要反射调用空参构造函数,所以必须有空参构造 */
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;//总流量
}
public void set(long upFlow, long downFlow) {
this.upFlow = upFlow;
this.downFlow = downFlow;
this.sumFlow = upFlow + downFlow;
}
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;
}
/**
* 序列化与反序列化
* 序列化:将对象转化为二进制序列在网络中传播或在磁盘中保存
* (将序列化写入IO流)
* 反序列化:在网络或磁盘中将二进制序列转换为对象
* (在IO流序列化中恢复(读取)对象)
*/
//序列化
@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;
}
}
2.FlowMapper类代码:
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class FlowMapper extends Mapper<LongWritable, Text,Text,FlowBean>{
FlowBean bean = new FlowBean();
Text k = new Text();
@Override
protected void map(LongWritable key, Text value,Context context) throws IOException, InterruptedException {
//1 获取一行数据,并将它转换为字符串
//比如2 13846544121 192.196.100.2 www.xiaosh.com 264 0 200
String line = value.toString();
//2 以制表符截取字段
//截取为[2,13846544121,192.196.100.2,www.xiaosh.com,264,0,200]字符串这样的数组
String[] words = line.split("\t");
//3 获取所需信息:13846544121 264 0
String phoneNum = words[1]; //电话号码在数组第2个位置
long upFlow = Long.parseLong(words[words.length - 3]);//上行流量在数组倒数第3个
long downFlow = Long.parseLong(words[words.length - 2]);//下行流量在数组倒数第2个
//4 封装到k,v中
k.set(phoneNum);
bean.set(upFlow,downFlow);
//4 写出去
context.write(k,bean);
}
}
3.FlowReducer类代码:
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class FlowReducer extends Reducer<Text,FlowBean,Text,FlowBean> {
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context)throws IOException, InterruptedException{
// 计算总的流量
long sumUpFlow = 0;
long sumDownFlow = 0;
for (FlowBean bean : values) { //遍历每个values,将其中的上行流量下行流量进行累加
sumUpFlow += bean.getUpFlow();
sumDownFlow += bean.getDownFlow();
}
//输出
context.write(key, new FlowBean(sumUpFlow, sumDownFlow));
}
}
4.FlowDriver类代码:
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 FlowDriver {
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
//1、获取job信息
Job job = Job.getInstance( configuration);
//2、获取jar的存储路径
job.setJarByClass(FlowDriver.class);
//3、关联map和reduce的class类
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
//4、设置map阶段输出的key和value类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
//5、设置最后输出数据的key和value类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
//6、设置输入数据的路径和输出数据的路径
FileInputFormat.setInputPaths(job,new Path("hdfs://hadoop01:9000/bigdata/input.txt"));
FileOutputFormat.setOutputPath(job,new Path("hdfs://hadoop01:9000/bigdata/mr02"));
//7、提交
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
//区别在于:system.exit(0):正常退出,程序正常执行结束退出
// system.exit(1):是非正常退出,就是说无论程序正在执行与否,都退出
}
}
运行截图(格式为:手机号 上+下=总):