我的故事你说,我的文字我落,我值几两你定,我去何方我挑。
2.1~2.2:客户端Client 主节点NameNode 从节点DataNode
2*掉线的检测时间(5分钟)+10*心跳时间
新节点的服役:dfs.hosts
旧节点的退役:dfs.hosts.exclude|
退役的时候,HDFS会把退役节点上的block块迁移到没有退役的节点上
类型 | 方法名 |
---|---|
boolean | BooleanWritable |
byte | ByteWritable |
int | IntWritable |
float | FloatWritable |
long | LongWritable |
double | DoubleWritable |
string | Text |
map | MapWritable |
array | ArrayWritable |
【注意】 1、如果以后MR程序运行没有报错,但是输出目录没有任何的内容,一般可能是因为输入和输出的key-value的自定义类型没有实现序列化 2、如果自定义的JavaBean充当Reducer阶段输出key-value时,最好把toString方法给重写了,否则Reducer最后输出的结果是JavaBean的地址值
InputFormat是一个抽象类,提供了两个抽象方法
getSplits
:这个方法是用来进行输入数据文件的切片计算的createRecordReader
:这个方法是MapTask读取切片数据时,是按照行读取还是按照其他规则读取,包括读取时key-value分别代表什么含义,什么类型常用的InputFormat的实现类:
FileInputFormat(是InputFormat的默认实现类)FileInputFormat是专门用来读取文件数据时使用的输入格式化类,但是FileInputFormat也是一个抽象类
FileInputFormat抽象类有五个常用的非抽象子类
TextInputFormat(是FileInputFormat默认实现类)
如何切片
两个核心参数:MinSplitSize=1LMaxSplitSize=Long.MAX_VALUE
confguration("mapreduce.input.fileinputformat.split.minsize",xxxL)
configuration("mapreduce.input.fileinputformat.split.maxsize",xxxL)
每一个输入文件单独进行切片
每一个文件先获取它的blockSize,然后计算文件的切片大小splitSize=Math.max(minSize, Math.min(maxSize, blockSize))
先判断文件是否能被切片,如果文件是一个压缩包(.gz、.zip),单独成为一个切片,如果文件能被切片,判断文件的长度是否大于splitSize的1.1倍,如果不大于 文件单独成为一个切片,如果大于1.1倍,按照splitsize切一片,然后将剩余的大小和splitsize继续比较
示例
【注意】
TextInputFormat是按照SplitSize进行切片的,默认情况下SplitSize=文件的BlockSize
如果你要让SplitSize大于blockSize,那么我们需要在MR程序调整minsize的大小即可
如果你要让SplitSize小于BlockSize 那么需要MR程序调整maxSize的大小即可
如何读取数据成为key-value
KeyValueTextInputFormat
NLineInputFormat
CombineInputFormat
SequenceFileInputFormat
如何自定义InputFormat实现类
MR程序运行需要在控制台输出日志,MR程序控制台输出的日志能清洗看到MR程序切片数量以及MapTask的数量和ReduceTask的数量 但是默认情况下控制台是无法输出日志的,如果要输出日志信息,我们需要对代码进行修改
需要在项目的resources目录引入log4j.properties文件 日志信息输出文件,文件当中定义了我们如何输出日志信息
引入一个日志框架的依赖,如果没有这个依赖,那么日志文件不会生效输出 pom.xml
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId> <version>1.7.21version>
dependency>
package com.sxuek.flow;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Objects;
/**
* JavaBean:是Java中一种很干净的类,类中只具备私有化的属性、构造器、getter setter方法 hashCode equals方法 toString方法
* 实体类:实体类又是一种特殊的JavaBean,当JavaBean是和数据库中数据表对应的类的时候,JavaBean称之为实体类
*
* JavaBean可以自己手动生成也可以使用lombok的技术基于注解快速的创建JavaBean这个类
* Lombok使用要慎重,Lombok对代码的侵占性是非常大的
*
* 如果自定义的JavaBean要当MR程序的输入和输出的KV值,最好让JavaBean存在一个无参构造器(MR程序底层反射构建这个类的对象)
* 如果自定义的JavaBean要充当Reducer阶段的KEY和Value,那也就意味着JavaBean的结果要写到最终的结果文件当中,JavaBean的数据往结果文件写的格式是按照
* JavaBean的toString方法去写的。
*/
public class FlowBean implements Writable {
//public class FlowBean {
private Long upFlow;//上行流量
private Long downFlow; //下行流量
private Long sumFlow; //总流量
public FlowBean() {
}
public FlowBean(Long upFlow, Long downFlow, Long sumFlow) {
this.upFlow = upFlow;
this.downFlow = downFlow;
this.sumFlow = sumFlow;
}
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;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FlowBean flowBean = (FlowBean) o;
return Objects.equals(upFlow, flowBean.upFlow) && Objects.equals(downFlow, flowBean.downFlow) && Objects.equals(sumFlow, flowBean.sumFlow);
}
@Override
public int hashCode() {
return Objects.hash(upFlow, downFlow, sumFlow);
}
@Override
public String toString() {
return upFlow+"\t"+downFlow+"\t"+sumFlow;
}
/**
* 序列化写的方法
* @param out DataOuput
to serialize this object into.
* @throws IOException
*/
@Override
public void write(DataOutput out) throws IOException {
out.writeLong(upFlow);
out.writeLong(downFlow);
out.writeLong(sumFlow);
}
/**
* 反序列化读取数据的方法
* @param in DataInput
to deseriablize this object from.
* @throws IOException
*/
@Override
public void readFields(DataInput in) throws IOException {
upFlow = in.readLong();
downFlow = in.readLong();
sumFlow = in.readLong();
}
}
package com.sxuek.flow;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
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.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* 现在有一个文件 phone_data.txt,文件中记录着手机号消耗的流量信息
* 文件中每一行数据代表一条手机的流量消耗,每一条数据是以\t制表符分割的多个字段组成的
* 使用MR程序统计每一个手机号消耗的总的上行流量、总的下行流量、总流量
*/
public class FlowDriver {
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException, URISyntaxException {
Configuration configuration = new Configuration();
configuration.set("fs.defaultFS","hdfs://192.168.68.101:9000");
// configuration.set("mapreduce.input.fileinputformat.split.minsize",150*1024*1024+"");
//maxsize 100M minsize 1L blocksize 128M
configuration.set("mapreduce.input.fileinputformat.split.maxsize",100*1024*1024+"");
Job job = Job.getInstance(configuration);
/**
* MR程序封装的时候,按道理需要指定InputFormat类,只有指定了这个实现类,才能实现切片和kv数据的读取
* 但是MR程序有个机制,如果没有指定InputFormat的实现类,默认就会实现FileInputFormat的一个实现子类TextInputFormat当作默认的切片机制
* 和KV数据读取的InputFormat类
*/
// job.setInputFormatClass(TextInputFormat.class);
//封装输入的文件路径 输入路径可以是一个 也可以是多个 输入路径可以是文件也可以是文件夹
/**
* 默认切片机制 每一个文件单独切片 n个文件 最小有n个文件
* splitSize 100M
* 文件能否被切割、文件的大小是否大于splitsize的1.1倍
* 300M 100M 100M 100M
* 120M 100M 20M
*/
FileInputFormat.setInputPaths(job,new Path("/test1"));
//封装Mapper阶段
job.setMapperClass(FlowMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
//封装Reducer阶段
job.setReducerClass(FlowReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
job.setNumReduceTasks(1);
//封装输出结果路径
//MR程序要求输出路径不能提前存在 如果提前存在会报错
Path path = new Path("/output");
//是用来解决输出目录如果存在MR程序报错问题的
FileSystem fs = FileSystem.get(new URI("hdfs://192.168.68.101:9000"), configuration, "root");
if (fs.exists(path)){
fs.delete(path,true);
}
FileOutputFormat.setOutputPath(job,path);
//最后提交程序运行即可
boolean b = job.waitForCompletion(true);
System.exit(b?0:1);
}
}
package com.sxuek.flow;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* 读取切片数据,一行数据读取一次 而且读取的key value LongWritable Text
* 输出的key value 是Text FlowBean
*
*/
public class FlowMapper extends Mapper<LongWritable, Text,Text,FlowBean> {
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, FlowBean>.Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] array = line.split("\t");
String phoneNumber = array[1];
Long upFlow = Long.parseLong(array[array.length - 3]);
Long downFlow = Long.parseLong(array[array.length - 2]);
FlowBean flowBean = new FlowBean(upFlow,downFlow,upFlow+downFlow);
//需要将这一条数据以手机号为key 以flowbean为value输出给reduce
context.write(new Text(phoneNumber),flowBean);
}
}
package com.sxuek.flow;
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, Reducer<Text, FlowBean, Text, FlowBean>.Context context) throws IOException, InterruptedException {
//计算手机号消耗的总的上行 下行 总流量 values中每一条流量的上 下 总累加起来即可
long upFlowSum = 0L;
long downFlowSum = 0L;
long sumFlowSum = 0L;
for (FlowBean value : values) {
upFlowSum += value.getUpFlow();
downFlowSum += value.getDownFlow();
sumFlowSum += value.getSumFlow();
}
//需要以手机号为key 以flowbean为value将结果输出,flowbean需要将我们计算出来总流量信息封装起来
FlowBean flowBean = new FlowBean(upFlowSum,downFlowSum,sumFlowSum);
context.write(key,flowBean);
}
}