上一章我们搭建了分布式的 Hadoop 集群。本章我们介绍 Hadoop 框架中的一个核心模块 - MapReduce。MapReduce 是并行计算模块,顾名思义,它包含两个主要的阶段,map 阶段和 reduce 阶段。每个阶段输入和输出都是键值对。map 阶段主要是对输入的原始数据做处理,按照 key-value 形式输出数据,输出的数据按照key是有序的。reduce 阶段的输入是 map 任务的输出,会对输入的数据会按照 key 做归并排序,使得输入 reduce 任务输入的 key 也是有序的,reduce 阶段进行完业务处理之后将把数据输出到HDFS中。下面以具体的例子说明 MapReduce 的机制。

本章以 WordCount 为例子讲解 MapReduce 机制,这个例子相当于学习编程语言的 "Hello, World"。假设我们在 HDFS 上有一个 10T 的文件,文件每一行有多个单词,单词之间空格分割,现在我们想统计一下这个文件中每个单词出现的次数,这就是 word count。我们的例子将在上一章搭建的 Hadoop 集群上进行。首先准备数据源,我们实际的例子中的数据量比较少,本地(hadoop0 机器)文件如下:

word1文件:
hello world hadoop
hadoop spark word2文件:
hadoop hbase
mapreduce hdfs

需要将这两个文件上传至 HDFS,命令如下:

hadoop fs -mkdir -p /hadoop-ex/wordcount/input        #mkdir:创建目录 -p:递归创建多级目录
hadoop fs -put word1 word2 /hadoop-ex/wordcount/input #上本地文件上传至HDFS目录

有了数据源,我们开始写 MapReduce 程序,我用的编辑器是 Intellj IDEA,创建一个 maven 项目,选择 archetype 为 maven-archetype-quickstart。项目创建完成后引入 Hadoop 依赖

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.9.2</version>
</dependency>

先创建在 map 阶段运行的类,也叫做 Mapper:

package com.cnblogs.duma.mapreduce;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper; import java.io.IOException; /**
* WordCountMapper 继承 Mapper 类,需要指定4个泛型类型,分别是
* 输入 key 类型:本例中输入的 key 为每行文本的行号,例子中用不到所以这里是 Object
* 输入 value 类型:本例中输入的 value 是每行文本,因此是Text
* 输出 key 类型:map 输出的是每个单词,类型为 Text
* 输出 value 类型:单词出现的次数,为 1,因此类型 IntWritable
*/
public class WordCountMapper
extends Mapper<Object, Text, Text, IntWritable> {
/**
* 把每个单词映射成 <word, 1> 的格式
*/
private final static IntWritable one = new IntWritable(1);
private Text outWord = new Text(); /**
* 每个 map 函数处理一行数据
* @param key 输入的行号
* @param value 每一行文本
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
String[] words = value.toString().split(" "); //空格分割一行中的每个单词
for (String word : words) {
outWord.set(word);
System.out.println("<" + outWord + ", " + one + ">"); //打印
context.write(outWord, one); // map输出
}
}
}

这个就是 map 阶段要执行的逻辑,代码本身比较简单。首先说明几个问题。第一,map 阶段会对输入的文件做分割,分割的大小可以通过参数指定,默认按照 HDFS 存储的块大小分割。假设 10TB 的文件,HDFS 块为128MB,那么大概会有 81920 个分块, 每个 map 任务处理一个分块。也就是会为每个分块创建 WordCountMapper 对象,遍历数据块的每一行,并调用 WordCountMapper 类中的 map 函数处理。因此,当前分片的数据有多少行,就会调用多少次 map 函数。我们的例子中有两个文件,每个文件都小于 128MB ,因此会启动2个 map 任务。由于 HDFS 中的数据存储在不同的机器上,因此 map 任务会在尽可能在存储数据块的机器上启动。 这样每个 map 任务可以处理本地的数据,如果有数据块的节点上资源比较紧张无法分配新的 map 任务,只能在其他机器启动 map 任务,将数据下载到该机器,这种情况将产生网络的消耗。第二,刚才提到过,map 函数会执行多次, 一些变量可以定义成类变量,防止创建过多的对象,浪费内存。该例子中,变量 one、outWord 被定义成类变量。第三,Hadoop 为了网络传输更优的序列化与反序列化,重新定义了数据类型,Text 对应java中的 String,IntWritable 对应 java 中的 int。第四,map 任务输出的数据放在本地磁盘上,等待 reduce 任务拉取。

map 函数执行完成后,输出的结果如下

map 任务1:
<hadoop, 1>
<hadoop, 1>
<hello, 1>
<spark, 1>
<world, 1> map 任务2:
<hadoop, 1>
<hbase, 1>
<hdfs, 1>
<mapreduce, 1>

首先可以看到输出是有序的。下面再看下 reduce 任务

package com.cnblogs.duma.mapreduce;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException;
/**
* WordCountReducer 继承 Reducer 类,需要指定4个泛型类型,分别是
* 输入 key 类型:map 任务输出的 key 类型, Text
* 输入 value 类型:map 任务输出的 value 类型,IntWritable
* 输出 key 类型:reduce 输出的是每个单词,类型为 Text
* 输出 value 类型:单词出现的次数,因此类型 IntWritable
*/
public class WordCountReducer
extends Reducer<Text, IntWritable, Text, IntWritable> {
private Log logger = LogFactory.getLog(WordCountMapper.class);
private IntWritable result = new IntWritable(); /**
* 一次 reduce 函数的调用,会处理一个 key
* @param key
* @param values 相同 key 对应的 values 的集合
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) { // 同一个单词出现次数相加,即为该单词的个数
sum += val.get();
}
result.set(sum);
System.out.println("<" + key + ", " + result + ">");
context.write(key, result); // 输出
}
}

reduce 任务的输入便是 map 任务的输出,这里也说明几个问题。第一,reduce 的个数需要通过参数或者代码指定, 默认为1。第二,map 任务输出的 key 去到哪个 reduce 任务,默认是 key 的 hash 值取模。第三,reduce 输入的key 有多个且经过排序,每个 key 对应的 value 组成一个 list,如 reduce 函数输入参数所示。输入多少 key 便调用多少次 reduce 函数。在这个例子中,reduce 任务个数为1。第四,reduce 任务的输出是输出到 HDFS 中。本例中输入数据如下

<hadoop, [1, 1, 1]>
<hbase, [1]>
<hdfs, [1]>
<hello, [1]>
<mapreduce, [1]>
<spark, [1]>
<world, [1]>

可以看到,reduce 任务的输入是有序的。reduce 任务处理完成后,输出如下

<hadoop, 3>
<hbase, 1>
<hdfs, 1>
<hello, 1>
<mapreduce, 1>
<spark, 1>
<world, 1>

现在需要一个驱动程序把他们串起来,代码如下

package com.cnblogs.duma.mapreduce;

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; public class WordCount {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "WordCount"); //第二参数为程序的名字
job.setJarByClass(WordCount.class); //需要设置类名 job.setMapperClass(WordCountMapper.class); //设置 map 任务的类
// job.setCombinerClass(WordCountReducer.class);
job.setReducerClass(WordCountReducer.class); // 设置 reduce 任务的类 job.setOutputKeyClass(Text.class); //设置输出的 key 类型
job.setOutputValueClass(IntWritable.class); //设置输出的 value 类型 FileInputFormat.addInputPath(job, new Path(args[0])); //增加输入文件
FileOutputFormat.setOutputPath(job, new Path(args[1])); //设置输出目录 System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}

现在需要打包放到集群上运行这个例子,可以在项目的根目录执行 mvn package 命令, 也可以利用 IDEA maven 可视化工具直接打包。打包完成后在项目目录中会生成 target 目录,里面有打包好的 jar 文件,我们将它上传到 hadoop0 机器,执行以下命令运行任务

hadoop jar hadoop-ex-1.0-SNAPSHOT.jar com.cnblogs.duma.mapreduce.WordCount /hadoop-ex/wordcount/input /hadoop-ex/wordcount/output

运行日志如下:

19/03/03 04:16:17 INFO client.RMProxy: Connecting to ResourceManager at hadoop0/192.168.29.132:8032
19/03/03 04:16:18 WARN mapreduce.JobResourceUploader: Hadoop command-line option parsing not performed. Implement the Tool interface and execute your application with ToolRunner to remedy this.
19/03/03 04:16:18 INFO input.FileInputFormat: Total input files to process : 2
19/03/03 04:16:19 INFO mapreduce.JobSubmitter: number of splits:2
19/03/03 04:16:19 INFO Configuration.deprecation: yarn.resourcemanager.system-metrics-publisher.enabled is deprecated. Instead, use yarn.system-metrics-publisher.enabled
19/03/03 04:16:19 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1551593879638_0009
19/03/03 04:16:20 INFO impl.YarnClientImpl: Submitted application application_1551593879638_0009
19/03/03 04:16:20 INFO mapreduce.Job: The url to track the job: http://hadoop0:8088/proxy/application_1551593879638_0009/
19/03/03 04:16:20 INFO mapreduce.Job: Running job: job_1551593879638_0009
19/03/03 04:16:35 INFO mapreduce.Job: Job job_1551593879638_0009 running in uber mode : false
19/03/03 04:16:35 INFO mapreduce.Job: map 0% reduce 0%
19/03/03 04:16:48 INFO mapreduce.Job: map 100% reduce 0%
19/03/03 04:17:01 INFO mapreduce.Job: map 100% reduce 100%
19/03/03 04:17:02 INFO mapreduce.Job: Job job_1551593879638_0009 completed successfully
19/03/03 04:17:02 INFO mapreduce.Job: Counters: 49
File System Counters
FILE: Number of bytes read=120
。。。

日志有几个问题需要说明,第一,可以看到第4行 number of splits:2,说明会启动2个 map 任务处理数据。第二,第8行 url :http://hadoop0:8088/proxy/application_1551593879638_0009/ ,可以访问它观察任务的状态、任务任性在那个节点、任务的日志等。

至此,简单的 MapReduce 入门已经介绍完毕,主要就是 map 任务和 reduce 任务两个主要的阶段。当然这两个阶段之间有一个更重要的过程叫做 shuffle,很多任务的优化都需要调这个过程的参数,shuffle 过程的详细介绍我们在之后会讨论。在进行这个例子的时候可能会出一些错,常见的错误我在这里先记录一下。第一,启动任务时候报一下错误

The auxService:mapreduce_shuffle does not exist

解决方法,在 yarn-site.xml 文件中增加参数

<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>

第二,在 web 页面查看任务 logs 的时候,可能会报一下错误

Aggregation is not enabled

解决方法,在 yarn-site.xml 文件中增加参数

<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>

总结

这篇文章主要介绍 MapReduce 的机制,用来入门。MapReduce 主要分两个阶段,map 阶段会对输入的文件分割,分割数决定启动多少 map 任务,map 任务中进行数据处理,并按照<key, value>的格式输出, map 任务的输出数据临时存放在本地磁盘, 经过 shuffle 过程后, 启动 reduce 任务, reduce 任务个数可以手动指定,reduce 任务输入的 key 有序且同一个 key 的 value 会聚合在一起,最终 reduce 任务结果输出到 HDFS。

大数据技术 - 通俗理解MapReduce之WordCount(二)的更多相关文章

  1. 大数据技术 - 通俗理解MapReduce之WordCount(三)

    上一章我们编写了简单的 MapReduce 程序,掌握这些就能编写大多数数据处理的代码.但是 MapReduce 框架提供给用户的能力并不止如此,本章我们仍然以上一章 word count 为例,继续 ...

  2. 大数据技术hadoop入门理论系列之二—HDFS架构简介

    HDFS简单介绍 HDFS全称是Hadoop Distribute File System,是一个能运行在普通商用硬件上的分布式文件系统. 与其他分布式文件系统显著不同的特点是: HDFS是一个高容错 ...

  3. 《Hadoop》大数据技术开发实战学习笔记(二)

    搭建Hadoop 2.x分布式集群 1.Hadoop集群角色分配 2.上传Hadoop并解压 在centos01中,将安装文件上传到/opt/softwares/目录,然后解压安装文件到/opt/mo ...

  4. 大数据技术 - MapReduce的Combiner介绍

    本章来简单介绍下 Hadoop MapReduce 中的 Combiner.Combiner 是为了聚合数据而出现的,那为什么要聚合数据呢?因为我们知道 Shuffle 过程是消耗网络IO 和 磁盘I ...

  5. 【学习笔记】大数据技术原理与应用(MOOC视频、厦门大学林子雨)

    1 大数据概述 大数据特性:4v volume velocity variety value 即大量化.快速化.多样化.价值密度低 数据量大:大数据摩尔定律 快速化:从数据的生成到消耗,时间窗口小,可 ...

  6. 大数据技术 - 为什么是SQL

    在大数据处理以及分析中 SQL 的普及率非常高,几乎是每一个大数据工程师必须掌握的语言,甚至非数据处理岗位的人也在学习使用 SQL.今天这篇文章就聊聊 SQL 在数据分析中作用以及掌握 SQL 的必要 ...

  7. TOP100summit:【分享实录-WalmartLabs】利用开源大数据技术构建WMX广告效益分析平台

    本篇文章内容来自2016年TOP100summitWalmartLabs实验室广告平台首席工程师.架构师粟迪夫的案例分享. 编辑:Cynthia 粟迪夫:WalmartLabs实验室广告平台首席工程师 ...

  8. Google大数据技术架构探秘

    原文地址:https://blog.csdn.net/bingdata123/article/details/79927507 Google是大数据时代的奠基者,其大数据技术架构一直是互联网公司争相学 ...

  9. 大数据技术之Hadoop入门

      第1章 大数据概论 1.1 大数据概念 大数据概念如图2-1 所示. 图2-1 大数据概念 1.2 大数据特点(4V) 大数据特点如图2-2,2-3,2-4,2-5所示 图2-2 大数据特点之大量 ...

随机推荐

  1. Confluence 6 理解你许可证的用户数

    基于你的许可证类型,在你 Confluence 可以被注册的用户也许有限制. 在许可证明细页面中,将会告诉当前使用了多少的许可证(你注册的用户数量). 包括仅仅在 Confluence 中可以使用gl ...

  2. Java的动手动脑(六)

    日期:2018.11.8 星期四 博客期:022 --------------------------------------------------------------------------- ...

  3. 第八单元 正文处理命令及tar命令

    使用cat命令进行文件的纵向合并  两种文件的纵向合并方法  归档文件和归档技术 归档的目的 什么是归档 tar命令的功能 tar命令的常用选项 使用tar命令创建.查看及抽取归档文件 使用tar命令 ...

  4. 3790:最短路径问题(HDU)

    Problem Description 给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的.   Inp ...

  5. 【linux】安装python依赖库confluent_kafka

    想跑https://github.com/ghaughian/mongo-kafka-spark/blob/master/src/pub.py这个程序,发现没有confluent_kafka库 1.p ...

  6. 《剑指offer》顺时针打印矩阵

    本题来自<剑指offer> 顺时针打印矩阵 题目: 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 1 ...

  7. spring-data-mongo的MongoTemplate开发

    spring-data-mongo的MongoTemplate开发 1.在实体类Customer.Java中引入注解表明转换方式 @Document   //文档 public class Custo ...

  8. json数组

    <!DOCTYPE html><html><head> <meta charset="utf-8"> <title>js ...

  9. linux基础练习题(2)

    Linux命令作业(关卡二) 练习题1 理解操作系统的作用,以及各种操作系统的不同 要求: 为什么要有OS?没有OS能行吗?原因是什么? Linux内核指的是什么? Linux主要应用在哪些地方? 使 ...

  10. C#Enum用Tuple保存值绑定到前端的CheckBox

    //把数字转成枚举 public static T[] NumStringsToEnums<T>(string enumNumString ) //where T:Enum { if (s ...