目录
6、自定义 bean 对象 实现序列化接口(Writable )
1.2、FileInputFormat切片源码解析(input.getSplits(job))
1.5、CombineTextInputFormat切片机制
一:Hadoop序列化
1、为什么要序列化?

2、什么是序列化?
3、为什么不用 Java ?

4、为什么序列化对 Hadoop 很重要?

5、常用数据序列化类型

6、自定义 bean 对象 实现序列化接口(Writable )
⑴自定义 bean 对象要想序列化传输,必须实现序列化接口,需要注意以下 7 项:
a:必须实现 Writable 接口
b:反序列化时,需要反射调用空参构造函数,所以必须有空参构造。
c:重写序列化方法
d:重写反序列化方法
e:注意反序列化的顺序和序列化的顺序完全一致
f:要想把结果显示在文件中,需要重写 toString(),可用”\t”分开,方便后续用
g:如果需要将自定义的 bean 放在 key 中传输,则还需要实现 comparable 接口,因为
mapreduce 框中的 shuffle 过程一定会对 key 进行排序。
7、案例之流量汇总
7.1、需求:
统计每一个手机号耗费的总上行流量、下行流量、总流量(序列化)
7.2、数据准备(phone_data.txt)
数据格式如下,现在我们只知道每一行倒数第二个是下行流量,倒数第三个是上行流量,总流量需要自己去算,第二个是手机号
7 13560436666 120.196.100.99 1116 954 200 id 手机号码 网络ip 上行流量 下行流量 网络状态码
7.3、最后输出的数据格式大概如下:
13560436666 1116 954 2070
手机号码 上行流量 下行流量 总流量
7.4、基本思路分析如下

