hadoop之MapReduce简介
一、MapReduce概述
1、MapReduce定义
MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上。
简单说MapReduce是一个框架,一个分布式计算框架,只需用户将业务逻辑放到框架中,就会和框架组成一个分布式运算程序,在Hadoop集群上实行分布式计算。
MapReduce的核心思想就是将大数据的任务,分解成多个小数据的任务,交由Map分布式处理,最后再由Reduce合并结果。
2、MapReduce的优缺点
优点:
(1)MapReduce易于编程,简单的实现一些接口,即可完成一个分布式程序
(2)良好的扩展性,当计算资源不足时,可以通过简单的增加廉价的机器来扩展计算能力
(3)高容错性,当一个任务计算失败时,可以将失败的计算任务转移到另外一个节点运行
(4)适合TB,PB以上海量数据的离线处理
缺点:
(1)不擅长实时计算,现在的实时计算框架,由Flink完成
(2)不擅长流式计算,流式计算的输入数据是动态的,而MapReduce的输入数据集是静态的,目前流式计算由Flink或者spark完成
(3)不擅长DAG(有向无环图)计算,即一个计算的结果,作为下一个计算的输入,现在的流式计算框架由Spark完成,因为Spark的计算结果存储在内存,而MapReduce的计算结果存在磁盘中,每次输出的结果都存在磁盘,会导致频繁的IO,使MapReduce的性能比较低。
Spark 和 Flink 都是分布式计算框架,但他们都是基于内存的,所以计算的速度要优于MapReduce。
3、MapReduce的进程
MapReduce的实例进程一般为三部分:
(1)MRAppMaster:负责整个MR程序的过程调度以及和 ResourceManager 的交互,一个MapReduce只开启1个。
(2)MapTask:负责Map阶段的过程调度以及具体实施,一般为1个到多个,根据切片数量来决定开启数量。
(3)ReduceTask:负责Reduce阶段的数据合并处理,一般为0个到多个,当数据在Map阶段就能合并时,Reduce可以不用开启。
其中 ResourceManager 就是Yarn的管理者,就是资源管理器的管理者,简称为RM。
MapReduce的代码处理过程分为三个阶段:
(1)Mapper 阶段
Map阶段将大的处理任务分为小任务,然后交由各个节点独立运行,互不干扰。
(2)Reduce 阶段
Reduce阶段将Map阶段的运行结果做汇总。
(3)Driver 阶段
Driver相当于Yarn集群的客户端,用于提交整个MapReduce程序到Yarn集群运行,提交的是封装了MapReduce程序相关运行参数的Job对象。因为所有的MapReduce最终都是交由节点来运行的,而具体分配到哪个节点,就由Yarn来做资源分配。
4、MapReduce的编程规范
Map阶段:
(1)用户自定义的Mapper需要继承Mapper的父类,extends Mapper<k,v,k,v>
(2)Mapper的输入数据是Key-Value对(健值对)的形式(KV的泛型类型需要根据业务逻辑来确定)
(3)Mapper中的业务逻辑是写在map()方法中的,重写父类的map方法来实现
(4)Mapper的输出数据也是Key-Value对(键值对)的形式(KV的泛型类型需要根据业务逻辑来确定)
(5)输出的健值对,通过context.write写入到上下文中
(6)针对每一对<key,value>都会调用一次map()方法(MapTask进程)
Reduce阶段:
(1)用户自定义的Reduce需要继承Reduce的父类
(2)Reduce的输入数据是KV对的形式,同时也是mapper阶段的输出数据,这里的健值对必须跟Map阶段的键值对类型一致,Mapper的输出,就是这里的输入。
(3)Reduce中的业务逻辑是写在reduce()方法中的,重写父类的reduce方法来实现
(4)输出的健值对,通过context.write写入到上下文中
(5)ReduceTask进程对每一组相同的key调用一次reduce()方法
Driver阶段:
(1)获取配置信息,获取Job对象实例
(2)关联本Driver的jar包
(3)关联mapper和reducer的jar包
(4)设置mapper的输出健值参数
(5)输出最终输出的健值参数,不一定是Reduce的,有些没有Reduce或其他
(6)设置输入和输出路径
(7)提交job到yarn运行
(8)根据其他需要设置,比如设置分区等
5、hadoop的数据类型
Java类型 | Hadoop Writable类型 |
---|---|
Boolean | BooleanWritable |
Byte | ByteWritable |
Int | IntWritable |
Float | FloatWritable |
Long | LongWritable |
Double | DoubleWritable |
String | Text |
Map | MapWritable |
Array | ArrayWritable |
Null | NullWritable |
其中只有java中的String类型,对应hadoop的类型写法不同,其他的都是原类型+Writable的写法,同时也要注意两种类型的切换。
6、wordCount的案例演示
wordCount就是计算每个单词出现的次数,假设有文件 words.txt 内容如下:
hello hadoop
hello map
hello reduce
hello map reduce
hadoop hadoop
程序思路分析:
mapper阶段:
(1)每次读取1行,hadoop中读取的文本为text,将类型转为string
(2)将每一行String根据空格进行拆分,将每个单词存到String类型的数组中
(3)取出每个单词,合并成<单词,1>的键值对,1代表该单词出现的次数
因为这里的输入是每一行,所以当数据量很大时,可以按行将任务划分为小任务,符合分布式思想,且怎么划分都不会影响后续的计算结果。
reducer阶段:
(1)读取所有<单词,1>的键值对的值
(2)根据<单词,1>的键值对,对每个相同的单词,对后面的数字1进行累加即可计算该单词的次数
driver阶段:
(1)获取配置信息,获取JOB对象
(2)关联本Driver的jar包
(3)关联mapper和reducer的jar包
(4)指定mapper输出类型的kv类型
(5)指定最终输出的数据的kv类型
(6)指定JOB输入和输出文件的路径
(7)提交作业
程序实现:
(1)创建maven工程,MapReduceDemo
(2)在pom.xml文件中添加如下依赖
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies>
(3)在项目的src/main/resources目录下,新建一个文件,命名为“log4j.properties”,在文件中填入。
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
(4)创建包名:com.mapreduce.wordcount
(5)编写程序
编写Mapper类
package com.mapreduce.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;
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类型来处理
String line = value.toString();
// 2 将String类型按照空格切割后存进String数组
String[] words = line.split(" ");
// 3 依次取出单词,将每个单词和次数包装成键值对,写入context上下文中供后续调用
for (String word : words) {
// 先将String类型,转为text,再包装成健值对
k.set(word);
context.write(k, v);
}
}
}
Mapper<LongWritable, Text, Text, IntWritable>继承Mapper父类的时候,需要加上泛型,这里有两对键值对 <LongWritable, Text,> 和 <Text, IntWritable> 第一个键值对表示输入的数据,LongWritable表示输入数据的索引,就是类似于第几行数据,Text表示读入的内容,就是当读取文件时,系统会将文件的索引和该行的值赋给这两个泛型。第二个键值对表示输出的数据,Text表示输入的单词,IntWritable表示该单词的次数,一般第一个健值对可以按照系统默认,第二个键值对需要根据业务逻辑来确定。
编写Reducer类
package com.mapreduce.