概念
Mapreduce是一个分布式运算程序的编程框架,是用户开发“基于hadoop的数据分析应用”的核心框架;
Mapreduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个hadoop集群上;
为什么要MAPREDUCE
(1)海量数据在单机上处理因为硬件资源限制,无法胜任
(2)而一旦将单机版程序扩展到集群来分布式运行,将极大增加程序的复杂度和开发难度
(3)引入mapreduce框架后,开发人员可以将绝大部分工作集中在业务逻辑的开发上,而将分布式计算中的复杂性交由框架来处理
MAPREDUCE中涉及到的三个角色
一个完整的mapreduce程序在分布式运行时有三类实例进程:
1、MRAppMaster:负责整个程序的过程调度(mapTask,ReduceTask)及状态协调
2、mapTask:负责map阶段的整个数据处理流程
3、ReduceTask:负责reduce阶段的整个数据处理流程
MAPREDUCE的基础工作流程猜想
具体流程:
1,统计数据中每个单词的出现次数,首先数据在hdfs上分块存放,blk_1,blk_2,blk_3…
2,mapreduce分两个阶段,maptask,reducetask阶段,maptask做第一阶段并发处理,reducetask做汇总统计处理,
reducetask1,统计a-h开头的单词,reducetask2,reducetask3,以此类推。
3,通过hdfs的机制进行数据的读取,按照一行一行的数据读取,然后提交maptask进行处理,然后,每个maptask会做一个总的统计,统计入类似HASHMAP的结构中,然后等自己的数据分片处理完成后,在对总的HASHMAP进行分区:a-h,i-s,t-z 开头的单词的统计进行分区,然后再分别提交给对应的reducetask做汇总处理。
4,reducetask 汇总后,会将统计信息存入hdfs中。
5,这里面涉及到以及问题,如图中所示,该问题的解决方法在于提供一个”总管“ mr application master 来进行管理。
到此,基础工作流程猜想结束,后面还会进一步接近实际工作流程,一步一步接近。
java 代码简单实现MapReduce
统计单词出现的个数
package com.example.demo.hadoop;
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;
/**
* 单词统计
*
* KEYIN: 默认情况下,是mr框架所读到的一行文本的起始偏移量,Long类型 hdfs读取数据是一行一行读取的
*
* 数据是hdfs框架转递给客户端,客户端再将处理好的数据转递给框架,都是靠网络进行传输的,数据就需要序列化。
* rm对于hdfs来说就是客户端
*
* 但是在hadoop中有自己的更精简的序列化接口,所以不直接用Long,而用LongWritable
* 因为:JDK带的序列化接口serializable 会序列化出来一些冗余的内容,比如:继承结构,而我们只需要数据的序列化即可。
*
* VALUEIN:默认情况下,是mr框架所读到的一行文本的内容,String,同上,用Text
* KEYOUT:是用户自定义逻辑处理完成之后输出数据中的key,在此处是单词,String,同上,用Text
* VALUEOUT:是用户自定义逻辑处理完成之后输出数据中的value,在此处是单词次数,Integer,同上,用IntWritable
*
*/
public class WordCountMap extends Mapper<LongWritable,Text,Text,IntWritable> {
/**
* map task 业务逻辑方法
*
*生命周期: 读取一行数据就调用一次map task,也就是一个<k,v>执行一次 maptask
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//该行的数据
String valueStr = value.toString();
//根据空格将这一行切分成单词
String[] split = valueStr.split(" ");
//因为是每行调一次maptask,所以将统计总的任务放到reduce task中做,然后每个单词记录1 输出为<单词,1>
for (String str : split) {
//将单词作为key,将次数1作为value,以便于后续的数据分发,可以根据单词分发,以便于相同单词会到相同的reduce task
context.write(new Text(str),new IntWritable(1));
}
}
}
package com.example.demo.hadoop;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* KEYIN, VALUEIN 对应 mapper输出的KEYOUT,VALUEOUT类型对应
*
* KEYOUT, VALUEOUT 是自定义reduce逻辑处理结果的输出数据类型
* KEYOUT是单词
* VLAUEOUT是总次数
* @author
*
*/
public class WordCountReduce extends Reducer<Text,IntWritable,Text,IntWritable> {
/**
* <angelababy,1><angelababy,1><angelababy,1><angelababy,1><angelababy,1>
* <hello,1><hello,1><hello,1><hello,1><hello,1><hello,1>
* <banana,1><banana,1><banana,1><banana,1><banana,1><banana,1>
* 入参key,是一组相同单词kv对的key
*生命周期: 每次是一组一组的调用reduce方法,每一组都是分好的相同的单词
*/
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int count = 0;
/*
迭代器的方式遍历
Iterator<IntWritable> iterator = values.iterator();
while(iterator.hasNext()){
count += iterator.next().get();
}*/
for (IntWritable value : values) {
count += value.get();
}
context.write(key,new IntWritable(count));
}
}
package com.example.demo.hadoop;
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;
/**
* 相当于一个yarn集群的客户端
* 需要在此封装我们的mr程序的相关运行参数,指定jar包,提交给yarn
*
*/
public class WordCount {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
if (args == null || args.length == 0) {
args = new String[2];
args[0] = "hdfs://master:9000/wordcount/input/wordcount.txt";
args[1] = "hdfs://master:9000/wordcount/output8";
}
Configuration conf = new Configuration();
/*conf.set("mapreduce.framework.name", "yarn");
conf.set("yarn.resoucemanager.hostname", "mini1");
本地run运行的时候需要指定yarn,不然不知道yarn在哪里。
*/
Job job = Job.getInstance(conf);
//指定本程序的jar包所在的本地路径
/*job.setJar("/home/hadoop/wc.jar"); 这样写就写死了,不好*/
job.setJarByClass(WordCount.class);
//指定本业务job要使用的mapper/Reducer业务类
job.setMapperClass(WordCountMap.class);
job.setReducerClass(WordCountReduce.class);
//指定mapper输出数据的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//指定最终输出的数据的kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//指定job的输入原始文件所在目录
FileInputFormat.setInputPaths(job,new Path(args[0]));
//指定job的输出结果所在目录
FileOutputFormat.setOutputPath(job,new Path(args[1]));
//将job中配置的相关参数,以及job所用的java类所在的jar包,提交给yarn去运行
/*job.submit(); 提交后就结束了,不知道jar的运行情况*/
//返回值代表jar执行成功了还是失败了,true参数代表集群的返回信息在客户端进行打印,该方法会一直阻塞只要返回结果。
boolean flag = job.waitForCompletion(true);
System.exit(flag ? 0: 1);
}
}
说明:
如果打包的时候不加入hadoop的相关jar依赖,那么运行命令使用hadoop jar 的方式,它会自动加载hadoop的依赖到classpath中。
另一种方式就是打包的时候加入hadoop的jar依赖,这样可以直接用java -jar的方式启动
最后提交的是一个描述了各种必要信息的job对象
上述代码的MapReduce流程:
流程解析:
1,客户端job会在提交submit前,去分析指定目录的数据的大小信息,进行数据化分(比如0-128,128-256),这里有具体的划分规则,job.split。
2,然后根据配置的参数形成配置文件,job.xml。
3,然后提交到yarn上进行资源分配,yarn先调用mrappmaster进行maptask和reducetask的调度。
4,map task 读取数据是通过组件inputFormt读取的数据,a)形成输入KV对,然后将输入的KV对传递给定义的map()方法,做逻辑运算,并将map()方法输出的KV对收集到缓存,最后将缓存中的KV对按照K分区排序后不断溢写到磁盘文件。
5,MRAppMaster监控到所有maptask进程任务完成之后,会根据客户指定的参数启动相应数量的reducetask进程,并告知reducetask进程要处理的数据范围(数据分区)。
6,Reducetask进程启动之后,根据MRAppMaster告知的待处理数据所在位置,从若干台maptask运行所在机器上获取到若干个maptask输出结果文件,并在本地进行重新归并排序,然后按相照同key的KV为一个组,调用客户定义的reduce()方法进行逻辑运算,并收集运算输出的结果KV,然后调用客户指定的outputformat将结果数据输出到外部存储。