文章目录
1 什么是序列化、什么是反序列化
-
序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储到磁盘(持久化)和网络传输
-
反序列化就是将收到的字节序列(或其他数据传输协议)或者是磁盘的持久化数据,转换成内存中的对象
2 为什么要序列化
- 需要将内存中的对象存储到媒介中
- 需要将一个对象通过网络传输到另外一个系统中
3 Hadoop为什么不适用JDK自带的序列化
JDK自带的序列化只需要类实现了Serializable接口,就可以通过ObjectOutputStream类将对象变成byte[]字节数组。
- 一个对象被序列化之后会附带很多额外的信息
- JDK 序列化会把对象类的描述信息和所有的属性以及继承的元数据都序列化为字节流,所以会导致生成的字节流相对比较大
- 这种序列化方式是 JDK 自带的,因此不支持跨语言
所以,JDK自带的序列化在实际项目和框架中使用较少
4 Hadoop序列化的特点
- 紧凑:高效使用存储空间
- 快速:读写数据的额外开销小
- 互操作:支持多语言的交互
- 例如,在hadoop102这台主机上用Java编写,在hadoop103上可使用C/C++进行反序列化
5 自定义bean对象实现序列化接口
实现bean对象序列化的步骤分为7步:
- 必须实现Writable接口
- 反序列化时,需要调用空参构造器,所以,必须有空参构造器
- 重写序列化方法
- 重写反序列化方法
序列化中的顺序是无所谓的,但是序列化的顺序和反序列化的顺序必须是保持一致
如果,序列化的顺序是1,2,3;那么反序列化的顺序也得是1,2,3
序列化的顺序是2,1,3或者3,2,1都是可以的,反序列化的顺序与它保持一致即可
- 要想把结果显示在文件中,需要重写toString方法,可用"\t"分隔,方便后续查看
- 如果将自定义的bean放在key中传输,还需要实现Comparable结果,因为MapReduce框架中的shuffle过程要求对key必须能排序
6 序列化案例实操
6.1 需求
统计每一个手机号的总上行流量、总下行流量、总流量(总流量 = 总上行流量 + 总下行流量)
(1)输入数据格式
存在部分数据域名为空的情况
id | 手机号码 | 网络ip | 域名 | 上行流量 | 下行流量 | 网络状态吗 |
---|---|---|---|---|---|---|
7 | 13560436666 | 120.196.100.99 | www.abc.com | 1116 | 954 | 200 |
8 | 15910133277 | 192.168.100.54 | 315 | 296 | 200 |
(2)输出数据格式
手机号码 | 上行流量 | 下行流量 | 总流量 |
---|---|---|---|
13560436666 | 1116 | 954 | 2070 |
6.2 需求分析
- Map阶段输入
- K:一般默认为偏移量 LongWritable
- V:某一行数据 Text
- Map阶段输出、Reduce阶段输入
- K:手机号 Text
- V:FlowBean对象,包括属性上行流量upFlow,下行流量downFlow,总流量sumFlow
- Reduce阶段输出
- K:手机号 Text
- V:FlowBean对象,重写FlowBean对象的toString方法,控制输出的格式
6.3 编写MapReduce程序
(1)FlowBean对象
- 实现Writable接口
- 重写write()方法
- 重写readFields()方法
- 重写toString()方法
- 添加了一个无参的setSumFlow()方法
package mapreduce.writable;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class FlowBean implements Writable {
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 String toString() {
return upFlow + "\t" + downFlow + "\t" + sumFlow;
}
@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();
}
}
(2)Mapper类
package mapreduce.writable;
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> {
private Text outK = new Text();
private FlowBean outV = new FlowBean();
//1 13736230513 192.196.100.1 www.atguigu.com 2481 24681 200
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, FlowBean>.Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] split = line.split("\t");
String phone = split[1];
String upFlow = split[split.length - 3];
String downFlow = split[split.length - 2];
outK.set(phone);
outV.setUpFlow(Long.parseLong(upFlow));
outV.setDownFlow(Long.parseLong(downFlow));
outV.setSumFlow();
context.write(outK, outV);
}
}
(3)Reducer类
package mapreduce.writable;
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> {
private long totalUpFlow;
private long totalDownFlow;
private FlowBean outV = new FlowBean();
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Reducer<Text, FlowBean, Text, FlowBean>.Context context) throws IOException, InterruptedException {
totalUpFlow = 0;
totalDownFlow = 0;
for (FlowBean value : values) {
totalUpFlow += value.getUpFlow();
totalDownFlow += value.getDownFlow();
}
outV.setUpFlow(totalUpFlow);
outV.setDownFlow(totalDownFlow);
outV.setSumFlow();
context.write(key, outV);
}
}
(4)Driver类
package mapreduce.writable;
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 FlowDriver {
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
job.setJarByClass(FlowDriver.class);
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
FileInputFormat.setInputPaths(job, new Path("D:\\input\\writable"));
FileOutputFormat.setOutputPath(job, new Path("D:\\output\\writable"));
boolean res = job.waitForCompletion(true);
System.exit(res ? 0 : 1);
}
}
6.4 本地运行并输出
下图是输出结果
下图是输入数据
两个蓝色框中的手机号是一样的,检查一下代码结果的正确性
6.5 提交到集群测试
(1)输入路径与输出路径的设置
上传到集群的代码,在获取输入路径和输出路径时,应该从命令中读取,所以Driver类代码要做一点小修改
//设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
(2)启动hadoop集群,把输入数据上传到hdfs中
[stu@hadoop102 hadoop-3.1.3]$ hadoop fs -mkdir /input
[stu@hadoop102 hadoop-3.1.3]$ hadoop fs -put phone_data.txt /input
(3)用maven打jar包
需要在pom.xml中添加打包插件依赖
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
在target目录下,生成了两个jar包,选择没有jar包依赖的
(4)将jar包上传到集群
上传到/opt/module/hadoop-3.1.3下
(5)执行MapReduce程序
[stu@hadoop102 hadoop-3.1.3]$ hadoop jar writable.jar mapreduce.writable.FlowDriver /input/phone_data.txt /output
查看part-r-00000的文件内容,和在本地运行时的结果一致