MapReduce的认识和运行机制

什么是MapReduce

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

MapReduce有什么用处

为什么需要MapReduce
       1、海量数据在单机上处理因为硬件资源限制,无法胜任。
       2、而一旦将单机版程序扩展到集群来分布式运行,将极大增加程序的复杂度和开发难度。
       3、引入 MapReduce 框架后,开发人员可以将绝大部分工作集中在业务逻辑的开发上,而将分布式计算中的复杂性交由框架来处理。极大地减缓了集群的压力。

MapReduce的WordCount程序

       首先我们准备一份数据放到Hadoop的HDFS上:
1、alt+p进入到sfdp–》cd ~–》cd apps/hadoop/data
2、 将数据test.text拖进去。
3、返回hdp03节点查看文件是否上传成功。
4、打开Eclipse创建一个Maven包将Maven包里的pom.xml文件添加hadoop配置。
网上搜索在这里插入图片描述                            网上搜索Maven 进去后搜索hadoop-client 找到相应的Hadoop版本
WordCount的程序编写规范
       1、用户编写的程序分成三个部分:Mapper,Reducer,Driver(提交运行 MR 程序的客户端)
       2、Mapper 的输入数据是 KV 对的形式(KV 的类型可自定义)
       3、Mapper 的输出数据是 KV 对的形式(KV 的类型可自定义)
       4、Mapper 中的业务逻辑写在 map()方法中
       5、map()方法(maptask 进程)对每一个<K,V>调用一次
       6、Reducer 的输入数据类型对应 Mapper 的输出数据类型,也是 KV 对的形式
       7、Reducer 的业务逻辑写在 reduce()方法中
       8、Reducetask 进程对每一组相同 k 的<K,V>组调用一次 reduce()方法
       9、用户自定义的 Mapper 和 Reducer 都要继承各自的父类
       10、整个程序需要一个 Drvier 来进行提交,提交的是一个描述了各种必要信息的 job 对象

WordCount的业务逻辑
        1、 maptask 阶段处理每个数据分块的单词统计分析,思路是每遇到一个单词则把其转换成一个 key-value 对,比如单词 hello,就转换成<’hello’,1>发送给 reducetask 去汇总
       2、 reducetask 阶段将接受 maptask 的结果,来做汇总计数

WordCount代码

package com.aura.cn.WordCount_;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.server.namenode.Content;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class WordCount {
static class MyMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
@Override
protected void map(LongWritable key, Text value,
Mapper<LongWritable, Text, Text, IntWritable>.Context context)
throws IOException, InterruptedException {
String line = value.toString();
String[] words = line.split("\t");
//String [] words=value.toString().split("\t");
for(String w:words) {
Text mk=new Text(w);
IntWritable mv=new IntWritable(1);
context.write(mk,mv);
}
}
}
static class MyReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
@Override
protected void reduce(Text key, Iterable values,
Context context)
throws IOException, InterruptedException {
int sum=0;
for(IntWritable v:values) {
sum+=v.get();
}
IntWritable rk=new IntWritable(sum);
context.write(key,rk);
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//加载hadoop的配置文件
//hdfs-default.xml mapred-default.xml yarn core 四个配置文件
System.setProperty(“HADOOP_USER_NAME”, “hadoop”);
Configuration conf=new Configuration();
conf.set(“fs.defaultFS”, “hdfs://hdp01:9000”);
//job
/*
* 在mapreduce中将一个map-reduce封装的一个任务 叫做一个job
* 对应的类 Job类
*/
//启动一个job 封装 mapper reducer

    		Job job=Job.getInstance(conf);//整个hadoop都需要配置文件进行启动
    		
    		//对job进行一系列的设置  封装map reduce 
    		//设置job的主类入口
    		/*
    		 * class 对象获取:
    		 * 	1)类名.class
    		 * 2) 对象.getClass
    		 * 3)Class.forname()
    		 */
    		job.setJarByClass(WordCount.class);//Job的主类
    		
    		//设置这个job对应的mapper和reducer的类
    		job.setMapperClass(MyMapper.class);//类名+.class
    		job.setReducerClass(MyReducer.class);
    		
    		/*
    		 * mapreduce 过程:
    		 * 	框架流----》 map ---》 reduce ----》 输出
    		 * map输入  k v 固定的  hadoop底层决定的 LongWritable Text 
    		 * map输出   业务
    		 * reduce的输入--- 》 map输出
    		 * reduce输出  业务
    		 * 
    		 * 泛型的生命周期:
    		 * 	编译的时候生效
    		 * 	运行的时候自动擦除
    		 */
    		//设置map的输出的k v类型
    		job.setMapOutputKeyClass(Text.class);
    		job.setMapOutputValueClass(IntWritable.class);
    		
    		//设置reduce输出的k v类型
    		job.setOutputKeyClass(Text.class);
    		job.setOutputValueClass(IntWritable.class);
    		
    		//指定输入  需要统计的路径  mapred -- 老版本  mapreduce--新版本
    		FileInputFormat.addInputPath(job, new Path(args[0]));//参数一   Job    参数二 输入路径
    		//指定输出  统计结果路径
    		/*
    		 * 优化  判断输出路径是否存在  
    		 * 存在  删除
    		 */
    		FileOutputFormat.setOutputPath(job, new Path(args[1]));     		
    		//提交job
    		//job.submit();//不建议使用   没有运行日志信息
    		//参数  boolean  是否打印日志  true
    		//底层调用的---submit
    		job.waitForCompletion(true);
					
    }	

}

