上一章我们搭建了分布式的 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 审查日志的对象

    审查日志记录一下事件的信息,这个记录不是详细的信息列表.但是这些信息能够让你了解你能够在日志中看到些什么内容. 空间 创建和删除一个空间. 编辑空间细节,主题,配色方案或者样式表. 修改空间权限,包括 ...

  2. 获取表单内的所有元素的值 表单格式化插件jquery.serializeJSON

    简单描述:一个form表单里有十几个input或者select,要获取到他们的值,我的做法一直都是$("#id").val();这样做本来没什么说的,但是如果有很多呢,就很麻烦,看 ...

  3. DSB

    Linux day01 计算机硬件知识整理 作业要求:整理博客,内容如下 编程语言的作用及与操作系统和硬件的关系 应用程序->操作系统->硬件 cpu->内存->磁盘 cpu与 ...

  4. cf581F 依赖背包+临时数组 好题

    这题得加个临时数组才能做.. /* 给定一棵树,树节点可以染黑白,要求叶子节点黑白平分 称连接黑白点的边为杂边,求使得杂边最少的染色方 那么设dp[i][j][0|1]表示i子树中有j个叶子节点,i染 ...

  5. Vue注意事项及用得较多的属性归纳

    1.prop是一个对象而不是字符串数组时,它包含验证要求.props: { // 基础类型检测 (`null` 意思是任何类型都可以) propA: Number, // 多种类型 propB: [S ...

  6. Loadrunner11.0 录制手机App脚本的方法一

    使用Loadrunner录制手机终端App脚本 1. 说明 目前手机APP上的功能日益丰富,对手机应用功能的性能测试需求也越来越多.公司比较抠门没有花钱买Loadrunner,可怜我们工作中一直用的破 ...

  7. 使用sysbench 0.5 对mysql 进行性能、压力测试

    sysbench是一个模块化的.跨平台.多线程基准测试工具,主要用于评估测试各种不同系统参数下的数据库负载情况.目前sysbench代码托管在launchpad上,项目地址:https://launc ...

  8. win10下右键菜单添加“打开cmd”

    早期版本的win10是可以在文件夹的左上角打开cmd的,更新后发现现在只有powershell能用了.这不方便. 通过修改注册表,可以实现这个功能. 具体做法:新建一个.reg文件win10_add_ ...

  9. RPC服务超时排查思路

    RPC服务超时排查思路- 1.查看服务提供者日志相关信息进行排查- 2.查看消费者的超时时间设置是否合理- 3.查看服务提供者业务逻辑是否有DB操作,有的话看是否有慢SQL- 4.查看服务提供者业务逻 ...

  10. Java基础知识➣Stream整理(二)

    概述 在Java数据流用到的流包括(Stream).文件(File流)和I/O流 ,利用该三个流操作数据的传输. Java控制台输入输出流 读取控制台使用数据流: BufferedReader和Inp ...