7.5代码实现如下
1)首先定义一个bean序列化对象
package com.kgf.mapreduce.flowsum;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.Writable;
/**
* 首先我们需要实现序列化接口Writable
* @author KGF
*
*/
public class FlowBean implements Writable{
/**上行流量**/
private long upFlow;
/**下行流量**/
private long downFlow;
/**总流量**/
private long sumFlow;
/**
* 必须要有无参构造方法
* 反序列化时,需要反射调用空参构造函数,所以必须有空参构造
*/
public FlowBean() {
super();
}
public void setFlowBean(long upFlow, long downFlow) {
this.upFlow = upFlow;
this.downFlow = downFlow;
this.sumFlow = upFlow+downFlow;//总流量等于上行流量加上下行流量
}
public FlowBean(long upFlow, long downFlow) {
this.upFlow = upFlow;
this.downFlow = downFlow;
this.sumFlow = upFlow+downFlow;//总流量等于上行流量加上下行流量
}
/***
* 这个是序列化方法:这个方法其实就是mapper阶段向Reduce阶段写数据的过程
*/
@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;//可用”\t”分开,方便后续用
}
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;
}
}
2)自定义mapper对象
package com.kgf.mapreduce.flowsum;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
/**
* 继承Mapper接口:定义输入和输出参数
* 输入参数:第一个是数据行号,第二个是一行数据
* 输出参数:第一个是手机号,第二个是自定义的实体对象
* @author kgf
*
*/
public class FlowMapper extends Mapper<LongWritable,Text, Text,FlowBean>{
//定义输出参数
Text k = new Text();
FlowBean v = new FlowBean();
@Override
protected void map(LongWritable key, Text value,Context context)
throws IOException, InterruptedException {
//1:获取一行数据
String line = value.toString();
//2:对数据进行切割,这里数据间的是以制表符分割的,就是tab
String[] fields = line.split("\t");
//3:获取我们需要的数据
String phoneNum = fields[1];//手机号
long upFlow = Long.parseLong(fields[fields.length-3]);//上行流量
long downFlow = Long.parseLong(fields[fields.length-2]);//下行流量
//封装数据
v.setFlowBean(upFlow,downFlow);
k.set(phoneNum);
//写出数据
context.write(k, v);
}
}
3)自定义FlowReducer
package com.kgf.mapreduce.flowsum;
import java.io.IOException;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
/**
* 继承Reducer接口:
* 输入参数:手机号-->自定义bean对象
* 输出参数:手机号-->自定义bean对象
* @author 86136
*
*/
public class FlowReducer extends Reducer<Text, FlowBean, Text, FlowBean>{
@Override
protected void reduce(Text key, Iterable<FlowBean> values,Context context)
throws IOException, InterruptedException {
//1:因为可能存在多条相同的手机号码,所以我们需要对相同的key数据进行数据汇总
long sum_upFlow = 0;
long sum_downFlow =0;
//2:求和累加
for (FlowBean flowBean : values) {
sum_upFlow+=flowBean.getUpFlow();
sum_downFlow+=flowBean.getDownFlow();
}
FlowBean flowBean = new FlowBean(sum_upFlow,sum_downFlow);
//3:输出数据
context.write(key, flowBean);
}
}
4)自定义FlowDriver
package com.kgf.mapreduce.flowsum;
import java.io.IOException;
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 IOException, ClassNotFoundException, InterruptedException {
//1:获取job对象
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//2:设置jar包路径
job.setJarByClass(FlowDriver.class);
//3:管理自定义的Mapper和Reducer类
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
//4:Mapper输出类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
//5:Reducer输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
//6:设置输出路径
FileInputFormat.setInputPaths(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//7:提交
boolean result = job.waitForCompletion(true);
System.exit(result?0:1);
}
}
5)本地测试
a:设置运行的环境变量
在输入目录下数据文件已经准备好。
b:效果(具体结果自己可以校验一下)
二:MapReduce框架原理
1、InputFormat数据输入
1.1、切片与MapTask并行度决定机制
1)问题引出
MapTask的并行度决定Map阶段的任务处理并发度,进而影响到整个Job的处理速度
思考:1G的数据,启动8个MapTask,可以提高集群的并发处理能力。那么1K的数据,也启动8个MapTask,会提高集群性能吗?MapTask并行任务是否越多越好呢?哪些因素影响了MapTask并行度?
2)MapTask并行度决定机制
数据块:Block是HDFS物理上把数据分成一块一块。数据块是HDFS存储数据单位
数据切片:数据切片只是在逻辑上对输入进行分片,并不会在磁盘上将其切分成片进行存储。
数据切片是MapReduce程序计算输入数据的单位,一个切片会对应启动一个MapTask
1.2、FileInputFormat切片源码解析(input.getSplits(job))
1.3、FileInputFormat切片机制
1.4、TextInputFormat
1)FileInputFormat实现类
思考:在运行MapReduce程序时,输入的文件格式包括:基于行的日志文件、二进制格式文件、数据库表等。那么,针对不同的数据类型,MapReduce是如何读取这些数据的呢?
FileInputFormat常见的接口实现类包括:TextInputFormat、KeyValueTextInputFormat、NLineInputFormat、CombineTextInputFormat和自定义InputFormat等。
2)TextInputFormat
TextInputFormat是默认的FileInputFormat实现类。按行读取每条记录。键是存储该行在整个文件中的起始字节偏移量, LongWritable类型。值是这行的内容,不包括任何行终止符(换行符和回车符),Text类型。
以下是一个示例,比如,一个分片包含了如下4条文本记录。
Rich learning form
Intelligent learning engine
Learning more convenient
From the real demand for more close to the enterprise
每条记录表示为以下键/值对:
(0,Rich learning form)
(20,Intelligent learning engine)
(49,Learning more convenient)
(74,From the real demand for more close to the enterprise)
1.5、CombineTextInputFormat切片机制
框架默认的TextInputFormat切片机制是对任务按文件规划切片,不管文件多小,都会是一个单独的切片,都会交给一个MapTask,这样如果有大量小文件,就会产生大量的MapTask,处理效率极其低下。
1)应用场景:
CombineTextInputFormat用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个MapTask处理
2)虚拟存储切片最大值设置
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
注意:虚拟存储切片最大值设置最好根据实际的小文件大小情况来设置具体的值
3)切片机制
生成切片过程包括:虚拟存储过程和切片过程二部分
(1)虚拟存储过程:
将输入目录下所有文件大小,依次和设置的setMaxInputSplitSize值比较,如果不大于设置的最大值,逻辑上划分一个块。如果输入文件大于设置的最大值且大于两倍,那么以最大值切割一块;当剩余数据大小超过设置的最大值且不大于最大值2倍,此时将文件均分成2个虚拟存储块(防止出现太小切片)。
例如setMaxInputSplitSize值为4M,输入文件大小为8.02M,则先逻辑上分成一个4M。剩余的大小为4.02M,如果按照4M逻辑划分,就会出现0.02M的小的虚拟存储文件,所以将剩余的4.02M文件切分成(2.01M和2.01M)两个文件。
(2)切片过程:
2、CombineTextInputFormat案例实操
2.1、需求
将输入的大量小文件合并成一个切片统一处理
(1)输入数据
a.txt: 1.7M
b.txt: 5.1M
c.txt: 3.4M
d.txt: 6.8M
(2)期望
期望一个切片处理4个文件
2.2、实现过程
(1)不做任何处理,运行之前的WordCount案例程序,观察切片个数为4。
09 - MapReduce之入门概述、Mapreduce 优缺点、核心思想、MapReduce进程、MapReduce 编程规范、以及WordCount 案例https://blog.youkuaiyun.com/K_520_W/article/details/97485863 注意:这里运行wordCount安利的输入文件是上面的4个小文件,不是之前案例的一个文件了
(2)在WordcountDriver中增加如下代码,运行程序,并观察运行的切片个数为3
(a)驱动类中添加代码如下:
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class);
//虚拟存储切片最大值设置4m
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);
(b)运行如果为3个切片。
(3)在WordcountDriver中增加如下代码,运行程序,并观察运行的切片个数为1
(a)驱动中添加代码如下:
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class);
//虚拟存储切片最大值设置20m
CombineTextInputFormat.setMaxInputSplitSize(job, 20971520);
(b)运行如果为1个切片