mapreduce的思想:分而治之,先分再和,分而治之,把复杂的问题分解,然后逐个解决,分别计算出结果。
模拟实现分布式计算:分布式计算和集中式计算相对,有些应用需要非常巨大的算力才能完成,如果采用集中式计算,需要耗费比较长的时间,分布式计算应该将该应用分解成许多小的部分,分配给多台计算机进行处理。
数据分布式存储,首先需要分布式下载数据,数据传输效率,按部就班数据读取,耗时问题,分布式计算,移动数据,还是移动程序。
map阶段:不能重复,不能依赖
reduce阶段:把map阶段的输出进行全局汇总
如何对付大数据处理场景:相互不具有依赖关系的大数据计算任务,最简单的方法就是采取mapreduce分而治之的策略。首先map阶段进行拆分,把大数据拆分成若干份小数据,reduce阶段,计算处理。
构建抽象编程模型
Mapreduce借鉴了函数式语言中的思想,用Map和Reduce两个函数提供高层的并行编程抽象模型
map:对一组数据元素进行某种重复式的处理
reduce:对map的中间结果某种重复的处理,在进行合并。
MapReduce中定义了Map和Reduce两个抽象的编程接口,由用户编程去实现。
map:(k1:v1)–> (k2:v2)
reduce(k2:[v2])–>(k3:v3),
处理的数据对象是键值对。
统一架构、隐藏底层细节:数据存储、划分、分发、结果收集、错误恢复等细节。
MapReduce最大的亮点在于通过抽象模型和计算框架把需要做什么和具体怎么做分开了,程序员仅需要关注应用层。
概念:分布式计算框架,用于轻松编写分布式程序,这些应用程序可以可靠的、容错的方式并行处理。
2004年,谷歌的论文《MapReduce:Simplified Data Processingon Large Clusters》,论文中谷歌把数据处理拆分为Map和Reduce两个操作函数,虽有被Hadoop作为开源支持。解决了易于使用,简单可靠的场景。
易于编程:Mapreduce框架提供了二次开发的接口,简单地实现一些接口,就可以完成一个分布式程序
。任务计算交给框架去处理。
良好的扩展性:基于Mapreduce的分布式计算特点可以随节点数目增长保持近似于线性的增长,可以处理海量数据。
高容错性:集群分布式搭建和部署,任何单一机器节点宕机了,它可以把上面的任务转移到另一个节点上运行,不影响整个作业任务完成
适合海量数据的离线处理:可以处理GB、TB和PB
MapReduce局限性。
实时计算性能差:主要用于离线作业,无法做到秒级或者压秒级的数据相应
不能进行流式计算:流式计算特点是数据源源不断的计算,并且数据是动态的;而MapReduce作为一个离线计算框架,主要是针对静态数据集,数据是不能动态变化的。
一个完成的Mapreduce程序运行三个实例流程
MRAPPMaster:负责整个程序的过程调度及状态协调
MapTask:负责map阶段的整个数据流程处理
ReduceTask:负责reduce阶段的整个数据处理流程
MapReduce编程模型只能包含一个Map阶段和一个Reduce阶段,如果业务逻辑非常复杂,只能多个MapReduce程序串行运行。
编程规范
Mapper、Reducer、Driver
用户自定义的Mapper和reducer都要集成各自的父类
Mappper中的业务逻辑写在map()方法中
Reduce的业务逻辑写在reduce()方法中
整个程序需要一个Driver来进行提交,提交的是一个描述了各种必要信息的job对象。
数据都是以KV键值对的形式流转的,实际编程过程中,需要考虑每个阶段输入输出的kv分别是什么
MapReduce内置了很多默认属性,比如排序属性、分组属性,都和数据的k相关,所有kv的类型确定及其重要。
MapReduce内部执行流程——基础版
外表看起来只有Map和Reduce两个阶段,但是内部包含了很多默认组件和默认的行为。包括:读取数据组件、输出数据组件,还有两个行为:排序(key的字典序排序)、分组(reduce阶段key相同的分为一组,一组调用一次reduce)。
序列化:将结构化对象转换成字节流以便于进行网络传输或写入持久存储的过程。
反序列化:将字节流转化为一系列结构化兑现的过程,重新创建该对象。
Java中的一切皆对象,开发中涉及到跨进程、跨网络传递对象。将对象数据持久化存储。两端数据协议。Java序列化机制结局以上问题。
Java对象序列化机制:把对象标识成一个二进制数字节数组,包含 对象的数据、对象的类型信息、对象内部的数据的类型信息。要实现序列化,需要实现java.io.Serializable接口
Hadoop序列化没有采用Java的序列化机制,而是实现了自己的序列化机制,原因Java比较臃肿,重量级,Hadoop的序列化机制,对象服用,减少了对象分配和回收,提高了效率。
通过Writable接口实现序列化机制,接口提供了两个方法:write和readFields
@InterfaceAudience.Public
@InterfaceStability.Stable
public interface Writable {
/**
* Serialize the fields of this object to out
.
*
* @param out DataOuput
to serialize this object into.
* @throws IOException
*/
void write(DataOutput out) throws IOException;
/**
* Deserialize the fields of this object from in
.
*
* For efficiency, implementations should attempt to re-use storage in the
* existing object where possible.
*
* @param in DataInput
to deseriablize this object from.
* @throws IOException
*/
void readFields(DataInput in) throws IOException;
}
Hadoop没有提供对象比较的功能,所以和java中的Comparable接口合并,提供了一个WritableComparable
@InterfaceAudience.Public
@InterfaceStability.Stable
public interface WritableComparable<T> extends Writable, Comparable<T> {
}
Hadoop封装的数据类型,这些数据类型都实现了WritableComparable接口
Hadoop数据类型 | Java数据类型 |
---|---|
BooleanWritable | boolean |
ByteWritable | byte |
IntWritable | int |
FloatWritable | float |
LongWritable | long |
DoubleWritable | double |
Text | String |
MapWritable | map |
ArrayWritable | array |
NullWritable | null |
WordCount:单词统计(词频统计)每个单词出现的次数,这个是大数据计算领域经典入门案例,业务简单,背后的MapReduce的执行流程和默认的行为机制
编程思路
map阶段的核心:把输入的数据进行切割,全部标记为1,因此输出的就是<单词,1>
shufflt阶段核心:经过默认的排序分组,key相同的单词会作为一组数据构成新的kv
reduce核心阶段:处理shuffle完的一组数据,该组数据就是该单词所有的键值对,所有的1
mapreduce程序,pom
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>cn.itcastgroupId>
<artifactId>test-mapreduceartifactId>
<version>1.0version>
<dependencies>
<dependency>
<groupId>org.apache.hadoopgroupId>
<artifactId>hadoop-commonartifactId>
<version>3.1.4version>
dependency>
<dependency>
<groupId>org.apache.hadoopgroupId>
<artifactId>hadoop-hdfsartifactId>
<version>3.1.4version>
dependency>
<dependency>
<groupId>org.apache.hadoopgroupId>
<artifactId>hadoop-clientartifactId>
<version>3.1.4version>
dependency>
<dependency>
<groupId>org.apache.hadoopgroupId>
<artifactId>hadoop-mapreduce-client-coreartifactId>
<version>3.1.4version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.32version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-jar-pluginartifactId>
<version>2.4version>
<configuration>
<archive>
<manifest>
<addClasspath>trueaddClasspath>
<classpathPrefix>lib/classpathPrefix>
<mainClass>cn.itcast.hadoop.mapreduce.wordcount.WordCountDriver_v2mainClass>
manifest>
archive>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.0version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
plugins>
build>
project>
log4j.properties
log4j.rootLogger=info,stdout,R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p - %m%n
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=mapreduce_test.log
log4j.appender.R.MaxFileSize=1MB
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
log4j.logger.com.codefutures=DEBUG
mapper
package cn.btks.mapreduce.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;
/**
* @description: WordCountMapper Mapper类,对应这MakTask
*
* KEYIN map阶段输入的k,每一行的起始位置的偏移量(通常无意义),LongWritable
* VALUEIN map阶段输入的value,每一行的内容
* KEYOUT map阶段输出的k,本需求是单词
* VALUEOUT map阶段输出的v,本需求是单词计数
*
* mapreduce有读数据的组件,叫做TextInputFormat
* 一行一行的读,返回kv键值对,k,v这一行的文本内容
*/
public class WordCountMapper extends Mapper<LongWritable, Text, Text, LongWritable >{
private final static LongWritable outValue = new LongWritable(1);
private Text outKey = new Text();
/**
* map方法时mapper的核心方法,也是业务实现的具体方法,
* 注意:该方法被调用的次数与输入的kv有关,每一个kv调用因此,因此是基于行处理数据的
* @param key
* @param value
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//拿取一行数据,转换为string类型
String line = value.toString();
//按照分割符,进行切割
String[] words = line.split("\\s+");
//遍历数组
for (String word : words) {
//使用上下对象,输出数据,把每个单词都标记1,结果<单词,1>
outKey.set(word);
context.write(outKey,outValue);
}
}
}
Reducer
package cn.btks.mapreduce.wordcount;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* 本类就是MapReduce程序中的Reduce阶段的处理类,对应ReduceTask
* KEYIN,map阶段输出的key的内容,单词
* VALUEIN,map阶段输出的value内容,1
* KEYOUT,输出的单词
* VALUEOUT,单词的总次数
*/
public class WordCountReducer extends Reducer<Text, LongWritable,Text,LongWritable> {
private LongWritable outValue = new LongWritable();
/**
*
* 当map的所有输出数据来到reduce之后,该如何调用reduce进行处理
* 1、排序,根据key的字典序进行排序a-z
* 2、分组,key相同的分为一组
* 3、分组之后,同一组的数据组成一个新的kv键值对,调用一次reduce方法。
* reduce方法基于分组调用的,一个分组调用一次,
* 同一组中新的key是该组共同的key,value是一个值的迭代器
*
* @param key
* @param values
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
//统计变量
long count = 0;
//遍历改组的value
for (LongWritable value : values) {
count+=value.get();
}
// while (values.iterator().hasNext()) {
// long l = values.iterator().next().get();
// }
//输出结果
outValue.set(count);
context.write(key,outValue);
}
}
驱动类实现的第一种方式
package cn.btks.mapreduce.wordcount;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
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;
/**
* 该类是map客户端的驱动类,主要是构造Job对象实例
* 指定各种组件属性:mapper、reducer类,输入输出的数据类型,输入输出的数据路径
* 提交job作业:job.submmit()
*/
public class WordCountDriver_v1 {
public static void main(String[] args) throws Exception {
//创建配置对象
Configuration conf = new Configuration();
//conf.set("mapreduce.framework.name","yarn");
//构建Job作业
Job job = Job.getInstance(conf, WordCountDriver_v1.class.getSimpleName());
//设置MR程序运行的主类
job.setJarByClass(WordCountDriver_v1.class);
//设置本次Mr程序的Mapper类,reducer类
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//设置mapper阶段输出的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(LongWritable.class);
//设置reducer阶段输出的kv类型,也是mr程序最终的输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
//配置本次作业的输入数据路径和输出数据路径,使用默认组件输入输出
FileInputFormat.setInputPaths(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job,new Path(args[1]));
//最终,提交作业,采用waitForCompletion,提交作业,参数表示是否开启实时追踪作业
boolean resultFlag = job.waitForCompletion(true);
//退出程序
System.exit(resultFlag?0:1);
}
}
驱动类实现的第二中方式
package cn.btks.mapreduce.wordcount;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
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.util.Tool;
import org.apache.hadoop.util.ToolRunner;
/**
* 使用工具类ToolRunner提交Mapreduce作业
*/
public class WordCountDriver_v2 extends Configured implements Tool {
public static void main(String[] args) throws Exception {
//创建配置对象
Configuration conf = new Configuration();
//使用工具类ToolRunner提交作业
int status = ToolRunner.run(conf, new WordCountDriver_v2(), args);
System.exit(status);
}
@Override
public int run(String[] args) throws Exception {
//构建Job作业
Job job = Job.getInstance(getConf(), WordCountDriver_v2.class.getSimpleName());
//设置MR程序运行的主类
job.setJarByClass(WordCountDriver_v2.class);
//设置本次Mr程序的Mapper类,reducer类
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//设置mapper阶段输出的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(LongWritable.class);
//设置reducer阶段输出的kv类型,也是mr程序最终的输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
//配置本次作业的输入数据路径和输出数据路径,使用默认组件输入输出
FileInputFormat.setInputPaths(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job,new Path(args[1]));
return job.waitForCompletion(true)?0:1;
}
}
打包,通过Maven的打包构建插件打包,并上传到集群
单机运行,还是分布式运行
运行需要的资源是hadoop yarn分配,还是自己分配
mapreduce.framework.name=yarn 集群模式
mapreduce.framework.name=local 本地模式
MapReduce程序提交给yarn集群,分发到多个节点上分布式并发执行。数据通常位于HDFS。
需要配置参数:
mapreduce.framework.name=yarn
yarn.resourcemanager.hostname=node1.itcast.cn
两个参数可以在程序中设置,也可以不写,集群环境中已经配置,如果集群环境没有配置,则需要程序中设置
数据准备
hadoop fs -mkdir -p /data/wordcount/input
cat /export/data/1.txt
#hello tom hello allen hello
#allen tom mac apple
#hello allen apple
#hello spark allen hadoop spark
hadoop fs -put /export/data/1.txt /data/wordcount/input
hadoop fs -cat /data/wordcount/input/1.txt
#hello tom hello allen hello
#allen tom mac apple
#hello allen apple
#hello spark allen hadoop spark
运行程序
hadoop jar /export/data/example-mr-1.0.jar /data/wordcount/input /data/wordcount/output
运行结果
本地模式运行
为了方便调试,本地运行是必须的。在开发环境下,集群环境运行及其不方便。
数据也可以放在本地
配置参数
运行结果
如果指定了运行模式为local,而提交到集群运行,则不会以集群的方式运行。
MapReduce框架运转在kv键值对上,框架把作业输入和输出都是kv。
Mr读数据组件TextInputFormat,当输入路径是一个文件,就处理这个文件,
Path inputPath = new Path(args[0]);
Path outputPath = new Path(args[1]);
FileInputFormat.setInputPaths(job,inputPath);
FileOutputFormat.setOutputPath(job,outputPath);
FileSystem fs = FileSystem.get(conf);
if (fs.exists(outputPath)){
fs.delete(outputPath,true);
}
7、MapReduce执行流程简单梳理
注:上图为markdown绘图,shuffling阶段会对统计单词按照字典排序,本图中为反应处理。
Map阶段,逻辑切片,默认根据数据Block size交给一个Task处理;按行读取数据,返回KV,K:偏移量,V一行数据内容;调用map方法处理数据;按照一定规则对map输出的键值进行分区,默认部分区,map输出数据写入内存缓存,达到比例溢出到磁盘,默认对key进行排序,溢出文件最终合并成一个文件。
Reduce阶段:ReduceTask会主动从MapTask复制拉取;把数据复制到Reduce本地,并进行合并,对合并后的数据排序;对键值对调用reduce方法,按组调用,key相同的为一组,一组调用一次。