一、概述
- 定义:MapReduce是一个分布式运算程序的编程框架,是接触到的第一个编程框架。
- 在框架下编程体验:非常不透明,大部分功能都已经实现了。
- 核心功能:将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并运行在一个Hadoop集群上。
- 优点:简单实现一些接口,我们可以非常快速的开发出一个分布式程序。
- 缺点:慢,因为资源占用相对低。在计算机中,时间和空间是一对矛盾的概念,想要快速就要占用多的空间,想节省资源就要多花费时间。MapReduce框架用的很少,但思想一致。
- 核心编程思想:map 映射,把原始的数据文件映射成希望的形式,把不好处理的数据变的好处理,映射阶段倾向于交给很多个map来处理。各个映射文件间没有关系。reduce 规约,把多个map文件整合到一个reduce中,进行输出。
- 最简单的MapReduce:wordcount,一个大文件,想要统计其中的单词数量不好统计,所以通过map把文件映射成不同的(K,V)值,K是各种单词,v = 1,表示每一个单词出现了一次。每个map映射出一堆东西,所有结果汇总到一个reduce中。
- 执行官方wordcount程序:
/opt/module/hadoop-3.1.3/share/hadoop/mapreduce/文件夹下的hadoop-mapreduce-examples-3.1.3.jar文件提供了官方给出的一些 示例程序,执行这个文件中的一些程序完成测试。
首先将程序 提交到yarn上,yarn jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar wordcount(想要运行的程序名称) /README.txt(源文件) /output(hdfs不存在的文件夹)
- 常用数据序列化类型:
- 在ieda中自定义wordCount程
Mapper
package com.hike.mr.wordcount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* key in : LongWritable : 框架将文件拆分成一行一行的,在每一行文件中的开头位置
* value in : Text : 这一行的内容
* key out: Text : 输出文本的内容
* value out: IntWritable : 文本出现的次数
*/
public class WcMapper extends Mapper<LongWritable,Text, Text, IntWritable> {
private Text word = new Text(); //拆分后的一个一个的单词
private IntWritable one = new IntWritable(1); //数字1
/**
* 框架将数据拆分成一行一行输入进来,将数据变成(单词,1)的形式输出
* @param key 行号
* @param value 行内容
* @param context 任务本身
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//拿到一行数据
String line = value.toString();
//把一行拆成很多个单词
String[] words = line.split(" ");
//将(单词,1)写回框架,调用很多次write方法,不要在方法内new对象
for(String wd : words){
this.word.set(wd);
context.write(this.word, this.one);
}
}
}
Reducer
package com.hike.mr.wordcount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* Mapper的输出就是Reducer的输入,输入(单词,1)输出(单词,n)
*/
public class WcReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
/**
* 框架把数据按照单词分好组输入给我们,我们需要将同一个单词的次数相加
* @param key 单词
* @param values 这个单词所有的1,放在一个可迭代的集合中,可理解为集合,但其不是集合,Iterable接口在集合之上
* @param context 任务本身
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//累加
int sum = 0;
for(IntWritable value : values){ //同一个单词
sum += value.get(); //将包装的数据取出来
}
//包装结果并写出去
result.set(sum); //将数字包装
context.write(key,result);
}
}
Driver
package com.hike.mr.wordcount;
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;
import java.io.IOException;
public class WcDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//1.获取job实例
Job job = Job.getInstance(new Configuration());
//2.设置jar包,分布式程序,很多程序需要执行此命令
job.setJarByClass(WcDriver.class);
//3.设置Mapper和Reducer
job.setMapperClass(WcMapper.class);
job.setReducerClass(WcReducer.class);
//4.设置Map和Reducer的输出类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
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
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
将maven项目打包好的jar包上传到服务器,执行
yarn jar mapreduce001-1.0-SNAPSHOT.jar(jar包名称) com.hike.mr.wordcount.WcDriver(Driver类的全引用) /LICENSE.txt(输入的文件) /output1(输出的文件【要求之前不存在的】)
- 常用数据序列化类型:
- 在idea执行自定义的wordcount程序:创建Maven程序,导入依赖和log4j2.xml,编写Mapper、Reducer、Driver类,打包,将jar包上传到服务器,执行。
二、Hadoop序列化
- 序列化概述
- 什么是序列化:把内存中“活的”的对象,转换成字节二进制序列,叫做序列化。序列化之后可以存储到磁盘上(持久化),可以通过网络传送(网络传输)。举个例子,当画家看到美丽的风景时,他将风景画成了一幅画,存储到他的画册中,并将这幅画供其他人查看。其中,风景变为画的过程称为序列化,画到画册中,称为持久化,供其他人观看,称为网络传输。
- 为什么要序列化:一般来说,“活的”对象只存储在内存里,关机断电就没有了。而且,只能由本地的进程使用,不能别发送到网络上的另一台计算机上。
- 为什么不使用java的序列化:在大数据背景下java的序列化不再适用,因为java序列化是一个重量级序列化框架,一个对象被序列化后,会附带很多额外信息(各种校验信息,Header,继承体系等),在网络中传送效率低,所以hadoop开发了一套序列化机制(Writable),只序列化必要的数据,如学生类中包含name,age,一些方法,那么hadoop之序列化name,age等必要信息,其他诸如继承体系等都不会被序列化,特点是紧凑(高效使用存储空间)、快速(读写数据的额外开销小)、可扩展(随着通信协议的升级而升级)、互操作(支持多语言的交互)。
- 自定义bean对象实现序列化
- 在Hadoop中想要用自定义的bean对象需要提供一个无参构造器,因为其会在框架中通过反射的方式调用无参构造器来构造对象,使用默认的无参构造器也可以。
FlowBean
package com.hike.mr.flow;
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;
@Override
public String toString(){
return upFlow + "/t" + downFlow + "/t" + sumFlow;
}
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;
}
public long getSumFlow() {
return sumFlow;
}
public void setSumFlow(long sumFlow) {
this.sumFlow = sumFlow;
}
/**
* 将对象数据写出到框架指定地方 序列化
* 当map端处理完数据之后,通过网络发送给reduce,需要经过序列化,当map端序列化时需要调用write方法,同时传一个容器
* @param dataOutput 数据的容器
* @throws IOException
*/
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeLong(upFlow);
dataOutput.writeLong(downFlow);
dataOutput.writeLong(sumFlow);
}
/**
* 从数据指定地方读取数据填充对象 反序列化
* 当map端序列化完成之后,将容器发送给reduce端,先进先出,通过反序列化读取数据
* @param dataInput 数据的容器
* @throws IOException
*/
public void readFields(DataInput dataInput) throws IOException {
this.upFlow = dataInput.readLong();
this.downFlow = dataInput.readLong();
this.sumFlow = dataInput.readLong();
}
}
FlowMapper
package com.hike.mr.flow;
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 Phone = new Text();
private FlowBean flow = new FlowBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//拿到一行数据
String line = value.toString();
//切分
String[] fields = line.split("/t");
//封装
Phone.set(fields[1]);
flow.set(
Long.parseLong(fields[fields.length-3]), //upflow
Long.parseLong(fields[fields.length-2]) //downflow
);
context.write(Phone, flow);
}
}
FlowReducer
package com.hike.mr.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> {
private FlowBean flow = new FlowBean();
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
//累加流量
long sumUpFlow = 0;
long sumDownFlow = 0;
for (FlowBean value : values) {
sumUpFlow += value.getUpFlow();
sumDownFlow += value.getDownFlow();
}
//封装Flow类型
flow.set(sumUpFlow,sumDownFlow);
context.write(key,flow);
}
}
FlowDriver
package com.hike.mr.flow;
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 {
// static {
// System.load("F:\\develop\\lib\\hadoop\\bin\\hadoop.dll");
// System.load("F:\\develop\\lib\\hadoop\\bin\\hdfs.dll");
// }
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Job job = Job.getInstance(new 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(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}