Hadoop——MapReduce组件(一)概述

1 MapReduce概述

1.1 MapReduce定义

MapReduce是一个分布式运算程序的编程框架,是用户开发基于"Hadoop的数据分析应用"的核心框架。
MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上。

1.2优缺点

优点

  1. MapReduce易于编程
    它简单的实现一些接口,就可以完成一个分布式程序,这个分布式程序可以分布到大量廉价的PC机器上运行。也就是说你写一个分布式程序,跟写一个简单的串行程序是一模一样的。就是因为这个特点使得MapReduce编程变得非常流行。
  2. 良好的扩展性
    当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力。
  3. 高容错性
    MapReduce设计的初衷就是使程序能够部署在廉价的PC机器上,这就要求它具有很高的容错性。比如其中一台机器挂了,它可以把上面的计算任务转移到另外一个节点上运行,不至于这个任务运行失败,而且这个过程不需要人工参与,而完全是由Hadoop内部完成的。
  4. 适合PB级以上海量数据的离线处理可以实现上干台服务器集群并发工作,提供数据处理能力。

缺点

  1. 不擅长卖时计算
    MapReduce无去像MySQL一样,在毫秒或者秒级内返回结果。
  2. 不擅长流式计算
    流式计算的输入数据是动态的,而MapReducel俞入数据集是静态的,不能动态变化。这是因为MapReduce自身的设计特点决定了数据源必须是静态的。
  3. 不擅长DAG(有向图)计算
    多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下,MpReduce并不是不能做,而是使用后,每个MapReduce作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下。

1.3 核心思想

在这里插入图片描述
1)分布式的运算程序往往需要分成至少2个阶段。
2)第一个阶段的MapTask并发实例,完全并行运行,互不相干。
3)第二个阶段的ReduceTask并发实例互不相干,但是他们的数据依赖于上一个阶段的所有MapTask并发实例的输出。
4)MapReduce编程模型只能包含一个Map阶段和一个Reduce阶段,如果用户的业务逻辑非常复杂,那就只能多个MapReduce程序,串行运行。

1.4 MapReduce进程

结构
一个完整的mapreduce程序在分布式运行时有三类实例进程:
**1、MRAppMaster:**负责整个程序的过程调度及状态协调
2、mapTask:负责map阶段的整个数据处理流程
**3、ReduceTask:**负责reduce阶段的整个数据处理流程

MR程序运行流程

  1. 一个mr程序启动的时候,最先启动的是MRAppMaster,MRAppMaster启动后根据本次job的描述信息,计算出需要的maptask实例数量,然后向集群申请机器启动相应数量的maptask进程
  2. maptask进程启动之后,根据给定的数据切片范围进行数据处理,主体流程为:
    a) 利用客户指定的inputformat来获取RecordReader读取数据,形成输入KV对
    b) 将输入KV对 传递给客户定义的map()方法,做逻辑运算,并将map()方法输出的KV对收集到缓存
    c) 将缓存中的KV对按照K分区排序后不断溢写到磁盘文件
  3. MRAppMaster监控到所有maptask进程任务完成之后,会根据客户指定的参数启动相应数量的reducetask进程,并告知reducetask进程要处理的数据范围(数据分区)
  4. Reducetask进程启动之后,根据MRAppMaster告知的待处理数据所在位置,从若干台maptask运行所在机器上获取到若干个maptask输出结果文件,并在本地进行重新归并排序,然后按照相同key的KV为一个组,调用客户定义的reduce()方法进行逻辑运算,并收集运算输出的结果KV,然后调用客户指定的outputformat将结果数据输出到外部存储

1.5 常用数据序列化类型

在这里插入图片描述

1.6 MapReduce编程规范

用户编写的程序分成三个部分:Mapper、Reducer和Driver。

  1. Mapper阶段
    (1) 用户自定义的Mapper要继承自己的父类
    (2)Mapper的输入数据是KV对的形式(KV的类型可自定义)
    (3)Mapper中的业务逻辑写在map()方法
    (4)Mapper的输出数据是KV对的方式(KV的类型可自定义)
    (5)map()方法(MapTask进程)对每一个<K,V>调用一次
  2. Reducer阶段
    (1)用户自定义的Reducer要继承自己的父类
    (2)Reducer 输入数据类型对应Mapper的输出数据类型 ,也是KV
    (3)Reducer的业务逻辑写在 reduce0方法
    (4)ReduceTask进程对每一组相同k的<k,v>组调用一次reduce方法
  3. Driver阶段
    相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是封装了MapReduce程序相关运行参数的job对象

1.6.1 word count 实例

在这里插入图片描述

  1. 创建maven工程
  2. pom.xml文件
<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>2.8.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-common</artifactId>
			<version>2.7.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-client</artifactId>
			<version>2.7.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-hdfs</artifactId>
			<version>2.7.2</version>
		</dependency>
</dependencies>

  1. 编写程序
    (1)编写Mapper类
package com.atguigu.mapreduce;
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 WordcountMapper 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();
		
		// 2 切割
		String[] words = line.split(" ");
		
		// 3 输出
		for (String word : words) {		
			k.set(word);
			context.write(k, v);
		}
	}
}