MapReduce 运行方式及 Debug

本地运行模式:Eclipse 开发环境下本地运行,好处是方便调试和测试
       要点一:MapReduce 程序是被提交给 LocalJobRunner 在本地以单进程的形式运行
       要点二:数据输入输出可以在本地,也可以在 HDFS
       要点三:怎么实现本地运行?在你的 MapReduce 程序当中不要带集群的配置文件(本质就是由mapreduce.framework.name和yarn.resourcemanager.hostname两个参数决定)
       要点四:要想实现本地运行,还得做一件事:给 eclipse 安装 hadoop。

集群运行模式:打 jar 包,提交任务到集群运行
       要点一:首先要把代码打成 jar 上传到 linux 服务器。
       要点二:用 hadoop jar 的命令去提交代码到 yarn 集群运行。
       要点三:处理的数据和输出结果应该位于 hdfs 文件系统。
       要点四:如果需要在 windows 中的 eclipse 当中直接提交 job 到集群,则需要修改YarnRunner 类,这个比较复杂,不建议使用

MapReduce 程序的核心运行机制

一个完整的 MapReduce 程序在分布式运行时有两类实例进程:
       1、MRAppMaster:负责整个程序的过程调度及状态协调
       2、Yarnchild:负责 map 阶段的整个数据处理流程
       3、Yarnchild:负责 reduce 阶段的整个数据处理流程以上两个阶段 MapTask 和 ReduceTask 的进程都是 YarnChild,并不是说这 MapTask 和ReduceTask 就跑在同一个 YarnChild 进行里
MapReduce 程序的运行流程
       1、一个 mr 程序启动的时候,最先启动的是 MRAppMaster,MRAppMaster 启动后根据本次
job 的描述信息,计算出需要的 maptask 实例数量,然后向集群申请机器启动相应数量的
maptask 进程
       2、 maptask 进程启动之后,根据给定的数据切片(哪个文件的哪个偏移量范围)范围进行数
据处理,主体流程为:
A、利用客户指定的 InputFormat 来获取 RecordReader 读取数据,形成输入 KV 对
B、将输入 KV 对传递给客户定义的 map()方法,做逻辑运算,并将 map()方法输出的 KV 对收
集到缓存
C、将缓存中的 KV 对按照 K 分区排序后不断溢写到磁盘文件
       3、 MRAppMaster 监控到所有 maptask 进程任务完成之后(真实情况是,某些 maptask 进
程处理完成后,就会开始启动 reducetask 去已完成的 maptask 处 fetch 数据),会根据客户指
定的参数启动相应数量的 reducetask 进程,并告知 reducetask 进程要处理的数据范围(数据
分区)
       4、Reducetask 进程启动之后,根据 MRAppMaster 告知的待处理数据所在位置,从若干台
maptask 运行所在机器上获取到若干个 maptask 输出结果文件,并在本地进行重新归并排序,
然后按照相同 key 的 KV 为一个组,调用客户定义的 reduce()方法进行逻辑运算,并收集运
算输出的结果 KV,然后调用客户指定的 OutputFormat 将结果数据输出到外部存储

MapTask 并行度决定机制
       maptask 的并行度决定 map 阶段的任务处理并发度,进而影响到整个 job 的处理速度,那么mapTask 并行实例是否越多越好呢?其并行度又是如何决定呢?
       一个 job 的 map 阶段并行度由客户端在提交 job 时决定,客户端对 map 阶段并行度的规划的基本逻辑为:
       将待处理数据执行逻辑切片(即按照一个特定切片大小,将待处理数据划分成逻辑上的多个 split),然后每一个 split 分配一个 mapTask 并行实例处理这段逻辑及形成的切片规划描述文件,是由FileInputFormat实现类的getSplits()方法完成的。
       该方法返回的是 List,InputSplit 封装了每一个逻辑切片的信息,包括长度和位置信息,而 getSplits()方法返回一组 InputSplit。

切片机制
FileInputFormat 中默认的切片机制:
1、简单地按照文件的内容长度进行切片
2、切片大小,默认等于 block 大小
3、切片时不考虑数据集整体,而是逐个针对每一个文件单独切片
比如待处理数据有两个文件:
File1.txt 200M
File2.txt 100M
经过 getSplits()方法处理之后,形成的切片信息是:
File1.txt-split1 0-128M
File1.txt-split2 129M-200M
File2.txt-split1 0-100M

FileInputFormat 中切片的大小的参数配置:
       通过分析源码,在 FileInputFormat 中,计算切片大小的逻辑:
long splitSize = computeSplitSize(blockSize, minSize, maxSize),翻译一下就是求这三个值的中
间值切片主要由这几个值来运算决定:
blocksize:默认是 128M,可通过 dfs.blocksize 修改
minSize:默认是 1,可通过 mapreduce.input.fileinputformat.split.minsize 修改
maxsize:默认是 Long.MaxValue,可通过 mapreduce.input.fileinputformat.split.maxsize 修改
因此,如果 maxsize 调的比 blocksize 小,则切片会小于 blocksize
如果 minsize 调的比 blocksize 大,则切片会大于 blocksize
但是,不论怎么调参数,都不能让多个小文件“划入”一个 split

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值