Hadoop学习 第三章 MapReduce
第1章 MapReduce概述
1.1 MapReduce定义
1.2 MapReduce优缺点
1.2.1 优点
1.2.2 缺点
1.3 MapReduce核心思想
1.4 MapReduce进程
1.5 官方WordCount源码
采用反编译工具反编译源码,发现WordCount案例有Map类、Reduce类和驱动类。且数据的类型是Hadoop自身封装的序列化类型。
1.6 常用数据序列化类型
1.7 MapReduce编程规范
用户编写的程序分成三个部分:Mapper、Reducer和Driver。
1.8 WordCount案例实操
解决:
添加以下:
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>
然后建三个class 分别输入以下代码:
package com.BW.mr.wordcount;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
//map阶段处理:
//KEYIN 输入数据的key 表示偏移量
//VALUEIN 输入数据的value
//KEYOUT 输出数据的key的类型
// VALUEOUT 输出数据的value的类型 也就是次数
public class WordcountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
//atguigu1 atguigu2
//1.读取一行数据
String line=value.toString();
//2.切割成多个单词:切割以后返回一个数组,切割的时候按照空格切。所以string[0]是atguigu1,string[1]是atguigu2
String[] words= line.split(" ");
Text k=new Text();
IntWritable v=new IntWritable(1);
//3.循环写出:遍历words 写到环形缓冲区
for (String word : words) {
//context 的参数为KV键值对,K为Text,V为IntWritable 所以要new一个Text和IntWritable,然后将word写入K 1写入V。
// 但是这样会创建很多对象,因此将new放到循环外面
//Text k=new Text();
//IntWritable v=new IntWritable();
//v.set(1);
k.set(word);
context.write(k, v);
}
}
}
package com.BW.mr.wordcount;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
//需要继承父类import org.apache.hadoop.mapreduce.Reducer
public class WordcountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
//KEYIN, VALUEIN map阶段输出的KV:Text(单词) 和IntWritable(单词的个数)
//KEYOUT, VALUEOUT 输出的类型 也是 Text(单词) 和IntWritable(单词的个数)
IntWritable v=new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values,
Context context) throws IOException, InterruptedException {
//Iterable<IntWritable> values 是一个迭代器,将次数叠加。
int sum=0;
//1.累加求和
for (IntWritable value : values) {
sum+=value.get();
}
//2.写出:
v.set(sum);
context.write(key, v);
}
}
package com.BW.mr.wordcount;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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 WordcountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//1. 获取job对象 import org.apache.hadoop.mapreduce.Job;
//接下来的操作都是设置job的参数
Configuration conf=new Configuration();
Job job=Job.getInstance(conf);
//2. 设置jar存储位置
//通过反射获取jar包位置 动态的 比较方便
job.setJarByClass(WordcountDriver.class);
//3.关联map和reduce类 使得之前写的map类和reduce类 和job产生关联。
job.setMapperClass(WordcountMapper.class);
job.setReducerClass(WordcountReducer.class);
//4.设置mapper阶段输出数据的key 和 value 类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//5.设置最终数据输出的key和value的类型。
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//6.设置程序的输入路径和输出路径
//import org.apache.hadoop.mapreduce.lib.input.FileInputFormat 这个包一定不能搞错了
//Path : import org.apache.hadoop.fs.Path;
FileInputFormat.setInputPaths(job, new Path(args[0])); //输入的第一个参数 input的路径
FileOutputFormat.setOutputPath(job, new Path(args[1])); //输入的第二个参数 output的路径
//7.提交job
boolean result=job.waitForCompletion(true);
System.exit(result?1:0);
}
}
然后输入两个参数 运行程序:
成功:
下面打断点 进行dedug:
第一个跳入方法 第二个 下一行 第三个跳出方法
接下来如何在真正的集群上运行刚才的程序:
将程序进行打包:
(0)用maven打jar包,需要添加的打包插件依赖
注意:标记红颜色的部分需要替换为自己工程主类
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</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>
<archive>
<manifest>
<mainClass>com.atguigu.mr.WordcountDriver</mainClass> //这里替换自己的工程主类。
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
如何替换:
在下面进行粘贴:
注意:如果工程上显示红叉。在项目上右键->maven->update project即可。
下面开始打包:
但是打包失败:报错:
解决方案:
<profile>
<id>maven-https</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>central</id>
<url>https://repo1.maven.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<url>https://repo1.maven.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
然后update project 后 重新打包 成功!
刷新一下 发现有两个包
上面是有依赖的 下面是没有依赖的 由于集群Hadoop环境是配置好的 所以只需要拷贝下面的没有依赖的jar包即可。
上传jar包:
然后启动集群:
然后运行jar包:
跟之前不一样 首先输入主类名:从下面方式查询:
然后输入 输入路径和输出路径
输入路径是集群的输入路径 而不是本地的输入路径。(由于etc配置文件已经配置成集群的路径了)
运行之前一定要把102 103 104 的防火墙全部关闭
不然会报以下问题:
关闭后成功运行:
第2章 Hadoop序列化
2.1 序列化概述
2.2 自定义bean对象实现序列化接口(Writable)
public FlowBean() {
super();
}
@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 {
upFlow = in.readLong();
downFlow = in.readLong();
sumFlow = in.readLong();
}
2.3 序列化案例实操
然后加入以下代码:
(1)编写流量统计的Bean对象
package com.BW.flowsum;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.Writable;
//实现这个接口 import org.apache.hadoop.io.Writable;
// 要实现这个接口 就要重写两个方法 序列化方法和反序列化方法。
public class FlowBean implements Writable{
private long upflow; //定义上传流量
private long downflow; //定义下行流量
private long sumflow; //定义总流量
//然后进行空参构造 alt+shift+s 然后看图1,确定后会产生一个 public FlowBean 如下所示:
//为了后续反射用
public FlowBean() {
super();
}
//然后进行有参构造,方便后面使用,看图2 ,结果如下图所示:
public FlowBean(long upflow, long downflow) {
super();
this.upflow = upflow;
this.downflow = downflow;
sumflow=upflow+downflow;
}
//序列化方法
@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 {
//反序列化方法必须和序列化方法保持一致
upflow=in.readLong();
downflow=in.readLong();
sumflow=in.readLong();
}
//然后重写toString方法 如图3操作,然后对下进行标准化
@Override
public String toString() {
return upflow + "/t" + downflow + "/t" + sumflow;
}
//写一个get set 方法 如图4操作
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;
}
}
图1
图2
图3
图4
(2)编写Mapper类
package com.atguigu.mapreduce.flowsum;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class FlowCountMapper extends Mapper<LongWritable, Text, Text, FlowBean>{
FlowBean v = new FlowBean();
Text k = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 获取一行
String line = value.toString();
// 2 切割字段
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]);
k.set(phoneNum);
v.set(downFlow, upFlow);
// 4 写出
context.write(k, v);
}
}
(3)编写Reducer类
package com.atguigu.mapreduce.flowsum;
import java.io.IOException;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class FlowCountReducer extends Reducer<Text, FlowBean, Text, FlowBean> {
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context)throws IOException, InterruptedException {
long sum_upFlow = 0;
long sum_downFlow = 0;
// 1 遍历所用bean,将其中的上行流量,下行流量分别累加
for (FlowBean flowBean : values) {
sum_upFlow += flowBean.getUpFlow();
sum_downFlow += flowBean.getDownFlow();
}
// 2 封装对象
FlowBean resultBean = new FlowBean(sum_upFlow, sum_downFlow);
// 3 写出
context.write(key, resultBean);
}
}
(4)编写Driver驱动类
package com.atguigu.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 FlowsumDriver {
public static void main(String[] args) throws IllegalArgumentException, IOException, ClassNotFoundException, InterruptedException {
// 输入输出路径需要根据自己电脑上实际的输入输出路径设置
args = new String[] { "e:/input/inputflow", "e:/output1" };
// 1 获取配置信息,或者job对象实例
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
// 6 指定本程序的jar包所在的本地路径
job.setJarByClass(FlowsumDriver.class);
// 2 指定本业务job要使用的mapper/Reducer业务类
job.setMapperClass(FlowCountMapper.class);
job.setReducerClass(FlowCountReducer.class);
// 3 指定mapper输出数据的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
// 4 指定最终输出的数据的kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
// 5 指定job的输入原始文件所在目录
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 7 将job中配置的相关参数,以及job所用的java类所在的jar包, 提交给yarn去运行
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
第3章 MapReduce框架原理
3.1 InputFormat数据输入
3.1.1 切片与MapTask并行度决定机制
一个切片启动一个maptask ,然后一个切片的数据都加载到这个maptask任务进行处理。
数据块:默认128M。
3:如果切片大小不等于块大小 例如100M,那么DataNode1的文件块的前100M会加载到第一个节点的Maptask中,后28M会加载到第二个节点的Maptask 里 这样会产生跨节点IO操作 效率较低 因此一般数据切片大小等于数据块大小。
4:不是按照ss和ss2的整体考虑切片的,是按照文件单独切片
3.1.2 Job提交流程源码和切片源码详解
1.Job提交流程源码详解,如图4-8所示
waitForCompletion()
submit();
// 1建立连接
connect();
// 1)创建提交Job的代理
new Cluster(getConfiguration());
// (1)判断是本地yarn还是远程
initialize(jobTrackAddr, conf);
// 2 提交job
submitter.submitJobInternal(Job.this, cluster)
// 1)创建给集群提交数据的Stag路径
Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);
// 2)获取jobid ,并创建Job路径
JobID jobId = submitClient.getNewJobID();
// 3)拷贝jar包到集群
copyAndConfigureFiles(job, submitJobDir);
rUploader.uploadFiles(job, jobSubmitDir);
// 4)计算切片,生成切片规划文件
writeSplits(job, submitJobDir);
maps = writeNewSplits(job, jobSubmitDir);
input.getSplits(job);
// 5)向Stag路径写XML配置文件
writeConf(conf, submitJobFile);
conf.writeXml(out);
// 6)提交Job,返回提交状态
status = submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());
2.FileInputFormat切片源码解析(input.getSplits(job))
本地模式文件块的大小是32M
老版本1.X是64M
集群上的文件块大小是128M
源码细节:1.1倍 大于的话就切 不大于的话 就不切。129M的存两块 但是不切。
3.1.3 FileInputFormat切片机制
3.1.4 CombineTextInputFormat切片机制
3.1.5 CombineTextInputFormat案例实操
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class);
//虚拟存储切片最大值设置4m
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class);
//虚拟存储切片最大值设置20m
CombineTextInputFormat.setMaxInputSplitSize(job, 20971520);
3.1.6 FileInputFormat实现类
3.1.7 KeyValueTextInputFormat使用案例
代码实现
(1)编写Mapper类
package com.BW.kv;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class KVTextMapper extends Mapper<Text, Text, Text, IntWritable>{
IntWritable v=new IntWritable(1);
@Override
protected void map(Text key, Text value, Context context)
throws IOException, InterruptedException {
//1.封装对象
//2.写出
context.write(key, v);
}
}
(2)编写Reducer类
package com.BW.kv;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class KVTextReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
IntWritable v=new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {
int sum=0;
//1.累加求和
for (IntWritable value : values) {
sum+=value.get();
}
v.set(sum);
//2.写出
context.write(key, v);
}
}
(3)编写Driver类
package com.BW.kv;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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.KeyValueLineRecordReader;
import org.apache.hadoop.mapreduce.lib.input.KeyValueTextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class KVTextDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
args=new String[] {"e:/hadoop/input/inputkv","e:/output/outputkv"};
//1.创建job对象
Configuration conf=new Configuration();
// 设置切割符
conf.set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, " ");
Job job=Job.getInstance(conf);
//2.设置jar存储路径
job.setJarByClass(KVTextDriver.class);
//3.关联mapper和reducer类
job.setMapperClass(KVTextMapper.class);
job.setReducerClass(KVTextReducer.class);
//4.设置mapper输出key和value类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//5.设置最终输出key和value类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 设置输入格式
job.setInputFormatClass(KeyValueTextInputFormat.class);
//6.设置输入输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//7,提交job
boolean result=job.waitForCompletion(true);
System.exit(result?0:1);
}
}
3.1.8 NLineInputFormat使用案例
代码实现
(1)编写Mapper类
package com.BW.newline;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class NLineMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
Text k=new Text();
IntWritable v=new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
//1.获取一行
String line=value.toString(); //将Text转化为string
//2.切割
String[] words=line.split(" ");
//3.循环写出
for (String word : words) {
k.set(word);
context.write(k, v);
}
}
}
(2)编写Reducer类
package com.BW.newline;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class NLineReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
IntWritable v=new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values,
Context context) throws IOException, InterruptedException {
int sum=0;
//1.累加求和
for (IntWritable value : values) {
sum+=value.get();
}
v.set(sum); //int 转为 IntWritable
//2.写出
context.write(key, v);
}
}
(3)编写Driver类
package com.BW.newline;
import java.io.IOException;
import java.net.URISyntaxException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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.NLineInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class NLineDriver {
public static void main(String[] args) throws IOException, URISyntaxException, ClassNotFoundException, InterruptedException {
// 输入输出路径需要根据自己电脑上实际的输入输出路径设置
args = new String[] { "e:/hadoop/input/inputword", "e:/hadoop/output/outputnewline" };
// 1 获取job对象
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
// 7设置每个切片InputSplit中划分三条记录
NLineInputFormat.setNumLinesPerSplit(job, 3);
// 8使用NLineInputFormat处理记录数
job.setInputFormatClass(NLineInputFormat.class);
// 2设置jar包位置,关联mapper和reducer
job.setJarByClass(NLineDriver.class);
job.setMapperClass(NLineMapper.class);
job.setReducerClass(NLineReducer.class);
// 3设置map输出kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 4设置最终输出kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 5设置输入输出数据路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 6提交job
job.waitForCompletion(true);
}
}
运行成功
3.1.9 自定义InputFormat
3.程序实现
(1)自定义InputFromat
package com.BW.mr.inputformat;
import java.io.IOException;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
//存放的key为,文件名加地址 value为BytesWritable字节流
public class WholeFileInputformat extends FileInputFormat<Text, BytesWritable>{
@Override
public RecordReader<Text, BytesWritable> createRecordReader(InputSplit split, TaskAttemptContext context)
throws IOException, InterruptedException {
WholeRecordReader recordReader=new WholeRecordReader();
recordReader.initialize(split, context); //初始化将切片和上下文都传到过去了,传到WholeRecordReader类里了。
return recordReader;
}
}
(2)自定义RecordReader类
package com.BW.mr.inputformat;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
//跟FileInputFormat<Text, BytesWritable>
public class WholeRecordReader extends RecordReader<Text, BytesWritable>{
FileSplit split;
Configuration configuration;
Text k= new Text();
BytesWritable v=new BytesWritable();
boolean isProgress=true;//设计一个标记位,只要标记位为true 就一直读 读完以后将标记位设置为false 然后返回true
//只有nextKeyValue()这个方法返回true ,才继续进入接下来的map方法进行处理。比如一个输入流有三个文件a b c 那么WholeRecordReader
//会分别创建一个对象 在这里对象的初始标记位都为true ,所以创建三次,第四次没读到返回false
@Override
public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
// 初始化
this.split=(FileSplit) split; //强制转换,将 InputSplit split 转换成 FileSplit split;
configuration = context.getConfiguration(); //获取配置信息。
}
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
// 核心业务处理逻辑 在这里对key和value进行封装。
if(isProgress) {
byte[] buf =new byte[(int) split.getLength()]; //buf的长度等于切片的长度
//1.获取fs对象,获取文件系统对象后就可以读切片信息了,比如读取a.txt b.txt 等
// 然后通过流的方式把整个文件的内容都封装到value(字节流里去) 然后文件的名称和地址封装到key里面去。
Path path=split.getPath(); //获取切片的路径
FileSystem fs=path.getFileSystem(configuration); //根据切片路径 就可以获得切片的文件系统了
//2.获取输入流 输入流里存放的就是a.txt b.txt 的文件了
FSDataInputStream fis=fs.open(path);//open 这个路径 就可以获得输入流,获取输入流后要将其写到value(BytesWritable)里去。
//3. 拷贝 先拷贝到一个缓存文件里去 为啥这样做 由于API的限制。
IOUtils.readFully(fis, buf, 0, buf.length); //将所有的内容都读到buff里去。从0开始读 读到buf的长度。这样就把文件的内容读到了buf里面去了。
//4.封装v 将buf里的内容封装到value里去
v.set(buf, 0, buf.length); // 从0开始读 读到buf的长度
//5.封装key
k.set(path.toString()); //获取路径 路径包含路径和文件名称,至此通过IO流的方式将其封装完
//6.关闭资源
IOUtils.closeStream(fis); //关闭的是fis 而不是fs。
isProgress=false;
return true;
}
return false;
}
@Override
public Text getCurrentKey() throws IOException, InterruptedException {
// 获取当前的key
return k;
}
@Override
public BytesWritable getCurrentValue() throws IOException, InterruptedException {
// 获取输出的value 就是 BytesWritable 需要啥就来啥
return v;
}
@Override
public float getProgress() throws IOException, InterruptedException {
// 获取进度条 暂不考虑
return 0;
}
@Override
public void close() throws IOException {
// 关闭资源 但是不用该方法关 调用别的方法管。暂不考虑
}
}
(3)编写SequenceFileMapper类处理流程
package com.BW.mr.inputformat;
import java.io.IOException;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class SequenceFileMapper extends Mapper<Text, BytesWritable, Text, BytesWritable>{
@Override
protected void map(Text key, BytesWritable value,Context context)
throws IOException, InterruptedException {
context.write(key, value);
}
}
(4)编写SequenceFileReducer类处理流程
package com.BW.mr.inputformat;
import java.io.IOException;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class SequenceFileReducer extends Reducer<Text, BytesWritable, Text, BytesWritable>{
@Override
protected void reduce(Text key, Iterable<BytesWritable> values,
Context context) throws IOException, InterruptedException {
//map传过来的是a.txt b.txt c.txt
for (BytesWritable value : values) {
context.write(key, value);
}
}
}
(5)编写SequenceFileDriver类处理流程
package com.BW.mr.inputformat;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
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 org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
public class SequenceFileDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException{
// 输入输出路径需要根据自己电脑上实际的输入输出路径设置
args = new String[] { "e:/hadoop/input/inputinputformat", "e:/hadoop/output/outputformat" };
// 1 获取job对象
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 设置jar包存储位置、关联自定义的mapper和reducer
job.setJarByClass(SequenceFileDriver.class);
job.setMapperClass(SequenceFileMapper.class);
job.setReducerClass(SequenceFileReducer.class);
// 7设置输入的inputFormat
job.setInputFormatClass(WholeFileInputformat.class);
// 8设置输出的outputFormat
job.setOutputFormatClass(SequenceFileOutputFormat.class);
// 3 设置map输出端的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(BytesWritable.class);
// 4 设置最终输出端的kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(BytesWritable.class);
// 5 设置输入输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 6 提交job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
运行成功
3.2 MapReduce工作流程
2.流程详解
上面的流程是整个MapReduce最全工作流程,但是Shuffle过程只是从第7步开始到第16步结束,具体Shuffle过程详解,如下:
1)MapTask收集我们的map()方法输出的kv对,放到内存缓冲区中
2)从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件
3)多个溢出文件会被合并成大的溢出文件
4)在溢出过程及合并的过程中,都要调用Partitioner进行分区和针对key进行排序
5)ReduceTask根据自己的分区号,去各个MapTask机器上取相应的结果分区数据
6)ReduceTask会取到同一个分区的来自不同MapTask的结果文件,ReduceTask会将这些文件再进行合并(归并排序)
7)合并成大文件后,Shuffle的过程也就结束了,后面进入ReduceTask的逻辑运算过程(从文件中取出一个一个的键值对Group,调用用户自定义的reduce()方法)
3.注意
Shuffle中的缓冲区大小会影响到MapReduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快。
缓冲区的大小可以通过参数调整,参数:io.sort.mb默认100M。
3.3 Shuffle机制
3.3.1 Shuffle机制
叫做洗牌
Map方法之后,Reduce方法之前的数据处理过程称之为Shuffle。如图4-14所示。
3.3.2 Partition分区
3.3.3 Partition分区案例实操
第一步 编写FlowBean类
package com.BW.mr.Partition;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.Writable;
public class FlowBean implements 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;
sumFlow = upFlow + downFlow;
}
// 序列化方法
@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 {
// 必须要求和序列化方法顺序一致
upFlow = in.readLong();
downFlow = in.readLong();
sumFlow = in.readLong();
}
@Override
public String toString() {
return upFlow + "\t" + downFlow + "\t" + 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;
}
public void set(long upFlow2, long downFlow2) {
upFlow = upFlow2;
downFlow = downFlow2;
sumFlow = upFlow2 + downFlow2;
}
}
第二步,编写 mapper类
package com.BW.mr.Partition;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class FlowCountMapper 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 {
// 7 13560436666 120.196.100.99 1116 954 200
// 1 获取一行
String line = value.toString();
// 2 切割 \t
String[] fields = line.split("\t");
// 3 封装对象
k.set(fields[1]);// 封装手机号
long upFlow = Long.parseLong(fields[fields.length - 3]);
long downFlow = Long.parseLong(fields[fields.length - 2]);
v.setUpFlow(upFlow);
v.setDownFlow(downFlow);
// v.set(upFlow,downFlow);
// 4 写出
context.write(k, v);
}
}
第三步 编写reducer类
package com.BW.mr.Partition;
import java.io.IOException;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class FlowCountReducer extends Reducer<Text, FlowBean, Text, FlowBean>{
FlowBean v = new FlowBean();
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context)
throws IOException, InterruptedException {
// 13568436656 2481 24681 30000
// 13568436656 1116 954 20000
long sum_upFlow = 0;
long sum_downFlow = 0;
// 1 累加求和
for (FlowBean flowBean : values) {
sum_upFlow += flowBean.getUpFlow();
sum_downFlow += flowBean.getDownFlow();
}
v.set(sum_upFlow, sum_downFlow);
// 2 写出
context.write(key, v);
}
}
第四步 编写Driver类
package com.BW.mr.Partition;
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 FlowsumDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
args = new String[]{"e:/hadoop/input/inputflow","e:/hadoop/output/outputpartition"};
Configuration conf = new Configuration();
// 1 获取job对象
Job job = Job.getInstance(conf );
// 2 设置jar的路径
job.setJarByClass(FlowsumDriver.class);
// 3 关联mapper和reducer
job.setMapperClass(FlowCountMapper.class);
job.setReducerClass(FlowCountReducer.class);
// 4 设置mapper输出的key和value类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
// 5 设置最终输出的key和value类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
// 8 指定自定义数据分区
job.setPartitionerClass(ProvincePartitioner.class);
// 9 同时指定相应数量的reduce task
job.setNumReduceTasks(5);
//job.setNumReduceTasks(1);//可以正常运行 虽然有五个分区,但是只有一个reducetask 所以只能输出5个文件。
//默认的reducetask就是1.
//job.setNumReduceTasks(2); //会报错IO异常, 5个分区的数据不知道如何传输到两个reducetask里。
//job.setNumReduceTasks(6);//可以正常运行,产生6个进程处理5个分区的数据,产生6个输出文件,
//前五个输出文件正常,最后一个输出文件为空,说明第六个reducetask是闲置的。
// 6 设置输入输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 7 将job中配置的相关参数,以及job所用的java类所在的jar包, 提交给yarn去运行
boolean result = job.waitForCompletion(true);
System.exit(result?0 :1);
}
}
第五步 编写partition类
package com.BW.mr.Partition;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
public class ProvincePartitioner extends Partitioner<Text, FlowBean>{
@Override
public int getPartition(Text key, FlowBean value, int numPartitions) {
// key是手机号
//value是流量信息
int partition=4; //partition要从0开始 然后是连续的
//获取手机号前三位
String prePhoneNum=key.toString().substring(0, 3); //左闭右开
if("136".equals(prePhoneNum)) {
partition=0;
}else if ("137".equals(prePhoneNum)) {
partition=1;
}else if ("138".equals(prePhoneNum)) {
partition=2;
}else if ("139".equals(prePhoneNum)) {
partition=3;
}
return partition;
}
}
运行成功
3.3.4 WritableComparable排序
reducetask里还有一个分组排序
3.3.5 WritableComparable排序案例实操(全排序)
2.需求分析
3.代码实现
(1)FlowBean对象在在需求1基础上增加了比较功能
package com.BW.mr.sort;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.WritableComparable;
public class FlowBean implements WritableComparable<FlowBean>{
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;
sumFlow=upFlow+downFlow;
}
//序列化
@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 {
upFlow=in.readLong();
downFlow=in.readLong();
sumFlow=in.readLong();
}
//比较
@Override
public int compareTo(FlowBean bean) {
// 核心比较条件判断
// 按照总流量大小,倒序排列
int result;
if (sumFlow>bean.getSumFlow()) {
result=-1; //倒序
}else if (sumFlow<bean.getSumFlow()) {
result=1; //正序
}else {
result=0; //相等情况
}
return result;
}
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 String toString() {
return upFlow + "\t" + downFlow + "\t" + sumFlow;
}
}
编写mapper
package com.BW.mr.sort;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class FlowCountSortMapper extends Mapper<LongWritable, Text, FlowBean, Text>{
FlowBean k=new FlowBean();
Text v=new Text();
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
//1.获取一行
String line=value.toString();
//2.切割
String[] fields=line.split("\t");
//3.封装对象
String phoneNum=fields[0];
Long upFlow =Long.parseLong(fields[1]);
Long downFlow =Long.parseLong(fields[2]);
Long sumFlow =Long.parseLong(fields[3]);
//都要封装
k.setUpFlow(upFlow);
k.setDownFlow(downFlow);
k.setSumFlow(sumFlow);
v.set(phoneNum);
context.write(k, v);
}
}
编写reducer
package com.BW.mr.sort;
import java.io.IOException;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class FlowCountSortReducer extends Reducer<FlowBean, Text, Text, FlowBean>{
@Override
protected void reduce(FlowBean key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
// 循环输出,避免总流量相同情况
for (Text value : values) {
context.write(value, key);
}
}
}
编写driver
package com.BW.mr.sort;
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 FlowCountSortDriver {
public static void main(String[] args) throws IllegalArgumentException, IOException, ClassNotFoundException, InterruptedException {
// 输入输出路径需要根据自己电脑上实际的输入输出路径设置 上次序列化后的输出结果就是这次的输入
args = new String[]{"e:/hadoop/output/outputpartition","e:/hadoop/output/outputsort"};
// 1 获取配置信息,或者job对象实例
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
// 2 指定本程序的jar包所在的本地路径
job.setJarByClass(FlowCountSortDriver.class);
// 3 指定本业务job要使用的mapper/Reducer业务类
job.setMapperClass(FlowCountSortMapper.class);
job.setReducerClass(FlowCountSortReducer.class);
// 4 指定mapper输出数据的kv类型
job.setMapOutputKeyClass(FlowBean.class);
job.setMapOutputValueClass(Text.class);
// 5 指定最终输出的数据的kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
// 6 指定job的输入原始文件所在目录
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 7 将job中配置的相关参数,以及job所用的java类所在的jar包, 提交给yarn去运行
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
运行成功:重复的没有被干掉
3.3.6 WritableComparable排序案例实操(区内排序)
3.案例实操
(1)增加自定义分区类 在上个案例sort的基础上增加一个ProvincePartitioner类即可
package com.BW.mr.sort;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
public class ProvincePartitioner extends Partitioner<FlowBean, Text>{
@Override
public int getPartition(FlowBean key, Text value, int numPartitions) {
String proPhoneNum= value.toString().substring(0, 3);
int partition=4;
if("136".equals(proPhoneNum)) {
partition=0;
}else if ("137".equals(proPhoneNum)) {
partition=1;
}else if ("138".equals(proPhoneNum)) {
partition=2;
}else if ("139".equals(proPhoneNum)) {
partition=3;
}else {
partition=4;
}
return partition;
}
}
运行成功:
3.3.7 Combiner合并
combiner 适合汇总求和 ,不适合求平均值 可能出错。
combiner相当于在mapper阶段就进行了汇总,在内存中进行计算 是优化Hadoop框架的一个主要的手段。
(6)自定义Combiner实现步骤
(a)自定义一个Combiner继承Reducer,重写Reduce方法
public class WordcountCombiner extends Reducer<Text, IntWritable, Text,IntWritable>{
@Override
protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {
// 1 汇总操作
int count = 0;
for(IntWritable v :values){
count += v.get();
}
// 2 写出
context.write(key, new IntWritable(count));
}
}
(b)在Job驱动类中设置:加上下面这行代码:
job.setCombinerClass(WordcountCombiner.class);
3.3.8 Combiner合并案例实操
3.案例实操-方案一
1)增加一个WordcountCombiner类继承Reducer
package com.atguigu.mr.combiner;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class WordcountCombiner extends Reducer<Text, IntWritable, Text, IntWritable>{
IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// 1 汇总
int sum = 0;
for(IntWritable value :values){
sum += value.get();
}
v.set(sum);
// 2 写出
context.write(key, v);
}
}
2)在WordcountDriver驱动类中指定Combiner
// 指定需要使用combiner,以及用哪个类作为combiner的逻辑
job.setCombinerClass(WordcountCombiner.class);
4.案例实操-方案二
1)将WordcountReducer作为Combiner在WordcountDriver驱动类中指定
// 指定需要使用Combiner,以及用哪个类作为Combiner的逻辑
job.setCombinerClass(WordcountReducer.class);
没有加conbiner之前
加conbiner
3.3.9 GroupingComparator分组(辅助排序)
对Reduce阶段的数据根据某一个或几个字段进行分组。
分组排序步骤:
(1)自定义类继承WritableComparator
(2)重写compare()方法
@Override
public int compare(WritableComparable a, WritableComparable b) {
// 比较的业务逻辑
return result;
}
(3)创建一个构造将比较对象的类传给父类
protected OrderGroupingComparator() {
super(OrderBean.class, true);
}
不然会报空指针的错误
3.3.10 GroupingComparator分组案例实操
先看案例
3.代码实现
(1)定义订单信息OrderBean类
package com.BW.mr.order;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.WritableComparable;
public class OrderBean implements WritableComparable<OrderBean>{
private int order_id; //订单ID
private double price; //订单价格
//空参构造
public OrderBean() {
super();
}
//有参构造
public OrderBean(int order_id, double price) {
super();
this.order_id = order_id;
this.price = price;
}
//序列化方法
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(order_id);
out.writeDouble(price);
}
//反序列化方法
@Override
public void readFields(DataInput in) throws IOException {
order_id=in.readInt();
price=in.readDouble();
}
// 二次排序
@Override
public int compareTo(OrderBean bean) {
// 先按照ID升序进行排序,如果ID相同,按照价格降序排序
int result; //定义排序的结果值
if(order_id>bean.getOrder_id()) {
result=1;
}else if (order_id<bean.getOrder_id()) {
result=-1;
}else {
if (price>bean.getPrice()) {
result=-1;
}else if (price<bean.getPrice()) {
result=1;
}else {
result=0;
}
}
return result;
}
public int getOrder_id() {
return order_id;
}
public void setOrder_id(int order_id) {
this.order_id = order_id;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return order_id + "\t" + price;
}
}
(2)编写OrderSortMapper类
package com.BW.mr.order;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class OrderSortMapper extends Mapper<LongWritable, Text, OrderBean, NullWritable>{
OrderBean k=new OrderBean();
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
//将传进去的数据的价格和ID封装到key里就结束了 这时候value没有值,所以value设置为NullWritable
//1.读取一行
String line=value.toString();
//2.切割
String[] field=line.split("\t");
//3.封装对象
k.setOrder_id(Integer.parseInt(field[0]));
k.setPrice(Double.parseDouble(field[2]));
//4.写出
context.write(k, NullWritable.get()); //虽然不返回 但是要new出一个 NullWritable的对象
}
}
(3)编写OrderSortGroupingComparator类
package com.BW.mr.order;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
public class OrderSortGroupingComparator extends WritableComparator{
protected OrderSortGroupingComparator() {
super(OrderBean.class,true);
}
//public int compare(WritableComparable a, WritableComparable b)
@Override
public int compare(WritableComparable a, WritableComparable b) {
// 要求只要ID相同,就认为是相同的key
int result;
OrderBean aBean=(OrderBean) a;
OrderBean bBean=(OrderBean) b; //先将WritableComparable对象转化为OrderBean对象
if (aBean.getOrder_id()>bBean.getOrder_id()) {
result=1; //正序排序
}else if (aBean.getOrder_id()<bBean.getOrder_id()) {
result=-1;
}else {
result=0; //之前还判断了价格 现在只判断ID 不判断价格 只要ID相等,那么就认为是相等的。
}
return result;
}
}
(4)编写OrderSortReducer类
package com.BW.mr.order;
import java.io.IOException;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;
public class OrderReducer extends Reducer<OrderBean, NullWritable, OrderBean, NullWritable>{
@Override
protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context)
throws IOException, InterruptedException {
//1.循环写出 只要key相同 就进入reducer,因此会出现打印ID相同的不同价格的信息,我们只需要最高价格的信息,因此
//不符合要求。
context.write(key, NullWritable.get());
}
}
(5)编写OrderSortDriver类
package com.BW.mr.order;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class OrderDriver {
public static void main(String[] args) throws IllegalArgumentException, IOException, ClassNotFoundException, InterruptedException {
// 输入输出路径需要根据自己电脑上实际的输入输出路径设置
args = new String[]{"e:/hadoop/input/inputorder" , "e:/hadoop/output/outputorder"};
// 1 获取配置信息
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 设置jar包加载路径
job.setJarByClass(OrderDriver.class);
// 3 加载map/reduce类
job.setMapperClass(OrderSortMapper.class);
job.setReducerClass(OrderReducer.class);
// 4 设置map输出数据key和value类型
job.setMapOutputKeyClass(OrderBean.class);
job.setMapOutputValueClass(NullWritable.class);
// 5 设置最终输出数据的key和value类型
job.setOutputKeyClass(OrderBean.class);
job.setOutputValueClass(NullWritable.class);
// 6 设置输入数据和输出数据路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 8 设置reduce端的分组
job.setGroupingComparatorClass(OrderSortGroupingComparator.class);
// 7 提交
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
3.4 MapTask工作机制
MapTask工作机制如图4-12所示。
图4-12 MapTask工作机制
(1)Read阶段:MapTask通过用户编写的RecordReader,从输入InputSplit中解析出一个个key/value。
(2)Map阶段:该节点主要是将解析出的key/value交给用户编写map()函数处理,并产生一系列新的key/value。
(3)Collect收集阶段:在用户编写map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。在该函数内部,它会将生成的key/value分区(调用Partitioner),并写入一个环形内存缓冲区中。
(4)Spill阶段:即“溢写”,当环形缓冲区满后,MapReduce会将数据写到本地磁盘上,生成一个临时文件。需要注意的是,将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、压缩等操作。
溢写阶段详情:
步骤1:利用快速排序算法对缓存区内的数据进行排序,排序方式是,先按照分区编号Partition进行排序,然后按照key进行排序。这样,经过排序后,数据以分区为单位聚集在一起,且同一分区内所有数据按照key有序。
步骤2:按照分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文件output/spillN.out(N表示当前溢写次数)中。如果用户设置了Combiner,则写入文件之前,对每个分区中的数据进行一次聚集操作。
步骤3:将分区数据的元信息写到内存索引数据结构SpillRecord中,其中每个分区的元信息包括在临时文件中的偏移量、压缩前数据大小和压缩后数据大小。如果当前内存索引大小超过1MB,则将内存索引写到文件output/spillN.out.index中。
(5)Combine阶段:当所有数据处理完成后,MapTask对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。
当所有数据处理完后,MapTask会将所有临时文件合并成一个大文件,并保存到文件output/file.out中,同时生成相应的索引文件output/file.out.index。
在进行文件合并过程中,MapTask以分区为单位进行合并。对于某个分区,它将采用多轮递归合并的方式。每轮合并io.sort.factor(默认10)个文件,并将产生的文件重新加入待合并列表中,对文件排序后,重复以上过程,直到最终得到一个大文件。
让每个MapTask最终只生成一个数据文件,可避免同时打开大量文件和同时读取大量小文件产生的随机读取带来的开销。
3.5 ReduceTask工作机制
1.ReduceTask工作机制
ReduceTask工作机制,如图4-19所示。
图4-19 ReduceTask工作机制
(1)Copy阶段:ReduceTask从各个MapTask上远程拷贝一片数据,并针对某一片数据,如果其大小超过一定阈值,则写到磁盘上,否则直接放到内存中。
(2)Merge阶段:在远程拷贝数据的同时,ReduceTask启动了两个后台线程对内存和磁盘上的文件进行合并,以防止内存使用过多或磁盘上文件过多。
(3)Sort阶段:按照MapReduce语义,用户编写reduce()函数输入数据是按key进行聚集的一组数据。为了将key相同的数据聚在一起,Hadoop采用了基于排序的策略。由于各个MapTask已经实现对自己的处理结果进行了局部排序,因此,ReduceTask只需对所有数据进行一次归并排序即可。
(4)Reduce阶段:reduce()函数将计算结果写到HDFS上。
2.设置ReduceTask并行度(个数)
ReduceTask的并行度同样影响整个Job的执行并发度和执行效率,但与MapTask的并发数由切片数决定不同,ReduceTask数量的决定是可以直接手动设置:
// 默认值是1,手动设置为4
job.setNumReduceTasks(4);
3.实验:测试ReduceTask多少合适
(1)实验环境:1个Master节点,16个Slave节点:CPU:8GHZ,内存: 2G
(2)实验结论:
4.注意事项
3.6 OutputFormat数据输出
3.6.1 OutputFormat接口实现类
自定义的,针对输出到MySQL redis Hbase 里 要自己写代码实现。
3.6.2 自定义OutputFormat
输出路径指的就是MySQL redis Hbase 等。
3.6.3 自定义OutputFormat案例实操
2.需求分析
3.案例实操
(1)编写FilterMapper类
package com.BW.mr.outputformat;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class FilterMapper extends Mapper<LongWritable, Text, Text, NullWritable>{
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
//注意输出的key格式是Text value格式是NullWritable
//写的是value 不作任何处理 ,为啥不需要按行读?
context.write(value, NullWritable.get());
}
}
(2)编写FilterReducer类
package com.BW.mr.outputformat;
import java.io.IOException;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class FilterReducer extends Reducer<Text, NullWritable, Text, NullWritable>{
@Override
protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
//有重复的,所以要把重复的利用循环写出。
for (NullWritable value : values) {
context.write(key, NullWritable.get());
}
}
}
(3)自定义一个OutputFormat类
package com.BW.mr.outputformat;
import java.io.IOException;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class OutputFormat extends FileOutputFormat<Text, NullWritable>{
//输入类型就是reduce的输出类型
@Override
public RecordWriter<Text, NullWritable> getRecordWriter(TaskAttemptContext job)
throws IOException, InterruptedException {
return new FRecordWriter(job); //将job传进来
}
}
(4)编写RecordWriter类
package com.BW.mr.outputformat;
import java.io.IOException;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
public class FRecordWriter extends RecordWriter<Text, NullWritable> {
//一定要这样写 不然会报空指针的错误 为啥????
FSDataOutputStream fosatguigu;
FSDataOutputStream fosother;
public FRecordWriter(TaskAttemptContext job) {
//1.获取文件系统
try {
//这时候的配置信息不要new 要从job这里得到配置信息
//这里也不要抛异常 使用try。
FileSystem fs=FileSystem.get(job.getConfiguration());
//2.创建到atguigu.log的输出流
fosatguigu= fs.create(new Path("e:/hadoop/output/outputformat/atguigu.log"));
//3.创建到other.log的输出流
fosother= fs.create(new Path("e:/hadoop/output/outputformat/other.log"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void write(Text key, NullWritable value) throws IOException, InterruptedException {
// 判断是否有atguigu 有的话写出到atguigu.log 没有的话写出到other.log
//key是Text类型 转换到String, 然后使用contains方法 判断是否包含atguigu
if(key.toString().contains("atguigu")) {
//将key转换成string 然后再转换成字节
fosatguigu.write(key.toString().getBytes());
}else {
fosother.write(key.toString().getBytes());
}
}
@Override
public void close(TaskAttemptContext context) throws IOException, InterruptedException {
//关闭资源
IOUtils.closeStream(fosatguigu);
IOUtils.closeStream(fosother);
}
}
(5)编写FilterDriver类
package com.BW.mr.outputformat;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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 FilterDriver {
public static void main(String[] args) throws ClassNotFoundException, IOException, InterruptedException {
// 输入输出路径需要根据自己电脑上实际的输入输出路径设置
args = new String[] { "e:/hadoop/input/outputformat", "e:/hadoop/output/outputformat1" };
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(FilterDriver.class);
job.setMapperClass(FilterMapper.class);
job.setReducerClass(FilterReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
// 要将自定义的输出格式组件设置到job中
job.setOutputFormatClass(OutputFormat.class);
FileInputFormat.setInputPaths(job, new Path(args[0]));
// 虽然我们自定义了outputformat,但是因为我们的outputformat继承自fileoutputformat
// 而fileoutputformat要输出一个_SUCCESS文件,所以,在这还得指定一个输出目录
FileOutputFormat.setOutputPath(job, new Path(args[1]));
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
运行成功:
3.7 Join多种应用
3.7.1 Reduce Join
3.7.2 Reduce Join案例实操
2.需求分析
通过将关联条件作为Map输出的key,将两表满足Join条件的数据并携带数据所来源的文件信息,发往同一个ReduceTask,在Reduce中进行数据的串联,如图4-20所示。
3.代码实现
1)创建商品和订合并后的Bean类
package com.BW.mr.table;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.Writable;
public class TableBean implements Writable{
//实现Writable接口 默认进行排序
private String id; //订单ID
private String pid; //产品ID
private int amount; //数量
private String pname;//产品名称
private String flag;//定义一个标记,标记是产品表还是订单表
//空参构造
public TableBean() {
super();
}
//有参构造
public TableBean(String id, String pid, int amount, String pname, String flag) {
super();
this.id = id;
this.pid = pid;
this.amount = amount;
this.pname = pname;
this.flag = flag;
}
@Override
public void write(DataOutput out) throws IOException {
// 序列化方法
out.writeUTF(id);
out.writeUTF(pid);
out.writeInt(amount);
out.writeUTF(pname);
out.writeUTF(flag);
}
@Override
public void readFields(DataInput in) throws IOException {
// 反序列化方法
id=in.readUTF();
pid=in.readUTF();
amount=in.readInt();
pname=in.readUTF();
flag=in.readUTF();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
// tostring方法 不要pid和flag,按照要求只输出id amount pname
@Override
public String toString() {
return id + "\t" + amount + "\t" + pname;
}
}
2)编写TableMapper类
package com.BW.mr.table;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
//输出以pid为key 然后bean对象为value
public class TableMapper extends Mapper<LongWritable, Text, Text, TableBean>{
String name; //文件名称
//之前输入的只有一种表,现在输入的有两种表,需要区分是哪张表里的数据,因为在map方法前面写setup方法,来获取文件名称,准备待用
@Override
protected void setup(Mapper<LongWritable, Text, Text, TableBean>.Context context)
throws IOException, InterruptedException {
//1.获取文件名称
FileSplit inputSplit=(FileSplit) context.getInputSplit(); //拿到切片信息InputSplit类型,强制转成FileSplit类型
name=inputSplit.getPath().getName(); //获取文件名称
}
TableBean tableBean =new TableBean();
Text k=new Text();
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
//1.获取一行
String line=value.toString();
//2.判断文件名称
if(name.startsWith("order")) {//订单表
//切割
String[] field=line.split("\t");
//封装bean对象的value
tableBean.setId(field[0]);
tableBean.setPid(field[1]);
tableBean.setAmount(Integer.parseInt(field[2]));
tableBean.setPname(""); //在订单表里没有pname 但是不能为空。如果为空 会报序列化的异常 所以来一个空字符串
tableBean.setFlag("order"); //标记设置为order
//封装bean对象的key
k.set(field[1]);
}else {//产品表
//切割
String[] field=line.split("\t");
//封装bean对象的value
tableBean.setId(""); //没有ID 默认空
tableBean.setPid(field[0]);
tableBean.setAmount(0); //没有amount 默认0
tableBean.setPname(field[1]);
tableBean.setFlag("pd"); //标记设置为pd
//封装bean对象的key
k.set(field[0]);
}
//封装完毕后就写出
context.write(k, tableBean);
}
}
3)编写TableReducer类
package com.BW.mr.table;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class TableReducer extends Reducer<Text, TableBean, TableBean, NullWritable>{//只需要把bean对象输出就可以了,不用输出value
@Override
protected void reduce(Text key, Iterable<TableBean> values,
Context context) throws IOException, InterruptedException {
//存储所有订单集合
ArrayList<TableBean> orderBeans=new ArrayList<>();
//存放产品信息 可以有多个订单表 但是只能有一个产品表
TableBean pdBean=new TableBean();
//map阶段已经将key和value封装好了 然后进行了快速排序,这时候对相同key的不同values进行遍历,遍历的时候可以拿到标记位
for (TableBean tableBean : values) {
if("order".equals(tableBean.getFlag())) { //订单表
//将订单信息存放到订单集合里去
TableBean tmpBean=new TableBean();//创建一个临时的对象
try {
BeanUtils.copyProperties(tmpBean, tableBean); //将tableBean对象拷贝到tmpBean对象里去
orderBeans.add(tmpBean); //如果这里写tableBean,这里只是一个引用,那么orderBeans只会记录最后一次的数据
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}else {
try {
BeanUtils.copyProperties(pdBean, tableBean);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
//至此已经将传入的对象封装到了 订单集合orderBeans里 和 pdBean对象里去了
//然后 遍历订单集合orderBeans表 设置其pname
for (TableBean tableBean : orderBeans) {
tableBean.setPname(pdBean.getPname());
context.write(tableBean, NullWritable.get());
}
}
}
4)编写TableDrive类
package com.BW.mr.table;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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 TableDriver {
public static void main(String[] args) throws IllegalArgumentException, IOException, ClassNotFoundException, InterruptedException {
// 0 根据自己电脑路径重新配置
args = new String[]{"e:/hadoop/input/inputtable","e:/hadoop/output/outputtable"};
// 1 获取配置信息,或者job对象实例
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
// 2 指定本程序的jar包所在的本地路径
job.setJarByClass(TableDriver.class);
// 3 指定本业务job要使用的Mapper/Reducer业务类
job.setMapperClass(TableMapper.class);
job.setReducerClass(TableReducer.class);
// 4 指定Mapper输出数据的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(TableBean.class);
// 5 指定最终输出的数据的kv类型
job.setOutputKeyClass(TableBean.class);
job.setOutputValueClass(NullWritable.class);
// 6 指定job的输入原始文件所在目录
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 7 将job中配置的相关参数,以及job所用的java类所在的jar包, 提交给yarn去运行
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
运行成功:
3.7.3 Map Join
3.7.4 Map Join案例实操
2.需求分析
MapJoin适用于关联表中有小表的情形。
3.实现代码
(1)先在驱动模块中添加缓存文件
package com.BW.mr.cache;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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 DistributedCacheDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, URISyntaxException {
// 0 根据自己电脑路径重新配置
args = new String[]{"e:/hadoop/input/inputtable2", "e:/hadoop/output/outputtable2"};
// 1 获取job信息
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
// 2 设置加载jar包路径
job.setJarByClass(DistributedCacheDriver.class);
// 3 关联map
job.setMapperClass(DistributedCacheMapper.class);
// 4 设置最终输出数据类型 这时候没有reduce 所以没有之前mapper输出key和value的格式
job.setOutputKeyClass(Text.class); //这时候每行的字符串,拼接为一个Text
job.setOutputValueClass(NullWritable.class);
// 5 设置输入输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 6 加载缓存数据 注意 需要提前缓存的产品表放到该路径下 订单表还是放到之前的input路径下
job.addCacheFile(new URI("file:///e:/hadoop/input/inputcache/pd.txt"));
// 7 Map端Join的逻辑不需要Reduce阶段,设置reduceTask数量为0
job.setNumReduceTasks(0);
// 8 提交
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
(2)读取缓存的文件数据 MAPPER
package com.BW.mr.cache;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class DistributedCacheMapper extends Mapper<LongWritable, Text, Text, NullWritable>{
HashMap<String, String> pdMap=new HashMap<>();
@Override
protected void setup(Mapper<LongWritable, Text, Text, NullWritable>.Context context)
throws IOException, InterruptedException {
//缓存小表
URI[] cacheFiles=context.getCacheFiles();//通过上下文获取缓存文件的路径
String path=cacheFiles[0].getPath().toString();//只缓存了一个文件,所以下标为0,获取其地址然后将其转换成string类型。
//将此路径放到下面第一个参数,可以确保获取文件,第二个参数是相应地解码方式,这样就拿到了该文件的输入流
BufferedReader reader=new BufferedReader(new InputStreamReader(new FileInputStream(path), "UTF-8"));
//拿到reader以后,开始读一行行的数据
String line;
while(StringUtils.isNotEmpty(line=reader.readLine())) { //读一行数据给line, 当line不为空时
//1.切割
String[] fields=line.split("\t");
//将产品表里的pid和pname封装到一个集合里去
pdMap.put(fields[0], fields[1]);
}
//2.关闭资源
IOUtils.closeStream(reader);
}
//至此将该文件缓存好了,下面的map对pdmap这张表进行join合并
Text k=new Text();
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, NullWritable>.Context context)
throws IOException, InterruptedException {
//1.获取一行
String line=value.toString();
//2.切割
String[] fields=line.split("\t");
//3.获取pid
String pid=fields[1];
//4.拿到pid后 就可以从pdmap里取出pname的值了
String pname=pdMap.get(pid);
//5.拼接
line=line+"\t"+pname;
//6.写出
k.set(line);
context.write(k, NullWritable.get());
}
}
运行成功:
3.8 计数器应用
3.9 数据清洗(ETL)
在运行核心业务MapReduce程序之前,往往要先对数据进行清洗,清理掉不符合用户要求的数据。清理的过程往往只需要运行Mapper程序,不需要运行Reduce程序。
3.9.1 数据清洗案例实操-简单解析版
3.实现代码
(1)编写LogMapper类
package com.BW.mr.log;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class LogMapper extends Mapper<LongWritable, Text, Text, NullWritable>{
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
//1.获取一行
String line=value.toString();
//2.解析数据
//数据清洗方法返回一个返回值result,清洗方法传进去两个参数line(清洗该行)和context(使用计数器),
boolean result=parseLog(line,context);
if(!result) { //清洗失败,该数据就不要了
return;
}
//3.清洗成功,随后写出
context.write(value,NullWritable.get());
}
private boolean parseLog(String line, Context context) {
// 切割 空格切割 实际企业开发中不使用空格切割
String[] fields=line.split(" ");
if(fields.length>11) { //如果数据的长度大于11 符合要求
//引入计数器,只要进来一次 计数器就加1,map是groupname, true是统计true的次数
context.getCounter("map", "true").increment(1);
return true;
}else {
context.getCounter("map", "false").increment(1);
return false;
}
}
}
(2)编写LogDriver类
package com.BW.mr.log;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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 LogDriver {
public static void main(String[] args) throws IllegalArgumentException, IOException, ClassNotFoundException, InterruptedException {
// 输入输出路径需要根据自己电脑上实际的输入输出路径设置
args = new String[] { "e:/hadoop/input/inputlog", "e:/hadoop/output/outputlog" };
// 1 获取job信息
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 加载jar包
job.setJarByClass(LogDriver.class);
// 3 关联map
job.setMapperClass(LogMapper.class);
// 4 设置最终输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
// 设置reducetask个数为0
job.setNumReduceTasks(0);
// 5 设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 6 提交
job.waitForCompletion(true);
}
}
运行成功:
控制台计数器
3.10 MapReduce开发总结
在编写MapReduce程序时,需要考虑如下几个方面: