MapReduce
MR简介
MR是一个分布式运算程序的编程框架,是用户开发基于Hadoop的数据分析应用的核心框架。
MR的核心功能是将用户编写的业务代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上。
MR优点
-
MR易于编程:写过的都懂
-
良好的扩展性:当计算资源得不到满足时,可以简单的通过增加机器拓展其计算能力
-
高容错性:当集群中一台机器挂了,它会将上面的计算任务转移到另一个节点上运行,不至于导致任务失败,且这个过程由Hadoop内部完成,不需要人工参与。
-
适合PB级以上的海量数据的离线处理
MR缺点
- 不擅长实时运算
- 不擅长流式运算
- 不擅长有向图运算
- 如果多个应用程序前后存在依赖关系,前者的输出为后者的输入。在这种情况下MR完成每一个阶段的结果都会写入磁盘,会造成大量IO,性能非常低下。
MR的核心思想:
以wordCount为例
需求:统计每一个单词出现的总次数,且结果中a-p,q-z分为两个文件
- 分布式的运算一般分成两个阶段:map阶段和reduce阶段
- map阶段的MapTask并发实例完全并发运行、互不相干
- reduce阶段的reduceTask并发实例且互不相干,但是他们的数据依赖于上一个阶段所有的MapTask并发实例的输出
- MapReduce编程模型只能包含一个map阶段和一个reduce阶段
MapReduce的两个运行阶段(map和reduce)
-
输入文件安装切块大小按文件切片(split),每一片对应一个MapTask
-
MapTask读取自身对应的文件切片后,运行map代码,对数据进行处理,然后按分区溢写到磁盘
- 在WC中就是将数据按空格切割,然后拆分成<单词,1>这样的键值对输出
-
reduceTask的个数取决于分区的个数,每个分区对应一个reduceTask,map阶段完成后由reduceTask获取每一个mapTask中对应分区中的数据,进行数据合并,然后输出到目标文件中
- 在WC中就是将每一个单词对应的所有k、v对进行合并
MapReduce进程
- MRAppMaster:负责整个程序过程的过程调度以及状态协调
- mapTask:负责map阶段的整个数据处理流程
- reduceTask:负责reduce阶段的整个数据处理流程
MapReduce编程规范
用户自己编写的程序由三个部分:Mapper、Reducer、Driver
- mapper阶段中
- 用户自定义的mapper要继承mapper父类方法
- mapper输入和输出数据都是键值对
- 业务逻辑写在map方法中
- map方法对每个kv对调用一次
- reducer阶段中
- 用户自定义的reducer要继承自己的父类
- reducer输入类型对应mapper的输出类型,也是键值对
- reudcer的输出也是键值对
- reudcer的业务逻辑写在reduce方法中
- reduce方法对每组建相同的键值对掉哦那个一次
- driver阶段
- 相当于yarn集群的客户端,用于提交整个程序到yarn集群,提交的是封装了mr程序及相关参数的job对象
wordCount案例实操
-
一、准备工作:
我的输入文件为:
e:/work/test/input/inputfile.txt
内容为:
leimiliya sikaleite meimei weiyan weiyan weiyan meimei jiejie xiaoye leimiliya leimiliya
预期输出为:
jiejie 1 leimiliya 3 meimei 2 sikaleite 1 weiyan 3 xiaoye 1
-
需求分析:
-
mapper中出入的k为偏移量,v为文件中一行的内容
-
将v切割,每个单词都打包成输出的key,value为1
-
reduce阶段汇总map输出的数据
-
diver配置各类信息
-
-
环境准备:
-
创建maven工程
-
配置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>
-
创建log4j文件并在其中写入:
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
-
-
Mapper代码编写:
/** * 继承hadoop提供的Mapper方法 * 其中泛型依次对应着输入k-v类型&输出k-v类型 */ public class WCMapper extends Mapper<LongWritable, Text, Text, IntWritable> { //输出的键 private Text k_out = new Text(); //输出的值 private IntWritable v_out = new IntWritable(1); /** * 重载父类中的map方法,父类中map方法为直接写出输入的k-v * @param key 输入的键,此处为文件指针的偏移量 * @param value 输入的值,此处为一行数据 * @param context 上下文 */ @Override protected void map( LongWritable key, Text value, Context context) throws IOException, InterruptedException { //按空格切割数据,得到一行中的每一个单词 String[] words = value.toString().split(" "); for (String word : words) { //封装输出的key k_out.set(word); //写出 context.write(k_out,v_out); } } }
-
Reducer代码编写:
/** * 继承hadoop提供的Reducer父类 * 其中泛型对应着输入的k-v与输出的k-v的类型 */ public class WCReducer extends Reducer<Text, IntWritable, Text, IntWritable> { //输出的值 private IntWritable v_out = new IntWritable(); /** * 重载父类中的reduce方法,父类中的reudce方法为直接写出每一个键值对 * @param key map方法中输入的键 * @param values map方法中输出的所有对应改键的值的迭代器 * @param context 上下文 */ @Override protected void reduce( Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { //计数器 int count = 0; for (IntWritable value : values) { //累加即可 count++; } //封装 v_out.set(count); //写出 context.write(key,v_out); } }
-
Driver代码编写:
/** * 此类直接写main方法封装一些必要的信息到job中并提交即可 */ public class WCDriver { public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { //设置输入输出路径 Path inputPath = new Path( "E:/work/test/input/inputfile.txt"); Path outputPath = new Path( "E:/work/test/output"); //如果输出路径已经存在会抛出异常, //所以判断输出路径是否存在,如果存在则将其删除 FileSystem fs = FileSystem.get(new Configuration()); if (fs.exists(outputPath)){ fs.delete(outputPath,true); } //创建job Job job = Job.getInstance(); //设置job //设置要运行的mapper和reducer类 job.setMapperClass(WCMapper.class); job.setReducerClass(WCReducer.class); //设置mapper和reducer的输出k-v类型, // 如果两者一致,直接设置最终输出类型即可 job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); //设置输入目录和输出目录 FileInputFormat.setInputPaths(job,inputPath); FileOutputFormat.setOutputPath(job, outputPath); //开启并等待任务执行完成 boolean result = job.waitForCompletion(true); System.out.println(result?1:-1); } }
-
运行结果
可以在输出的日志最后看到输出了1,代表顺利运行结束了
找到输出文件,符合预期: