上一章我们搭建了分布式的 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 被配置自动备份数据,使用压缩的 XML 格式.同时你也可以通过 Confluence 的 管理员控制台(Administration Console)手动进行备份. 你需要具有 ...

  2. Confluence 6 让 Jira 应用停止发送通知到 Confluence

    你可能希望仅仅配置 Confluence 在 workbox 中仅仅显示自己的通知,禁用在 workbox 中显示从 Jira 来的通知.有可能这个 Jira 的应用已经通过应用链接功能正确链接到 C ...

  3. PL\SQL 随学笔记

    一.在PL\SQL语句块begin...end;中,不能直接使用select,必须与into结合查询. 例如: declare aa:=22; id2 integer; begin select * ...

  4. Ionic3.0 输入状态时隐藏Tabs栏

    刚接触ionic3 不久 ,发现遍地都是坑,昨天遇到一个问题就是当键盘弹起的时候tabs 也被 弹了起来,最初预想是放在tabs 的一个子页面内处理这个问题, Tabs隐藏后,我们发现底部有部分空白, ...

  5. React-Native到0.44版本后Navigator 不能用的问题

    新升级  到0.46版本以后 Navigator 不能使用报错. 'Navigator is deprecated and has been removed from this package. It ...

  6. ubuntu MySQL配置文件

    查看配置文件: locate my.cnf 查看配置文件路径: /记得要在配置环境变量后执行下列命令 which mysqld /usr/local/mysql/bin/mysqld --verbos ...

  7. java----Java的栈,堆,代码,静态存储区的存储顺序和位置

    转载:https://blog.csdn.net/zhangbaoanhadoop/article/details/82193497

  8. poj1155 依赖背包

    /* 依赖背包 dp[i][j]表示i结点为根的树选择j个用户时的最大剩余费用 即背包容量是j,价值是最大费用 */ #include<iostream> #include<cstr ...

  9. document.getElementsByClassName() 原生方法 通过className 选择DOM节点

    <div id="box"> <div class="box">1</div> <div class="bo ...

  10. C++11 中的function和bind、lambda用法

    std::function 1. std::bind绑定一个成员函数 #include <iostream> #include <functional> struct Foo ...