(2)编写Reducer类

package com.atguigu.mapreduce.wordcount;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

public class WordcountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{

int sum;
IntWritable v = new IntWritable();

	@Override
	protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {
		
		// 1 累加求和
		sum = 0;
		for (IntWritable count : values) {
			sum += count.get();
		}		
		// 2 输出
       v.set(sum);
		context.write(key,v);
	}
}

(3)编写Driver驱动类

public class WordcountDriver {
	public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
		// 1 获取配置信息以及封装任务
		Configuration configuration = new Configuration();
		Job job = Job.getInstance(configuration);
		// 2 设置jar加载路径
		job.setJarByClass(WordcountDriver.class);
		// 3 设置map和reduce类
		job.setMapperClass(WordcountMapper.class);
		job.setReducerClass(WordcountReducer.class);
		// 4 设置map输出
		job.setMapOutputKeyClass(Text.class);
		job.setMapOutputValueClass(IntWritable.class);
		// 5 设置最终输出kv类型
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(IntWritable.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);
	}
}

  1. 集群上测试
    (0)用maven打jar包,需要添加打包插件依赖
    (1)将程序打成jar包,然后拷贝到Hadoop集群中
    步骤详情:右键->Run as->maven install。等待编译完成就会在项目的target文件夹中生成jar包。如果看不到。在项目上右键->Refresh,即可看到。修改不带依赖的jar包名称为wc.jar,并拷贝该jar包到Hadoop集群。
    (2)启动Hadoop集群
    (3)执行WordCount程序
[atguigu@hadoop102 software]$ hadoop jar  wc.jar com.atguigu.wordcount.WordcountDriver /user/atguigu/input /user/atguigu/output

2 Hadoop序列化

序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储到磁盘(持久化)和网络传输。
反序列化就是将收到字节序列(或其他数据传输协议)或者是磁盘的持久化数据,转换成内存中的对象。

一般来说,“活的”对象只生存在内存里,关机断电就没有了。而且“活的”对象只能由本地的进程使用,不能被发送到网络上的另外一台计算机。然而序列化可以存储“活的”对象,可以将“活的”对象发送到远程计算机。

Java的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带很多额外的信息(各种校验信息,Header,继承体系等),不便于在网络中高效传输。所以,Hadoop自己开发了一套序列化机制(Writable)。

Hadoop序列化特点:

(1)紧凑:高效使用存储空间。
(2)快速:读写数据的额外开销小。
(3)可扩展:随着通信协议的升级而可升级
(4)交互操作:支持多语言的交互

2.1 自定义bean对象实现序列化接口(Writable)

在企业开发中往往常用的基本序列化类型不能满足所有需求,比如在Hadoop框架内部传递一个bean对象,那么该对象就需要实现序列化接口。
具体实现bean对象序列化步骤如下7步。
(1)必须实现Writable接口
(2)反序列化时,需要反射调用空参构造函数,所以必须有空参构造

public FlowBean() {
	super();
}

(3)重写序列化方法

@Override
public void write(DataOutput out) throws IOException {
	out.writeLong(upFlow);
	out.writeLong(downFlow);
	out.writeLong(sumFlow);
}

(4)重写反序列化方法

@Override
public void readFields(DataInput in) throws IOException {
	upFlow = in.readLong();
	downFlow = in.readLong();
	sumFlow = in.readLong();
}

(5)注意反序列化的顺序和序列化的顺序完全一致
(6)要想把结果显示在文件中,需要重写toString(),可用”\t”分开,方便后续用。
(7)如果需要将自定义的bean放在key中传输,则还需要实现Comparable接口,因为MapReduce框中的Shuffle过程要求对key必须能排序。

@Override
public int compareTo(FlowBean o) {
	// 倒序排列,从大到小
	return this.sumFlow > o.getSumFlow() ? -1 : 1;
}

2.2 序列化案例实操

需求分析
在这里插入图片描述
编写MapReduce程序

(1)编写流量统计的Bean对象

 
    // 1 实现writable接口
 public class FlowBean implements Writable{
 
 	private long upFlow;
 	private long downFlow;
 	private long sumFlow;
 	
 	//2  反序列化时,需要反射调用空参构造函数,所以必须有
 	public FlowBean() {
 		super();
 	}
 
 	public FlowBean(long upFlow, long downFlow) {
 		super();
 		this.upFlow = upFlow;
 		this.downFlow = downFlow;
 		this.sumFlow = upFlow + downFlow;
 	}
 	
 	//3  写序列化方法
 	@Override
 	public void write(DataOutput out) throws IOException {
 		out.writeLong(upFlow);
 		out.writeLong(downFlow);
 		out.writeLong(sumFlow);
 	}
 	
 	//4 反序列化方法
 	//5 反序列化方法读顺序必须和写序列化方法的写顺序必须一致
 	@Override
 	public void readFields(DataInput in) throws IOException {
 		this.upFlow  = in.readLong();
 		this.downFlow = in.readLong();
 		this.sumFlow = in.readLong();
 	}
 
 	// 6 编写toString方法,方便后续打印到文本
 	@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;
 	}
 }

(2)编写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类

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驱动类

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);
	}
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值