Hadoop-MapReduce-v2-秘籍-全-

Hadoop MapReduce v2 秘籍(全)

原文:Hadoop MapReduce v2 Cookbook

协议:CC BY-NC-SA 4.0

零、前言

我们目前正面临着雪崩般的数据,这些数据包含了许多洞察力,这些洞察力掌握着数据驱动的世界中成败的关键。 下一代 Hadoop(V2)提供了一个尖端平台来存储和分析这些海量数据集,并改进了广泛使用且非常成功的 Hadoop MapReduce v1。 将帮助您使用下一代 Hadoop MapReduce 分析大型复杂数据集的食谱将为您提供使用下一代 Hadoop 生态系统处理大型复杂数据集所需的技能和知识。

本书介绍了许多令人兴奋的主题,例如使用 Hadoop 解决分析、分类、数据索引和搜索的 MapReduce 模式。 您还将了解几个 Hadoop 生态系统组件,包括 Have、Pig、HBase、Mahout、Nutch 和 Sqoop。

本书向您介绍简单的示例,然后深入探讨如何解决深入的大数据使用案例。 本书以简单明了的方式介绍了 90 多个即用型 Hadoop MapReduce 食谱,并提供了分步说明和实际示例。

这本书涵盖了哪些内容

第 1 章Hadoop v2入门,介绍 Hadoop MapReduce、Yarn 和 HDFS,并逐步完成 Hadoop v2 的安装。

第 2 章云部署-在云环境中使用 Hadoop Yarn,说明如何使用 Amazon Elastic MapReduce(EMR)和 Apache Whirr 在云基础架构上部署和执行 Hadoop MapReduce、Pig、Have 和 HBase 计算。

第 3 章Hadoop Essentials-配置、单元测试和其他 API介绍了基本的 Hadoop Yarn 和 HDFS 配置、HDFS Java API 以及 MapReduce 应用的单元测试方法。

第 4 章开发复杂的 Hadoop MapReduce 应用向您介绍了几个高级 Hadoop MapReduce 功能,这些功能将帮助您开发高度自定义且高效的 MapReduce 应用。

第 5 章Analytics解释了如何使用 Hadoop MapReduce 执行基本数据分析操作。

第 6 章Hadoop 生态系统-Apache Have介绍了 Apache Have,它使用类似 SQL 的查询语言在 Hadoop 之上提供数据仓库功能。

第 7 章Hadoop 生态系统 II-Pig、HBase、Mahout 和 Sqoop介绍了 Apache Pig 数据流样式数据处理语言、Apache HBase NoSQL 数据存储、Apache Mahout 机器学习和数据挖掘工具包,以及用于在 Hadoop 和关系数据库之间传输数据的 Apache Sqoop 批量数据传输实用程序。

第 8 章搜索和索引介绍了几种可用于 Apache Hadoop 执行大规模搜索和索引的工具和技术。

第 9 章分类、建议和查找关系解释了如何使用 Hadoop 实现复杂的算法,如分类、推荐和查找关系。

第 10 章海量文本数据处理解释了如何使用 Hadoop 和 Mahout 处理大型文本数据集,以及如何使用 Hadoop 执行数据预处理和加载操作。

这本书你需要什么

您需要具备一定的 Java 知识,能够访问 Internet 和一台运行 Linux 操作系统的计算机。

这本书是给谁看的

如果您是一个大数据爱好者,并且希望使用 Hadoop v2 来解决您的问题,那么这本书是为您准备的。 本书面向对 Hadoop MapReduce 知之甚少的 Java 程序员。 这也是希望快速掌握使用 Hadoopv2 的开发人员和系统管理员的一站式参考。 掌握使用 Java 进行软件开发的基本知识和 Linux 的基本工作知识会很有帮助。

公约

在这本书中,你会发现许多区分不同信息的文本样式。 以下是这些风格的一些示例,并解释了它们的含义。

文本、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄中的代码字如下所示:“以下是我们在hadoop.properties文件中使用的属性的说明。”

代码块设置如下:

Path file = new Path("demo.txt");
FSDataOutputStream outStream = fs.create(file);
outStream.writeUTF("Welcome to HDFS Java API!!!");
outStream.close();

当我们希望您注意代码块的特定部分时,相关行或项将以粗体显示:

Job job = Job.getInstance(getConf(), "MLReceiveReplyProcessor");
job.setJarByClass(CountReceivedRepliesMapReduce.class);
job.setMapperClass(AMapper.class);
job.setReducerClass(AReducer.class);
job.setNumReduceTasks(numReduce);

job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
job.setInputFormatClass(MBoxFileInputFormat.class);
FileInputFormat.setInputPaths(job, new Path(inputPath));
FileOutputFormat.setOutputPath(job, new Path(outputPath));

int exitStatus = job.waitForCompletion(true) ? 0 : 1;

任何命令行输入或输出都如下所示:

205.212.115.106 - - [01/Jul/1995:00:00:12 -0400] "GET /shuttle/countdown/countdown.html HTTP/1.0" 200 3985

新术语重要单词以粗体显示。 例如,您在屏幕、菜单或对话框中看到的文字会出现在文本中,如下所示:“在Add Bootstrap Actions下拉框中选择Custom Action。单击Configure and Add。”

备注

警告或重要说明会出现在这样的框中。

提示

提示和技巧如下所示。

读者反馈

欢迎读者的反馈。 让我们知道你对这本书的看法-你喜欢什么或不喜欢什么。 读者反馈对于我们开发真正能让您获得最大收益的图书非常重要。

要向我们发送一般反馈,只需发送电子邮件至<[feedback@packtpub.com](mailto:feedback@packtpub.com)>,并通过消息主题提及书名。

如果有一个您擅长的主题,并且您有兴趣撰写或投稿一本书,请参阅我们位于www.Packtpub.com/Authors上的作者指南。

客户支持

现在您已经成为 Packt 图书的拥有者,我们有很多东西可以帮助您从购买中获得最大价值。

下载示例代码

您可以从您的帐户http://www.packtpub.com下载购买的所有 Packt 图书的示例代码文件。 如果您在其他地方购买了本书,您可以访问http://www.packtpub.com/support并注册,以便将文件通过电子邮件直接发送给您。

勘误表

虽然我们已经竭尽全力确保内容的准确性,但错误还是会发生。 如果您在我们的一本书中发现错误--可能是文本或代码中的错误--如果您能向我们报告,我们将不胜感激。 通过这样做,您可以将其他读者从挫折中解救出来,并帮助我们改进本书的后续版本。 如果您发现任何勘误表,请访问http://www.packtpub.com/submit-errata,选择您的图书,单击勘误表提交表链接,然后输入勘误表的详细信息。 一旦您的勘误表被核实,您提交的勘误表将被接受,勘误表将被上传到我们的网站上,或添加到该标题勘误表部分下的任何现有勘误表列表中。 通过从http://www.packtpub.com/support中选择您的书目,可以查看任何现有勘误表。

盗版

在互联网上盗版版权材料是所有媒体持续存在的问题。 在 Packt,我们非常重视版权和许可证的保护。 如果您在互联网上发现任何形式的非法复制我们的作品,请立即提供我们的位置、地址或网站名称,以便我们采取补救措施。

请拨打<[copyright@packtpub.com](mailto:copyright@packtpub.com)>与我们联系,并提供疑似盗版材料的链接。

我们感谢您在保护我们的作者方面的帮助,以及我们为您提供有价值内容的能力。

问题

如果您对本书的任何方面有任何问题,可以拨打<[questions@packtpub.com](mailto:questions@packtpub.com)>与我们联系,我们将尽最大努力解决。

一、Hadoop v2 入门

在本章中,我们将介绍以下食谱:

  • 在本地计算机上设置独立 Hadoop v2
  • 编写字数统计 MapReduce 应用,捆绑它,并使用 Hadoop 本地模式运行它
  • 向 Wordcount MapReduce 程序添加组合器步骤
  • 设置 HDFS
  • 使用 Hadoop v2 在分布式群集环境中设置 Hadoop Yarn
  • 使用 Hadoop 发行版在分布式群集环境中设置 Hadoop 生态系统
  • HDFS 命令行文件操作
  • 在分布式集群环境中运行 WordCount 程序
  • 使用 DFSIO 对 HDFS 进行基准测试
  • 使用 TeraSort 对 Hadoop MapReduce 进行基准测试

简介

我们生活在大数据时代,网络、社交网络、智能手机等现象的指数级增长每天产生数万亿字节的数据。 从分析这些海量数据中获得洞察力已成为许多行业必备的竞争优势。 然而,这些数据源的大小和可能的非结构化性质使得不可能使用诸如关系数据库之类的传统解决方案来存储和分析这些数据集。

*要以有意义和及时的方式存储、处理和分析 PB 级数据,需要许多具有数千个磁盘和数千个处理器的计算节点,以及它们之间高效通信海量数据的能力。 这样的规模使得磁盘故障、计算节点故障、网络故障等故障屡见不鲜,使得容错成为此类系统的一个非常重要的方面。 出现的其他常见挑战包括巨大的资源成本、处理通信延迟、处理异构计算资源、跨节点同步以及负载平衡。 正如您可以推断的那样,开发和维护分布式并行应用以在处理所有这些问题的同时处理大量数据并非易事。 这就是 Apache Hadoop 拯救我们的地方。

备注

谷歌是首批面临处理海量数据问题的组织之一。 Google 借鉴了函数式编程领域的mapReduce范例,构建了一个用于大规模数据处理的框架,并将其命名为MapReduce。 在 Google 的基础上,MapReduce 是 Google 文件系统,它是一个高吞吐量的并行文件系统,能够使用商用计算机可靠地存储海量数据。 介绍 Google MapReduce 和 Google File System 概念的开创性研究出版物可以在http://research.google.com/archive/mapreduce.htmlhttp://research.google.com/archive/gfs.html找到。

Apache Hadoop MapReduce 是 Google MapReduce 范例中最广为人知、使用最广泛的开源实现。 ApacheHadoop 分布式文件系统(HDFS)提供了 Google 文件系统概念的开源实现。

Apache Hadoop MapReduce、HDFS 和 YAR 为跨商用计算机群集存储和处理超大型数据集提供了一个可伸缩的、容错的分布式平台。 与传统的高性能计算(HPC)群集不同,Hadoop 使用相同的计算节点集进行数据存储和执行计算,从而允许 Hadoop 通过将计算与存储进行配置来提高大规模计算的性能。 此外,由于使用商用硬件和商用互连,Hadoop 群集的硬件成本比 HPC 群集和数据库设备便宜数量级。 总之,基于 Hadoop 的框架已经成为存储和处理大数据的事实标准。

Hadoop 分布式文件系统-HDFS

HDFS 是一种块结构的分布式文件系统,旨在将 PB 级的数据可靠地存储在由商用硬件组成的计算集群上。 HDFS 覆盖在计算节点的现有文件系统之上,并通过将文件拆分成更粗粒度的块(例如,128 MB)来存储文件。 HDFS 在处理大文件时性能更好。 HDFS 将大文件的数据块分布到群集的所有节点,以便在处理数据时实现极高的并行聚合读取带宽。 HDFS 还将这些数据块的冗余副本存储在多个节点中,以确保可靠性和容错性。 数据处理框架(如 MapReduce)利用这些分布式数据块集和冗余来最大化大型数据集的数据本地处理,其中大多数数据块将在存储它们的同一物理节点中进行本地处理。

HDFS 由NameNodeDataNode服务组成,为分布式文件系统提供基础。 NameNode 存储、管理和服务文件系统的元数据。 NameNode 不存储任何实际数据块。 DataNode 是一项针对每个节点的服务,用于管理 DataNode 中的实际数据块存储。 在检索数据时,客户端应用首先联系 NameNode 以获取请求的数据所在位置的列表,然后直接联系 DataNode 以检索实际数据。 下图概括介绍了 HDFS 的结构:

Hadoop Distributed File System – HDFS

Hadoop v2 为 HDFS 带来了几个性能、可伸缩性和可靠性改进。 其中最重要的是High Availability(HA)对 HDFSNameNode 的支持,它为 HDFS NameNode 服务提供手动和自动故障转移功能。 这解决了 HDFS 广为人知的 NameNode 单点故障弱点。 自动 NameNode Hadoop v2 的高可用性使用 Apache ZooKeeper 进行故障检测和活动 NameNode 选举。 另一个重要的新特性是对 HDFS 联合的支持。 HDFS 联合支持在单个 HDFS 群集中使用个独立的 HDFS 命名空间。 这些命名空间将由独立的 NameNode 管理,但是共享集群的 DataNode 来存储数据。 HDFS 联合功能通过允许我们分配 NameNode 的工作负载,提高了 HDFS 的水平可伸缩性。 Hadoop v2 中 HDFS 的其他重要改进包括对 HDFS 快照的支持、异构存储层次结构支持(Hadoop 2.3 或更高版本)、内存数据缓存支持(Hadoop 2.3 或更高版本)以及许多性能改进。

几乎所有 Hadoop 生态系统数据处理技术都使用 HDFS 作为主要数据存储。 HDFS 可以被认为是 Hadoop 生态系统中最重要的组件,因为它在 Hadoop 体系结构中具有中心性质。

Hadoop Yarn

Yarn(还有另一个资源谈判者)是 Hadoopv2 中引入的主要新改进。 Yar 是一个资源管理系统,它允许多个分布式处理框架有效地共享 Hadoop 集群的计算资源,并利用存储在 HDFS 中的数据。 Year 是 Hadoopv2 生态系统中的核心组件,为许多不同类型的分布式应用提供了一个公共平台。

基于批处理的 MapReduce 框架是 Hadoopv1 中唯一本地支持的数据处理框架。 虽然 MapReduce 可以很好地分析大量数据,但 MapReduce 本身不足以支持不断增加的其他分布式处理用例,例如实时数据计算、图形计算、迭代计算和实时数据查询。 YAIN 的目标是允许用户利用多个分布式应用框架,这些框架并排提供这样的功能,共享单个集群和 HDFS 文件系统。 当前 Yarn 应用的一些示例包括 MapReduce 框架、TEZ 高性能处理框架、Spark 处理引擎和 Storm 实时流处理框架。 下图描述了 Yarn 生态系统的高级架构:

Hadoop YARN

YAR ResourceManager 进程是中央资源调度器,用于管理资源并将其分配给提交给集群的不同应用(也称为作业)。 Yanson NodeManager 是一个针对每个节点的进程,用于管理单个计算节点的资源。 ResourceManager 的调度器组件响应于应用做出的资源请求来分配资源,考虑到集群容量和可以通过 Yarn 策略插件框架指定的其他调度策略。

Yarn 有一个叫做容器的概念,它是资源分配的单位。 每个已分配的容器都有权访问特定计算节点中的一定数量的 CPU 和内存。 应用可以通过指定所需的容器数量以及每个容器所需的 CPU 和内存来请求来自 Yarn 的资源。

ApplicationMaster 是一个针对每个应用的进程,它协调单个应用的计算。 执行 Yarn 应用的第一步是部署 ApplicationMaster。 在 YAINE 客户端提交应用后,ResourceManager 为该应用分配一个容器并部署 ApplicationMaster。 部署后,ApplicationMaster 负责向 ResourceManager 请求和协商必要的资源容器。 一旦 ResourceManager 分配了资源,ApplicationMaster 就会与 NodeManagers 协调以启动和监视分配的资源中的应用容器。 将应用协调职责转移到 ApplicationMaster 减轻了 ResourceManager 的负担,使其能够只专注于管理群集资源。 此外,对于每个提交的应用都有单独的 ApplicationMaster 可以提高集群的可伸缩性,而不是使用单个进程瓶颈来协调所有应用实例。 下图描述了将 MapReduce 应用提交到群集时各种 Yarn 组件之间的交互:

Hadoop YARN

虽然 Yar 支持许多不同的分布式应用执行框架,但本书主要关注传统的 MapReduce 和相关技术。

Hadoop MapReduce

HadoopMapReduce 是一个数据处理框架,可用于处理存储在 HDFS 中的海量数据。 正如我们前面提到的,以可靠和高效的方式分布式处理海量数据并非易事。 Hadoop MapReduce 旨在通过提供程序的自动并行化和框架管理的容错支持,为程序员提供清晰的抽象,从而简化用户。

MapReduce 编程模型由 Map 和 Reduce 函数组成。 Map 函数接收输入数据的每条记录(文件的行、数据库的行等)作为键-值对,并输出键-值对作为结果。 通过设计,每个 Map 函数调用都是相互独立的,从而允许框架使用分而治之的方式并行执行计算。 这还允许在出现故障或负载不平衡的情况下重复执行或重新执行 Map 任务,而不会影响计算结果。 通常,Hadoop 会为输入数据的每个 HDFS 数据块创建一个 Map 任务实例。 Map 任务实例内的 Map 函数调用数等于特定 Map 任务实例的输入数据块中的数据记录数。

Hadoop MapReduce 使用对计算的所有 Map 任务的输出键值记录进行分组,并将它们分发给 Reduce 任务。 这种将数据分发和传输到 Reduce 任务的过程称为 MapReduce 计算的无序阶段。 每个 Reduce 任务的输入数据也将按键进行排序和分组。 将按键的排序顺序为每个键和该键的值组(Reduce<key,list_of_value>)调用 Reduce 函数。 在典型的 MapReduce 程序中,用户只需实现 Map 和 Reduce 函数,Hadoop 负责并行调度和执行它们。 Hadoop 将重新运行任何失败的任务,并提供缓解任何不平衡计算的措施。 为了更好地理解 MapReduce 数据流和计算流,请查看下图:

Hadoop MapReduce

在 Hadoop1.x 中,MapReduce(MR1)组件由JobTracker进程和TaskTracker组成,前者在管理集群和协调作业的主节点上运行,后者在每个计算节点上运行,启动和协调在该节点中执行的任务。 Hadoop 2.x MapReduce(MR2)中没有这两个进程。 在 MR2 中,JobTracker 的工作协调职责由 ApplicationMaster 处理,该 ApplicationMaster 将通过 Yarn 按需部署。 JobTracker 的集群管理和作业调度职责在 MR2 中由 YAR ResourceManager 处理。 JobHistoryServer 已经接管了提供有关已完成的 MR2 作业的信息的责任。 通过在计算节点中管理资源和启动容器(在 MapReduce 2 中启动容器,在 MapReduce 2 中包含 Map 或 Reduce 任务),Yanson NodeManager 提供了与 MR1 TaskTracker 有些类似的功能。

Hadoop 安装模式

Hadoopv2 提供了三个安装选项:

  • 本地模式:本地模式允许我们仅使用解压的 Hadoop 发行版运行 MapReduce 计算。 这种非分布式模式在单个 Java 进程中执行 Hadoop MapReduce 的所有部分,并使用本地文件系统作为存储。 本地模式对于本地测试/调试 MapReduce 应用非常有用。
  • 伪分布式模式:使用此模式,我们可以在模拟分布式集群的单机上运行 Hadoop。 此模式将 Hadoop 的不同服务作为不同的 Java 进程运行,但在一台机器内运行。 这种模式很适合让您玩和体验 Hadoop。
  • 分布式模式:这是真正的分布式模式,支持从几个节点到数千个节点的集群。 对于生产集群,我们建议使用众多打包的 Hadoop 发行版之一,而不是使用 Hadoop 发行版二进制文件从头开始安装 Hadoop,除非您有需要普通 Hadoop 安装的特定用例。 有关 Hadoop 发行版的更多信息,请参阅使用 Hadoop 发行版在分布式集群环境中建立 Hadoop 生态系统食谱。

备注

本书的示例代码文件可以在 giHub 上的https://github.com/thilg/hcb-v2获得。 代码库的chapter1文件夹包含本章的示例源代码文件。 您还可以使用https://github.com/thilg/hcb-v2/archive/master.zip链接下载存储库中的所有文件。

本书的示例代码使用 Gradle 自动编译和构建项目。 您可以按照http://www.gradle.org/docs/current/userguide/installation.html提供的指南安装 Gradle。 通常,您只需从http://www.gradle.org/downloads下载并解压缩 Gradle 发行版,并将提取的 Gradle 发行版的 bin 目录添加到您的 PATH 变量中。

所有示例代码都可以通过在代码库的主文件夹中发出gradle build命令来构建。

Eclipse IDE 的项目文件可以通过在代码库的主文件夹中运行gradle eclipse命令来生成。

IntelliJ IDEA IDE 的项目文件可以通过在代码库的主文件夹中运行gradle idea命令来生成。

在本地计算机上设置 Hadoop v2

本菜谱介绍了如何使用本地模式在本地计算机上设置 Hadoop v2。 本地模式是一种非分布式模式,可用于测试和调试 Hadoop 应用。 在本地模式下运行 Hadoop 应用时,所有必需的 Hadoop 组件和应用都在单个Java 虚拟机(JVM)进程内执行。

做好准备

下载并安装 JDK 1.6 或更高版本,最好是 Oracle JDK 1.7。 Oracle JDK 可以从http://www.oracle.com/technetwork/java/javase/downloads/index.html下载。

怎么做……

现在,让我们开始 Hadoop v2 安装:

  1. http://hadoop.apache.org/releases.html下载最新的 Hadoopv2 分支发行版(Hadoop2.2.0 或更高版本)。

  2. 使用以下命令解压缩 Hadoop 发行版。 您必须将文件名中的x.x.更改为您下载的实际版本。 从现在开始,我们将把解压后的 Hadoop 目录命名为{HADOOP_HOME}

    $ tar -zxvf hadoop-2.x.x.tar.gz
    
    
  3. 现在,您可以通过{HADOOP_HOME}/bin/hadoop命令运行 Hadoop 作业,我们将在下一个菜谱中进一步详细说明这一点。

它是如何工作的.

Hadoop 本地模式不启动任何服务器,但在单个 JVM 内完成所有工作。 当您以本地模式向 Hadoop 提交作业时,Hadoop 会启动 JVM 来执行该作业。 该作业的输出和行为与分布式 Hadoop 作业相同,不同之处在于该作业仅使用当前节点运行任务,而本地文件系统用于数据存储。 在下一个菜谱中,我们将了解如何使用 Hadoop 本地模式运行 MapReduce 程序。

编写字数统计 MapReduce 应用,将其捆绑在一起,并使用 Hadoop 本地模式运行它

这个配方解释了如何实现一个简单的 MapReduce 程序来计算数据集中单词的出现次数。 Wordcount 作为 Hadoop MapReduce 的 HelloWorld 等价物而闻名。

要运行 MapReduce 作业,用户应该提供map函数、reduce函数、输入数据和存储输出数据的位置。 执行时,Hadoop 执行以下步骤:

  1. Hadoop 使用提供的InputFormat将输入数据分解为键-值对,并为每个键-值对调用map函数,提供键-值对作为输入。 执行时,map函数可以输出零个或多个键值对。
  2. Hadoop 将映射器发出的键值对传输到还原器(此步骤称为无序)。 Hadoop 然后按键对这些键-值对进行排序,并将属于同一键的值分组在一起。
  3. 对于每个不同的键,Hadoop 调用一次 Reduce 函数,同时传递该键的特定键和值列表作为输入。
  4. reduce函数可以输出零个或多个键值对,Hadoop 将它们写入输出数据位置作为最终结果。

做好准备

从本书的源代码存储库中选择第一章的源代码。 导出指向解压缩的 Hadoop 发行版的根的$HADOOP_HOME环境变量。

怎么做……

现在,让我们编写我们的第一个 Hadoop MapReduce 程序:

  1. WordCount 示例使用 MapReduce 计算一组输入文档中出现的单词数。 示例代码位于本章源文件夹的chapter1/Wordcount.java文件中。 代码由三部分组成-映射器、Reducer 和主程序。

  2. 映射器从org.apache.hadoop.mapreduce.Mapper接口扩展。 Hadoop InputFormat 将输入文件中的每一行作为输入键-值对提供给map函数。 函数map使用空格字符(如分隔符)将每行分成子字符串,并为每个标记(Word)发出(word,1)作为输出。

    public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
      // Split the input text value to words
      StringTokenizer itr = new StringTokenizer(value.toString());
    
      // Iterate all the words in the input text value
      while (itr.hasMoreTokens()) {
        word.set(itr.nextToken());
        context.write(word, new IntWritable(1));
      }
    }
    
  3. 每个reduce函数调用都接收一个键和该键的所有值作为和输入。 reduce函数输出键和键的出现次数作为输出。

    public void reduce(Text key, Iterable<IntWritable>values, Context context) throws IOException, InterruptedException
    {
      int sum = 0;
      // Sum all the occurrences of the word (key)
      for (IntWritableval : values) {
        sum += val.get();
      }
      result.set(sum);
      context.write(key, result);
    }
    
  4. main驱动程序配置 MapReduce 作业并将其提交给 Hadoop Yarn 集群:

    Configuration conf = new Configuration();
    ……
    // Create a new job
    Job job = Job.getInstance(conf, "word count");
    // Use the WordCount.class file to point to the job jar
    job.setJarByClass(WordCount.class);
    
    job.setMapperClass(TokenizerMapper.class);
    job.setReducerClass(IntSumReducer.class);
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    
    // Setting the input and output locations
    FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
    FileOutputFormat.setOutputPath(job, newPath(otherArgs[1]));
    // Submit the job and wait for it's completion
    System.exit(job.waitForCompletion(true) ? 0 : 1);
    
  5. 通过从示例代码库的chapter1文件夹发出gradle build命令,使用本章介绍中提到的使用 Gradle 版本编译示例。 或者,您也可以通过发出ant compile命令来使用提供的 Apache Ant 构建文件。

  6. 使用以下命令运行 wordcount 示例。 在此命令中,chapter1.WordCountmain类的名称。 wc-input是输入数据目录,wc-output是输出路径。 源库的wc-input目录包含一个示例文本文件。 或者,您可以将任何文本文件复制到wc-input目录。

    $ $HADOOP_HOME/bin/hadoop jar \
    hcb-c1-samples.jar \
    chapter1.WordCount wc-input wc-output
    
    
  7. 输出目录(wc-output)将有一个名为part-r-XXXXX的文件,该文件将包含文档中每个单词的计数。 祝贺你!。 您已经成功运行了您的第一个 MapReduce 程序。

    $ cat wc-output/part*
    
    

它是如何工作的.

在前面的示例中,MapReduce 在本地模式下工作,不启动任何服务器,并使用本地文件系统作为输入、输出和工作数据的存储系统。 下图显示了隐藏在 WordCount 程序中发生的情况:

How it works...

字数统计 MapReduce 工作流的工作方式如下:

  1. Hadoop 读取输入,使用新行个字符作为分隔符来中断它,然后运行map函数,将每行作为参数传递,行号作为键,行内容作为值。
  2. 函数的作用是:标记该行,并为每个标记(Word)发出一个键-值对(word,1)
  3. Hadoop 收集所有(word,1)对,按单词对它们进行排序,根据每个唯一键对发出的所有值进行分组,并为每个唯一键调用一次reduce函数,将该键的键和值作为参数传递。
  4. reduce函数使用这些值计算每个单词的出现次数,并将其作为键-值对发出。
  5. Hadoop 将最终输出写入输出目录。

还有更多...

作为可选步骤,您可以直接从您喜欢的Java 集成开发环境(IDE)设置和运行 WordCount 应用。 Eclipse IDE 和 IntelliJ IDEA IDE 的项目文件可以通过在代码库的主文件夹中分别运行gradle eclipsegradle idea命令来生成。

对于其他 IDE,您必须将以下目录中的 JAR 文件添加到为示例代码创建的 IDE 项目的类路径中:

  • {HADOOP_HOME}/share/hadoop/common
  • {HADOOP_HOME}/share/hadoop/common/lib
  • {HADOOP_HOME}/share/hadoop/mapreduce
  • {HADOOP_HOME}/share/hadoop/yarn
  • {HADOOP_HOME}/share/hadoop/hdfs

通过传递wc-inputwc-output作为参数来执行chapter1.WordCount类。 这将像以前一样运行示例。 以这种方式从 IDE 运行 MapReduce 作业对于调试 MapReduce 作业非常有用。

另请参阅

尽管您在本地机器上安装了 Hadoop 来运行示例,但是您可以使用分布式 Hadoop 集群设置和 HDFS 分布式文件系统来运行它。 本章的在分布式集群环境中运行 Wordcount 程序配方将讨论如何在分布式设置中运行此示例。

向 Wordcount MapReduce 程序添加组合器步骤

一个单个映射任务可能会输出许多具有相同键的键值对,导致 Hadoop 通过网络混洗(移动)所有这些值到 Reduce 任务,这会产生很大的开销。 例如,在前面的 Wordcount MapReduce 程序中,当 Mapper 在单个 Map 任务中遇到同一单词的多次出现时,map函数将输出许多<word,1>个中间键-值对以通过网络传输。 但是,如果在通过网络将数据发送到减少器之前,我们可以将<word,1>对的所有实例求和为单个<word, count>对,则可以优化此场景。

为了优化这样的场景,Hadoop 支持一个称为组合器的特殊函数,该函数执行 Map 任务输出键-值对的本地聚合。 如果提供,Hadoop 会在将数据持久化到磁盘之前调用 Map 任务输出上的组合器函数,以调整 Reduce 任务。 这可以显著减少从 Map 任务转移到 Reduce 任务的数据量。 应该注意,组合器是 MapReduce 流的一个可选步骤。 即使您提供了组合器实现,Hadoop 也可能决定只为 Map 输出数据的子集调用它,或者根本不调用它。

本食谱解释了如何将组合器与前面食谱中介绍的 Wordcount MapReduce 应用结合使用。

怎么做……

现在,让我们向 Wordcount MapReduce 应用添加一个组合器:

  1. 组合器必须与reduce函数具有相同的接口。 组合器发出的输出键-值对类型应该与 Reducer 输入键-值对的类型匹配。 对于 wordcount 示例,我们可以重用 wordcountreduce函数作为组合器,因为 wordcount reduce函数的输入和输出数据类型是相同的。

  2. 取消注释WordCount.java文件中的以下行,以启用 WordCount 应用的组合器:

    job.setCombinerClass(IntSumReducer.class);
    
  3. 通过重新运行 Gradle(gradle build)或 Ant 构建(ant compile)重新编译代码。

  4. 使用以下命令运行 wordcount 示例。 在运行作业之前,请确保删除旧的输出目录(wc-output)。

    $ $HADOOP_HOME/bin/hadoop jar \
    hcb-c1-samples.jar \
    chapter1.WordCount wc-input wc-output
    
    
  5. 最终结果将从wc-output目录中获得。

它是如何工作的.

如果提供,Hadoop 会在将数据持久存储到磁盘上以转移到 Reduce 任务之前,调用 Map 任务输出上的组合器函数。 组合器可以在将映射器生成的数据发送到 Reducer 之前对其进行预处理,从而减少需要传输的数据量。

在 WordCount 应用中,组合器接收N(word,1)对作为输入,并输出单个(word, N)对。 例如,如果由 Map 任务处理的输入有 1,000 个单词“the”出现,映射器将生成 1,000 个(the,1)对,而组合器将生成一个(the,1000)对,从而减少需要传输到 Reduce 任务的数据量。 下图显示了 Wordcount MapReduce 应用中合并器的用法:

How it works...

还有更多...

仅当reduce函数输入和输出键值数据类型相同时,才能将作业的reduce函数用作组合器。 在不能重用reduce函数作为组合器的情况下,可以编写专用的reduce函数实现来充当组合器。 组合器输入和输出键-值对类型应该与映射器输出键-值对类型相同。

我们重申,组合器是 MapReduce 流的一个可选步骤。 即使您提供了组合器实现,Hadoop 也可能决定只为 Map 输出数据的子集调用它,或者根本不调用它。 注意不要使用组合器执行计算的任何基本任务,因为 Hadoop 不保证组合器的执行。

在非分布式模式下使用组合器不会产生显著的增益。 但是,在使用 Hadoop v2配方在分布式群集环境中设置 Hadoop Yarn 中所述的分布式设置中,组合器可以提供显著的性能提升。

设置 HDFS

HDFS 是一个块结构的分布式文件系统,设计用于在商用硬件组成的集群上可靠地存储 PB 级的数据。 HDFS 支持存储海量数据,并提供对数据的高吞吐量访问。 HDFS 通过冗余跨多个节点存储文件数据,以确保容错和高聚合带宽。

HDFS 是 Hadoop MapReduce 计算使用的默认分布式文件系统。 Hadoop 支持对存储在 HDFS 中的数据进行数据位置感知处理。 HDFS 体系结构主要由处理文件系统元数据的集中式 NameNode 和存储实际数据块的 DataNode 组成。 HDFS 数据块通常粒度较粗,在大型流读取时性能更好。

要设置 HDFS,我们首先需要配置 NameNode 和 DataNodes,然后在slaves文件中指定 DataNode。 当我们启动 NameNode 时,启动脚本将启动 DataNode。

提示

建议使用本食谱中提到的 Hadoop 版本构件直接安装 HDFS,仅用于开发测试和高级使用情形。 对于常规生产集群,我们建议使用打包的 Hadoop 发行版,如使用 Hadoop 发行版配方在分布式集群环境中设置 Hadoop 生态系统中所述。 打包的 Hadoop 发行版使安装、配置、维护和更新 Hadoop 生态系统的组件变得更加容易。

做好准备

您可以使用一台机器或多台机器遵循此食谱。 如果您使用多台计算机,则应选择一台计算机作为运行 HDFS NameNode 的主节点。 如果您使用的是单台计算机,请同时将其用作名称节点和数据节点。

  1. 在将用于设置 HDFS 集群的所有计算机上安装 JDK 1.6 或更高版本(首选 Oracle JDK 1.7)。 将JAVA_HOME环境变量设置为指向 Java 安装。
  2. 按照在本地计算机上设置 Hadoop v2 的方法下载 Hadoop。

怎么做……

现在,让我们在分布式模式下设置 HDFS:

  1. Set up password-less SSH from the master node, which will be running the NameNode, to the DataNodes. Check that you can log in to localhost and to all other nodes using SSH without a passphrase by running one of the following commands:

    $ ssh localhost
    $ ssh <IPaddress>
    
    

    提示

    配置无密码 SSH

    如果步骤 1 中的命令返回错误或要求输入密码,请通过执行以下命令创建 SSH 密钥(您可能需要事先手动启用 SSH,具体取决于您的操作系统):

    $ ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa
    
    

    ~/.ssh/id_dsa.pub文件移动到集群中的所有节点。 然后通过运行以下命令将 SSH 密钥添加到每个节点的~/.ssh/authorized_keys文件中(如果authorized_keys文件不存在,请运行以下命令。 否则,跳到cat命令):

    $ touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys
    
    

    现在,设置权限后,将您的密钥添加到~/.ssh/authorized_keys文件:

    $ cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys
    
    

    然后,您应该能够成功执行以下命令,而无需提供密码:

    $ ssh localhost
    
    
  2. 在每台服务器中,创建一个用于存储 HDFS 数据的目录。 让我们将该目录命名为{HADOOP_DATA_DIR}。 在数据目录内创建两个子目录{HADOOP_DATA_DIR}/data{HADOOP_DATA_DIR}/name。 通过对每个目录运行以下命令,将目录权限更改为755

    $ chmod –R 755 <HADOOP_DATA_DIR>
    
    
  3. 在 NameNode 中,将所有从节点的 IP 地址添加到{HADOOP_HOME}/etc/hadoop/slaves文件中,每个从节点都在单独的一行上。 当我们启动 NameNode 时,它将使用这个slaves文件来启动 DataNode。

  4. 将以下配置添加到{HADOOP_HOME}/etc/hadoop/core-site.xml。 在添加配置之前,请将{NAMENODE}字符串替换为主节点的 IP:

    <configuration>
      <property>
        <name>fs.defaultFS</name>
        <value>hdfs://{NAMENODE}:9000/</value>
      </property>
    </configuration>
    
  5. 将以下配置添加到{HADOOP_HOME}/etc/hadoop目录下的{HADOOP_HOME}/etc/hadoop/hdfs-site.xml文件中。 在添加配置之前,请将{HADOOP_DATA_DIR}替换为您在第一步中创建的目录。 将我们在步骤 4 和 5 中修改的core-site.xmlhdfs-site.xml文件复制到所有节点。

    <configuration>
      <property>
        <name>dfs.namenode.name.dir</name>
        <!-- Path to store namespace and transaction logs -->
        <value>{HADOOP_DATA_DIR}/name</value>
      </property>
      <property>
        <name>dfs.datanode.data.dir</name>
        <!-- Path to store data blocks in datanode -->
        <value>{HADOOP_DATA_DIR}/data</value>
      </property>
    </configuration>
    
  6. From the NameNode, run the following command to format a new filesystem:

    $ $HADOOP_HOME/bin/hdfs namenode –format
    
    

    成功完成上一个命令后,您将在输出中看到以下行:

    …
    13/04/09 08:44:51 INFO common.Storage: Storage directory /…/dfs/name has been successfully formatted.
    ….
    
  7. Start the HDFS using the following command:

    $ $HADOOP_HOME/sbin/start-dfs.sh
    
    

    此命令将首先在主节点中启动 NameNode。 然后,它将在slaves文件中提到的计算机中启动 DataNode 服务。 最后,它将启动辅助 NameNode。

  8. HDFS 附带一个监控 Web 控制台,用于验证安装和监控 HDFS 群集。 它还允许用户浏览 HDFS 文件系统的内容。 可以从http://{NAMENODE}:50070/访问 HDFS 监控控制台。 访问监控控制台,确认是否可以看到 HDFS 启动页面。 在这里,将{NAMENODE}替换为运行 HDFS NameNode 的节点的 IP 地址。

  9. 或者,您可以使用以下命令获取有关 HDFS 状态的报告:

    $ $HADOOP_HOME/bin/hadoop dfsadmin -report
    
    
  10. 最后,使用以下命令关闭 HDFS 集群:

```scala
$ $HADOOP_HOME/sbin/stop-dfs.sh

```

另请参阅

  • HDFS 命令行文件操作配方中,我们将探索如何使用 HDFS 存储和管理文件。
  • HDFS 设置只是 Hadoop 安装的一部分。 使用 Hadoop v2在分布式集群环境中设置 Hadoop Yarn 食谱介绍了如何设置 Hadoop 的其余部分。
  • 使用 Hadoop 发行版在分布式集群环境中设置 Hadoop 生态系统食谱探索了如何使用打包的 Hadoop 发行版在集群中安装 Hadoop 生态系统。

使用 Hadoop v2 在分布式群集环境中设置 Hadoop Yarn

Hadoop v2 Yarn 部署包括在主节点上部署 ResourceManager 服务和在从节点上部署 NodeManager 服务。 Yar ResourceManager 是对集群的所有资源进行仲裁的服务,NodeManager 是管理单个节点中的资源的服务。

Hadoop MapReduce 应用可以在 Yarn 上运行,使用一个 Yarn ApplicationMaster 来协调每个作业和一组资源容器来运行 Map 和 Reduce 任务。

提示

建议使用本食谱中提到的 Hadoop 版本构件直接安装 Hadoop,仅用于开发测试和高级用例。 对于常规生产集群,我们建议使用打包的 Hadoop 发行版,如使用 Hadoop 发行版配方在分布式集群环境中设置 Hadoop 生态系统中所述。 打包的 Hadoop 发行版使安装、配置、维护和更新 Hadoop 生态系统的组件变得更加容易。

做好准备

您可以使用台机器作为伪分布式安装,也可以使用多台机器集群来遵循这个配方。 如果您使用多台计算机,则应该选择一台计算机作为运行 HDFS NameNode 和 Yar ResourceManager 的主节点。 如果您使用的是单台计算机,请同时将其用作主节点和从节点。

按照设置 HDFS配方设置 HDFS。

怎么做……

让我们通过设置 Yarn 资源管理器和节点管理器来设置 Hadoop Yarn。

  1. 在每台机器上,创建一个名为 local inside{HADOOP_DATA_DIR}, which的目录,该目录是您在设置 HDFS配方中创建的。 将目录权限更改为755

  2. 将以下内容添加到{HADOOP_HOME}/etc/hadoop/mapred-site.xml模板中,并将其另存为{HADOOP_HOME}/etc/hadoop/mapred-site.xml

    <property>
      <name>fs.default.name</name>
      <value>hdfs://localhost:9000</value>
    </property>
    
  3. 将以下内容添加到{HADOOP_HOME}/etc/hadoop/yarn-site.xml文件:

    <property>
      <name>yarn.nodemanager.aux-services</name>
      <value>mapreduce_shuffle</value>
    </property>
    <property>
      <name>yarn.nodemanager.aux-services.mapreduce_shuffle.class</name>
      <value>org.apache.hadoop.mapred.ShuffleHandler</value>
    </property>
    
  4. 使用以下命令启动 HDFS:

    $ $HADOOP_HOME/sbin/start-dfs.sh
    
    
  5. 运行以下命令以启动 Yarn 服务:

    $ $HADOOP_HOME/sbin/start-yarn.sh
    starting yarn daemons
    starting resourcemanager, logging to ………
    xxx.xx.xxx.xxx: starting nodemanager, logging to ………
    
    
  6. 运行以下命令以启动 MapReduce JobHistoryServer。 这将启用用于 MapReduce 作业历史记录的 Web 控制台:

    $ $HADOOP_HOME/sbin/mr-jobhistory-daemon.sh start historyserver
    
    
  7. 通过jps命令列出进程来验证安装。 主节点将列出 NameNode、ResourceManager 和 JobHistoryServer 服务。 从节点将列出 DataNode 和 NodeManager 服务:

    $ jps
    27084 NameNode
    2073 JobHistoryServer
    2106 Jps
    2588
    1536 ResourceManager
    
    
  8. 请访问http://{MASTER_NODE}:8088/上提供的 ResourceManager 的基于 Web 的监控页面。

它是如何工作的.

如本章简介中所述,Hadoopv2 安装由 HDFS 节点、Yanth ResourceManager 和 Worker 节点组成。 当我们启动 NameNode 时,它通过HADOOP_HOME/slaves文件查找从节点,并在启动时使用 SSH 启动远程服务器中的 DataNode。 此外,当我们启动 ResourceManager 时,它会通过HADOOP_HOME/slaves文件查找从属对象并启动 NodeManagers。

另请参阅

使用 Hadoop 发行版在分布式集群环境中设置 Hadoop 生态系统食谱探索了如何使用打包的 Hadoop 发行版在集群中安装 Hadoop 生态系统。

使用 Hadoop 发行版在分布式群集环境中设置 Hadoop 生态系统

HadoopYarn 生态系统现在包含许多有用的组件,为存储在 HDFS 中的数据提供广泛的数据处理、存储和查询功能。 然而,使用单独的版本构件手动安装和配置所有这些组件以正确地协同工作是一项相当具有挑战性的任务。 这种方法的其他挑战包括监控和维护集群和多个 Hadoop 组件。

幸运的是,有几家商业软件供应商提供了集成良好的打包 Hadoop 发行版,使得在我们的集群中配置和维护 Hadoop Yarn 生态系统变得更加容易。 这些发行版通常带有简单的基于 GUI 的安装程序,可以引导您完成整个安装过程,并允许您选择和安装 Hadoop 集群中所需的组件。 它们还提供工具来轻松监控群集和执行维护操作。 对于常规生产集群,我们建议使用知名供应商提供的打包 Hadoop 发行版,使您的 Hadoop 之旅更加轻松。 其中一些商业 Hadoop 发行版(或发行版)拥有许可,允许我们通过可选的付费支持协议免费使用它们。

Hortonworks Data Platform(HDP)就是这样一种免费提供的众所周知的 Hadoop Yarn 分布。 HDP 的所有组件都是免费的开源软件。 您可以从http://hortonworks.com/hdp/downloads/下载 hdp。 有关安装说明,请参阅下载页面中提供的安装指南。

Cloudera CDH是另一个著名的 Hadoop Yarn 分布。 专用宿主机速成版免费提供。 Cloudera 发行版的一些组件是专有的,只对付费客户可用。 您可以从http://www.cloudera.com/content/cloudera/en/products-and-services/cloudera-express.html下载 Cloudera Express。 有关安装说明,请参阅下载页面上提供的安装指南。

Hortonworks HDP、Cloudera CDH 和其他一些供应商提供完全配置的快速入门虚拟机映像,您可以使用虚拟化软件产品下载这些映像并在本地计算机上运行。 在决定集群的 Hadoop 发行版之前,这些虚拟机是学习和尝试不同 Hadoop 组件以及进行评估的极佳资源。

Apachebigtop 是一个开源项目,旨在为各种 Hadoop 生态系统组件提供打包和集成/互操作性测试。 Bigtop 还提供了厂商中立的打包 Hadoop 发行版。 虽然它不像商业发行版那么复杂,但 Bigtop 比使用每个 Hadoop 组件的二进制发行版更容易安装和维护。 在本食谱中,我们提供了使用 Apache bigtop 在本地计算机上安装 Hadoop 生态系统的步骤。

前面提到的任何发行版本(包括 bigtop)都适用于遵循本书中提供的食谱和执行示例的目的。 但是,如果可能,我们建议使用 Hortonworks HDP、Cloudera CDH 或其他商业 Hadoop 发行版。

做好准备

本食谱提供了 CENT OS 和 Red Hat 操作系统的说明。 停止您在前面的食谱中启动的所有 Hadoop 服务。

怎么做……

以下步骤将指导您完成使用 Apache bigtop for Cent OS 和 Red Hat 操作系统的 Hadoop 集群的安装过程。 请将这些命令相应地修改为适用于其他基于 Linux 的操作系统。

  1. 安装 Bigtop 存储库:

    $ sudo wget -O \
    /etc/yum.repos.d/bigtop.repo \ 
    http://www.apache.org/dist/bigtop/stable/repos/centos6/bigtop.repo
    
    
  2. 搜索 Hadoop:

    $ yum search hadoop
    
    
  3. 使用 YUM 安装 Hadoop v2。 这将安装 Hadoopv2 组件(MapReduce、HDFS 和 YAR)以及 ZooKeeper 依赖项。

    $ sudo yum install hadoop\*
    
    
  4. 使用您喜欢的编辑器将以下行添加到/etc/default/bigtop-utils文件。 建议将JAVA_HOME指向 JDK 1.6 或更高版本的安装(首选 Oracle JDK 1.7 或更高版本)。

    export JAVA_HOME=/usr/java/default/
    
  5. 初始化并格式化 NameNode:

    $ sudo  /etc/init.d/hadoop-hdfs-namenode init
    
    
  6. 启动 Hadoop NameNode 服务:

    $ sudo service hadoop-hdfs-namenode start
    
    
  7. 启动 Hadoop DataNode 服务:

    $ sudo service hadoop-hdfs-datanode start
    
    
  8. 运行以下脚本以在 HDFS 中创建必要的目录:

    $ sudo  /usr/lib/hadoop/libexec/init-hdfs.sh
    
    
  9. 在 HDFS 中创建您的主目录并应用必要的权限:

    $ sudo su -s /bin/bash hdfs \
    -c "/usr/bin/hdfs dfs -mkdir /user/${USER}"
    $ sudo su -s /bin/bash hdfs \
    -c "/usr/bin/hdfs dfs -chmod -R 755 /user/${USER}"
    $ sudo su -s /bin/bash hdfs \
    -c "/usr/bin/hdfs dfs -chown ${USER} /user/${USER}"
    
    
  10. 启动 Yarn 资源管理器和节点管理器:

```scala
$ sudo service hadoop-yarn-resourcemanager start
$ sudo service hadoop-yarn-nodemanager start
$ sudo service hadoop-mapreduce-historyserver start

```
  1. 尝试使用以下命令验证安装:
```scala
$ hadoop fs -ls  /
$ hadoop jar \
/usr/lib/hadoop-mapreduce/hadoop-mapreduce-examples.jar \
pi 10 1000

```
  1. 您还可以使用http://<namenode_ip>:50070上提供的监控控制台监控 HDFS 的状态。
  2. 使用 Bigtop 安装配置单元、HBase、Mahout 和 Pig,如下所示:
```scala
$ sudo yum install hive\*, hbase\*, mahout\*, pig\*

```

还有更多...

HDFS 命令行文件操作

HDFS 是分布式文件系统,就像任何其他文件系统一样,它允许用户使用 shell 命令操作文件系统。 本食谱介绍了其中一些命令,并展示了如何使用 HDFS shell 命令。

值得注意的是,一些 HDFS 命令与最常用的 Unix 命令具有一一对应关系。 例如,考虑以下命令:

$ bin/hdfs dfs –cat /user/joe/foo.txt

该命令读取/user/joe/foo.txt文件并将其打印到屏幕上,就像 Unix 系统中的cat命令一样。

做好准备

按照设置 HDFS食谱或使用 Hadoop 分发版食谱在分布式集群环境中设置 Hadoop 生态系统来启动 HDFS 服务器。

怎么做……

  1. 运行以下命令以列出 HDFS 主目录的内容。 如果您的 HDFS 主目录不存在,请按照中的步骤 9 使用 Hadoop 分发版在分布式群集环境中设置 Hadoop 生态系统来创建 HDFS 主目录。

    $ hdfs dfs -ls
    
    
  2. 运行以下命令,在 HDFS 的主目录中创建名为test的新目录:

    $ hdfs dfs -mkdir test
    
    
  3. HDFS 文件系统以/作为根目录。 运行以下命令以列出 HDFS 中新创建的目录的内容:

    $ hdfs dfs -ls test
    
    
  4. 运行以下命令将本地自述文件复制到test

    $ hdfs dfs -copyFromLocal README.txt test
    
    
  5. 运行以下命令以列出test目录:

    $ hdfs dfs -ls test
    Found 1 items
    -rw-r--r--   1 joesupergroup1366 2013-12-05 07:06 /user/joe/test/README.txt
    
    
  6. 运行以下命令将/test/README.txt文件复制回本地目录:

    $ hdfs dfs –copyToLocal \
    test/README.txt README-NEW.txt
    
    

它是如何工作的.

当命令发出时,HDFS 客户端将代表我们与 HDFS NameNode 对话并执行操作。 客户端将从HADOOP_HOME/etc/hadoop/conf目录中的配置中获取 NameNode。

但是,如果需要,我们可以使用完全限定路径强制客户端与特定的 NameNode 对话。 例如,hdfs://bar.foo.com:9000/data将要求客户端在端口9000处与在bar.foo.com上运行的 NameNode 对话。

还有更多...

HDFS 支持大多数 Unix 命令,如cpmvchown,它们遵循与前面讨论的命令相同的模式。 以下命令会列出所有可用的 HDFS shell 命令:

$ hdfs dfs -help

将特定命令与help一起使用将显示该命令的用法。

$ hdfs dfs –help du

在分布式集群环境中运行 wordcount 程序

本配方描述了如何在分布式 Hadoopv2 集群中运行 MapReduce 计算。

做好准备

按照设置 HDFS食谱或使用 Hadoop 分发版食谱在分布式集群环境中设置 Hadoop 生态系统来启动 Hadoop 群集。

怎么做……

现在,让我们在分布式 Hadoop v2 设置中运行 wordcount 示例:

  1. 将源存储库中的wc-input目录上载到 HDFS 文件系统。 或者,您也可以上传任何其他文本文档集。

    $ hdfs dfs -copyFromLocal wc-input .
    
    
  2. 执行HADOOP_HOME目录中的 WordCount 示例:

    $ hadoop jar hcb-c1-samples.jar \
    chapter1.WordCount \
    wc-input wc-output
    
    
  3. 运行以下命令以列出输出目录,然后查看结果:

    $hdfs dfs -ls wc-output
    Found 3 items
    -rw-r--r--   1 joesupergroup0 2013-11-09 09:04 /data/output1/_SUCCESS
    drwxr-xr-x   - joesupergroup0 2013-11-09 09:04 /data/output1/_logs
    -rw-r--r--   1 joesupergroup1306 2013-11-09 09:04 /data/output1/part-r-00000
    
    $ hdfs dfs -cat wc-output/part*
    
    

它是如何工作的.

当我们提交作业时,Yar 会安排一个 MapReduce ApplicationMaster 来协调和执行计算。 ApplicationMaster 从 ResourceManager 请求必要的资源,并使用从资源请求接收的容器执行 MapReduce 计算。

还有更多...

您还可以通过 HDFS 监控 UI 访问http://NAMANODE:50070来查看 WordCount 应用的结果。

使用 DFSIO 对 HDFS 进行基准测试

Hadoop 包含几个基准测试,您可以使用它们来验证 HDFS 集群是否设置正确并按预期执行。 DFSIO 是 Hadoop 附带的基准测试,可用于分析 HDFS 集群的 I/O 性能。 本食谱展示了如何使用 DFSIO 对 HDFS 群集的读/写性能进行基准测试。

做好准备

在运行这些基准测试之前,您必须设置和部署 HDFS 和 Hadoop v2 Yarn MapReduce。 在 Hadoop 安装中找到hadoop-mapreduce-client-jobclient-*-tests.jar文件。

怎么做……

以下步骤将向您展示如何运行写入和读取 DFSIO 性能基准:

  1. 执行以下命令以运行 HDFS 写入性能基准。 –nrFiles参数指定基准要写入的文件数。 使用一个足够大的数字来饱和群集中的任务槽。 -fileSize参数指定每个文件的文件大小(以 MB 为单位)。 根据您的 Hadoop 安装,在以下命令中更改hadoop-mapreduce-client-jobclient-*-tests.jar文件的位置。

    $ hadoop jar \
    $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-*-tests.jar \
    TestDFSIO -write -nrFiles 32 –fileSize 1000
    
    
  2. 写入基准测试将结果写入控制台,并将其附加到名为TestDFSIO_results.log的文件中。 您可以使用–resFile参数提供您自己的结果文件名。

  3. 以下步骤将向您展示如何运行 HDFS 读取性能基准测试。 读性能基准使用第一步中写基准写入的文件,因此在运行读基准之前应该先执行写基准,并且写基准写入的文件必须在 HDFS 中存在,读基准才能正常工作。 基准测试将结果写入控制台,并将结果附加到日志文件,这与写入基准测试类似。

    $hadoop jar \
    $HADOOP_HOME/share/Hadoop/mapreduce/hadoop-mapreduce-client-jobclient-*-tests.jar \
    TestDFSIO -read \
    -nrFiles 32 –fileSize 1000
    
    
  4. 可以使用以下命令清理由上述基准测试生成的文件:

    $hadoop jar \
    $HADOOP_HOME/share/Hadoop/mapreduce/hadoop-mapreduce-client-jobclient-*-tests.jar \
    TestDFSIO -clean
    
    

它是如何工作的.

DFSIO 执行 MapReduce 作业,其中 Map 任务并行写入和读取文件,而 Reduce 任务用于收集和汇总性能数字。 您可以将此基准测试的吞吐量和 IO 速率结果与磁盘总数及其原始速度进行比较,以验证您是否从群集获得了预期的性能。 验证写入性能结果时,请注意复制因素。 由于某些原因,这些测试中的高标准偏差可能暗示有一个或多个表现不佳的节点。

还有更多...

将这些测试与监控系统一起运行可以帮助您轻松识别 Hadoop 集群的瓶颈。

使用 TeraSort 对 Hadoop MapReduce 进行基准测试

HadoopTeraSort 是一个众所周知的基准测试,旨在使用 Hadoop MapReduce 尽可能快地对 1TB 数据进行排序。 TeraSort 基准测试强调了 Hadoop MapReduce 框架的几乎每个部分以及 HDFS 文件系统,这使得它成为微调 Hadoop 集群配置的理想选择。

最初的 TeraSort 基准对 1000 万条 100 字节的记录进行排序,总数据大小为 1TB。 但是,我们可以指定记录的数量,从而可以配置数据的总大小。

做好准备

在运行这些基准测试之前,您必须设置和部署 HDFS 和 Hadoop v2 Yarn MapReduce,并在 Hadoop 安装中找到hadoop-mapreduce-examples-*.jar文件。

怎么做……

以下步骤将向您展示如何在 Hadoop 群集上运行 TeraSort 基准测试:

  1. The first step of the TeraSort benchmark is the data generation. You can use the teragen command to generate the input data for the TeraSort benchmark. The first parameter of teragen is the number of records and the second parameter is the HDFS directory to generate the data. The following command generates 1 GB of data consisting of 10 million records to the tera-in directory in HDFS. Change the location of the hadoop-mapreduce-examples-*.jar file in the following commands according to your Hadoop installation:

    $ hadoop jar \
    $HADOOP_HOME/share/Hadoop/mapreduce/hadoop-mapreduce-examples-*.jar \
    teragen 10000000 tera-in
    
    

    提示

    teragen计算指定 Map 任务的数量是一个好主意,以加快数据生成速度。 这可以通过指定–Dmapred.map.tasks参数来实现。

    此外,您还可以增加生成数据的 HDFS 块大小,以便 TeraSort 计算的 Map 任务的粒度更粗(Hadoop 计算的 Map 任务的数量通常等于输入数据块的数量)。 这可以通过指定–Ddfs.block.size参数来实现。

    $ hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar \
    teragen –Ddfs.block.size=536870912 \
    –Dmapred.map.tasks=256 10000000 tera-in
    
    
  2. The second step of the TeraSort benchmark is the execution of the TeraSort MapReduce computation on the data generated in step 1 using the following command. The first parameter of the terasort command is the input of HDFS data directory, and the second part of the terasort command is the output of the HDFS data directory.

    $ hadoop jar \
    $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar \
    terasort tera-in tera-out
    
    

    提示

    最好为 TeraSort 计算指定 Reduce 任务的数量,以加快计算的 Reducer 部分。 这可以通过指定–Dmapred.reduce.tasks参数来实现,如下所示:

    $ hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar terasort –Dmapred.reduce.tasks=32 tera-in tera-out
    
    
  3. TeraSort 基准测试的最后一步是验证结果。 这可以通过使用teravalidate应用来完成,如下所示。 第一个参数是包含排序数据的目录,第二个参数是存储包含结果的报告的目录。

    $ hadoop jar \
    $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar \
    teravalidate tera-out tera-validate
    
    

它是如何工作的.

TeraSort 使用 MapReduce 框架的排序功能和自定义范围分割器在 Reduce 任务之间划分 Map 输出,以确保全局排序顺序。*

二、云部署——在云环境中使用 Hadoop Yarn

在本章中,我们将介绍以下食谱:

  • 使用 Amazon Elastic MapReduce 运行 Hadoop MapReduce v2 计算
  • 使用 Amazon EC2 Spot 实例执行 EMR 作业流节省资金
  • 使用 EMR 执行 Pig 脚本
  • 使用 EMR 执行配置单元脚本
  • 使用 AWS 命令行界面创建 Amazon EMR 作业流
  • 使用 EMR 在 Amazon EC2 上部署 Apache HBase 群集
  • 使用 EMR 引导操作为 Amazon EMR 作业配置虚拟机
  • 使用 Apache Whirr 在 EC2 环境中部署 Apache Hadoop 群集

简介

在本章中,我们将探索在云环境中部署和执行 Hadoop MapReduce v2 和其他 Hadoop 相关计算的几种机制。

像 Amazon EC2 和 Microsoft Azure 这样的云计算环境通过 Web 以服务的形式提供按需计算和存储资源。 这些云计算环境使我们能够在不需要前期资本投资的情况下执行偶尔的大规模 Hadoop 计算,并且只需要为实际的使用付费。 使用云环境的另一个优势是能够以最小的额外成本水平扩展计算资源的数量,从而提高 Hadoop 计算的吞吐量。 例如,使用 10 个云实例 100 小时的成本相当于使用 100 个云实例 10 小时的成本。 除了存储、计算和托管 MapReduce 服务之外,这些云环境还提供许多其他分布式计算服务,在实现整个应用体系结构时,您可能会发现这些服务很有用。

虽然云环境比传统的同类环境具有许多优势,但由于基础设施的虚拟化、多租户特性,它们也带来了几个独特的可靠性和性能挑战。 对于数据密集型 Hadoop 计算,主要挑战之一将是将大型数据集传入和传出云环境。 我们还需要确保使用永久存储介质来存储您需要保存的任何数据。 云实例的临时实例存储中存储的任何数据都将在这些实例终止时丢失。

与 Microsoft Azure 云等其他商业云产品相比,由于 Linux 实例支持的成熟度和托管 Hadoop 服务的成熟度,我们将主要使用 Amazon AWS 云作为本章的食谱。

本章将指导您使用托管 Hadoop 基础设施 AmazonElastic MapReduce(EMR)在 Amazon EC2 基础设施上执行传统的 MapReduce 计算以及 PIG 和 Hive 计算。 本章还介绍了如何使用 Amazon EMR 调配 HBase 群集,以及如何备份和恢复 EMR HBase 群集的数据。 我们还将使用 Apache Whirr,一个用于在云环境中部署服务的云中立库,在云环境中配置 Apache Hadoop 和 Apache HBase 集群。

提示

示例代码

本书的示例代码文件位于https://github.com/thilg/hcb-v2存储库中。 代码库的chapter2文件夹包含本章的示例源代码文件。

使用 Amazon Elastic MapReduce 运行 Hadoop MapReduce v2 计算

Amazon Elastic MapReduce(EMR)在 Amazon Web Services(AWS)云中提供按需管理的 Hadoop 群集来执行 Hadoop MapReduce 计算。 EMR 使用 AmazonElastic Compute Cloud(EC2)实例作为计算资源。 EMR 支持从 AmazonSimple Storage Service(S3)读取输入数据,并将输出数据存储在 Amazon S3 中。 EMR 负责云实例的配置、Hadoop 集群的配置以及 MapReduce 计算流的执行。

在本配方中,我们将使用 Amazon Elastic MapReduce 服务在 Amazon EC2 中执行 wordcount MapReduce 示例(编写 Wordcount MapReduce 应用,将其捆绑,并使用第 1 章Hadoop v2中的 Hadoop 本地模式配方运行)。

做好准备

通过运行示例代码存储库的chapter1文件夹中的 Gradle 构建来构建hcb-c1-samples.jar文件。

怎么做……

以下是在 Amazon Elastic MapReduce 上执行 Wordcount MapReduce 应用的步骤:

  1. 通过访问http://aws.amazon.com注册。

  2. https://console.aws.amazon.com/s3打开 Amazon S3 监控控制台并登录。

  3. 通过单击创建存储桶,创建一个S3 存储桶以上传输入数据。 为您的存储桶提供唯一的名称。 让我们假设存储桶的名称是wc-input-data。 您可以在http://docs.amazonwebservices.com/AmazonS3/latest/gsg/CreatingABucket.html找到有关创建 S3 存储桶的更多信息。 亚马逊 S3 还存在多个第三方桌面客户端。 您也可以使用其中一个客户端来管理 S3 中的数据。

  4. Upload your input data to the bucket we just created by selecting the bucket and clicking on Upload. The input data for the WordCount sample should be one or more text files:

    How to do it...

  5. 创建一个 S3 存储桶来上传我们的 MapReduce 计算所需的 JAR 文件。 让我们假设存储桶的名称为sample-jars。 将hcb-c1-samples.jar上传到新创建的存储桶中。

  6. Create an S3 bucket to store the output data of the computation. Let's assume the name of this bucket as wc-output-data. Create another S3 bucket to store the logs of the computation. Let's assume the name of this bucket is hcb-c2-logs.

    备注

    请注意,所有 S3 用户共享 S3 存储桶命名空间。 因此,使用本食谱中给出的示例存储桶名称可能对您不起作用。 在这种情况下,您应该为存储桶指定您自己的自定义名称,并在本食谱的后续步骤中替换这些名称。

  7. https://console.aws.amazon.com/elasticmapreduce打开亚马逊电子邮件记录控制台。 单击Create Cluster按钮创建新的电子病历群集。 为您的群集提供名称。

  8. In the Log folder S3 location textbox, enter the path of the S3 bucket you created earlier to store the logs. Select the Enabled radio button for Debugging.

    How to do it...

  9. 软件配置部分中选择 Hadoop 发行版和版本。 选择带有 Amazon Hadoop 发行版的 AMI 版本 3.0.3 或更高版本以部署 Hadoop v2 群集。 保留要安装的应用部分中的默认选定应用(Hive、PIG 和色调)。

  10. Select the EC2 instance types, instance counts, and the availability zone in the Hardware Configuration section. The default options use two EC2 m1.large instances for the Hadoop slave nodes and one EC2 m1.large instance for the Hadoop Master node.

![How to do it...](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/hadoop-mapreduce-v2-cb/img/5471OS_02_03.jpg)
  1. 保留Security and AccessBootstrap Actions部分中的默认选项。
  2. Select the Custom Jar option under the Add Step dropdown of the Steps section. Click on Configure and add to configure the JAR file for our computation. Specify the S3 location of hcb-c1-samples.jar in the Jar S3 location textbox. You should specify the location of the JAR in the format s3n://bucket_name/jar_name. In the Arguments textbox, type chapter1.WordCount followed by the bucket location where you uploaded the input data in step 4 and the output data bucket you created in step 6. The output path should not exist and we use a directory (for example, wc-output-data/out1) inside the output bucket you created in step 6 as the output path. You should specify the locations using the format, s3n://bucket_name/path.
![How to do it...](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/hadoop-mapreduce-v2-cb/img/5471OS_02_04.jpg)
  1. Click on Create Cluster to launch the EMR Hadoop cluster and run the WordCount application.
### 备注

在第 13 步中单击**创建群集**时,Amazon 将为您使用的计算和存储资源收费。请参阅*使用 Amazon EC2 Spot 实例执行 EMR 作业流*食谱,了解如何通过使用 Amazon EC2 Spot 实例来节省资金。

请注意,AWS 按小时向您收费,任何部分使用都将按小时计费。 实例的每次启动和停止都将按小时计费,即使只需要几分钟。 为进行测试而频繁重新启动群集时,请注意相关费用。
  1. Monitor the progress of your MapReduce cluster deployment and the computation in the Cluster List | Cluster Details page of the Elastic MapReduce console. Expand the Steps section of the page to see the status of the individual steps of the cluster setup and the application execution. Select a step and click on View logs to view the logs and to debug the computation. Since EMR uploads the logfiles periodically, you might have to wait and refresh to access the logfiles. Check the output of the computation in the output data bucket using the AWS S3 console.
![How to do it...](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/hadoop-mapreduce-v2-cb/img/5471OS_02_05.jpg)
  1. 终止您的集群以避免为剩余实例计费。 但是,您可以让集群继续运行,以尝试本章中的其他食谱。

另请参阅

  • 编写字数计数 MapReduce 应用,将其捆绑在一起,并使用第 1 章《Hadoop v2 入门》中的 Hadoop 本地模式配方运行它
  • 在分布式群集环境中运行 wordcount 程序的Hadoop v2快速入门中的中的秘诀

使用 Amazon EC2 Spot 实例执行 EMR 作业流可节省资金

AmazonEC2 Spot 实例允许我们以的大幅折扣购买未充分利用的 EC2 计算资源。 Spot 实例的价格根据需求而变化。 我们可以为 Spot 实例提交投标,如果我们的出价超过当前 Spot 实例价格,我们将收到所请求的计算实例。 亚马逊根据 Spot 实例的实际价格向这些实例收费,该价格可能低于您的出价。 如果 Spot 实例价格超过您的出价,亚马逊将终止您的实例。 但是,如果亚马逊终止您的实例,亚马逊不会按部分 Spot 实例小时数收费。 您可以在http://aws.amazon.com/ec2/spot-instances/上找到有关 AmazonEC2 Spot 实例的更多信息。

Amazon EMR 支持将 Spot 实例用作主实例和辅助计算实例。 Spot 实例是执行批处理作业等非时间关键型计算的理想选择。

怎么做……

以下步骤说明如何将 Amazon EC2 Spot 实例与 Amazon Elastic MapReduce 配合使用,以执行 Wordcount MapReduce 应用:

  1. 按照使用 Amazon Elastic MapReduce 配方运行 Hadoop MapReduce v2 计算的中的步骤 1 到 9 操作。

  2. 硬件配置部分中配置 EMR 群集以使用 Spot 实例。 (请参阅使用 Amazon Elastic MapReduce配方运行 Hadoop MapReduce v2 计算的步骤 10)。 在Hardware Configuration部分中,选中实例类型旁边的Request Spot复选框。

  3. Specify your bid price in the Bid price textboxes. You can find the Spot Instance pricing history in the Spot Requests window of the Amazon EC2 console (https://console.aws.amazon.com/ec2).

    How to do it...

  4. 按照使用 Amazon Elastic MapReduce 配方运行 Hadoop MapReduce v2 计算的步骤 11 到 16 进行操作。

还有更多...

您还可以在传统 EC2 按需实例和 EC2 Spot 实例的组合上运行 EMR 计算,从而安全地保护您的计算免受 Spot 实例可能终止的影响。

由于 Amazon 使用当前 Spot 价格对 Spot 实例进行计费,而不考虑您的出价,因此最好不要将 Spot 实例价格设置得太低,以避免频繁终止的风险。

另请参阅

运行 Hadoop MapReduce v2 计算的使用 Amazon Elastic MapReduce配方。

使用 EMR 执行 Pig 脚本

AmazonEMR 支持对存储在 S3 中的数据执行 Apache Pig 脚本。 有关使用 Apache Pig 进行数据分析的更多详细信息,请参阅第 7 章Hadoop 生态系统 II 中的Pig 相关食谱-Pig、HBase、Mahout 和 Sqoop

*在本食谱中,我们将使用 Amazon EMR 执行一个简单的 Pig 脚本。 此示例将使用人类发展报告数据(http://hdr.undp.org/en/statistics/data/)打印按 GNI 排序的人均国民总收入大于 2,000 美元的国家名称。

怎么做……

以下步骤向您展示了如何将 Pig 脚本与 Amazon Elastic MapReduce 一起使用来处理存储在 Amazon S3 上的数据集:

  1. 使用 Amazon S3 控制台在 S3 中创建一个存储桶来上传输入数据。 将本章源存储库中的resources/hdi-data.csv文件上传到新创建的存储桶中。 您还可以使用现有存储桶或存储桶内的目录。 我们假设上载文件的 S3 路径为hcb-c2-data/hdi-data.csv

  2. 查看本章源代码存储库的resources/countryFilter-EMR.pig 文件中提供的 Pig 脚本。 此脚本使用STORE命令将结果保存在文件系统中。 此外,我们通过添加$INPUT作为输入文件来参数化 Pig 脚本的LOAD命令,并通过添加$OUTPUT作为输出目录来参数化 store 命令。 这两个参数将替换为我们在步骤 5 中指定的 S3 输入和输出位置。

    A = LOAD '$INPUT' using PigStorage(',')  AS 
    (id:int, country:chararray, hdi:float, lifeex:int,
    mysch:int, eysch:int, gni:int);
    B = FILTER A BY gni > 2000;
    C = ORDER B BY gni;
    STORE C into '$OUTPUT';
    
  3. 使用 Amazon S3 控制台在 S3 中创建存储桶以上传 Pig 脚本。 将resources/countryFilter-EMR.pig脚本上传到新创建的存储桶中。 您还可以使用现有存储桶或存储桶内的目录。 我们假设上载文件的 S3 路径为hcb-c2-resources/countryFilter-EMR.pig

  4. Open the Amazon EMR console at https://console.aws.amazon.com/elasticmapreduce. Click on the Create Cluster button to create a new EMR cluster. Provide a name for your cluster. Follow steps 8 to 11 of the Running Hadoop MapReduce v2 computations using Amazon Elastic MapReduce recipe to configure your cluster.

    备注

    您可以使用 Amazon Elastic MapReduce 配方重用在运行 Hadoop MapReduce v2 计算的中创建的 EMR 集群,以遵循本配方的步骤。 为此,请使用正在运行的集群的Cluster Details页面中的Add Step选项来执行步骤 5 中提到的操作。

  5. Select the Pig Program option under the Add Step dropdown of the Steps section. Click on Configure and add to configure the Pig script, input, and output data for our computation. Specify the S3 location of the Pig script we uploaded in step 3, in the Script S3 location textbox. You should specify the location of the script in the format s3://bucket_name/script_filename. Specify the S3 location of the uploaded input data file in the Input S3 Location textbox. In the Output S3 Location textbox, specify an S3 location to store the output. The output path should not exist; we use a non-existing directory (for example, hcb-c2-out/pig) inside the output bucket as the output path. You should specify the locations using the format s3://bucket_name/path. Click on Add.

    How to do it...

  6. Click on Create Cluster to launch the EMR Hadoop cluster and to run the configured Pig script.

    备注

    Amazon 将在第 8 步中单击Create Job Flow,对您使用的计算和存储资源收费。请参阅我们之前讨论的使用 EC2 Spot 实例执行 EMR 作业流食谱,了解如何通过使用 Amazon EC2 Spot 实例来节省资金。

  7. 在 Elastic MapReduce 控制台的群集列表|群集详细信息页面中监视 MapReduce 群集部署和计算的进度。 展开并刷新页面的步骤部分,查看集群设置和应用执行的各个步骤的状态。 选择一个步骤并单击查看日志以查看日志并调试计算。 使用 AWS S3 控制台检查输出数据桶中的计算输出。

还有更多...

Amazon EMR 还允许我们在交互模式下使用 Apache Pig。

启动 PIG 互动会话

  1. https://console.aws.amazon.com/elasticmapreduce打开亚马逊电子邮件记录控制台。 单击Create Cluster按钮创建新的电子病历群集。 为您的群集提供名称。

  2. 您必须从安全和访问部分的Amazon EC2 密钥对下拉列表中选择密钥对。 如果您没有可用的 Amazon EC2 密钥对访问私钥,请登录 Amazon EC2 控制台并创建新的密钥对。

  3. 在不指定任何步骤的情况下,单击创建集群。 确保在步骤部分下的自动终止选项中选择了

  4. Monitor the progress of your MapReduce cluster deployment and the computation in the Cluster Details page under Cluster List of the Elastic MapReduce console. Retrieve Master Public DNS from the cluster details in this page.

    Starting a Pig interactive session

  5. 使用步骤 2 中指定的 Amazon EC2 密钥对的主公共 DNS 名称和私钥文件通过 SSH 连接到集群的主节点:

    $ ssh -i <path-to-the-key-file> hadoop@<master-public-DNS>
    
    
  6. 在主节点中启动 Pig 交互式 Grunt shell 并发出您的 Pig 命令:

    $ pig
    .........
    grunt>
    
    

使用 EMR 执行配置单元脚本

HIVE 利用其下的 Hadoop MapReduce 为 HDFS 中存储的数据提供了类似 SQL 的查询层。 Amazon EMR 支持对存储在 S3 中的数据执行配置单元查询。 有关使用配置单元进行大规模数据分析的更多信息,请参阅Hadoop 生态系统-Apache 配置单元中的 Apache Have 配方。

在这个配方中,我们将执行一个配置单元脚本,以执行前面使用 EMR 配方在Executing a Pig 脚本中执行的计算。 我们将使用“人类发展报告”数据(http://hdr.undp.org/en/statistics/data/)打印按国民总收入排序的国民总收入大于人均国民总收入 2,000 美元的国家的名称。

怎么做……

以下步骤显示如何将配置单元脚本与 Amazon Elastic MapReduce 配合使用,以查询存储在 Amazon S3 上的数据集:

  1. 使用 Amazon S3 控制台在 S3 中创建一个存储桶来上传输入数据。 在存储桶内创建一个目录。 将本章源包中的resources/hdi-data.csv文件上传到存储桶内新创建的目录中。 您还可以使用现有存储桶或存储桶内的目录。 我们假设上载文件的 S3 路径为hcb-c2-data/data/hdi-data.csv

  2. 查看本章来源资料库的resources/countryFilter-EMR.hql文件中提供的配置单元脚本。 该脚本首先创建输入数据到配置单元表的映射。 然后,我们创建一个配置单元表来存储查询结果。 最后,我们发出一个查询以选择国民总收入大于 2000 美元的国家/地区列表。 我们使用$INPUT$OUTPUT变量来指定输入数据的位置和存储输出表数据的位置。

    CREATE EXTERNAL TABLE 
    hdi(
        id INT, 
        country STRING, 
        hdi FLOAT, 
        lifeex INT, 
        mysch INT, 
        eysch INT, 
        gni INT) 
    ROW FORMAT DELIMITED 
    FIELDS TERMINATED BY ',' 
    STORED AS TEXTFILE
    LOCATION '${INPUT}';
    
    CREATE EXTERNAL TABLE 
    output_countries(
        country STRING, 
        gni INT) 
        ROW FORMAT DELIMITED
        FIELDS TERMINATED BY ','
        STORED AS TEXTFILE
        LOCATION '${OUTPUT}';
    
    INSERT OVERWRITE TABLE 
    output_countries
      SELECT 
        country, gni 
      FROM 
        hdi 
      WHERE 
        gni > 2000;
    
  3. 使用 Amazon S3 控制台在 S3 中创建存储桶以上传配置单元脚本。 将resources/countryFilter-EMR.hql脚本上传到新创建的存储桶中。 您还可以使用现有存储桶或存储桶内的目录。 我们假设上载文件的 S3 路径为hcb-resources/countryFilter-EMR.hql

  4. Open the Amazon EMR console at https://console.aws.amazon.com/elasticmapreduce. Click on the Create Cluster button to create a new EMR cluster. Provide a name for your cluster. Follow steps 8 to 11 of the Running Hadoop MapReduce v2 computations using Amazon Elastic MapReduce recipe to configure your cluster.

    备注

    您可以重用为前面的某个食谱创建的 EMR 集群,以遵循本食谱的步骤。 为此,请使用正在运行的集群的Cluster Details页面中的Add Step选项来执行步骤 5 中提到的操作。

  5. Select the Hive Program option under the Add Step dropdown of the Steps section. Click on Configure and add to configure the Hive script, and input and output data for our computation. Specify the S3 location of the Hive script we uploaded in step 3 in the Script S3 location textbox. You should specify the location of the script in the format s3://bucket_name/script_filename. Specify the S3 location of the uploaded input data directory in the Input S3 Location textbox. In the Output S3 Location textbox, specify an S3 location to store the output. The output path should not exist and we use a nonexisting directory (for example, hcb-c2-out/hive) inside the output bucket as the output path. You should specify the locations using the format s3://bucket_name/path. Click on Add.

    How to do it...

  6. Click on Create Cluster to launch the EMR Hadoop cluster and to run the configured Hive script.

    备注

    Amazon 将在第 8 步中单击Create Job Flow,对您使用的计算和存储资源收费。请参阅前面讨论的使用 Amazon EC2 Spot 实例执行 EMR 作业流来执行 EMR 作业流食谱,了解如何通过使用 Amazon EC2 Spot 实例来节省资金。

  7. 在 Elastic MapReduce 控制台的群集列表下的群集详细信息页面中监视 MapReduce 群集部署和计算的进度。 展开并刷新页面的步骤部分,查看集群设置和应用执行的各个步骤的状态。 选择一个步骤并单击查看日志以查看日志并调试计算。 使用 AWS S3 控制台检查输出数据桶中的计算输出。

还有更多...

Amazon EMR 还允许我们在交互模式下使用 Hive 外壳。

启动配置单元互动会话

按照前面的中的启动 Pig 交互式会话部分中的步骤 1 到 5,使用 EMR配方执行 Pig 脚本,创建一个集群并使用 SSH 登录到该集群。

在主节点中启动配置单元外壳,并发出您的配置单元查询:

$ hive
hive >
.........

另请参阅

第 6 章,Hadoop 生态系统简单 SQL 样式的数据查询使用 Apache Have配方-Apache Have。**

使用 AWS 命令行界面创建 Amazon EMR 作业流

AWS命令行界面(CLI)是一个允许我们从命令行管理我们的 AWS 服务的工具。 在本配方中,我们使用 AWS CLI 来管理 Amazon EMR 服务。

此配方使用 AWS CLI 创建一个 EMR 作业流,以执行本章使用 Amazon Elastic MapReduce 配方运行 Hadoop MapReduce 计算的中的 WordCount 示例。

做好准备

以下是开始使用本食谱的前提条件:

  • Python 2.6.3 或更高版本
  • PIP-Python 包管理系统

怎么做……

以下步骤显示了如何使用 EMR 命令行界面创建 EMR 作业流:

  1. Install AWS CLI in your machine using the pip installer:

    $ sudo pip install awscli
    
    

    备注

    有关安装 AWS CLI 的更多信息,请参阅http://docs.aws.amazon.com/cli/latest/userguide/installing.html。 本指南提供了在不使用sudo的情况下安装 AWS CLI 的说明,以及使用其他方法安装 AWS CLI 的说明。

  2. 通过登录 AWS IAM 控制台(https://console.aws.amazon.com/iam)创建访问密钥 ID 和秘密访问密钥。 下载密钥文件并将其保存在安全位置。

  3. Use the aws configure utility to configure your AWS account to the AWC CLI. Provide the access key ID and the secret access key you obtained in the previous step. This information would get stored in the .aws/config and .aws/credentials files in your home directory.

    $ aws configure
    AWS Access Key ID [None]: AKIA….
    AWS Secret Access Key [None]: GC…
    Default region name [None]: us-east-1a
    Default output format [None]: 
    
    

    提示

    如果您已完成本章中使用 Amazon Elastic MapReduce 进行 Hadoop MapReduce 计算的运行 Hadoop MapReduce 计算中的步骤 2 至 6,则可以跳到步骤 7。

  4. 在 AmazonS3 监控控制台(https://console.aws.amazon.com/s3)中单击create Bucket,创建一个存储桶来上传输入数据。 为您的存储桶提供一个唯一的名称。 通过选择存储桶并单击Upload将输入数据上传到新创建的存储桶。 WordCount 示例的输入数据应该是一个或多个文本文件。

  5. 创建一个 S3 存储桶来上传我们的 MapReduce 计算所需的 JAR 文件。 上传hcb-c1-samples.jar到新创建的存储桶中。

  6. 创建一个 S3 存储桶来存储计算的输出数据。 创建另一个 S3 存储桶来存储计算日志。 让我们假设这个存储桶的名称是hcb-c2-logs

  7. 通过执行以下命令创建 EMR 集群。 此命令将输出创建的 EMR 群集的群集 ID:

    $ aws emr create-cluster --ami-version 3.1.0 \
    --log-uri s3://hcb-c2-logs \
    --instance-groups \
    InstanceGroupType=MASTER,InstanceCount=1,\
    InstanceType=m3.xlarge \
    InstanceGroupType=CORE,InstanceCount=2,\
    InstanceType=m3.xlarge
    {
     “ClusterId”: “j-2X9TDN6T041ZZ”
    }
    
    
  8. 您可以使用list-clusters命令检查创建的 EMR 群集的状态:

    $ aws emr list-clusters
    {
     “Clusters”: [
     {
     “Status”: {
     “Timeline”: {
     “ReadyDateTime”: 1421128629.1830001,
     “CreationDateTime”: 1421128354.4130001
     },
     “State”: “WAITING”,
     “StateChangeReason”: {
     “Message”: “Waiting after step completed”
     }
     },
     “NormalizedInstanceHours”: 24,
     “Id”: “j-2X9TDN6T041ZZ”,
     “Name”: “Development Cluster”
     }
     ]
    }
    
    
  9. 通过执行以下命令将作业步骤添加到此 EMR 集群。 将 JAR 文件的路径、输入数据位置和输出数据位置替换为步骤 5、6 和 7 中使用的位置。将cluster-id替换为新创建的 EMR 集群的集群 ID。

    $ aws emr add-steps \
    --cluster-id j-2X9TDN6T041ZZ \
    --steps Type=CUSTOM_JAR,Name=CustomJAR,ActionOnFailure=CONTINUE,\
    Jar=s3n://[S3 jar file bucket]/hcb-c1-samples.jar,\
    Args=chapter1.WordCount,\
    s3n://[S3 input data path]/*,\
    s3n://[S3 output data path]/wc-out
    {
     “StepIds”: [
     “s-1SEEPDZ99H3Y2”
     ]
    }
    
    
  10. 使用describe-step命令检查提交的作业步骤的状态,如下所示。 您还可以使用 Amazon EMR 控制台(https://console.aws.amazon.com/elasticmapreduce)检查状态并调试作业流。

```scala
$ aws emr describe-step \
–cluster-id j-2X9TDN6T041ZZ \
–step-id s-1SEEPDZ99H3Y2

```
  1. 作业流完成后,使用 S3 控制台在输出数据位置检查计算结果。
  2. 使用terminate-clusters命令终止群集:
```scala
$ aws emr terminate-clusters --cluster-ids j-2X9TDN6T041ZZ

```

还有更多...

您可以将 EC2 Spot 实例与 EMR 集群配合使用,以降低计算成本。 通过将--BidPrice参数添加到您的create-cluster命令的实例组,将投标价格添加到您的请求中:

$ aws emr create-cluster --ami-version 3.1.0 \
--log-uri s3://hcb-c2-logs \
--instance-groups \
InstanceGroupType=MASTER,InstanceCount=1,\
InstanceType=m3.xlarge,BidPrice=0.10 \
InstanceGroupType=CORE,InstanceCount=2,\
InstanceType=m3.xlarge,BidPrice=0.10

有关 Amazon Spot 实例的更多详细信息,请参阅本章中的使用 Amazon EC2 Spot 实例执行 EMR 作业流食谱。

另请参阅

使用 EMR 在 Amazon EC2 上部署 Apache HBase 群集

我们可以使用 Amazon Elastic MapReduce 在 Amazon 基础设施上启动 Apache HBase 集群,以便在面向列的数据存储中存储大量数据。 我们还可以将存储在 Amazon EMR HBase 集群上的数据用作 EMR MapReduce 计算的输入和输出。 我们可以将存储在 Amazon EMR HBase 群集中的数据增量备份到 Amazon S3 以实现数据持久性。 我们还可以通过从以前的 S3 备份恢复数据来启动 EMR HBase 群集。

在本食谱中,我们使用 Amazon EMR 在 Amazon EC2 上启动 Apache HBase 群集;在新创建的 HBase 群集上执行几个简单操作,并在关闭群集之前将 HBase 数据备份到 Amazon S3。 然后,我们启动一个新的 HBase 集群,从原来的 HBase 集群恢复 HBase 数据备份。

做好准备

您应该安装 AWS CLI 并将其配置为手动备份 HBase 数据。 有关安装和配置 AWS CLI 的详细信息,请参阅本章中的使用 AWS 命令行界面配方创建 Amazon EMR 作业流。

怎么做……

以下步骤显示如何使用 Amazon EMR 在 Amazon EC2 上部署 Apache HBase 群集:

  1. 创建一个 S3 存储桶来存储 HBase 备份。 我们假设 HBase 数据备份的 S3 存储桶为hcb-c2-data

  2. https://console.aws.amazon.com/elasticmapreduce打开 Amazon EMR 控制台。 单击Create Cluster按钮创建新的电子病历群集。 为您的群集提供名称。

  3. 日志文件夹 S3 位置中提供路径,并选择具有 Hadoop v2 的AMI 版本(例如,具有 Hadoop 2.4.0 的 AMI 版本 3.1.0)。

  4. 要安装的应用部分下的其他应用下拉框中选择HBase。 单击配置并添加

  5. Make sure the Restore from backup radio button is not selected. Select the Schedule regular backups and Consistent Backup radio buttons. Specify a Backup frequency for automatic scheduled incremental data backups and provide a path inside the Blob we created in step 1 as the backup location. Click on Continue.

    How to do it...

  6. 硬件配置部分配置 EC2 实例。

  7. Select a key pair in the Amazon EC2 Key Pair drop-down box. Make sure you have the private key for the selected EC2 key pair downloaded on your computer.

    备注

    如果没有可用的密钥对,请转到 EC2 控制台(https://console.aws.amazon.com/ec2)创建密钥对。 要创建密钥对,请登录 EC2 仪表板,选择一个区域,然后单击网络和安全菜单下的密钥对。 单击密钥对窗口中的创建密钥对按钮,并提供新密钥对的名称。 下载私钥文件(PEM 格式)并将其保存在安全位置。

  8. Click on the Create Cluster button to deploy the EMR HBase cluster.

    备注

    Amazon 将通过单击上一步中的Create Cluster向您收取您使用的计算和存储资源的费用。 请参考前面讨论的使用 Amazon EC2 Spot 实例执行 EMR 工作流食谱,了解如何通过使用 Amazon EC2 Spot 实例来节省资金。

以下步骤将向您展示如何连接到已部署的 HBase 集群的主节点以启动 HBase shell:

  1. 转到 Amazon EMR 控制台(https://console.aws.amazon.com/elasticmapreduce)。 选择 HBase 群集的群集详细信息以查看有关该群集的更多信息。 从信息窗格中检索主公共 DNS 名称

  2. 使用主公共 DNS 名称和基于 EC2 PEM 的密钥(在步骤 4 中选择)连接到 HBase 群集的主节点:

    $ ssh -i ec2.pem hadoop@ec2-184-72-138-2.compute-1.amazonaws.com
    
    
  3. Start the HBase shell using the hbase shell command. Create a table named 'test' in your HBase installation and insert a sample entry to the table using the put command. Use the scan command to view the contents of the table.

    $ hbase shell
    .........
    
    hbase(main):001:0> create 'test','cf'
    0 row(s) in 2.5800 seconds
    
    hbase(main):002:0> put 'test','row1','cf:a','value1'
    0 row(s) in 0.1570 seconds
    
    hbase(main):003:0> scan 'test'
    ROW                   COLUMN+CELL
     row1                 column=cf:a, timestamp=1347261400477, value=value1 
    1 row(s) in 0.0440 seconds
    
    hbase(main):004:0> quit
    
    

    下面的步将备份存储在 Amazon EMR HBase 群集中的数据。

  4. 使用 AWS CLI 执行以下命令,计划对 EMR HBase 集群中存储的数据进行定期备份。 从 EMR 控制台检索群集 ID(例如,j-FDMXCBZP9P85)。 使用检索到的作业流名称替换<cluster_id>。 根据您的备份数据 Blob 更改备份目录路径(s3://hcb-c2-data/hbase-backup)。 等待几分钟以执行备份。

    $ aws emr schedule-hbase-backup --cluster-id <cluster_id> \
     --type full –dir s3://hcb-c2-data/hbase-backup \
    --interval 1 --unit hours 
    
    
  5. Go to the Cluster Details page in the EMR console and click on Terminate.

    现在,我们将通过从备份恢复数据来启动新的 Amazon EMR HBase 群集:

  6. 通过单击 EMR 控制台中的Create Clusters按钮创建新的作业流。 为您的群集提供名称。 在Log Folder S3 位置中提供路径,并选择具有 Hadoop v2 的 AMI 版本(例如,具有 Hadoop 2.4.0 的 AMI 版本 3.1.0)。

  7. 要安装的应用部分下的其他应用下拉框中选择HBase。 单击配置并添加

  8. 配置 EMR HBase 群集以从以前的数据备份恢复数据。 选择从备份还原选项,并在备份位置文本框中提供您在步骤 9 中使用的备份目录路径。 您可以将备份版本文本框留空,电子病历将还原最新的备份。 单击继续

  9. 重复步骤 4、5、6 和 7。

  10. 登录到?启动 HBase shell。 新 HBase 群集的主节点。 使用list命令列出 HBase 中的集合表格,使用scan 'test'命令查看'test'表格的内容。

```scala
$ hbase shell
.........

hbase(main):001:0> list
TABLE
test
1 row(s) in 1.4870 seconds

hbase(main):002:0> scan 'test'
ROW                   COLUMN+CELL
 row1                 column=cf:a, timestamp=1347318118294, value=value1 
1 row(s) in 0.2030 seconds

```
  1. 通过转到Cluster Details页面并单击Terminate按钮,使用 EMR 控制台终止您的群集。

另请参阅

第 7 章Hadoop 生态系统 II 中与 HBase 相关的配方--Pig、HBase、Mahout 和 Sqoop

使用 EMR 引导操作为 Amazon EMR 作业配置虚拟机

EMR 引导操作为我们提供了一种在运行 MapReduce 计算之前配置 EC2 实例的机制。 引导操作的示例包括为 Hadoop 提供自定义配置、安装任何相关软件、分发公共数据集等。 Amazon 提供了一组预定义的引导操作,并允许我们编写自己的自定义引导操作。 EMR 在启动 Hadoop 群集服务之前在每个实例中运行引导操作。

在本食谱中,我们将使用停用词列表从单词计数示例中筛选出常用单词。 我们使用自定义引导操作将停止单词列表下载到工人。

怎么做……

以下步骤显示如何使用引导脚本将文件下载到 EMR 计算的所有 EC2 实例:

  1. 将以下脚本保存到名为download-stopwords.sh的文件中。 将文件上传到 Amazon S3 中的 Blob 容器。 此自定义引导文件将停用词列表下载到每个实例,并将其复制到实例内的预先指定的目录中。

    #!/bin/bash
    set -e
    wget http://www.textfixer.com/resources/common-english-words-with-contractions.txt
    mkdir –p /home/hadoop/stopwords
    mv common-english-words-with-contractions.txt /home/hadoop/stopwords
    
    
  2. 完成本章中使用 Amazon Elastic MapReduce 配方运行 Hadoop MapReduce 计算的步骤 1 至 10。

  3. Select the Add Bootstrap Actions option in the Bootstrap Actions tab. Select Custom Action in the Add Bootstrap Actions drop-down box. Click on Configure and add. Give a name to your action in the Name textbox and provide the S3 path of the location where you uploaded the download-stopwords.sh file in the S3 location textbox. Click on Add.

    How to do it...

  4. 如果需要,添加步骤

  5. 单击创建集群按钮启动实例并部署 MapReduce 集群。

  6. 单击 EMR 控制台中的刷新,然后转到群集详细信息页面以查看群集的详细信息。

还有更多...

Amazon 为我们提供了以下预定义的引导操作:

  • configure-daemons:这允许我们为 Hadoop 守护进程设置Java 虚拟机(JVM)选项,比如堆大小和垃圾收集行为。
  • configure-hadoop:这允许我们修改 Hadoop 配置设置。 我们可以上传 Hadoop 配置 XML,也可以将各个配置选项指定为键-值对。
  • memory-intensive:此允许我们为内存密集型工作负载配置 Hadoop 集群。
  • run-if:这允许我们基于实例的属性运行引导操作。 当我们只想在 Hadoop 主节点中运行命令时,可以使用此操作。

您还可以通过将脚本写入实例中的指定目录来创建关机操作。 SHUTDOWN 操作在作业流终止后执行。

有关详细信息,请参阅http://docs.amazonwebservices.com/ElasticMapReduce/latest/DeveloperGuide/Bootstrap.html

使用 Apache Whirr 在云环境中部署 Apache Hadoop 群集

Apache Whirr 提供了一组与云供应商无关的库,用于在云资源上提供服务。 Apache Whirr 支持在几个云环境中配置、安装和配置 Hadoop 集群。 除了 Hadoop,Apache Whirr 还支持在云环境中提供 Apache Cassandra、Apache ZooKeeper、Apache HBase、Voldemort(键值存储)和 Apache Hama 集群。

备注

几个商业 Hadoop 发行版(如 Hortonworks HDP 和 Cloudera CDH)的安装程序现在支持在 Amazon EC2 实例上安装和配置这些发行版。 与使用 Apache Whirr 相比,这些基于商业分发的安装将在云上为您提供功能更丰富的 Hadoop 集群。

在本菜谱中,我们将使用 Apache Whirr 在 Amazon EC2 上启动 Hadoop 集群,并在该集群上运行 wordcount MapReduce 示例(编写 Wordcount MapReduce 应用,将其捆绑,并使用第 1 章《Hadoop v2 入门》中的 Hadoop 本地模式菜谱运行)。

怎么做……

以下是使用 Apache Whirr 在 Amazon EC2 上部署 Hadoop 群集以及在部署的群集上执行 Wordcount MapReduce 示例的步骤:

  1. http://whirr.apache.org/下载并解压缩 Apache Whirr 二进制发行版。 您也可以通过 Hadoop 发行版安装 Whirr。

  2. 从解压的目录运行以下命令以验证您的 Whirr 安装:

    $ whirr version
    Apache Whirr 0.8.2
    jclouds 1.5.8
    
    
  3. 将您的 AWS 访问密钥导出到AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY环境参数:

    $ export AWS_ACCESS_KEY_ID=AKIA…
    $ export AWS_SECRET_ACCESS_KEY=…
    
    
  4. 使用下面的 the 命令生成rsa 密钥对。 此密钥对与您的 AWS 密钥对不同。

    $ssh-keygen -t rsa -P ''
    
    
  5. 在 Apache Whirr 安装中找到名为recipes/hadoop-yarn-ec2.properties的文件。 将其复制到您的工作目录。 更改whirr.hadoop.version属性以匹配 Apache Hadoop 下载页面中提供的当前 Hadoop 版本(例如,2.5.2)。

  6. If you provided a custom name for your key-pair in the previous step, change the whirr.private-key-file and the whirr.public-key-file property values in the hadoop-yarn-ec2.properties file to the paths of the private key and the public key you generated.

    提示

    whirr.aws-ec2-spot-price属性是一个可选属性,它允许我们使用更便宜的 EC2 Spot 实例。 您可以删除该属性以使用 EC2 传统按需实例。

  7. 执行以下命令,指向您的hadoop-yarn-ec2.properties文件,在 EC2 上启动您的 Hadoop 集群。 成功创建集群后,此命令将输出一个 SSH 命令,我们可以使用该命令登录 EC2Hadoop 集群。

    $bin/whirr launch-cluster --config hadoop-yarn-ec2.properties
    
    
  8. 从外部到调配的 EC2 Hadoop 群集的流量通过主节点路由。 Whirr 在~/.whirr目录中以 Hadoop 集群命名的子目录下生成一个脚本,我们可以使用该脚本启动该代理。 在一个新的终端上运行这个。 Whirr 启动集群并生成此脚本需要几分钟时间。

    $cd ~/.whirr/Hadoop-yarn/
    $hadoop-proxy.sh
    
    
  9. 您可以通过在 Web 浏览器中配置此代理,在本地计算机中打开基于 Hadoop Web 的监控控制台。

  10. Whirr 在~/.whirr/<your cluster name>目录中为您的集群生成一个hadoop-site.xml文件。 您可以使用它从本地计算机向 EC2 上的 Hadoop 集群发出 Hadoop 命令。 将生成的hadoop-site.xml的路径导出到名为HADOOP_CONF_DIR的环境变量。 将此目录中的hadoop-site.xml文件复制到名为core-site.xml的另一个文件中。 要执行 Hadoop 命令,您应该在您的机器上安装 Hadoopv2 二进制文件。

```scala
$ cp ~/.whirr/hadoop-yarn/hadoop-site.xml ~/.whirr/hadoop-yarn/core-site.xml
$ export HADOOP_CONF_DIR=~/.whirr/hadoop-yarn/
$ hdfs dfs -ls /

```
  1. 在 HDFS 中创建名为wc-input-data的目录,并将文本数据集上传到该目录。 根据 Whirr 的版本,您可能需要首先创建您的主目录。
```scala
$ hdfs dfs –mkdir /user/<user_name>
$ hdfs  dfs -mkdir wc-input-data
$ hdfs dfs -put sample.txt wc-input-data

```
  1. 在本步骤中,我们在 Amazon EC2 中启动的 Hadoop 集群中运行 Hadoop wordcount 示例:
```scala
$ hadoop jar hcb-c1-samples.jar chapter1.WordCount \
wc-input-data wc-out

```
  1. 通过执行以下命令查看字数计算结果:
```scala
$hadoop fs -ls wc-out
Found 3 items
-rw-r--r--   3 thilina supergroup          0 2012-09-05 15:40 /user/thilina/wc-out/_SUCCESS
drwxrwxrwx   - thilina supergroup          0 2012-09-05 15:39 /user/thilina/wc-out/_logs
-rw-r--r--   3 thilina supergroup      19908 2012-09-05 15:40 /user/thilina/wc-out/part-r-00000

$ hadoop fs -cat wc-out/part-* | more

```
  1. 发出以下命令关闭 Hadoop 集群。 请确保在关闭群集之前下载所有重要数据,因为关闭群集后数据将永久丢失。
```scala
$bin/whirr destroy-cluster --config hadoop.properties

```

它是如何工作的.

以下是我们在hadoop.properties文件中使用的属性的说明。

whirr.cluster-name=Hadoop-yarn

前面的属性提供了群集的名称。 群集的实例将使用此名称进行标记。

whirr.instance-templates=1 hadoop-namenode+yarn-resource-manager+mapreduce-historyserver, 1 hadoop-datanode+yarn-nodemanager

此属性指定要用于每组角色的实例数以及实例的角色类型。

whirr.provider=aws-ec2

我们使用 Whirr Amazon EC2 提供程序来配置我们的群集。

whirr.private-key-file=${sys:user.home}/.ssh/id_rsa
whirr.public-key-file=${sys:user.home}/.ssh/id_rsa.pub

前面提到的属性都指向您为集群提供的私钥和公钥的路径。

whirr.hadoop.version=2.5.2

我们使用前面的属性指定自定义 Hadoop 版本。

whirr.aws-ec2-spot-price=0.15

此属性指定 Amazon EC2 Spot 实例的投标价格。 指定此属性将触发 Whirr 对簇使用 EC2 Spot 实例。 如果没有达到投标价格,Apache Whirr Spot 实例会在 20 分钟后请求超时。 有关更多详细信息,请参阅使用 Amazon EC2 Spot 实例执行 EMR 工作流食谱。

有关 Whirr 配置的更多详细信息,请参见http://whirr.apache.org/docs/0.8.1/configuration-guide.html

另请参阅

使用 Amazon EC2 Spot 实例执行 EMR 作业流节省资金秘诀。*

三、Hadoop 基础知识——配置、单元测试和其他 API

在本章中,我们将介绍:

  • 针对群集部署优化 Hadoop Yarn 和 MapReduce 配置
  • 共享用户 Hadoop 群集-使用公平和容量调度器
  • 将类路径优先级设置为用户提供的 JAR
  • 推测性地执行分散的任务
  • 使用 MRUnit 对 Hadoop MapReduce 应用进行单元测试
  • 使用 MiniYarnCluster 集成测试 Hadoop MapReduce 应用
  • 添加新的 DataNode
  • 停用数据节点
  • 使用多个磁盘/卷并限制 HDFS 磁盘使用
  • 设置 HDFS 块大小
  • 设置文件复制系数
  • 使用 HDFS Java API

简介

本章介绍如何在 Hadoop 群集中执行高级管理步骤,如何为 Hadoop MapReduce 程序开发单元和集成测试,以及如何使用 HDFS 的 Java API。 本章假设您已经阅读了第一章,并且已经在集群或伪分布式安装中安装了 Hadoop。

备注

示例代码和数据

本书的示例代码文件可以在 giHub 的https://github.com/thilg/hcb-v2中找到。 代码库的chapter3文件夹包含本章的示例源代码文件。

可以通过在代码库的chapter3文件夹中发出gradle build命令来编译和构建示例代码。 Eclipse IDE 的项目文件可以通过在代码库的主文件夹中运行gradle eclipse命令来生成。 IntelliJ IDEA IDE 的项目文件可以通过在代码存储库的主文件夹中运行gradle idea命令来生成。

针对群集部署优化 Hadoop Yarn 和 MapReduce 配置

在本食谱中,我们探索了 Hadoop Yarn 和 Hadoop MapReduce 的一些重要配置选项。 商业 Hadoop 发行版通常提供基于 GUI 的方法来指定 Hadoop 配置。

Yarn 根据应用发出的资源请求和集群的可用资源容量为应用分配资源容器。 应用的资源请求将由所需容器的数量和每个容器的资源要求组成。 目前,大多数容器资源需求都是使用内存量指定的。 因此,我们在本配方中的重点将主要放在配置 Yarn 集群的内存分配上。

做好准备

按照第一章中的食谱设置 Hadoop 集群。

怎么做……

以下说明将向您展示如何在 Yarn 集群中配置内存分配。 每个节点的任务数是使用以下配置得出的:

  1. 以下属性指定辅助节点中 Yarn 容器可以使用的内存量(RAM)。 建议将其设置为略小于节点中存在的物理 RAM 的大小,从而为操作系统和其他非 Hadoop 进程留出一些内存。 在yarn-site.xml文件中添加或修改以下行:

    <property>
      <name>yarn.nodemanager.resource.memory-mb</name>
      <value>100240</value>
    </property>
    
  2. The following property specifies the minimum amount of memory (RAM) that can be allocated to a YARN container in a worker node. Add or modify the following lines in the yarn-site.xml file to configure this property.

    如果我们假设所有 Yarn 资源请求请求容器只具有最小内存量,则在一个节点中可以执行的最大并发资源容器数等于(步骤 1 中指定的每个节点的 Yarn 内存)/(下面配置的 Yarn 最小分配)。 基于此关系,我们可以使用以下属性的值来实现每个节点所需的资源容器数量。

    每个节点的资源容器数量建议小于或等于(2个 CPU 核)(2磁盘数量)中的最小值。

    <property>
      <name>yarn.scheduler.minimum-allocation-mb</name>
      <value>3072</value>
    </property>
    
  3. 通过运行HADOOP_HOME目录中的sbin/stop-yarn.shsbin/start-yarn.sh来重新启动 SPAINE ResourceManager 和 NodeManager 服务。

以下说明将向您展示如何配置 MapReduce 应用的内存要求。

  1. 以下属性定义了每个 Map 和 Reduce 任务可用的最大内存量(RAM)。 当 MapReduce 应用为 Map 和 Reduce 任务容器从 YAR 请求资源时,将使用这些内存值。 将以下行添加到mapred-site.xml文件:

    <property>
      <name>mapreduce.map.memory.mb</name>
      <value>3072</value>
    </property>
    <property>
      <name>mapreduce.reduce.memory.mb</name>
      <value>6144</value>
    </property>
    
  2. 以下属性分别定义了 Map 和 Reduce 任务的 JVM 堆大小。 将这些值设置为略小于步骤 4 中的对应值,以便它们不会超过 Yarn 容器的资源限制。 将以下行添加到mapred-site.xml文件:

    <property>
      <name>mapreduce.map.java.opts</name>
      <value>-Xmx2560m</value>
    </property>
    <property>
      <name>mapreduce.reduce.java.opts</name>
      <value>-Xmx5120m</value>
    </property>
    

它是如何工作的.

我们可以通过以下四个配置文件控制 Hadoop 配置。 Hadoop 在群集重新启动后从以下配置文件重新加载配置:

  • core-site.xml:包含整个 Hadoop 发行版通用的配置
  • hdfs-site.xml:包含 HDFS 的配置
  • mapred-site.xml:包含 MapReduce 的配置
  • yarn-site.xml:包含 Yarn 资源管理器和节点管理器进程的配置

每个配置文件都有以 XML 格式表示的名称-值对,定义了 Hadoop 的不同方面的配置。 下面是配置文件中的属性示例。 <configuration>标记是顶级父 XML 容器,定义各个属性的<property>标记被指定为<configuration>标记内的子标记:

<configuration>
   <property>
      <name>mapreduce.reduce.shuffle.parallelcopies</name>
      <value>20</value>
   </property>
...
</configuration>

某些配置可以使用 Hadoop MapReduce 作业驱动程序代码中的job.getConfiguration().set(name, value)方法按作业进行配置。

还有更多...

在 Hadoop 中定义了许多类似的重要配置属性。 以下是其中一些建议:

|

conf/core-site.xml

|
| --- |
| 名称 | 默认值 | 说明 |
| fs.inmemory.size.mb | 200 | 分配给内存中文件系统的内存量,用于合并减少器上的映射输出(MB |
| io.file.buffer.size | 131072 | 序列文件使用的读/写缓冲区的大小 |

|

conf/mapred-site.xml

|
| --- |
| 名称 | 默认值 | 说明 |
| mapreduce.reduce.shuffle.parallelcopies | 20 | Reduce 步骤将执行的从多个并行作业获取输出的最大并行副本数 |
| mapreduce.task.io.sort.factor | 50 | 排序文件时合并的最大流数 |
| mapreduce.task.io.sort.mb | 200 | 以 MB 为单位对数据进行排序时的内存限制 |

|

conf/hdfs-site.xml

|
| --- |
|

名称

|

默认值

|

说明

|
| --- | --- | --- |
| dfs.blocksize | 134217728 | HDFS 数据块大小 |
| dfs.namenode.handler.count | 200 | 在 NameNode 中处理 RPC 调用的服务器线程数 |

备注

您可以在http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/DeprecatedProperties.html中找到最新版本的 Hadoop 中不推荐使用的属性列表以及它们的新替换属性。

以下文档提供了属性列表、它们的默认值以及前面提到的每个配置文件的说明:

共享用户 Hadoop 群集-使用公平和容量调度器

HadoopYarn 调度程序负责将资源分配给用户提交的应用。 在 Hadoop Yarn 中,除了 MapReduce 应用之外,这些应用还可以是任何 Yarn 应用。 目前默认的 Yarn 资源分配是基于应用的内存需求,也可以额外配置基于 CPU 等其他资源的资源分配。

Hadoop Yarn 支持可插拔调度框架,其中集群管理员可以选择为集群选择合适的调度器。 默认情况下,YAINE 支持先进先出(FIFO)调度器,该调度器使用作业队列以与作业到达时相同的顺序执行作业。 但是,FIFO 调度可能不是大型多用户 Hadoop 部署的最佳选择,在这种部署中,群集资源必须在不同的用户和不同的应用之间共享,以确保最大限度地提高群集的工作效率。 请注意,商业 Hadoop 版本可能使用不同的调度器,如公平调度器(例如 Cloudera CDH)或容量调度器(例如 Hortonworks HDP)作为默认 Yarn 调度器。

在中,除了默认的 FIFO 调度器之外,Yarn 还包含以下两个调度器(如果需要,您也可以编写自己的调度器):

  • 公平调度器:公平调度器允许所有作业接收相等份额的资源。 当资源可用时,会将资源分配给新提交的作业,直到所有提交和运行的作业都具有相同的资源量。 公平调度程序可确保短作业以实际速度完成,同时不会使长时间运行的较大作业处于饥饿状态。 使用公平调度器,还可以定义多个队列和队列层次结构,并保证每个队列的最小资源,其中特定队列中的作业平等地共享资源。 分配给任何空队列的资源在具有活动作业的队列之间分配。 公平调度程序还允许我们设置作业优先级,用于计算队列中的资源分配比例。
  • 容量调度器:容量调度器允许在个组织实体之间共享大型集群,同时确保每个实体的有保证的容量,并且没有单个用户或作业占用所有资源。 这使组织可以通过维护在不同实体之间共享的集中式 Hadoop 群集来实现规模经济。 为了实现这一点,容量调度器定义队列和队列层次结构,每个队列具有保证的容量。 容量调度程序允许作业使用其他队列中的多余资源(如果有的话)。

怎么做……

本食谱介绍如何在 Hadoop 中更改调度程序:

  1. 关闭 Hadoop 群集。

  2. 将以下内容添加到yarn-site.xml file

    <property>
      <name>yarn.resourcemanager.scheduler.class</name>
     <value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler</value>
    </property>
    
  3. 重新启动 Hadoop 群集。

  4. 转到安装中的http://<master-noe>:8088/cluster/scheduler,验证是否已应用新的调度程序。

它是如何工作的.

执行上述步骤后,Hadoop 将在启动时加载新的调度程序设置。 公平调度程序在用户之间共享等量的资源,除非另有配置。

我们可以提供 XML 格式的分配文件,使用yarn-site.xml文件中的yarn.scheduler.fair.allocation.file属性为公平调度器定义队列。

有关公平调度器及其配置的更多详细信息,请参阅https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/FairScheduler.html

还有更多...

您可以通过将以下内容添加到yarn-site.xml file并重新启动群集来启用容量计划程序:

<property>
  <name>yarn.resourcemanager.scheduler.class</name>
<value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler</value>
</property>

可以使用 ResourceManager 节点的 Hadoop 配置目录中的capacity-scheduler.xml文件配置容量调度器。 在 Yar ResourceManager 节点中发出以下命令以加载配置并刷新队列:

$ yarn rmadmin -refreshQueues

有关容量调度器及其配置的更多详细信息,请参阅http://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/CapacityScheduler.html

将类路径优先级设置为用户提供的 JAR

在开发 Hadoop MapReduce 应用时,您可能会遇到 MapReduce 应用需要 Hadoop 中已包含的较新版本的辅助库的情况。 默认情况下,Hadoop 将类路径优先于 Hadoop 包含的库,这可能会导致与您的应用提供的库的版本冲突。 本食谱向您展示了如何配置 Hadoop,以便为用户提供的库提供类路径优先级。

怎么做……

以下步骤向您展示了如何将外部库添加到 Hadoop 任务类路径,以及如何为用户提供的 JAR 提供优先级:

  1. 在 MapReduce 计算的驱动程序中设置以下属性:

    job.getConfiguration().set("mapreduce.job.user.classpath.first","true");
    
  2. 使用 hadoop 命令中的–libjars选项提供您的库,如下所示:

    $hadoop jar hcb-c3-samples.jar \ 
    chapter3.WordCountWithTools \
    –libjars guava-15.0.jar \
    InDir OutDir …
    
    

它是如何工作的.

Hadoop 将把–libjars指定的 JAR 复制到 HadoopDistributedCache中,并且它们将可用于属于该特定作业的所有任务的类路径。 设置mapreduce.user.classpath.first时,用户提供的 JAR 将附加到类路径中的默认 Hadoop JAR 和 Hadoop 依赖项之前。

推测性地执行分散的任务

使用 Hadoop MapReduce 的主要优势之一是框架管理的容错。 在执行大规模分布式计算时,部分计算可能会由于网络故障、磁盘故障和节点故障等外部原因而失败。 当 Hadoop 检测到无响应的任务或失败的任务时,Hadoop 将在新节点中重新执行这些任务。

Hadoop 群集可能由异构节点组成,因此可能会有速度很慢的节点,也可能会有速度很快的节点。 一些速度较慢的节点和在这些节点上执行的任务可能会控制计算的执行时间。 Hadoop 引入了推测性的执行优化,以避免这些运行缓慢的任务,这些任务被称为落后者

当计算的大部分 Map(或 Reduce)任务完成时,Hadoop 推测性执行功能将在可用的备用节点中调度剩余缓慢任务的重复执行。 任务的缓慢程度取决于相同计算的其他任务所花费的运行时间。 Hadoop 将从一组重复任务中选择第一个已完成任务的结果,并终止该任务的任何其他重复执行。

怎么做……

默认情况下,Hadoop 中为 Map 和 Reduce 任务启用了推测性执行。 如果由于某种原因,您的计算不需要这样的重复执行,您可以禁用(或启用)推测性执行,如下所示:

  1. 运行将以下选项作为参数传递的 wordcount 示例:

    $ hadoop jar hcb-c32-samples.jar chapter3.WordCountWithTools \
     –Dmapreduce.map.speculative=false \
     –Dmapreduce.reduce.speculative=false \
     /data/input1 /data/output1
    
    
  2. 但是,仅当作业实现org.apache.hadoop.util.Tools接口时,上述命令才有效。 否则,请使用以下方法在 MapReduce 驱动程序中设置这些属性:

    • 对于整个作业,使用job.setSpeculativeExecution(boolean specExec)
    • 对于地图任务,使用job.setMapSpeculativeExecution(boolean specExec)
    • 对于 Reduce 任务,使用Job.setReduceSpeculativeExecution(boolean specExec)

还有更多...

您可以使用属性mapreduce.map.maxattemptsmapreduce.reduce.maxattempts分别为 Map 和 Reduce 任务配置任务的最大重试次数。 Hadoop 在任务超过给定的重试次数后将其声明为失败。 您还可以使用JobConf.setMaxMapAttempts()JobConf.setMaxReduceAttempts()函数来配置这些属性。 这些属性的默认值为4

使用 MRUnit 单元测试 Hadoop MapReduce 应用

MRUnit是一个基于 JUnit 的 Java 库,允许用户对 Hadoop MapReduce 程序进行单元测试。 这使得开发和维护 Hadoop MapReduce 代码库变得容易。 MRUnit 支持单独测试Mappers and Reducers以及整体测试 MapReduce 计算。 在本食谱中,我们将探索所有三个测试场景。 本食谱中使用的测试程序的源代码位于 Git 存储库的chapter3\test\chapter3\WordCountWithToolsTest.java文件中。

做好准备

我们使用 Gradle 作为示例代码库的构建工具。 如果您还没有安装 Gradle,请按照Hadoopv2入门介绍部分中的说明安装 Gradle。

怎么做……

以下步骤说明如何使用 MRUnit 执行映射器的单元测试:

  1. 在测试类的setUp方法中,使用要测试的 Mapper 类初始化 MRUnitMapDriver实例。 在本例中,我们将测试我们在前面的食谱中讨论的 Wordcount MapReduce 应用的映射器:

    public class WordCountWithToolsTest {
    
      MapDriver<Object, Text, Text, IntWritable> mapDriver;
    
      @Before
      public void setUp() {
        WordCountWithTools.TokenizerMapper mapper = new WordCountWithTools.TokenizerMapper();
        mapDriver = MapDriver.newMapDriver(mapper);
      }
    ……
    }
    
  2. Write a test function to test the Mapper logic. Provide the test input to the Mapper using the MapDriver.withInput method. Then, provide the expected result of the Mapper execution using the MapDriver.withOutput method. Now, invoke the test using the MapDriver.runTest method. The MapDriver.withAll and MapDriver.withAllOutput methods allow us to provide a list of test inputs and a list of expected outputs, rather than adding them individually.

    @Test
      public void testWordCountMapper() throws IOException {
        IntWritable inKey = new IntWritable(0);
        mapDriver.withInput(inKey, new Text("Test Quick"));
        ….
        mapDriver.withOutput(new Text("Test"),new IntWritable(1));
        mapDriver.withOutput(new Text("Quick"),new IntWritable(1));
        …
        mapDriver.runTest();
      }
    

    以下步骤将向您展示如何使用 MRUnit 执行 Reducer 的单元测试。

  3. Similar to step 1 and 2, initialize a ReduceDriver by providing the Reducer class under test and then configure the ReduceDriver with the test input and the expected output. The input to the reduce function should conform to a key with a list of values. Also, in this test, we use the ReduceDriver.withAllOutput method to provide a list of expected outputs.

    public class WordCountWithToolsTest {
      ReduceDriver<Text,IntWritable,Text,IntWritable> reduceDriver;
    
    @Before
      public void setUp() {
        WordCountWithTools.IntSumReducer reducer = new WordCountWithTools.IntSumReducer();
        reduceDriver = ReduceDriver.newReduceDriver(reducer);
      }
    
    @Test
      public void testWordCountReduce() throws IOException {
        ArrayList<IntWritable> reduceInList = new ArrayList<IntWritable>();
        reduceInList.add(new IntWritable(1));
        reduceInList.add(new IntWritable(2));
    
        reduceDriver.withInput(new Text("Quick"), reduceInList);
        ...
        ArrayList<Pair<Text, IntWritable>> reduceOutList = new ArrayList<Pair<Text,IntWritable>>();
        reduceOutList.add(new Pair<Text, IntWritable> (new Text("Quick"),new IntWritable(3)));
        ...
        reduceDriver.withAllOutput(reduceOutList);
        reduceDriver.runTest();
      }
    }
    

    以下步骤向您展示了如何使用 MRUnit 对整个 MapReduce 计算执行单元测试。

  4. 在此步骤中,通过提供要测试的 MapReduce 程序的 Mapper 类和 Reducer 类来初始化 aMapReduceDriver。 然后,使用测试输入数据和预期输出数据配置MapReduceDriver。 执行时,此测试将执行从 Map 输入阶段到 Reduce 输出阶段的 MapReduce 执行流。 也可以为该测试提供组合器实现。

    public class WordCountWithToolsTest {
      ……
      MapReduceDriver<Object, Text, Text, IntWritable, Text, IntWritable> mapReduceDriver;
    
    @Before
      public void setUp() {
        ....
        mapReduceDriver = MapReduceDriver.newMapReduceDriver(mapper, reducer);
      }
    
    @Test
      public void testWordCountMapReduce() throws IOException {
    
        IntWritable inKey = new IntWritable(0);
        mapReduceDriver.withInput(inKey, new Text("Test Quick"));
        ……
        ArrayList<Pair<Text, IntWritable>> reduceOutList = new ArrayList<Pair<Text,IntWritable>>();
        reduceOutList.add(new Pair<Text, IntWritable>(new Text("Quick"),new IntWritable(2)));
        ……
        mapReduceDriver.withAllOutput(reduceOutList);
        mapReduceDriver.runTest();
      }
    }
    
  5. Gradle Build 脚本(或任何其他 Java 构建机制)可以配置为对每个构建执行这些单元测试。 我们可以将 MRUnit 依赖项添加到 Gradle 构建(chapter3/build.gradle)文件,如下所示:

    dependencies {
      testCompile group: 'org.apache.mrunit', name: 'mrunit', version: '1.1.+',classifier: 'hadoop2'
    ……
    }
    
  6. 使用以下 Gradle 命令仅执行WordCountWithToolsTest单元测试。 此命令执行与模式**/WordCountWith*.class

    $ gradle –Dtest.single=WordCountWith test
    :chapter3:compileJava UP-TO-DATE
    :chapter3:processResources UP-TO-DATE
    :chapter3:classes UP-TO-DATE
    :chapter3:compileTestJava UP-TO-DATE
    :chapter3:processTestResources UP-TO-DATE
    :chapter3:testClasses UP-TO-DATE
    :chapter3:test
    BUILD SUCCESSFUL
    Total time: 27.193 secs
    
    

    匹配的任何测试类

  7. 您还可以在 IDE 中执行基于 MRUnit 的单元测试。 您可以使用gradle eclipsegradle idea命令分别为 Eclipse 和 IDEA IDE 生成项目文件。

另请参阅

使用 MiniYarnCluster 集成测试 Hadoop MapReduce 应用

虽然使用 MRUnit 的单元测试非常有用,但是可能有某些集成测试场景需要在集群环境中进行测试。 Hadoop Yarn 的 MiniYARNCluster 是一个集群模拟器,我们可以使用它来为这样的集成测试创建测试环境。 在本食谱中,我们将使用 MiniYARNCluster 执行 WordCountWithTools MapReduce 应用的集成测试。

本食谱中使用的测试程序的源代码位于 Git 存储库的chapter3\test\chapter3\minicluster\WordCountMiniClusterTest.java文件中。

做好准备

我们使用 Gradle 作为示例代码库的构建工具。 如果您还没有安装 Gradle,请按照Hadoopv2入门介绍部分中的说明安装 Gradle。 导出指向 JDK 安装的JAVA_HOME环境变量。

怎么做……

以下步骤显示如何使用MiniYarnCluster环境执行 MapReduce 应用的集成测试:

  1. 在 JUnit 测试的setup方法中,使用MiniMRClientClusterFactory创建MiniYarnCluster的实例,如下所示。 MiniMRClientClusterMiniMRYarnCluster的包装器接口,用于使用 Hadoop 1.x 集群提供支持测试。

    public class WordCountMiniClusterTest {
      private static MiniMRClientCluster mrCluster;
      private class InternalClass {
      }
    
    @BeforeClass
      public static void setup() throws IOException {
        // create the mini cluster to be used for the tests
        mrCluster = MiniMRClientClusterFactory.create(InternalClass.class, 1,new Configuration());
      }
    }
    
  2. 确保停止测试的setup方法内的集群:

    @AfterClass
      public static void cleanup() throws IOException {
        // stopping the mini cluster
        mrCluster.stop();
      }
    
  3. 在您的测试方法中,使用我们刚刚创建的MiniYARNCluster的 Configuration 对象准备 MapReduce 计算。 提交作业并等待其完成。 然后测试作业是否成功。

    @Test
      public void testWordCountIntegration() throws Exception{
    ……
        Job job = (new WordCountWithTools()).prepareJob(testInput,outDirString, mrCluster.getConfig());
        // Make sure the job completes successfully
        assertTrue(job.waitForCompletion(true));
        validateCounters(job.getCounters(), 12, 367, 201, 201);
      }
    
  4. 在本例中,我们将使用计数器来验证 MapReduce 计算的预期结果。 您还可以实现逻辑,将计算的输出数据与预期的计算输出进行比较。 但是,必须小心处理由于存在多个 Reduce 任务而可能有多个输出文件的情况。

     private void validateCounters(Counters counters, long mapInputRecords,…) {
     assertEquals("MapInputRecords", mapInputRecords, counters.findCounter("org.apache.hadoop.mapred.Task$Counter", "MAP_INPUT_RECORDS").getValue());
     ………
     }
    
    
  5. 使用以下 Gradle 命令仅执行WordCountMiniClusterTestJUnit 测试。 该命令执行与模式**/WordCountMini*.class匹配的任何测试类。

    $ gradle -Dtest.single=WordCountMini test
    :chapter3:compileJava UP-TO-DATE
    :chapter3:processResources UP-TO-DATE
    :chapter3:classes UP-TO-DATE
    :chapter3:compileTestJava UP-TO-DATE
    :chapter3:processTestResources UP-TO-DATE
    :chapter3:testClasses UP-TO-DATE
    :chapter3:test UP-TO-DATE
    
    BUILD SUCCESSFUL
    
    
  6. 您还可以在 IDE 中执行基于MiniYarnCluster的单元测试。 您可以使用gradle eclipsegradle idea命令分别为 Eclipse 和 IDEA IDE 生成项目文件。

另请参阅

  • 本章中的单元测试 Hadoop MapReduce 应用使用 MRUnit菜谱
  • 第 4 章《开发复杂 Hadoop MapReduce 应用》中用于报告自定义指标的Hadoop 计数器配方

添加新的 DataNode

此配方向您展示了如何在不重新启动整个集群的情况下向现有 HDFS 集群添加新节点,以及如何在添加新节点后强制 HDFS 重新平衡。 商业 Hadoop 发行版通常提供基于 GUI 的方法来添加和删除 DataNode。

做好准备

  1. 在新节点上安装 Hadoop 并复制现有 Hadoop 群集的配置文件。 您可以使用rsync从另一个节点复制 Hadoop 配置;例如:

    $ rsync -a <master_node_ip>:$HADOOP_HOME/etc/hadoop/ $HADOOP_HOME/etc/hadoop
    
    
  2. 确保 Hadoop/HDFS 集群的主节点可以对新节点执行无密码 SSH。 如果您不打算从主节点使用bin/*.sh脚本启动/停止群集,则无密码 SSH 设置是可选的。

怎么做……

以下步骤将向您展示如何向现有 HDFS 集群添加新的 DataNode:

  1. 将新节点的 IP 或 DNS 添加到主节点的$HADOOP_HOME/etc/hadoop/slaves文件中。

  2. Start the DataNode on the newly added slave node by using the following command:

    $ $HADOOP_HOME/sbin/hadoop-deamons.sh start datanode
    
    

    提示

    您还可以从主节点使用$HADOOP_HOME/sbin/start-dfs.sh脚本在新添加的节点中启动 DataNode 守护进程。 如果要向集群添加多个新的 DataNode,这将非常有用。

  3. 检查新从节点中的$HADOOP_HOME/logs/hadoop-*-datanode-*.log是否有任何错误。

这些步骤既适用于添加新节点,也适用于重新加入已崩溃并重新启动的节点。

还有更多...

同样,您也可以向 Hadoop Yarn 集群添加新节点:

  1. 使用以下命令在新节点中启动 NodeManager:

    > $HADOOP_HOME/sbin/yarn-deamons.sh start nodemanager
    
    
  2. 检查新从节点中的$HADOOP_HOME/logs/yarn-*-nodemanager-*.log是否有任何错误。

重新平衡 HDFS

当您添加新节点时,HDFS 不会自动重新平衡。 但是,HDFS 提供了可以手动调用的重新平衡器工具。 此工具将跨群集平衡数据块,最高可达可选的阈值百分比。 如果您在其他现有节点中遇到空间问题,重新平衡将非常有用。

  1. Execute the following command:

    > $HADOOP_HOME/sbin/start-balancer.sh –threshold 15
    
    

    (可选)–threshold参数指定在标识节点未充分利用或过度利用时要考虑的磁盘容量回旋余地百分比。 未充分利用的数据节点是其利用率小于(平均利用率阈值)的节点。 过度利用的 DataNode 是利用率大于(平均利用率+阈值)的节点。 较小的阈值将实现更均匀的节点平衡,但重新平衡将需要更多时间。 默认阈值为 10%。

  2. 通过执行sbin/stop-balancer.sh命令可以停止重新平衡。

  3. $HADOOP_HOME/logs/hadoop-*-balancer*.out文件中提供了重新平衡的摘要。

另请参阅

本章中的取消数据节点配方。

停用数据节点

可能有种情况,您希望从 HDFS 群集中停用一个或多个 DataNode。 本食谱展示了如何优雅地使 DataNode 退役,而不会导致数据丢失。

怎么做……

以下步骤向您展示了如何正常停用 DataNode:

  1. 如果您的集群没有exclude文件,请向集群添加一个exclude文件。 在 NameNode 中创建一个空文件,并通过添加以下属性从$HADOOP_HOME/etc/hadoop/hdfs-site.xml文件指向该文件。 重新启动 NameNode:

    <property>
    <name>dfs.hosts.exclude</name>
    <value>FULL_PATH_TO_THE_EXCLUDE_FILE</value>
    <description>Names a file that contains a list of hosts that are not permitted to connect to the namenode. The full pathname of the file must be specified. If the value is empty, no hosts are excluded.</description>
    </property>
    
  2. 将要停用的节点的主机名添加到exclude文件。

  3. Run the following command to reload the NameNode configuration:

    $ hdfs dfsadmin –refreshNodes
    
    

    这将启动退役过程。 此过程可能需要大量时间,因为它需要复制数据块,而不会使群集的其他任务不堪重负。

  4. 停用过程的进度显示在停用节点页面下的 HDFS UI 中。 也可以使用以下命令监视进度。 在停用完成之前不要关闭节点。

    $ hdfs dfsadmin -report
    .....
    .....
    Name: myhost:50010
    Decommission Status : Decommission in progress
    Configured Capacity: ....
    .....
    
    
  5. 当您想要将节点重新添加到集群中时,可以从exclude文件中删除节点并执行hdfs dfsadmin –refreshNodes命令。

  6. 可以通过从exclude文件中删除节点名称,然后执行hdfs dfsadmin –refreshNodes命令来停止停用过程。

它是如何工作的.

当某个节点处于停用过程中时,HDFS 会将该节点中的数据块复制到群集中的其他节点。 停用可能是一个缓慢的过程,因为 HDFS 故意缓慢地停用,以避免使群集不堪重负。 在不停用的情况下关闭节点可能会导致数据丢失。

停用完成后,排除文件中提到的节点不允许与 NameNode 通信。

另请参阅

本章中添加新的 DataNode配方的重新平衡 HDFS部分。

使用多个磁盘/卷并限制 HDFS 磁盘使用

Hadoop 支持为 DataNode 数据目录指定多个目录。 此功能允许我们利用多个磁盘/卷在 DataNode 中存储数据块。 Hadoop 尝试在每个目录中存储等量的数据。 它还支持限制 HDFS 使用的磁盘空间量。

怎么做……

以下步骤将向您展示如何添加多个磁盘卷:

  1. 在每个卷中创建 HDFS 数据存储目录。

  2. 找到hdfs-site.xml配置文件。 在dfs.datanode.data.dir属性下提供与每个卷中的数据存储位置相对应的以逗号分隔的目录列表,如下所示:

    <property>
             <name>dfs.datanode.data.dir</name>
             <value>/u1/hadoop/data, /u2/hadoop/data</value>
    </property>
    
  3. 为了限制磁盘使用,请将以下属性添加到hdfs-site.xml文件中,以便为非 DFS 使用保留空间。 该值指定 HDFS 不能在每个卷上使用的字节数:

      <property>
        <name>dfs.datanode.du.reserved</name>
        <value>6000000000</value>
        <description>Reserved space in bytes per volume. Always leave this much space free for non dfs use.
        </description>
      </property>
    

设置 HDFS 块大小

HDFS 通过将文件分解成粗粒度、固定大小的块来跨群集中存储文件。 默认 HDFS 数据块大小为 64 MB。 数据产品的数据块大小可能会影响文件系统操作的性能,在存储和处理非常大的文件时,较大的数据块大小会更有效。 数据产品的块大小也会影响 MapReduce 计算的性能,因为 Hadoop 的默认行为是为输入文件的每个数据块创建一个 Map 任务。

怎么做……

以下步骤显示如何使用 NameNode 配置文件设置 HDFS 块大小:

  1. $HADOOP_HOME/etc/hadoop/hdfs-site.xml文件中添加或修改以下代码。 块大小是使用字节数提供的。 此更改不会更改 HDFS 中已有文件的块大小。 只有更改后复制的文件才具有新的块大小。

    <property>
            <name>dfs.blocksize</name>
            <value>134217728</value>
    </property>
    
  2. 您还可以为特定文件路径指定不同的 HDFS 块大小。 还可以在从命令行将文件上载到 HDFS 时指定块大小,如下所示:

    $ hdfs dfs \
     -Ddfs.blocksize=134217728 \
     -put data.in foo/test
    
    

还有更多...

您还可以在使用 HDFS Java API 创建文件时指定块大小,方法如下:

public FSDataOutputStream create(Path f,boolean overwrite, int bufferSize, short replication,long blockSize)

您可以使用fsck命令查找 HDFS 中特定文件路径的块大小和块位置。 您也可以通过从 HDFS 监控控制台浏览文件系统来查找此信息。

 > $HADOOP_HOME/bin/hdfs fsck \
 /user/foo/data.in \
 -blocks -files -locations
......
/user/foo/data.in 215227246 bytes, 2 block(s): ....
0\. blk_6981535920477261584_1059 len=134217728 repl=1 [hostname:50010]
1\. blk_-8238102374790373371_1059 len=81009518 repl=1 [hostname:50010]

......

另请参阅

本章中的设置文件复制因子配方。

设置文件复制系数

HDFS 通过将文件分解成粗粒度、固定大小的块来跨群集中存储文件。 这些粗粒度的数据块被复制到不同的 DataNode,主要是出于容错目的。 数据块复制还能够增加 MapReduce 计算的数据局部性,并增加总数据访问带宽。 降低复制系数有助于节省 HDFS 中的存储空间。

HDFS 复制因子是可以按文件设置的文件级属性。 本食谱向您展示如何更改 HDFS 部署的默认复制系数(影响随后将创建的新文件),如何在 HDFS 中创建文件时指定自定义复制系数,以及如何更改 HDFS 中现有文件的复制系数。

怎么做……

按照以下说明使用 NameNode 配置设置文件复制因子:

  1. $HADOOP_HOME/etc/hadoop/hdfs-site.xml中添加或修改dfs.replication属性。 此更改不会更改 HDFS 中已有文件的复制系数。 只有更改后复制的文件才具有新的复制因子。 请注意,降低复制系数会降低存储文件的可靠性,在处理该数据时还可能导致性能下降。

    <property>
            <name>dfs.replication</name>
            <value>2</value>
    </property>
    
  2. 上传文件时设置文件复制因子。 您可以在从命令行上传文件时指定复制因子,如下所示:

    $ hdfs dfs \
     -Ddfs.replication=1 \
     -copyFromLocal \
     non-critical-file.txt /user/foo
    
    
  3. 更改现有文件路径的文件复制系数。 setrep命令可用于通过以下方式更改 HDFS 中已存在的文件或文件路径的复制因子:

    $ hdfs dfs \
     -setrep 2 non-critical-file.txt
    
    Replication 2 set: hdfs://myhost:9000/user/foo/non-critical-file.txt
    
    

它是如何工作的.

看一下下面的命令:

hdfs dfs -setrep [-R] <path>

setrep命令的<path>参数指定必须更改复制因子的 HDFS 路径。 –R选项递归地设置目录中文件和目录的复制因子。

还有更多...

使用ls命令列出文件时,会显示文件的复制系数:

$ hdfs fs -ls
Found 1 item
-rw-r--r-- 2 foo supergroup ... /user/foo/non-critical-file.txt

文件的复制系数也会显示在监控 UI 的 HDFS 中。

另请参阅

本章中的设置 HDFS 块大小配方。

使用 HDFS Java API

HDFS Java API可用于从任何 Java 程序与 HDFS 交互。 此 API 使我们能够从其他 Java 程序利用存储在 HDFS 中的数据,并使用其他非 Hadoop 计算框架处理这些数据。 有时,您可能还会遇到希望从 MapReduce 应用中直接访问 HDFS 的用例。 但是,如果您直接从 Map 或 Reduce 任务在 HDFS 中写入或修改文件,请注意您违反了 MapReduce 的无副作用特性,这可能会导致基于您的使用案例的数据一致性问题。

怎么做……

以下步骤显示了如何使用 HDFS Java API 通过 Java 程序在 HDFS 安装上执行文件系统操作:

  1. 下面的示例程序在 HDFS 中创建一个新文件,在新创建的文件中写入一些文本,然后从 HDFS 读回该文件:

    import java.io.IOException;
    
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.FSDataInputStream;
    import org.apache.hadoop.fs.FSDataOutputStream;
    import org.apache.hadoop.fs.FileSystem;
    import org.apache.hadoop.fs.Path;
    
    public class HDFSJavaAPIDemo {
      public static void main(String[] args) throws IOException {
        Configuration conf = new Configuration();
        FileSystem fs = FileSystem.get(conf);
        System.out.println(fs.getUri());
    
        Path file = new Path("demo.txt");
    
        if (fs.exists(file)) {
          System.out.println("File exists.");
        } else {
          // Writing to file
          FSDataOutputStream outStream = fs.create(file);
          outStream.writeUTF("Welcome to HDFS Java API!!!");
          outStream.close();
        }
    
        // Reading from file
        FSDataInputStream inStream = fs.open(file);
        String data = inStream.readUTF();
        System.out.println(data);
        inStream.close();
    
        fs.close();
      }
    
  2. 在源库的chapter3文件夹中发出gradle build命令,编译并打包前面的程序。 将在build/libs文件夹中创建hcb-c3-samples.jar文件。

  3. 您可以使用以下命令执行前面的示例。 使用hadoop脚本运行此示例可确保它使用当前配置的 HDFS 和 Hadoop类路径中的必要依赖项。

    $ hadoop jar \
     hcb-c3-samples.jar \
     chapter3.hdfs.javaapi.HDFSJavaAPIDemo
    
    hdfs://yourhost:9000
    Welcome to HDFS Java API!!!
    
    
  4. 使用ls命令列出新创建的文件,如下图所示:

    $ hdfs dfs -ls
    Found 1 items
    -rw-r--r--   3 foo supergroup         20 2012-04-27 16:57 /user/foo/demo.txt
    
    

它是如何工作的.

为了以编程方式与 HDFS 交互,我们首先需要获取当前配置的文件系统的句柄。 为此,我们实例化一个Configuration对象并获得一个FileSystem句柄,它将指向我们运行该程序的 Hadoop 环境的 HDFS NameNode。 本章的配置文件系统对象一节讨论了配置FileSystem对象的几种替代方法:

Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);

FileSystem.create(filePath) 方法在给定的路径中创建一个新文件,并为新创建的文件提供一个FSDataOutputStream对象。 FSDataOutputStream包装java.io.DataOutputStream并允许程序将原始 Java 数据类型写入文件。 如果文件存在,则FileSystem.Create()方法重写。 在本例中,该文件将相对于 HDFS 主目录创建,这将产生类似于/user/<user_name>/demo.txt的路径。 您的 HDFS 主目录必须事先创建。

Path file = new Path("demo.txt");
FSDataOutputStream outStream = fs.create(file);
outStream.writeUTF("Welcome to HDFS Java API!!!");
outStream.close();

FileSystem.open(filepath)打开给定文件的FSDataInputStreamFSDataInputStream包装java.io.DataInputStream并允许程序从文件中读取原始 Java 数据类型。

FSDataInputStream inStream = fs.open(file);
String data = inStream.readUTF();
System.out.println(data);
inStream.close();

还有更多...

HDFS Java API 支持的文件系统操作比我们在前面的示例中使用的多得多。 完整的接口文档可以在http://hadoop.apache.org/docs/current/api/org/apache/hadoop/fs/FileSystem.html找到。

配置文件系统对象

我们也可以从 Hadoop 环境外部使用 HDFS Java API。 在执行此操作时,我们必须显式配置 HDFS NameNode 和端口。 以下是执行该配置的几种方法:

  • 您可以在检索FileSystem对象之前将配置文件加载到configuration对象,如下所示。 确保将所有 Hadoop 和依赖库添加到类路径中。

    Configuration conf = new Configuration();
    conf.addResource(new Path("/etc/hadoop/core-site.xml"));
    conf.addResource(new Path("/etc/hadoop/conf/hdfs-site.xml"));
    FileSystem fileSystem = FileSystem.get(conf);
    
  • 您还可以按如下方式指定 NameNode 和端口。 将NAMENODE_HOSTNAMEPORT替换为 HDFS 安装的 NameNode 的主机名和端口。

    Configuration conf = new Configuration();
    conf.set("fs.defaultFS, "hdfs://NAMENODE_HOSTNAME:PORT");
    FileSystem fileSystem = FileSystem.get(conf);
    

HDFS 文件系统 API 是支持多个文件系统的抽象。 如果前面的程序没有找到有效的 HDFS 配置,它将指向本地文件系统,而不是 HDFS。 您可以使用getUri()函数标识fileSystem对象的当前文件系统,如下所示。 如果它使用的是正确配置的 HDFS,则会产生hdfs://your_namenode:port,如果使用的是本地文件系统,则会产生file:///

fileSystem.getUri();

检索文件的数据块列表

the fileSystem对象的getFileBlockLocations()函数允许您检索 HDFS 中存储的文件的数据块列表,以及存储这些块的主机名和块偏移量。 如果您计划在上使用除 Hadoop MapReduce 之外的框架对文件数据执行任何本地操作,此信息将非常有用。

FileStatus fileStatus = fs.getFileStatus(file);
BlockLocation[] blocks = fs.getFileBlockLocations(
  fileStatus, 0, fileStatus.getLen());

四、开发复杂的 Hadoop MapReduce 应用

在本章中,我们将介绍以下食谱:

  • 选择合适的 Hadoop 数据类型
  • 实现自定义 Hadoop 可写数据类型
  • 实现自定义 Hadoop 密钥类型
  • 从映射器发出不同值类型的数据
  • 为您的输入数据格式选择合适的 Hadoop InputFormat
  • 添加对新输入数据格式的支持-实现自定义 InputFormat
  • 设置 MapReduce 计算结果的格式-使用 Hadoop OutputFormats
  • 从 MapReduce 计算写入多个输出
  • Hadoop 中间数据分区
  • 二次排序-排序可减少输入值
  • 向 MapReduce 作业中的任务广播和分发共享资源-Hadoop DistributedCache
  • 将 Hadoop 与旧式应用配合使用-Hadoop 流
  • 在 MapReduce 作业之间添加依赖关系
  • 用于报告自定义指标的 Hadoop 计数器

简介

本章将向您介绍几个高级 Hadoop MapReduce 功能,这些功能将帮助您开发高度自定义、高效的 MapReduce 应用。

Introduction

上图描述了 Hadoop MapReduce 计算的典型流程。 InputFormat 从 HDFS 读取输入数据,并解析数据以创建map函数的键-值对输入。 InputFormat 还执行数据的逻辑分区,以创建计算的 Map 任务。 典型的 MapReduce 计算为每个输入 HDFS 数据块创建一个 Map 任务。 Hadoop 为每个生成的键-值对调用用户提供的map函数。 正如在第 1 章Hadoop v2入门中提到的,如果提供,可以使用map函数的输出数据调用可选的组合器步骤。

然后,分区程序步骤对 Map 任务的输出数据进行分区,以便将它们发送到相应的 Reduce 任务。 这种分区是使用 Map 任务输出键-值对的键字段执行的,其结果是分区的数量等于 Reduce 任务的数量。 每个 Reduce 任务从 Map 任务获取各自的输出数据分区(也称为改组),并基于键字段对数据执行合并排序。 在调用reduce函数之前,Hadoop 还根据数据的关键字段将输入数据分组到 Reduce 函数。 Reduce 任务的输出键-值对将根据 OutputFormat 类指定的格式写入 HDFS。

在本章中,我们将详细探讨前面提到的 Hadoop MapReduce 计算高级流程的不同部分,并探索每个步骤可用的选项和定制。 首先,您将了解 Hadoop 提供的不同数据类型,以及为 Hadoop MapReduce 计算实现自定义数据类型的步骤。 然后,我们将介绍 Hadoop 提供的不同数据 InputFormats 和 OutputFormats。 接下来,我们将基本了解如何在 Hadoop 中添加对新数据格式的支持,以及从单个 MapReduce 计算中输出多个数据产品的机制。 我们还将探索 Map 输出数据分区,并使用该知识介绍reduce函数输入数据值的二次排序。

除了上面的之外,我们还将讨论其他高级 Hadoop 功能,比如使用DistributedCache分发数据,使用 Hadoop 流功能快速构建 Hadoop 计算的原型,使用 Hadoop 计数器报告计算的自定义指标,以及添加作业依赖项来管理简单的基于 DAG 的 Hadoop MapReduce 计算工作流。

备注

示例代码和数据

本书的示例代码文件位于 gihub 的https://github.com/thilg/hcb-v2。 代码库的chapter4文件夹包含本章的示例源代码文件。

您可以从http://ita.ee.lbl.gov/html/contrib/NASA-HTTP.html下载日志处理示例的数据。 您可以在http://ita.ee.lbl.gov/html/contrib/NASA-HTTP.html中找到此数据结构的说明。 此数据集的一小部分可用于测试,位于chapter4/resources的代码存储库中提供了该数据集的一小部分。

可以通过在代码库的chapter4文件夹中发出gradle build命令来编译示例代码。 Eclipse IDE 的项目文件可以通过在代码库的主文件夹中运行gradle eclipse命令来生成。 IntelliJ IDEA IDE 的项目文件可以通过在代码存储库的主文件夹中运行gradle idea命令来生成。

选择合适的 Hadoop 数据类型

Hadoop 使用可写的基于接口的类作为 MapReduce 计算的数据类型。 这些数据类型在整个 MapReduce 计算流程中使用,从读取输入数据开始,在 Map 和 Reduce 任务之间传输中间数据,最后在写入输出数据时使用。 为输入、中间和输出数据选择适当的Writable数据类型会对 MapReduce 程序的性能和可编程性产生很大影响。

要用作 MapReduce 计算的value数据类型,数据类型必须实现org.apache.hadoop.io.Writable接口。 Writable接口定义在传输和存储数据时 Hadoop 应该如何序列化和反序列化这些值。 为了用作 MapReduce 计算的key数据类型,数据类型必须实现org.apache.hadoop.io.WritableComparable<T>接口。 除了Writable接口的功能之外,WritableComparable接口还定义了如何将此类型的键实例相互比较以进行排序。

备注

Hadoop 的可写入性与 Java 的可序列化

与使用通用 Java 的本机序列化框架相比,Hadoop 基于 Writable 的序列化框架为 MapReduce 程序提供了更高效、更定制的数据序列化和表示。 与 Java 的序列化相反,Hadoop 的 Writable 框架不会写入类型名,每个对象都希望序列化数据的所有客户端都知道序列化数据中使用的类型。 省略类型名称可以使序列化过程更快,并产生可由非 Java 客户端轻松解释的紧凑、随机访问的序列化数据格式。 Hadoop 基于 Writable 的序列化还能够通过重用Writable对象来减少对象创建开销,这在 Java 的本机序列化框架中是不可能的。

怎么做……

以下步骤显示如何配置 Hadoop MapReduce 应用的输入和输出数据类型:

  1. 使用泛型变量

    public class SampleMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
    
      public void map(LongWritable key, Text value,
        Context context) … {
    ……  }
    }
    

    指定映射器的输入(键:LongWritable,值:Text)和输出(键:Text,值:IntWritable)键-值对的数据类型

  2. 使用泛型变量指定 Reducer 的输入(键:Text,值:IntWritable)和输出(键:Text,值:IntWritable)键-值对的数据类型。 Reducer 的输入键-值对数据类型应该与 Mapper 的输出键-值对匹配。

    public class Reduce extends Reducer<Text, IntWritable, Text, IntWritable> {
    
      public void reduce(Text key,
        Iterable<IntWritable> values, Context context) {
      ……  }
    }
    
  3. 使用Job对象指定 MapReduce 计算的输出数据类型,如以下代码片段所示。 这些数据类型将用作 Reducer 和 Mapper 的输出类型,除非您按照步骤 4 专门配置 Mapper 输出类型。

    Job job = new Job(..);
    ….
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    
  4. 或者,当 Mapper 和 Reducer 的输出键-值对具有不同的数据类型时,可以使用以下步骤为映射器的输出键-值对配置不同的数据类型。

    job.setMapOutputKeyClass(Text.class);
    job.setMapOutputValueClass(IntWritable.class);
    

还有更多...

Hadoop 提供了几种原始数据类型,如IntWritableLongWritableBooleanWritableFloatWritableByteWritable,它们是各自 Java 原始数据类型的Writable版本。 我们既可以使用这些类型作为key类型,也可以使用这些类型作为value类型。

下面的是另外几种 Hadoop 内置数据类型,我们既可以用作key类型,也可以用作value类型:

  • Text:此存储 UTF8 文本
  • BytesWritable:这个存储一个字节序列
  • VIntWritableVLongWritable:这些存储变量长度整数值和长整型值
  • NullWritable:此是零长度可写类型,可在不想使用keyvalue类型时使用

以下 Hadoop 内置集合数据类型只能用作value类型:

  • ArrayWritable:这存储了一个属于Writable类型的值数组。 要使用ArrayWritable类型作为 Reducer 输入的value类型,您需要创建ArrayWritable的子类,以指定存储在其中的Writable值的类型。

    public class LongArrayWritable extends ArrayWritable {
      public LongArrayWritable() {
        super(LongWritable.class);
      }
    }
    
  • TwoDArrayWritable:此存储属于相同Writable类型的值的矩阵。 要使用TwoDArrayWritable类型作为 Reducer 输入的value类型,您需要通过创建类似于ArrayWritable类型的TwoDArrayWritable类型的子类来指定存储值的类型。

    public class LongTwoDArrayWritable extends TwoDArrayWritable {
      public LongTwoDArrayWritable() {
        super(LongWritable.class);
      }
    }
    
  • MapWritable:这个存储键-值对的映射。 键和值应为Writable数据类型。 您可以使用MapWritable函数,如下所示。 但是,您应该意识到,由于包含了映射中存储的每个对象的类名,所以MapWritable的序列化增加了轻微的性能损失。

    MapWritable valueMap = new MapWritable();
    valueMap.put(new IntWritable(1),new Text("test"));
    
  • SortedMapWritable:它存储键-值对的排序映射。 键应该实现WritableComparable接口。 SortedMapWritable的用法类似于MapWritable函数。

另请参阅

  • 实现自定义 Hadoop 可写数据类型配方的
    ** 实现自定义 Hadoop 密钥类型配方的*

**# 实现自定义 Hadoop 可写数据类型

在某些用例中,内置数据类型可能都不符合您的需求,或者针对您的用例优化的自定义数据类型可能比 Hadoop 内置数据类型执行得更好。 在这种情况下,我们可以通过实现org.apache.hadoop.io.Writable接口来定义数据类型的序列化格式,从而轻松地编写自定义的Writable数据类型。 基于Writable接口的类型可以用作 Hadoop MapReduce 计算中的value类型。

在本食谱中,我们为 HTTP 服务器日志条目实现一个示例 HadoopWritable数据类型。 对于本示例,我们认为日志条目由五个字段组成:请求主机、时间戳、请求 URL、响应大小和 HTTP 状态代码。 以下是示例日志条目:

192.168.0.2 - - [01/Jul/1995:00:00:01 -0400] "GET /history/apollo/ HTTP/1.0" 200 6245

您可以从ftp://ita.ee.lbl.gov/traces/NASA_access_log_Jul95.gz下载示例 HTTP 服务器日志数据集。

怎么做……

以下是为 HTTP 服务器日志条目实现自定义 HadoopWritable数据类型的步骤:

  1. 编写实现org.apache.hadoop.io.Writable接口的新LogWritable类:

    public class LogWritable implements Writable{
    
      private Text userIP, timestamp, request;
      private IntWritable responseSize, status;
    
      public LogWritable() {
        this.userIP = new Text();
        this.timestamp=  new Text();
        this.request = new Text();
        this.responseSize = new IntWritable();
        this.status = new IntWritable();
      }
      public void readFields(DataInput in) throws IOException {
        userIP.readFields(in);
        timestamp.readFields(in);
        request.readFields(in);
        responseSize.readFields(in);
        status.readFields(in);
      }
    
      public void write(DataOutput out) throws IOException {
        userIP.write(out);
        timestamp.write(out);
        request.write(out);
        responseSize.write(out);
        status.write(out);
      }
    
    ……… // getters and setters for the fields
    }
    
  2. 在 MapReduce 计算中使用新的LogWritable类型作为value类型。 在下面的示例中,我们使用LogWritable类型作为 Map 输出值类型:

    public class LogProcessorMap extends Mapper<LongWritable,
    Text, Text, LogWritable> {
    ….
    }
    
    public class LogProcessorReduce extends Reducer<Text,
    LogWritable, Text, IntWritable> {
    
      public void reduce(Text key,
      Iterable<LogWritable> values, Context context) {
         ……  }
    }
    
  3. 相应地配置作业的输出类型。

    Job job = ……
    ….
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    job.setMapOutputKeyClass(Text.class);
    job.setMapOutputValueClass(LogWritable.class);
    

它是如何工作的.

Writable接口由两个方法readFields()write()组成。 在readFields()方法内部,我们反序列化输入数据并填充Writable对象的字段。

  public void readFields(DataInput in) throws IOException {
    userIP.readFields(in);
    timestamp.readFields(in);
    request.readFields(in);
    responseSize.readFields(in);
    status.readFields(in);
  }

在前面的示例中,我们使用Writable类型作为自定义Writable类型的字段,并使用字段的readFields()方法来反序列化来自DataInput对象的数据。 还可以将 Java 基元数据类型用作Writable类型的字段,并使用DataInput对象的相应读取方法从基础流中读取值,如以下代码片段所示:

int responseSize = in.readInt();
String userIP = in.readUTF();

write()方法内部,我们将Writable对象的字段写入底层流。

  public void write(DataOutput out) throws IOException {
    userIP.write(out);
    timestamp.write(out);
    request.write(out);
    responseSize.write(out);
    status.write(out);
  }

如果您使用 Java 基元数据类型作为Writable对象的字段,则可以使用DataOutput对象的相应写入方法将值写入基础流,如下所示:

out.writeInt(responseSize);
out.writeUTF(userIP);

还有更多...

请在实现自定义Writable数据类型时注意以下问题:

  • 如果要向自定义Writable类添加自定义构造函数,请确保保留默认的空构造函数。
  • TextOutputFormat使用toString()方法序列化keyvalue类型。 如果您使用TextOutputFormat来序列化自定义Writable类型的实例,请确保您的自定义Writable数据类型有一个有意义的toString()实现。
  • 在读取输入数据时,Hadoop 可能会重复使用Writable 类的实例。 在readFields()方法中填充对象时,不应依赖对象的现有状态。

另请参阅

实现自定义 Hadoop 密钥类型配方的

实现自定义 Hadoop 密钥类型

Hadoop MapReducekey类型的实例应该能够相互比较以进行排序。 为了在 MapReduce 计算中用作key类型,HadoopWritable数据类型应该实现org.apache.hadoop.io.WritableComparable<T>接口。 WritableComparable接口扩展了org.apache.hadoop.io.Writable接口,并添加了compareTo()方法来执行比较。

在此配方中,我们修改了实现自定义 Hadoop Writable 数据类型配方的LogWritable数据类型,以实现WritableComparable接口。

怎么做……

以下是为 HTTP 服务器日志条目实现自定义 HadoopWritableComparable数据类型的步骤,该数据类型使用请求主机名和时间戳进行比较。

  1. 修改LogWritable类以实现org.apache.hadoop.io.WritableComparable接口:

    public class LogWritable implements
      WritableComparable<LogWritable> {
    
      private Text userIP, timestamp, request;
      private IntWritable responseSize, status;
    
      public LogWritable() {
        this.userIP = new Text();
        this.timestamp=  new Text();
        this.request = new Text();
        this.responseSize = new IntWritable();
        this.status = new IntWritable();
      }
    
      public void readFields(DataInput in) throws IOException {
        userIP.readFields(in);
        timestamp.readFields(in);
        request.readFields(in);
        responseSize.readFields(in);
        status.readFields(in);
      }
    
      public void write(DataOutput out) throws IOException {
        userIP.write(out);
        timestamp.write(out);
        request.write(out);
        responseSize.write(out);
        status.write(out);
      }
    
      public int compareTo(LogWritable o) {
        if (userIP.compareTo(o.userIP)==0){
             return (timestamp.compareTo(o.timestamp));
        }else return (userIP.compareTo(o.userIP);
      }
    
      public boolean equals(Object o) {
        if (o instanceof LogWritable) {
             LogWritable other = (LogWritable) o;
             return userIP.equals(other.userIP) && timestamp.equals(other.timestamp);
        }
        return false;
      }
    
      public int hashCode()
      {
        Return userIP.hashCode();
      }
       ……… // getters and setters for the fields
    }
    
  2. 在 MapReduce 计算中,可以将LogWritable类型用作key类型或value类型。 在下面的示例中,我们使用LogWritable类型作为映射输出key类型:

    public class LogProcessorMap extends Mapper<LongWritable,
    Text, LogWritable, IntWritable> {
    …
    }
    
    public class LogProcessorReduce extends Reducer<LogWritable,
    IntWritable, Text, IntWritable> {
    
    public void reduce(LogWritablekey,
    Iterable<IntWritable> values, Context context) {
         ……  }
    }
    
  3. 相应地配置作业的输出类型。

    Job job = ……
    …
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    job.setMapOutputKeyClass(LogWritable.class);
    job.setMapOutputValueClass(IntWritable.class);
    

它是如何工作的.

除了Writable接口的readFields()write()方法外,WritableComparable接口还引入了compareTo()方法。 如果此对象小于、等于或大于与其进行比较的对象,则compareTo()方法应返回负整数、零或正整数。 在LogWritable实现中,如果用户的 IP 地址和时间戳相同,我们认为对象相等。 如果对象不相等,我们将决定排序顺序,首先基于用户 IP 地址,然后基于时间戳。

  public int compareTo(LogWritable o) {
    if (userIP.compareTo(o.userIP)==0){
        return (timestamp.compareTo(o.timestamp));
    }else return (userIP.compareTo(o.userIP);
  }

Hadoop 使用HashPartitioner作为默认的分割器实现来计算中间数据到减少器的分布。 HashPartitioner要求 Key 对象的hashCode()方法满足以下两个属性:

  • 跨不同的 JVM 实例提供相同的哈希值
  • 提供哈希值的统一分布

因此,您必须为满足上述两个要求的自定义 Hadoopkey类型实现稳定的hashCode()方法。 在LogWritable实现中,我们使用请求主机名/IP 地址的散列码作为LogWritable实例的散列码。 这确保中间LogWritable数据将根据请求的主机名/IP 地址进行分区。

  public int hashCode()
  {
    return userIP.hashCode();
  }

另请参阅

实现自定义 Hadoop 可写数据类型配方的

从映射器发出不同值类型的数据

从映射器发出属于多个值类型的数据产品在执行 Reducer 端联接时非常有用,而且当我们需要避免使用多个 MapReduce 计算来汇总数据集中不同类型的属性时,也是非常有用的。 但是,Hadoop 减少器不允许多个输入值类型。 在这些场景中,我们可以使用GenericWritable类包装属于不同数据类型的多个value实例。

在这个配方中,我们重用 HTTP 服务器日志条目,分析实现自定义 Hadoop Writable 数据类型配方的示例。 然而,在当前的配方中,我们没有使用自定义数据类型,而是从映射器输出多个值类型。 此示例汇总了从 Web 服务器向特定主机提供服务的总字节数,并输出特定主机请求的以制表符分隔的 URL 列表。 我们使用IntWritable从映射器输出字节数,使用Text输出请求 URL。

怎么做……

以下步骤显示如何实现可以包装IntWritableText数据类型实例的 HadoopGenericWritable数据类型:

  1. 编写一个扩展org.apache.hadoop.io.GenericWritable类的类。 实现getTypes()方法以返回要使用的Writable类的数组。 如果要添加自定义构造函数,请确保还添加了无参数的默认构造函数。

    public class MultiValueWritable extends GenericWritable {
    
      private static Class[] CLASSES =  new Class[]{
        IntWritable.class,
        Text.class
      };
    
      public MultiValueWritable(){
      }
    
      public MultiValueWritable(Writable value){
        set(value);
      }
    
      protected Class[] getTypes() {
        return CLASSES;
      }
    }
    
  2. MultiValueWritable设置为映射器的输出值类型。 用MultiValueWritable类的实例包装映射器的输出Writable值。

    public class LogProcessorMap extends
        Mapper<Object, Text, Text, MultiValueWritable> {
      private Text userHostText = new Text();
      private Text requestText = new Text();
      private IntWritable responseSize = new IntWritable();
    
      public void map(Object key, Text value,
                                  Context context)…{
        ……// parse the value (log entry) using a regex.
        userHostText.set(userHost);
        requestText.set(request);
        bytesWritable.set(responseSize);
    
        context.write(userHostText,
        new MultiValueWritable(requestText));
        context.write(userHostText,
        new MultiValueWritable(responseSize));
      }
    }
    
  3. 将减速器输入值类型设置为MultiValueWritable。 实现reduce()方法以处理多个值类型。

    public class LogProcessorReduce extends
      Reducer<Text,MultiValueWritable,Text,Text> {
      private Text result = new Text();
    
      public void reduce(Text key, Iterable<MultiValueWritable>values, Context context)…{
      int sum = 0;
      StringBuilder requests = new StringBuilder();
      for (MultiValueWritable multiValueWritable : values) {
        Writable writable = multiValueWritable.get();
        if (writable instanceof IntWritable){
          sum += ((IntWritable)writable).get();
        }else{
          requests.append(((Text)writable).toString());
          requests.append("\t");
        }
      }
      result.set(sum + "\t"+requests);
      context.write(key, result);
      }
    }
    
  4. MultiValueWritable设置为此计算的映射输出值类别:

        Job job = …
        job.setMapOutputValueClass(MultiValueWritable.class);
    

它是如何工作的.

GenericWritable实现应该扩展org.apache.hadoop.io.GenericWritable,并且应该通过从getTypes()方法返回一个CLASSES数组来指定一组要包装的Writable值类型。 GenericWritable实现使用指向该类数组的索引序列化和反序列化数据。

  private static Class[] CLASSES =  new Class[]{
    IntWritable.class,
    Text.class
  };

  protected Class[] getTypes() {
    return CLASSES;
  }

在映射器中,使用GenericWritable实现的实例包装每个值:

private Text requestText = new Text();
context.write(userHostText,new MultiValueWritable(requestText));

Reducer 实现必须手动处理不同的值类型。

if (writable instanceof IntWritable){
  sum += ((IntWritable)writable).get();
}else{
  requests.append(((Text)writable).toString());
  requests.append("\t");
}

还有更多...

org.apache.hadoop.io.ObjectWritable是另一个可用于实现与GenericWritable相同目标的类。 ObjectWritable类可以处理 Java 基元类型、字符串和数组,而不需要Writable包装器。 然而,Hadoop 通过在每个序列化条目中写入实例的类名来序列化ObjectWritable实例,这与基于GenericWritable类的实现相比效率低下。

另请参阅

实现自定义 Hadoop 可写数据类型配方的

为您的输入数据格式选择合适的 Hadoop InputFormat

Hadoop 支持通过 InputFormat 处理许多不同格式和类型的数据。 Hadoop MapReduce 计算的 InputFormat 通过解析输入数据为映射器生成键-值对输入。 InputFormat 还执行将输入数据拆分成逻辑分区,实质上确定 MapReduce 计算的 Map 任务的数量,并间接决定 Map 任务的执行位置。 Hadoop 为每个逻辑数据分区生成一个 Map 任务,并使用逻辑拆分的键-值对作为输入调用相应的映射器。

怎么做……

以下步骤说明如何使用基于FileInputFormatKeyValueTextInputFormat作为 Hadoop MapReduce 计算的 InputFormat:

  1. 在本例中,我们将使用Job对象将 Hadoop MapReduce 计算的KeyValueTextInputFormat指定为 InputFormat,如下所示:

    Configuration conf = new Configuration();
    Job job = new Job(conf, "log-analysis");
    ……
    job.SetInputFormatClass(KeyValueTextInputFormat.class)
    
  2. 设置作业的输入路径:

    FileInputFormat.setInputPaths(job, new Path(inputPath));
    

它是如何工作的.

KeyValueTextInputFormat是纯文本文件的一种输入格式,它为输入文本文件的每一行生成一个 key-value 记录。 使用分隔符将输入数据的每一行分成键(文本)和值(文本)对。 默认分隔符是制表符。 如果一行不包含分隔符,则整行将被视为键,值将为空。 我们可以通过设置作业的 Configuration 对象中的属性来指定自定义分隔符,如下所示,其中我们使用逗号字符作为键和值之间的分隔符。

conf.set("key.value.separator.in.input.line", ",");

KeyValueTextInputFormat基于FileInputFormat,它是基于文件的 InputFormats 的基类。 因此,我们使用FileInputFormat类的setInputPaths()方法指定 MapReduce 计算的输入路径。 当使用任何基于FileInputFormat类的 InputFormat 时,我们必须执行此步骤。

FileInputFormat.setInputPaths(job, new Path(inputPath));

通过提供逗号分隔的路径列表,我们可以为 MapReduce 计算提供多个 HDFS 输入路径。 您还可以使用FileInputFormat类的addInputPath()静态方法向计算中添加额外的输入路径。

public static void setInputPaths(JobConf conf,Path... inputPaths)
public static void addInputPath(JobConf conf, Path path)

还有更多...

确保 Mapper 输入数据类型与 MapReduce 计算使用的 InputFormat 生成的数据类型匹配。

以下是 Hadoop 为支持几种常见数据格式而提供的一些 InputFormat 实现:

  • TextInputFormat:此选项用于纯文本文件。 TextInputFormat为输入文本文件的每一行生成键值记录。 对于每一行,键(LongWritable)是文件中该行的字节偏移量,值(Text)是文本行。 TextInputFormat是 Hadoop 的默认 InputFormat。

  • NLineInputFormat:此选项用于纯文本文件。 NLineInputFormat将输入文件拆分为固定行数的逻辑拆分。 当我们希望 Map 任务接收固定数量的行作为输入时,可以使用NLineInputFormat。 类似于TextInputFormat类,为拆分中的每一行生成键(LongWritable)和值(Text)记录。 默认情况下,NLineInputFormat每行创建一个逻辑拆分(和一个映射任务)。 可以按如下方式指定每个拆分的行数(或每个映射任务的键值记录)。 NLineInputFormat为输入文本文件的每一行生成键值记录。

    NLineInputFormat.setNumLinesPerSplit(job,50);
    
  • SequenceFileInputFormat:用于 Hadoop SequenceFile 输入数据。 Hadoop SequenceFiles 将数据存储为二进制键-值对,并支持数据压缩。 当使用 SequenceFile 格式的上一次 MapReduce 计算结果作为 MapReduce 计算的输入时,SequenceFileInputFormat非常有用。 下面是它的子类:

    • SequenceFileAsBinaryInputFormat:这是SequenceInputFormat类的子类,它以原始二进制格式表示键(BytesWritable)和值(BytesWritable)对。
    • SequenceFileAsTextInputFormat:这是SequenceInputFormat类的子类,它将键(Text)和值(Text)对表示为字符串。
  • DBInputFormat:这支持从 SQL 表中读取 MapReduce 计算的输入数据。 DBInputFormat使用记录号作为关键字(LongWritable),使用查询结果记录作为值(DBWritable)。

另请参阅

添加了对新输入数据格式的支持-实现自定义 InputFormat配方

添加对新输入数据格式的支持-实现自定义 InputFormat

Hadoop 使我们能够实现和指定 MapReduce 计算的自定义 InputFormat 实现。 我们可以实现自定义的 InputFormat 实现,以获得对输入数据的更多控制,并支持专有或特定于应用的输入数据文件格式作为 Hadoop MapReduce 计算的输入。 InputFormat 实现应该扩展org.apache.hadoop.mapreduce.InputFormat<K,V>抽象类,覆盖createRecordReader()getSplits()方法。

在本食谱中,我们为 HTTP 日志文件实现一个 InputFormat 和一个 RecordReader。 此 InputFormat 将生成LongWritable个实例作为键,生成LogWritable个实例作为值。

怎么做……

以下是基于FileInputFormat类为 HTTP 服务器日志文件实现自定义 InputFormat 的步骤:

  1. LogFileInputFormat对 HDFS 文件中的数据进行操作。 因此,我们实现了扩展FileInputFormat类的LogFileInputFormat子类:

    public class LogFileInputFormat extends FileInputFormat<LongWritable, LogWritable>{
    
      public RecordReader<LongWritable, LogWritable>createRecordReader(InputSplit arg0,TaskAttemptContext arg1) throws …… {
        return new LogFileRecordReader();
      }
    
    }
    
  2. 实现LogFileRecordReader类:

    public class LogFileRecordReader extends RecordReader<LongWritable, LogWritable>{
    
      LineRecordReader lineReader;
      LogWritable value;
    
      public void initialize(InputSplit inputSplit, TaskAttemptContext attempt)…{
        lineReader = new LineRecordReader();
        lineReader.initialize(inputSplit, attempt);
      }
    
      public boolean nextKeyValue() throws IOException, ..{
        if (!lineReader.nextKeyValue()){
          return false;
      }
    
        String line  =lineReader.getCurrentValue().toString();
        ……………//Extract the fields from 'line' using a regex
    
        value = new LogWritable(userIP, timestamp, request,
            status, bytes);
        return true;
      }
    
      public LongWritable getCurrentKey() throws..{
        return lineReader.getCurrentKey();
      }
    
      public LogWritable getCurrentValue() throws ..{
        return value;
      }
    
      public float getProgress() throws IOException ..{
        return lineReader.getProgress();
      }
    
      public void close() throws IOException {
        lineReader.close();
      }
    }
    
  3. 使用Job对象将LogFileInputFormat指定为 MapReduce 计算的 InputFormat,如下所示。 使用底层的FileInputFormat指定计算的输入路径。

    Job job = ……
    ……
    job.setInputFormatClass(LogFileInputFormat.class);
    FileInputFormat.setInputPaths(job, new Path(inputPath));
    
  4. 确保计算的映射器使用LongWritable作为输入key类型,使用LogWritable作为输入value类型:

    public class LogProcessorMap extendsMapper<LongWritable, LogWritable, Text, IntWritable>{
        public void map(LongWritable key, LogWritable value, Context context) throws ……{
        ………}
    }
    

它是如何工作的.

LogFileInputFormat扩展了FileInputFormat,它为基于 HDFS 文件的 InputFormat 提供了通用拆分机制。 我们覆盖LogFileInputFormat中的createRecordReader()方法,以提供自定义RecordReader实现LogFileRecordReader的一个实例。 或者,我们也可以覆盖FileInputFormat类的isSplitable()方法来控制输入文件是拆分到逻辑分区还是用作整个文件。

Public RecordReader<LongWritable, LogWritable>createRecordReader(InputSplit arg0,TaskAttemptContext arg1) throws …… {
    return new LogFileRecordReader();
}

LogFileRecordReader类扩展了org.apache.hadoop.mapreduce.RecordReader<K,V>抽象类,并在内部使用LineRecordReader来执行输入数据的基本解析。 LineRecordReader从输入数据读取文本行:

    lineReader = new LineRecordReader();
    lineReader.initialize(inputSplit, attempt);

我们在nextKeyValue()方法中对输入数据的日志条目执行自定义解析。 我们使用正则表达式从 HTTP 服务日志条目中提取字段,并使用这些字段填充LogWritable类的一个实例。

  public boolean nextKeyValue() throws IOException, ..{
    if (!lineReader.nextKeyValue())
      return false;

    String line = lineReader.getCurrentValue().toString();
    ……………//Extract the fields from 'line' using a regex

    value = new LogWritable(userIP, timestamp, request, status, bytes);
    return true;
  }

还有更多...

我们可以通过覆盖 InputFormat 类的getSplits()方法来执行输入数据的自定义拆分。 getSplits()方法应该返回InputSplit对象的列表。 InputSplit对象表示输入数据的逻辑分区,并将被分配给单个映射任务。 InputSplit类扩展了InputSplit抽象类,并且应该覆盖getLocations()getLength()方法。 getLength()方法应提供拆分的长度,getLocations()方法应提供物理存储此拆分表示的数据的节点列表。 Hadoop 使用数据本地节点列表进行 Map 任务调度。 我们在前面示例中使用的FileInputFormat类使用org.apache.hadoop.mapreduce.lib.input.FileSplit作为InputSplit实现。

您也可以为非 HDFS 数据编写 InputFormat 实现。 org.apache.hadoop.mapreduce.lib.db.DBInputFormatInputFormat.DBInputFormat支持从 SQL 表读取输入数据的一个示例。

另请参阅

为您的输入数据格式选择合适的 Hadoop InputFormat配方。

格式化 MapReduce 计算结果-使用 Hadoop OutputFormats

通常,MapReduce 计算的输出将被其他应用使用。 因此,以目标应用可以高效使用的格式存储 MapReduce 计算的结果非常重要。 在目标应用可以高效访问的位置存储和组织数据也很重要。 我们可以使用 Hadoop OutputFormat 接口来定义 MapReduce 计算的数据存储格式、数据存储位置和输出数据的组织。 OutputFormat 准备输出位置并提供RecordWriter实现来执行实际的数据序列化和存储。

Hadoop 使用org.apache.hadoop.mapreduce.lib.output.TextOutputFormat<K,V>抽象类作为 MapReduce 计算的默认 OutputFormat。 TextOutputFormat将输出数据的记录写入 HDFS 中的纯文本文件,对每个记录使用单独的行。 TextOutputFormat使用制表符分隔记录的键和值。 TextOutputFormat扩展了FileOutputFormat,它是所有基于文件的输出格式的基类。

怎么做……

以下步骤向您展示了如何使用基于FileOutputFormatSequenceFileOutputFormat作为 Hadoop MapReduce 计算的 OutputFormat。

  1. 在本例中,我们将使用Job对象将org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat<K,V>指定为 Hadoop MapReduce 计算的 OutputFormat,如下所示:

    Job job = ……
    ……
    job.setOutputFormatClass(SequenceFileOutputFormat.class)
    
  2. 设置作业的输出路径:

    FileOutputFormat.setOutputPath(job, new Path(outputPath));
    

它是如何工作的.

SequenceFileOutputFormat将数据序列化到 Hadoop SequenceFiles。 Hadoop SequenceFiles 将数据存储为二进制键-值对,并支持数据压缩。 SequenceFiles 特别适用于存储非文本数据。 如果 MapReduce 计算的输出将作为另一个 Hadoop MapReduce 计算的输入,我们可以使用 SequenceFiles 来存储 MapReduce 计算的结果。

SequenceFileOutputFormat基于FileOutputFormat,它是基于文件的OutputFormat的基类。 因此,我们使用FileOutputFormatsetOutputPath()方法指定 MapReduce 计算的输出路径。 当使用任何基于FileOutputFormat的 OutputFormat 时,我们必须执行此步骤。

FileOutputFormat.setOutputPath(job, new Path(outputPath));

还有更多...

您可以实现自定义 OutputFormat 类,以专有或自定义数据格式写入 MapReduce 计算的输出,和/或通过扩展org.apache.hadoop.mapreduce.OutputFormat<K,V>抽象类将结果存储在 HDFS 以外的存储中。 如果您的 OutputFormat 实现将数据存储在文件系统中,您可以从FileOutputFormat类扩展以使您的工作更轻松。

从 MapReduce 计算写入多个输出

我们可以使用 Hadoop 的MultipleOutputs特性来发出 MapReduce 计算的多个输出。 当我们想要将不同的输出写入不同的文件时,以及除了作业的主输出之外还需要输出其他输出时,此功能非常有用。 MultipleOutputs功能还允许我们为每个输出指定不同的 OutputFormat。

怎么做……

以下步骤显示如何使用MultipleOutputs功能从 Hadoop MapReduce 计算输出两个不同的数据集:

  1. 使用 Hadoop 驱动程序配置并命名多个输出:

    Job job = Job.getInstance(getConf(), "log-analysis");
    …
    FileOutputFormat.setOutputPath(job, new Path(outputPath));
    MultipleOutputs.addNamedOutput(job, "responsesizes", TextOutputFormat.class,Text.class, IntWritable.class);
    MultipleOutputs.addNamedOutput(job, "timestamps", TextOutputFormat.class,Text.class, Text.class);
    
  2. 将数据写入来自reduce函数的不同命名输出:

    public class LogProcessorReduce …{
      private MultipleOutputs mos;
    
      protected void setup(Context context) .. {
        mos = new MultipleOutputs(context);
      }
    
      public void reduce(Text key, … {
        …
        mos.write("timestamps", key, val.getTimestamp());
        …
        mos.write("responsesizes", key, result);
      }
    }
    
  3. 通过将以下内容添加到 Reduce 任务的cleanup函数来关闭所有打开的输出:

    @Override
      public void cleanup(Context context) throws IOException,
        InterruptedException {
          mos.close();
      }
    
  4. 对于写入的每种输出类型,输出文件名将采用namedoutput-r-xxxxx格式。 对于当前示例,示例输出文件名为responsesizes-r-00000timestamps-r-00000

它是如何工作的.

我们首先使用MultipleOutputs类的以下静态方法将命名输出添加到驱动程序中的作业:

public static addNamedOutput(Job job, String namedOutput, Class<? extends OutputFormat> outputFormatClass, Class<?> keyClass, Class<?> valueClass)

然后,我们初始化 Reduce 任务的setup方法中的MultipleOutputs特性,如下所示:

protected void setup(Context context) .. {
  mos = new MultipleOutputs(context);
  }

我们可以使用在驱动程序中定义的名称,使用MultipleOutputs类的以下方法编写不同的输出:

public <K,V> void write (String namedOutput, K key, V value)

您可以使用MultipleOutputs的以下方法直接写入输出路径,而无需定义命名输出。 此输出将使用为作业定义的 OutputFormat 格式化输出。

public void write(KEYOUT key, VALUEOUT value,
  String baseOutputPath)

最后,我们确保使用MultipleOutputs类的 Close 方法关闭 Reduce 任务的cleanup方法的所有输出。 应该这样做,以避免丢失写入不同输出的任何数据。

public void close()

在单个 MapReduce 应用中使用多种输入数据类型和多种 Mapper 实现

我们可以使用 Hadoop 的MultipleInputs特性来运行具有多个输入路径的 MapReduce 作业,同时为每个路径指定不同的 InputFormat 和(可选)Mapper。 Hadoop 会将不同映射器的输出路由到 MapReduce 计算的 Single Reducer 实现的实例。 当我们想要处理具有相同含义但不同 InputFormats(逗号分隔的数据集和制表符分隔的数据集)的多个数据集时,具有不同 InputFormats 的多个输入非常有用。

我们可以使用MutlipleInputs类的以下addInputPath静态方法将输入路径和各自的 InputFormats 添加到 MapReduce 计算中:

Public static void addInputPath(Job job, Path path, Class<?extendsInputFormat>inputFormatClass)

以下是上述方法的用法示例:

MultipleInputs.addInputPath(job, path1, CSVInputFormat.class);
MultipleInputs.addInputPath(job, path1, TabInputFormat.class);

多输入功能能够指定不同的映射器实现,InputFormats在执行两个或多个数据集的减边联接时非常有用:

public static void addInputPath(JobConfconf,Path path,
     Class<?extendsInputFormat>inputFormatClass,
     Class<?extends Mapper>mapperClass)

下面的是通过不同的 InputFormats 和不同的 Mapper 实现使用多个输入的示例:

MultipleInputs.addInputPath(job, accessLogPath, TextInputFormat.class, AccessLogMapper.class);
MultipleInputs.addInputPath(job, userDataPath, TextInputFormat.class, UserDataMapper.class);

另请参阅

增加了对新输入数据格式的支持-实现了自定义的 InputFormat配方。

Hadoop 中间数据分区

Hadoop MapReduce 跨计算的 Reduce 任务对Map 任务生成的中间数据进行分区。 确保个 Reduce 任务负载均衡的适当分区函数对 MapReduce 计算的性能至关重要。 分区还可以用于将相关记录集组合在一起,以执行特定的 Reduce 任务,在这些任务中,您希望将某些输出处理或分组在一起。 本章简介部分中的图描述了分区在哪里适合整个 MapReduce 计算流程。

Hadoop 根据中间数据的密钥空间对中间数据进行分区,并决定哪个 Reduce 任务将接收哪个中间记录。 分区的已排序键集及其值将是 Reduce 任务的输入。 在 Hadoop 中,分区总数应该等于 MapReduce 计算的 Reduce 任务数。 Hadoop 分割器应该扩展org.apache.hadoop.mapreduce.Partitioner<KEY,VALUE>抽象类。 Hadoop 使用org.apache.hadoop.mapreduce.lib.partition.HashPartitioner作为 MapReduce 计算的默认分区程序。 HashPartiator使用公式key.hashcode()mod r根据键的hashcode()对键进行分区,其中r是 Reduce 任务的数量。 下图说明了具有两个 Reduce 任务的计算的HashPartitioner

Hadoop intermediate data partitioning

可能会有这样的场景:我们的计算逻辑需要或可以使用应用的特定数据分区模式更好地实现。 在本食谱中,我们为 HTTP 日志处理应用实现了一个自定义分区程序,它根据键(IP 地址)的地理区域对键(IP 地址)进行分区。

怎么做……

以下步骤显示如何实现自定义分区程序,该程序根据请求 IP 地址或主机名的位置对中间数据进行分区:

  1. 实现扩展Partitioner抽象类的IPBasedPartitioner类:

    public class IPBasedPartitioner extends Partitioner<Text, IntWritable>{
    
      public int getPartition(Text ipAddress,
        IntWritable value, int numPartitions) {
        String region = getGeoLocation(ipAddress);
    
        if (region!=null){
          return ((region.hashCode() & Integer.MAX_VALUE) % numPartitions);
        }
      return 0;
      }
    }
    
  2. 设置Job对象中的Partitioner类参数:

    Job job = ……
    ……
    job.setPartitionerClass(IPBasedPartitioner.class);
    

它是如何工作的.

在前面的示例中,我们对中间数据执行分区,以便将来自相同地理区域的请求发送到相同的 Reducer 实例。 方法的作用是:返回给定 IP 地址的地理位置。 我们省略了getGeoLocation()方法的实现细节,因为它对于理解此示例不是必需的。 然后,我们获得地理位置的hashCode()方法,并执行模运算来选择请求的 Reducer 存储桶。

还有更多...

TotalOrderPartitionerKeyFieldPartitioner是 Hadoop 提供的几个内置分区程序实现中的两个。

Колибрипрограммирования

TotalOrderPartitioner扩展org.apache.hadoop.mapreduce.lib.partition.TotalOrderPartitioner<K,V>。 到还原器的输入记录集合按排序顺序,确保输入分区内的正确排序。 然而,Hadoop 默认分区策略(HashPartitioner)在对中间数据进行分区时不强制排序,并将键分散在分区之间。 在我们希望确保全局订单的用例中,我们可以使用TotalOrderPartitioner强制执行总订单,以减少 Reducer 任务中的输入记录。 TotalOrderPartitioner需要分区文件作为定义分区范围的输入。 org.apache.hadoop.mapreduce.lib.partition.InputSampler实用程序允许我们通过采样输入数据为TotalOrderPartitioner生成分区文件。 TotalOrderPartitioner用于 Hadoop TeraSort 基准测试。

Job job = ……
……
job.setPartitionerClass(TotalOrderPartitioner.class);
TotalOrderPartitioner.setPartitionFile(jobConf,partitionFile);

KeyFieldBasedPartiator

org.apache.hadoop.mapreduce.lib.partition.KeyFieldBasedPartitioner<K,V>抽象类可用于根据键的部分划分中间数据。 可以使用分隔符字符串将关键字拆分为一组字段。 我们可以指定分区时要考虑的字段集的索引。 我们还可以指定字段中字符的索引。

二次排序-排序减少输入值

MapReduce 框架根据键-值对中的键对 Reduce 输入数据进行排序,还根据键对数据进行分组。 Hadoop 以键的排序顺序为每个唯一键调用reduce函数,并将属于该键的值列表作为第二个参数。 但是,每个键的值列表不会按任何特定顺序排序。 在许多情况下,根据某些条件对每个键的 Reduce 输入值列表进行排序也会很有用。 这些示例包括在不迭代整个列表的情况下查找给定键的最大值或最小值、优化缩减端连接、识别重复的数据产品,等等。

我们可以使用 Hadoop 框架通过一种称为辅助排序的机制对约简输入值进行排序。 我们通过强制 Hadoop 框架使用键以及使用值中的几个指定字段对 Reduce 输入键-值对进行排序来实现这一点。 但是,Map 输出数据的分区和 Reduce 输入数据的分组仍然仅使用键执行。 这确保了 Reduce 输入数据按键分组和排序,而属于某个键的值列表也将按排序顺序排列。

怎么做……

以下步骤说明如何在 Hadoop MapReduce 计算中对 Reduce 输入值执行二次排序:

  1. 首先,实现一个WritableComparable数据类型,该类型可以包含需要包含在排序顺序中的值中的实际键(visitorAddress)和字段(responseSize)。 此新复合类型的比较器应强制排序顺序,其中首先是实际键,然后是从此新类型中包含的值字段派生的排序条件。 我们使用此复合类型作为 Map Output 键类型。 或者,您也可以使用现有的WritableComparable类型作为 Map Output Key 类型,它包含实际键和值中的其他字段,方法是为该数据类型提供类似的实现以强制排序。

    public class SecondarySortWritable … {
      private String visitorAddress;
      private int responseSize;
      ………
      @Override
      public boolean equals(Object right) {
        if (right instanceof SecondarySortWritable) {
          SecondarySortWritable r = (SecondarySortWritable) right;
          return (r.visitorAddress.equals(visitorAddress) && (r.responseSize == responseSize));
        } else {
          return false;
        }
      }
    
      @Override
      public int compareTo(SecondarySortWritable o) {
        if (visitorAddress.equals(o.visitorAddress)) {
          return responseSize < o.responseSize ? -1 : 1;
        } else {
          return visitorAddress.compareTo(o.visitorAddress);
        }
      }
    }
    
  2. 修改mapreduce函数以使用我们创建的复合键:

    public class LogProcessorMap extends Mapper<Object, LogWritable, SecondarySortWritable, LogWritable > {
      private SecondarySortWritable outKey ..
    
      public void map(Object key, …..{
        outKey.set(value.getUserIP().toString(), value.getResponseSize().get());
        context.write(outKey,value);
      }
    }
    
    public class LogProcessorReduce extends
      Reducer<SecondarySortWritable, LogWritable..{
      ……
      public void reduce(SecondarySortWritable key, Iterable<LogWritable> values {
        ……
      }
    }
    
  3. 实现自定义分区程序,以便仅根据复合键

    public class SingleFieldPartitioner extends… {
      public int getPartition(SecondarySortWritable key, Writable value, int numPartitions) {
        return (int)(key.getVisitorAddress().hashCode() % numPartitions);
      }
    }
    

    中包含的实际键(visitorAddress)对映射输出数据进行分区

  4. 实现自定义分组比较器,以便仅根据实际键(visitorAddress)对 Reduce 输入进行分组:

    public class GroupingComparator extends WritableComparator {
      public GroupingComparator() {
        super(SecondarySortWritable.class, true);
      }
    
      @Override
      public int compare(WritableComparable o1, WritableComparable o2) {
        SecondarySortWritable firstKey = (SecondarySortWritable) o1;
        SecondarySortWritable secondKey = (SecondarySortWritable) o2;
        return (firstKey.getVisitorAddress()).compareTo(secondKey.getVisitorAddress());
      }
    
  5. 在驱动程序中配置分区程序GroupingComparator和映射输出键类型:

    Job job = Job.getInstance(getConf(), "log-analysis");
    ……
    job.setMapOutputKeyClass(SecondarySortWritable.class);
    ……
    
    // group and partition by the visitor address
    job.setPartitionerClass(SingleFieldPartitioner.class);
    job.setGroupingComparatorClass(GroupingComparator.class);
    

它是如何工作的.

我们首先实现了一个自定义的WritableComparable键类型,该类型将保存值的实际键和排序字段。 我们确保这个新复合键类型的排序顺序为实际键,后跟来自值的排序字段。 这将确保 Reduce 输入数据首先根据实际键排序,然后是值的给定字段。

然后,我们实现了一个自定义分区程序,它只根据来自新复合键的实际键字段对 Map 输出数据进行分区。 此步骤确保具有相同实际密钥的每个键-值对将由相同的 Reducer 处理。 最后,我们实现了一个分组比较器,在对减少的输入键-值对进行分组时,它将只考虑新键的实际键字段。 这确保每个reduce函数输入将是新的复合键以及属于实际键的值列表。 值列表将按照复合键的比较器中定义的排序顺序排列。

另请参阅

增加了对新输入数据格式的支持-实现了自定义的 InputFormat配方。

向 MapReduce 作业中的任务广播和分发共享资源-Hadoop DistributedCache

我们可以使用 HadoopDistributedCache将基于文件的只读资源分发到 Map 和 Reduce 任务。 这些资源可以是映射器或缩减器执行计算所需的简单数据文件、存档或 JAR 文件。

怎么做……

以下步骤向您展示了如何将文件添加到 Hadoop DistributedCache,以及如何从 Map 和 Reduce 任务中检索该文件:

  1. 将资源复制到 HDFS。 您还可以使用 HDFS 中已有的文件。

    $ hadoop fs –copyFromLocal ip2loc.dat ip2loc.dat
    
    
  2. 将资源从您的驱动程序添加到 DistributedCache:

    Job job = Job.getInstance……
    ……
    job.addCacheFile(new URI("ip2loc.dat#ip2location"));
    
  3. 检索 Mapper 或 Reducer 的setup()方法中的资源,并使用map()reduce()函数中的数据:

    public class LogProcessorMap extends Mapper<Object, LogWritable, Text, IntWritable> {
      private IPLookup lookupTable;
    
      public void setup(Context context) throws IOException{
    
        File lookupDb = new File("ip2location");
        // Load the IP lookup table (a simple hashmap of ip
        // prefixes as keys and country names as values) to
        // memory
        lookupTable = IPLookup.LoadData(lookupDb);
      }
    
      public void map(…) {
         String country = lookupTable.getCountry(value.ipAddress);
         ……
      }
    }
    

它是如何工作的.

Hadoop 在执行作业的任何任务之前将添加到 DistributedCache 的文件复制到所有工作节点。 DistributedCache 在每个作业中只复制这些文件一次。 Hadoop 还支持通过将具有所需符号链接名称的片段添加到 URI 来创建指向计算工作目录中的 DistributedCache 文件的符号链接。 在以下示例中,我们使用ip2location作为指向 DistributedCache 中的ip2loc.dat文件的符号链接:

job.addCacheArchive(new URI("/data/ip2loc.dat#ip2location"));

我们在 Mapper 或 Reducer 的setup()方法中解析并加载 DistributedCache 中的数据。 可以使用提供的符号链接的名称从工作目录访问带有符号链接的文件。

private IPLookup lookup;
public void setup(Context context) throws IOException{

  File lookupDb = new File("ip2location");
  // Load the IP lookup table to memory
  lookup = IPLookup.LoadData(lookupDb);
}

public void map(…) {
  String location =lookup.getGeoLocation(value.ipAddress);
  ……
}

我们还可以使用getLocalCacheFiles()方法直接访问 DistributedCache 中的数据,而不使用符号链接:

URI[] cacheFiles = context.getCacheArchives();

备注

DistributedCache 在 Hadoop 本地模式下不工作。

还有更多...

以下节介绍如何使用 DistributedCache 分发压缩存档,如何使用命令行将资源添加到 DistributedCache,以及如何使用 DistributedCache 将资源添加到映射器和 Reducer 的类路径。

使用 DistributedCache 分发档案

我们也可以使用 DistributedCache 分发档案。 Hadoop 提取工作节点中的存档。 您还可以使用 URI 片段提供指向存档的符号链接。 在下一个示例中,我们使用ip2locationdb符号链接作为ip2locationdb.tar.gz存档。

考虑以下 MapReduce 驱动程序:

Job job = ……
job.addCacheArchive(new URI("/data/ip2locationdb.tar.gz#ip2locationdb"));

可以使用前面提供的符号链接从 Mapper 或 Reducer 的工作目录访问提取的归档目录:

请考虑以下映射器程序:

  public void setup(Context context) throws IOException{
    Configuration conf = context.getConfiguration();

    File lookupDbDir = new File("ip2locationdb");
    String[] children = lookupDbDir.list();

    …
  }

您还可以在 Mapper 或 Reducer 实现中使用以下方法直接访问未解压的 DistributedCache 存档文件:

URI[] cachePath;

public void setup(Context context) throws IOException{
  Configuration conf = context.getConfiguration();
  cachePath = context.getCacheArchives();
    …
}

从命令行向 DistributedCache 添加资源

Hadoop 支持使用命令行将文件或档案添加到 DistributedCache,前提是您的 MapReduce 驱动程序实现了org.apache.hadoop.util.Tool接口或利用了org.apache.hadoop.util.GenericOptionsParser。 文件可以使用–files命令行选项添加到 DistributedCache,而归档文件可以使用–archives命令行选项添加。 文件或存档可以位于 Hadoop 可访问的任何文件系统中,包括您的本地文件系统。

这些选项支持逗号分隔的路径列表和使用 URI 片段创建符号链接。

$ hadoop jar C4LogProcessor.jar LogProcessor-files ip2location.dat#ip2location  indir outdir
$ hadoop jar C4LogProcessor.jar LogProcessor-archives ip2locationdb.tar.gz#ip2locationdb indir outdir

使用 DistributedCache 将资源添加到类路径

您可以使用 DistributedCache 将 JAR 文件和其他依赖库分发到 Mapper 或 Reducer。 您可以在驱动程序中使用以下方法将 JAR 文件添加到运行 Mapper 或 Reducer 的 JVM 的类路径中:

public static void addFileToClassPath(Path file,Configuration conf,FileSystem fs)

public static void addArchiveToClassPath(Path archive,Configuration conf, FileSystem fs)

与我们在从命令行小节向 DistributedCache 添加资源中描述的–files–archives命令行选项类似,我们还可以使用–libjars命令行选项将 JAR 文件添加到 MapReduce 计算的类路径中。 为了使–libjars命令行选项工作,MapReduce 驱动程序应该实现Tool接口或者应该利用GenericOptionsParser

$ hadoop jar C4LogProcessor.jar LogProcessor-libjars ip2LocationResolver.jar  indir outdir

将 Hadoop 与旧式应用配合使用-Hadoop 流

Hadoop 流允许我们使用任何可执行文件或脚本作为 Hadoop MapReduce 作业的 Mapper 或 Reducer。 Hadoop Streaming 使我们能够使用 Linux shell 实用程序或脚本语言执行 MapReduce 计算的快速原型。 Hadoop 流还允许具有一定 Java 知识或没有 Java 知识的用户利用 Hadoop 处理存储在 HDFS 中的数据。

在本食谱中,我们使用 Python 为我们的 HTTP 日志处理应用实现了一个映射器,并使用了一个基于 Hadoop 聚集包的 Reducer。

怎么做……

以下是使用 Python 程序作为映射器处理 HTTP 服务器日志文件的步骤:

  1. 编写logProcessor.pyPython 脚本:

    #!/usr/bin/python
    import sys
    import re
    def main(argv):
      regex =re.compile('……')
      line = sys.stdin.readline()
      try:
        while line:
          fields = regex.match(line)
          if(fields!=None):
            print"LongValueSum:"+fields.group(1)+
              "\t"+fields.group(7)
          line =sys.stdin.readline()
      except"end of file":
        return None
    if __name__ == "__main__":
      main(sys.argv)
    
  2. 从 Hadoop 安装目录使用以下命令执行 MapReduce 流计算:

    $ hadoop jar \
     $HADOOP_MAPREDUCE_HOME/hadoop-streaming-*.jar \
     -input indir \
     -output outdir \
     -mapper logProcessor.py \
     -reducer aggregate \
     -file logProcessor.py
    
    

它是如何工作的.

每个 Map 任务在 Worker 节点中将 Hadoop Streaming 可执行文件作为单独的进程启动。 映射器的输入记录(日志文件的条目或行,未拆分为键-值对)以行的形式提供给该进程的标准输入。 可执行文件应该读取并处理标准输入中的记录,直到到达文件末尾。

line = sys.stdin.readline()
try:
    while line:
      ………
      line =sys.stdin.readline()
except "end of file":
    return None

Hadoop Streaming 从进程的标准输出中收集可执行文件的输出。 Hadoop Streaming 将标准输出的每一行转换为键-值对,其中直到第一个制表符的文本被视为键,而该行的其余部分被视为值。 根据此约定,logProcessor.pypython 脚本输出键-值对,如下所示:

If (fields!=None):
      print "LongValueSum:"+fields.group(1)+ "\t"+fields.group(7);

在我们的示例中,我们使用 Hadoopaggregate包进行计算的约简部分。 Hadoopaggregate包为简单的聚合操作(如求和、最大值、唯一值计数和直方图)提供了 Reducer 和 Composer 实现。 当与 Hadoop 流一起使用时,映射器输出必须将当前计算的聚合操作类型指定为输出键的前缀,在我们的示例中是LongValueSum

Hadoop 流还支持使用–file选项将文件分发到工作节点。 我们可以使用此选项来分发流计算所需的可执行文件、脚本或任何其他辅助文件。 我们可以为一次计算指定多个–file选项。

$ hadoop jar …… \
 -mapper logProcessor.py \
 -reducer aggregate \
 -file logProcessor.py

还有更多...

我们可以指定 Java 类作为 Hadoop 流计算的 Mapper 和/或 Reducer 和/或组合器程序。 我们还可以为 Hadoop 流计算指定 InputFormat 和其他选项。

Hadoop Streaming 还允许我们使用 Linux shell 实用程序作为 Mapper 和 Reducer。 下面的示例显示了如何使用grep作为 Hadoop 流计算的映射器。

$ hadoop jar 
 $HADOOP_MAPREDUCE_HOME/hadoop-streaming-*.jar \
 –input indir \
 -output outdir \ 
 -mapper 'grep "wiki"'

Hadoop 流逐行向执行可执行文件的进程的标准输入提供每个键组的 Reducer 输入记录。 然而,Hadoop 流没有一种机制来区分它何时开始向进程提供新密钥的记录。 因此,Reducer 程序的脚本或可执行文件应该跟踪最后看到的输入记录的键,以便在键组之间进行划分。

有关 Hadoop 流的详细文档,请参阅http://hadoop.apache.org/mapreduce/docs/stable1/streaming.html

另请参阅

使用 Hadoop 流的数据预处理和使用 Hadoop 流的 Python重复数据消除第 10 章海量文本数据处理中介绍。

在 MapReduce 作业之间添加依赖关系

通常我们需要以类似于工作流的方式执行多个 MapReduce 应用来实现我们的目标。 HadoopControlledJobJobControl类通过指定 MapReduce 作业之间的依赖关系,提供了一种执行 MapReduce 作业的简单工作流图的机制。

在本配方中,我们先对 HTTP 服务器日志数据集执行log-grepMapReduce 计算,然后执行log-analysisMapReduce 计算。 log-grep计算根据正则表达式过滤输入数据。 log-analysis计算分析过滤后的数据。 因此,log-analysis计算依赖于log-grep计算。 我们使用ControlledJob类来表示这种依赖关系,并使用JobControl类来执行两个相关的 MapReduce 计算。

怎么做……

以下步骤说明如何将 MapReduce 计算添加为另一个 MapReduce 计算的依赖项:

  1. 为第一个 MapReduce 作业创建ConfigurationJob对象,并使用其他所需配置填充它们:

    Job job1 = ……
    job1.setJarByClass(RegexMapper.class);
    job1.setMapperClass(RegexMapper.class);
    FileInputFormat.setInputPaths(job1, new Path(inputPath));
    FileOutputFormat.setOutputPath(job1, new Path(intermedPath));
    ……
    
  2. 为第二个 MapReduce 作业创建ConfigurationJob对象,并使用必要的配置填充它们:

    Job job2 = ……
    job2.setJarByClass(LogProcessorMap.class);
    job2.setMapperClass(LogProcessorMap.class);
    job2.setReducerClass(LogProcessorReduce.class);
    FileOutputFormat.setOutputPath(job2, new Path(outputPath));
    ………
    
  3. 将第一个作业的输出目录设置为第二个作业的输入目录:

    FileInputFormat.setInputPaths(job2, new Path(intermedPath +"/part*"));
    
  4. 使用先前创建的Job对象创建ControlledJob对象:

    ControlledJob controlledJob1 = new ControlledJob(job1.getConfiguration());
    ControlledJob controlledJob2 = new ControlledJob(job2.getConfiguration());
    
  5. 将第一个作业作为依赖项添加到第二个作业:

    controlledJob2.addDependingJob(controlledJob1);
    
  6. 为这组作业创建JobControl对象,并将在步骤 4 中创建的ControlledJob对象添加到新创建的JobControl对象:

    JobControl jobControl = new  JobControl("JobControlDemoGroup");
    jobControl.addJob(controlledJob1);
    jobControl.addJob(controlledJob2);
    
  7. 创建一个新线程来运行添加到JobControl对象的作业组。 启动线程并等待其完成:

        Thread jobControlThread = new Thread(jobControl);
        jobControlThread.start();
        while (!jobControl.allFinished()){
          Thread.sleep(500);
        }
        jobControl.stop();
    

它是如何工作的.

ControlledJob类封装 MapReduce 作业并跟踪作业的依赖关系。 具有相关作业的ControlledJob类只有在其所有相关作业都成功完成时才会准备好提交。 如果任何相关作业失败,则ControlledJob类将失败。

JobControl类封装了一组ControlledJobs及其依赖项。 JobControl跟踪封装的ControlledJobs的状态,并包含提交处于就绪状态的作业的线程。

如果要使用 MapReduce 作业的输出作为从属作业的输入,则必须手动设置从属作业的输入路径。 默认情况下,Hadoop 为每个 Reduce 任务名称生成一个带有part前缀的输出文件夹。 我们可以使用通配符将所有part前缀的子目录指定为相关作业的输入。

FileInputFormat.setInputPaths(job2, new Path(job1OutPath +"/part*"));

还有更多...

我们还可以使用JobControl类来执行和跟踪一组非依赖任务。

ApacheOozie是一个用于 Hadoop MapReduce 计算的工作流系统。 您可以使用 Oozie 执行 MapReduce 计算的有向无环图(DAG)。 您可以从该项目的主页http://oozie.apache.org/找到有关 Oozie 的更多信息。

Hadoop MapReduce API 的旧版本中提供了ChainMapper类,它允许我们在管道中的单个 Map 任务计算中执行 Mapper 类的管道。 ChainReducer为 Reduce 任务提供了类似的支持。 出于向后兼容的原因,该 API 仍然存在于 Hadoop2 中。

用于报告自定义指标的 Hadoop 计数器

Hadoop 使用一组计数器来聚合 MapReduce 计算的指标。 Hadoop 计数器有助于了解 MapReduce 程序的行为,并跟踪 MapReduce 计算的进度。 我们可以定义自定义计数器来跟踪 MapReduce 计算中特定于应用的指标。

怎么做……

以下步骤向您展示了如何定义自定义计数器来计算日志处理应用中损坏或损坏的记录数:

  1. 使用enum

      public static enum LOG_PROCESSOR_COUNTER {
         BAD_RECORDS
        };
    

    定义自定义计数器列表

  2. 在映射器或还原器中递增计数器:

    context.getCounter(LOG_PROCESSOR_COUNTER.BAD_RECORDS).increment(1);
    
  3. 将以下内容添加到您的驱动程序中以访问计数器:

    Job job = new Job(getConf(), "log-analysis");
    ……
    Counters counters = job.getCounters();
    Counter badRecordsCounter = counters.findCounter(LOG_PROCESSOR_COUNTER.BAD_RECORDS);
    System.out.println("# of Bad Records:"+ badRecordsCounter.getValue());
    
  4. 执行 Hadoop MapReduce 计算。 您还可以在管理控制台或命令行中查看计数器值。

    $ hadoop jar C4LogProcessor.jar \
     demo.LogProcessor in out 1
    ………
    12/07/29 23:59:01 INFO mapred.JobClient: Job complete: job_201207271742_0020
    12/07/29 23:59:01 INFO mapred.JobClient: Counters: 30
    12/07/29 23:59:01 INFO mapred.JobClient:   demo.LogProcessorMap$LOG_PROCESSOR_COUNTER
    12/07/29 23:59:01 INFO mapred.JobClient:   BAD_RECORDS=1406
    12/07/29 23:59:01 INFO mapred.JobClient:   Job Counters
    ………
    12/07/29 23:59:01 INFO mapred.JobClient:     Map output records=112349
    # of Bad Records :1406
    
    

它是如何工作的.

您必须使用enum定义您的自定义计数器。 enum中的计数器集合将形成一组计数器。 ApplicationMaster 聚合映射器和减少器报告的计数器值。**

五、分析

在本章中,我们将介绍以下食谱:

  • 使用 MapReduce 进行简单分析
  • 使用 MapReduce 执行分组
  • 使用 MapReduce 计算频率分布和排序
  • 使用 gnplot 绘制 Hadoop MapReduce 结果
  • 使用 MapReduce 计算直方图
  • 使用 MapReduce 计算散点图
  • 使用 Hadoop 解析复杂数据集
  • 使用 MapReduce 连接两个数据集

简介

在本章中,我们将讨论如何使用 Hadoop 处理数据集并了解其基本特征。 在后面的章节中,我们将介绍更复杂的方法,如数据挖掘、分类、聚类等等。

本章将介绍如何使用给定的数据集计算基本分析。 对于本章中的食谱,我们将使用两个数据集:

  • 可在http://ita.ee.lbl.gov/html/contrib/NASA-HTTP.html获得的 NASA 网络日志数据集是使用 NASA 网络服务器收到的请求收集的真实数据集。 您可以在此链接中找到此数据结构的说明。 在代码存储库的chapter5/resources文件夹中提供了可用于测试的该数据集的一个小摘录。
  • 可从http://tomcat.apache.org/mail/dev/获得的 Apache Tomcat 开发人员电子邮件归档列表。 这些档案是 mbox 格式的。

备注

本章的内容基于本书的上一版 Hadoop MapReduce Cookbook 的分析。 这一章是由合著者斯里纳特·佩雷拉(Srinath Perera)贡献的。

提示

示例代码

本书的示例代码文件位于 gihub 的https://github.com/thilg/hcb-v2。 代码库的chapter5文件夹包含本章的示例源代码文件。

可以通过在代码库的chapter5文件夹中发出gradle build命令来编译示例代码。 Eclipse IDE 的项目文件可以通过在代码库的主文件夹中运行gradle eclipse命令来生成。 IntelliJ IDEA IDE 的项目文件可以通过在代码存储库的主文件夹中运行gradle idea命令来生成。

使用 MapReduce 进行简单分析

聚合指标(如平均值、最大值、最小值、标准偏差等)可提供数据集的基本概况。 您可以对整个数据集、数据集的子集或样本执行这些计算。

在本食谱中,我们将使用 Hadoop MapReduce 通过处理 Web 服务器的日志来计算 Web 服务器提供的文件的最小、最大和平均大小。 下图显示了此计算的执行流程:

Simple analytics using MapReduce

如图所示,Map函数发出文件大小作为值,字符串msgSize作为键。 我们使用单个 Reduce 任务,所有中间键-值对都将发送到该 Reduce 任务。 然后,Reduce函数使用 Map 任务发出的信息计算聚合值。

做好准备

本配方假定您对 Hadoop MapReduce 的处理工作方式有基本的理解。 如果没有,请按照第 1 章《Hadoop v2入门》中的编写字数 MapReduce 应用、捆绑它并使用 Hadoop 本地模式在分布式集群环境中使用 Hadoop v2食谱设置 Hadoop Yarn。 您还需要安装可以正常工作的 Hadoop。

怎么做……

以下步骤介绍如何使用 MapReduce 计算有关博客数据集的简单聚合指标:

  1. ftp://ita.ee.lbl.gov/traces/NASA_access_log_Jul95.gz下载博客数据集并将其解压缩。 让我们将提取的位置称为<DATA_DIR>

  2. 通过运行以下命令将提取的数据上传到 HDFS:

    $ hdfs dfs -mkdir data
    $ hdfs dfs -mkdir data/weblogs
    $ hdfs dfs –copyFromLocal \
    <DATA_DIR>/NASA_access_log_Jul95 \
    data/weblogs
    
    
  3. 通过从源代码存储库的chapter5文件夹运行gradle build命令,编译本章的示例源代码。

  4. 使用以下命令运行 MapReduce 作业:

    $ hadoop jar hcb-c5-samples.jar \
    chapter5.weblog.MsgSizeAggregateMapReduce \
    data/weblogs data/msgsize-out
    
    
  5. 通过运行以下命令读取结果:

    $ hdfs dfs -cat data/msgsize-out/part*
    ….
    Mean    1150
    Max     6823936
    Min     0
    
    

它是如何工作的.

您可以从chapter5/src/chapter5/weblog/MsgSizeAggregateMapReduce.java中找到此配方的源文件。

HTTP 日志遵循如下标准模式。 最后一个标记是提供的网页的大小:

205.212.115.106 - - [01/Jul/1995:00:00:12 -0400] "GET /shuttle/countdown/countdown.html HTTP/1.0" 200 3985

我们将使用 Java 正则表达式来解析日志行,类顶部的Pattern.compile()方法定义正则表达式。 在编写文本处理 Hadoop 计算时,正则表达式是一个非常有用的工具:

public void map(Object key, Text value, Context context) … {
  Matcher matcher = httplogPattern.matcher(value.toString());
  if (matcher.matches()) {
    int size = Integer.parseInt(matcher.group(5));
    context.write(new Text("msgSize"), new IntWritable(size));
  }
}

映射任务将日志文件中的每一行作为不同的键-值对接收。 它使用正则表达式解析行,并发出以msgSize为键的值的文件大小。

然后,Hadoop 收集来自 Map 任务的所有输出键-值对,并调用 Reduce 任务。 Reducer 迭代所有值并计算从 Web 服务器提供的文件的最小、最大和平均大小。 值得注意的是,通过将值作为迭代器提供,Hadoop 允许我们在不将数据存储在内存中的情况下处理数据,从而允许减法器扩展到大型数据集。 只要有可能,您就应该处理reduce函数输入值,而不将它们存储在内存中:

public void reduce(Text key, Iterable<IntWritable> values,…{
  double total = 0;
  int count = 0;
  int min = Integer.MAX_VALUE;
  int max = 0;

  Iterator<IntWritable> iterator = values.iterator();
  while (iterator.hasNext()) {
    int value = iterator.next().get();
    total = total + value;
    count++;
    if (value < min)
      min = value;

    if (value > max)
      max = value;
  }
  context.write(new Text("Mean"),
    new IntWritable((int) total / count));
  context.write(new Text("Max"), new IntWritable(max));
  context.write(new Text("Min"), new IntWritable(min));
}

作业的main()方法与 wordcount 示例类似,不同之处在于突出显示的行已更改为以容纳输出数据类型:

job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);

还有更多...

您可以从 Java 教程http://docs.oracle.com/javase/tutorial/essential/regex/了解更多关于 Java 正则表达式的信息。

使用 MapReduce 执行分组

这个配方展示了如何使用 MapReduce 将数据分组到简单的组中,并计算每个组的指标。 我们还将在此食谱中使用 Web 服务器的日志数据集。 此计算类似于select page, count(*) from weblog_table group by pageSQL 语句。 下图显示了此计算的执行流程:

Performing GROUP BY using MapReduce

如图所示,Map任务发送请求的 URL 路径作为关键字。 然后,Hadoop 根据键对中间数据进行排序和分组。 给定键的所有值将被提供给单个 Reduce 函数调用,该函数将计算该 URL 路径的出现次数。

做好准备

本食谱假设您对 Hadoop MapReduce 处理的工作方式有基本的了解。

怎么做……

以下步骤显示了如何对 Web 服务器日志数据进行分组并计算分析:

  1. ftp://ita.ee.lbl.gov/traces/NASA_access_log_Jul95.gz下载博客数据集并将其解压缩。 让我们将提取的位置称为<DATA_DIR>

  2. 通过运行以下命令将提取的数据上传到 HDFS:

    $ hdfs dfs -mkdir data
    $ hdfs dfs -mkdir data/weblogs
    $ hdfs dfs –copyFromLocal \
    <DATA_DIR>/NASA_access_log_Jul95 \
    data/weblogs
    
    
  3. 通过从源代码存储库的chapter5文件夹运行gradle build命令,编译本章的示例源代码。

  4. 使用以下命令运行 MapReduce 作业:

    $ hadoop jar hcb-c5-samples.jar \
    chapter5.weblog.HitCountMapReduce \
    data/weblogs data/hit-count-out
    
    
  5. Read the results by running the following command:

    $ hdfs dfs -cat data/hit-count-out/part*
    
    

    您将看到它将按如下方式打印结果:

    /base-ops/procurement/procurement.html  28
    /biomed/                                1
    /biomed/bibliography/biblio.html        7
    /biomed/climate/airqual.html            4
    /biomed/climate/climate.html            5
    /biomed/climate/gif/f16pcfinmed.gif     4
    /biomed/climate/gif/f22pcfinmed.gif     3
    /biomed/climate/gif/f23pcfinmed.gif     3
    /biomed/climate/gif/ozonehrlyfin.gif    3
    
    

它是如何工作的.

您可以从chapter5/src/chapter5/HitCountMapReduce.java中找到此食谱的来源。

如前面的食谱所述,我们将使用正则表达式来解析 Web 服务器日志并提取请求的 URL 路径。 例如,/shuttle/countdown/countdown.html将从以下示例日志条目中提取:

205.212.115.106 - - [01/Jul/1995:00:00:12 -0400] "GET /shuttle/countdown/countdown.html HTTP/1.0" 200 3985

以下代码段显示了映射器:

private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object key, Text value, Context context) …… {
  Matcher matcher = httplogPattern.matcher(value.toString());
  if (matcher.matches()) {
    String linkUrl = matcher.group(4);
    word.set(linkUrl);
    context.write(word, one);
  }
}

Map 任务将日志文件中的每一行作为个不同的键-值对接收。 映射任务使用正则表达式解析行,并发出链接作为键,数字one作为值。

然后,Hadoop 收集不同键(链接)的所有值,并为每个链接调用一次 Reducer。 然后,每个 Reducer 计算每个链接的点击数:

private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values,… {
  int sum = 0;
  for (IntWritable val : values) {
    sum += val.get();
  }
  result.set(sum);
  context.write(key, result);
}

使用 MapReduce 计算频率分布和排序

频率分布是按升序排序的每个 URL 收到的命中数。 我们已经计算了前面配方中每个 URL 的点击数。 该配方将根据命中次数对该列表进行排序。

做好准备

本食谱假设您已经安装了可以正常工作的 Hadoop。 此配方将通过使用本章的 MapReduce 配方使用执行组的结果。 如果你还没有这样做,那就遵循这个食谱。

怎么做……

以下步骤说明如何使用 MapReduce 计算频率分布:

  1. 使用以下命令运行 MapReduce 作业。 我们假设data/hit-count-out路径包含上一个配方

    $ bin/hadoop jar hcb-c5-samples.jar \
    chapter5.weblog.FrequencyDistributionMapReduce \
    data/hit-count-out data/freq-dist-out
    
    

    HitCountMapReduce计算的输出

  2. Read the results by running the following command:

    $ hdfs dfs -cat data/freq-dist-out/part*
    
    

    您将看到它将打印类似于以下内容的结果:

    /cgi-bin/imagemap/countdown?91,175      12
    /cgi-bin/imagemap/countdown?105,143     13
    /cgi-bin/imagemap/countdown70?177,284   14
    
    

它是如何工作的.

本章的执行组使用 MapReduce配方计算每个 URL 路径收到的命中数。 MapReduce 在调用reduce函数之前按键对 Map 输出的中间键-值对进行排序。 在本菜谱中,我们使用此排序功能根据命中次数对数据进行排序。

您可以从chapter5/src/chapter5/FrequencyDistributionMapReduce.java中找到此食谱的来源。

映射任务输出命中次数作为键,输出 URL 路径作为值:

public void map(Object key, Text value, Context context) …… {
  String[] tokens = value.toString().split("\\s");
  context.write(new IntWritable(Integer.parseInt(tokens[1])),
  new Text(tokens[0]));
}

Reduce 任务接收按键(命中次数)排序的键-值对:

public void reduce(IntWritable key, Iterable<Text> values, …… {
  Iterator<Text> iterator = values.iterator();
  while (iterator.hasNext()) {
  context.write(iterator.next(), key);
  }
}

为了确保结果的全局排序,我们在此计算中只使用了一个 Reduce 任务。

还有更多...

通过利用 HadoopTotalOrderPartitioner,即使使用多个 Reduce 任务,也可以实现全局排序。 有关TotalOrderPartitioner的更多信息,请参考开发复杂 Hadoop MapReduce 应用的Hadoop 中间数据分区配方。

使用 gnplot 绘制 Hadoop MapReduce 结果

虽然 Hadoop MapReduce 作业可以生成有趣的分析,但理解这些结果并详细了解数据通常需要我们看到数据的总体趋势。 人眼非常擅长检测模式,绘制数据往往能加深对数据的理解。 因此,我们经常使用绘图程序来绘制 Hadoop 作业的结果。

本食谱解释了如何使用 gnplot,这是一个免费且功能强大的绘图程序,用于绘制 Hadoop 结果。

做好准备

本方法假定您遵循了前面的方法,即计算频率分布并使用 MapReduce进行排序。 如果你还没有做到这一点,请遵循下面的食谱。 按照http://www.gnuplot.info/中的说明安装 GNOUPLOT 绘图程序。

怎么做……

以下步骤显示了如何使用 gnplot 绘制 Hadoop 作业结果:

  1. 通过运行以下命令将上一个配方的结果下载到本地计算机:

    $ hdfs dfs -copyToLocal data/freq-dist-out/part-r-00000 2.data
    
    
  2. chapter5/plots文件夹中的所有*.plot文件复制到下载数据的位置。

  3. 通过运行以下命令生成绘图:

    $ gnuplot httpfreqdist.plot
    
    
  4. It will generate a file called freqdist.png, which will look like the following:

    How to do it...

前面的图是以对数-对数比例绘制的,分布的第一部分遵循Zipf(幂律)分布,这是 Web 中常见的分布。 最后几个最受欢迎的链接的费率比 Zipf 发行版的预期要高得多。

有关此发行版的更多细节的讨论超出了本书的范围。 然而,这个曲线图展示了我们通过绘制分析结果可以获得的洞察力。 在未来的大多数食谱中,我们将使用 gnplot 来绘制和分析结果。

它是如何工作的.

以下步骤描述了使用 gnplot 进行绘图的工作原理:

  • 您可以从chapter5/plots/httpfreqdist.plot找到 gnplot 文件的源代码。 绘图的来源如下所示:

    set terminal png
    set output "freqdist.png"
    
    set title "Frequnecy Distribution of Hits by Url";
    set ylabel "Number of Hits";
    set xlabel "Urls (Sorted by hits)";
    set key left top
    set log y
    set log x
    
    plot"2.data" using 2 title "Frequency" with linespoints
    
  • 这里,前两行定义了输出格式。 本例使用 PNG,但 gnplot 支持许多其他终端,如 Screen、PDF、EPS 等。

  • 接下来的四行定义了轴标签和标题。

  • 接下来的两条线定义了每个轴的比例,此图对两个轴都使用对数比例。

  • 最后一行定义了情节。 这里,它要求 gnplot 从2.data文件中读取数据,通过using 2使用文件第二列中的数据,并使用线条绘制它。 列之间必须用空格分隔。

  • 在这里,如果您想要绘制一列与另一列的关系图,例如,列1中的数据与列2之间的关系,则应编写using 1:2而不是using 2

还有更多...

您可以从http://www.gnuplot.info/了解有关 gnplot 的更多信息。

使用 MapReduce 计算直方图

数据集的另一个有趣的视图是直方图。 直方图仅在连续维度(例如,访问时间和文件大小)下的有意义。 它将事件的发生次数分组到维度中的多个组中。 例如,在这个食谱中,如果我们将访问时间作为维度,那么我们将按小时对访问时间进行分组。

下图显示了此计算的执行摘要。 映射器发出访问小时作为键,1作为值。 然后,每个reduce函数调用接收一天中个特定小时的所有事件,并计算该小时的总事件数。

Calculating histograms using MapReduce

做好准备

本食谱假设您已经安装了可以正常工作的 Hadoop。 安装 gnplot。

怎么做……

以下步骤显示如何计算和绘制直方图:

  1. ftp://ita.ee.lbl.gov/traces/NASA_access_log_Jul95.gz下载博客数据集并将其解压。

  2. 通过运行以下命令将提取的数据上传到 HDFS:

    $ hdfs dfs -mkdir data
    $ hdfs dfs -mkdir data/weblogs
    $ hdfs dfs –copyFromLocal \
    <DATA_DIR>/NASA_access_log_Jul95 \
    data/weblogs
    
    
  3. 通过从源代码存储库的chapter5文件夹运行gradle build命令,编译本章的示例源代码。

  4. 使用以下命令运行 MapReduce 作业:

    $ hadoop jar hcb-c5-samples.jar \
    chapter5.weblog.HistogramGenerationMapReduce \
    data/weblogs data/histogram-out
    
    
  5. 通过运行以下命令检查结果:

    $ hdfs dfs -cat data/histogram-out/part*
    
    
  6. 通过运行以下命令将结果下载到本地计算机:

    $ hdfs dfs -copyToLocal data/histogram-out/part-r-00000 3.data
    
    
  7. chapter5/plots文件夹中的所有*.plot文件复制到下载数据的位置。

  8. 通过运行以下命令生成绘图:

    $gnuplot httphistbyhour.plot
    
    
  9. It will generate a file called hitsbyHour.png, which will look like the following:

    How to do it...

它是如何工作的.

您可以从chapter5/src/chapter5/weblog/HistogramGenerationMapReduce.java中找到此食谱的源代码。 与本章前面的配方类似,我们使用正则表达式来解析日志文件,并从日志文件中提取访问时间。

下面的代码段显示了map函数:

public void map(Object key, Text value, Context context) … {
  try {
    Matcher matcher = httplogPattern.matcher(value.toString());
    if (matcher.matches()) {
      String timeAsStr = matcher.group(2);
      Date time = dateFormatter.parse(timeAsStr);
      Calendar calendar = GregorianCalendar.getInstance();
      calendar.setTime(time);
      int hour = calendar.get(Calendar.HOUR_OF_DAY);
      context.write(new IntWritable(hour), one);
    }
  } ……
}

map函数提取每个网页访问的访问时间,并从访问时间中提取一天中的小时数。 它发出一天中的小时作为键,one作为值。

然后,Hadoop 收集所有键-值对,对它们进行排序,然后为每个键调用一次 Reduce 函数。 减少任务计算每小时的总页面浏览量:

public void reduce(IntWritable key, Iterable<IntWritable> values,..{
  int sum = 0;
  for (IntWritable val : values) {
    sum += val.get();
  }
  context.write(key, new IntWritable(sum));
}

使用 MapReduce 计算散点图

分析数据时的另一个有用工具是散点图,它可用于查找两个测量(尺寸)之间的关系。 它将这两个维度相互对比。

例如,此配方分析数据以找出网页大小和网页接收到的点击量之间的关系。

下图显示了此计算的执行摘要。 这里,map函数计算并发出消息大小(四舍五入为 1024 字节)作为键,one作为值。 然后,Reducer 计算每个邮件大小的出现次数:

Calculating Scatter plots using MapReduce

做好准备

本食谱假设您已经安装了可以正常工作的 Hadoop。 安装 gnplot。

怎么做……

以下步骤说明如何使用 MapReduce 计算两个数据集之间的相关性:

  1. ftp://ita.ee.lbl.gov/traces/NASA_access_log_Jul95.gz下载博客数据集并将其解压。

  2. 通过运行以下命令将提取的数据上传到 HDFS:

    $ hdfs dfs -mkdir data
    $ hdfs dfs -mkdir data/weblogs
    $ hdfs dfs –copyFromLocal \
    <DATA_DIR>/NASA_access_log_Jul95 \
    data/weblogs
    
    
  3. 通过从源代码存储库的chapter5文件夹运行gradle build命令,编译本章的示例源代码。

  4. 使用以下命令运行 MapReduce 作业:

    $ hadoop jar hcb-c5-samples.jar \
    chapter5.weblog.MsgSizeScatterMapReduce \
    data/weblogs data/scatter-out
    
    
  5. 通过运行以下命令检查结果:

    $ hdfs dfs -cat data/scatter-out/part*
    
    
  6. 通过从HADOOP_HOME运行以下命令将上一个配方的结果下载到本地计算机:

    $ hdfs dfs –copyToLocal data/scatter-out/part-r-00000 5.data
    
    
  7. chapter5/plots文件夹中的所有*.plot文件复制到下载数据的位置。

  8. 通过运行以下命令生成绘图:

    $ gnuplot httphitsvsmsgsize.plot
    
    
  9. It will generate a file called hitsbymsgSize.png, which will look like the following image:

    How to do it...

图显示命中次数与日志范围中消息大小之间的负相关关系。

它是如何工作的.

您可以从chapter5/src/chapter5/MsgSizeScatterMapReduce.java中找到食谱的来源。

以下代码段显示了map函数:

public void map(Object key, Text value, Context context) …… {
  Matcher matcher = httplogPattern.matcher(value.toString());
  if (matcher.matches()) {
    int size = Integer.parseInt(matcher.group(5));
    context.write(new IntWritable(size / 1024), one);
  }
}

映射任务解析日志条目,并发出以千字节为关键字、以one为值的文件大小。

每个 Reducer 遍历值,并计算每个文件大小的页面访问次数:

public void reduce(IntWritable key, Iterable<IntWritable> values,……{
  int sum = 0;
  for (IntWritable val : values) {
    sum += val.get();
  }
  context.write(key, new IntWritable(sum));
}

使用 Hadoop 解析复杂数据集

到目前为止,我们使用的个数据集在行中包含一个数据项,这使得我们可以使用 Hadoop 默认解析支持来解析这些数据集。 但是,某些数据集具有更复杂的格式,其中单个数据项可能跨越多行。 在本食谱中,我们将分析 Tomcat 开发人员的邮件列表归档。 在存档中,每封电子邮件都由多行存档文件组成。 因此,我们将编写一个自定义 Hadoop InputFormat 来处理电子邮件归档。

此配方解析复杂的电子邮件列表归档,并找到所有者(启动该线程的人)和每个电子邮件线程收到的回复数量。

下图显示了此计算的执行摘要。 Map函数发出邮件主题作为关键字,将发送者的电子邮件地址与日期相结合作为值。 然后,Hadoop 按照电子邮件主题对数据进行分组,并将与该线程相关的所有数据发送到同一 Reducer。

Parsing a complex dataset with Hadoop

然后,Reduce 任务标识每个电子邮件线程的创建者以及每个线程接收的回复数量。

做好准备

本食谱假设您已经安装了可以正常工作的 Hadoop。

怎么做……

以下步骤介绍如何通过编写输入格式化程序,使用 Hadoop 解析数据格式复杂的 Tomcat 电子邮件列表数据集:

  1. http://tomcat.apache.org/mail/dev/下载并解压缩 2012 年的 Apache Tomcat 开发人员列表电子邮件存档。 我们将目标文件夹称为DATA_DIR

  2. 通过运行以下命令将提取的数据上传到 HDFS:

    $ hdfs dfs -mkdir data
    $ hdfs dfs -mkdir data/mbox
    $ hdfs dfs –copyFromLocal \
    <DATA_DIR>/* \
    data/mbox
    
    
  3. 通过从源代码存储库的chapter5文件夹运行gradle build命令,编译本章的示例源代码。

  4. 使用以下命令运行 MapReduce 作业:

    $ hadoop jar hcb-c5-samples.jar \
    chapter5.mbox.CountReceivedRepliesMapReduce \
    data/mbox data/count-replies-out
    
    
  5. 通过运行以下命令检查结果:

    $ hdfs dfs -cat data/count-replies-out/part*
    
    

它是如何工作的.

正如前面解释的那样,此数据集具有跨多行的数据项。 因此,我们必须编写自定义 InputFormat 和自定义 RecordReader 来解析数据。 此配方的源代码文件是源代码存档的chapter5/src/chapter5/mbox目录中的CountReceivedRepliesMapReduce.javaMBoxFileInputFormat.javaMBoxFileReader.java文件。

我们通过 Hadoop 驱动程序将新的 InputFormat 添加到 Hadoop 作业中,如以下代码片段中突出显示的那样:

Job job = Job.getInstance(getConf(), "MLReceiveReplyProcessor");
job.setJarByClass(CountReceivedRepliesMapReduce.class);
job.setMapperClass(AMapper.class);
job.setReducerClass(AReducer.class);
job.setNumReduceTasks(numReduce);

job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
job.setInputFormatClass(MBoxFileInputFormat.class);
FileInputFormat.setInputPaths(job, new Path(inputPath));
FileOutputFormat.setOutputPath(job, new Path(outputPath));

int exitStatus = job.waitForCompletion(true) ? 0 : 1;

如以下代码所示,新的格式化程序创建了一个 RecordReader,Hadoop 使用它来读取 Map 任务的键-值对输入:

public class MboxFileFormat extends FileInputFormat<Text, Text>{
  private MBoxFileReaderboxFileReader = null;
  public RecordReader<Text, Text> createRecordReader(
  InputSplit inputSplit, TaskAttemptContext attempt) …{
    fileReader = new MBoxFileReader();
    fileReader.initialize(inputSplit, attempt);
    return fileReader;
  }
}

下面的代码片段显示了 RecordReader 的功能:

public class MBoxFileReader extends RecordReader<Text, Text> {

  public void initialize(InputSplitinputSplit, … {
    Path path = ((FileSplit) inputSplit).getPath();
    FileSystem fs = FileSystem.get(attempt.getConfiguration());
    FSDataInputStream fsStream = fs.open(path);
    reader = new BufferedReader(new InputStreamReader(fsStream));
  }
  public Boolean nextKeyValue() ……{
    if (email == null) {
    return false;
  }
  count++;
  while ((line = reader.readLine()) != null) {
    Matcher matcher = pattern1.matcher(line);
    if (!matcher.matches()) {
      email.append(line).append("\n");
    } else {
      parseEmail(email.toString());
      email = new StringBuffer();
      email.append(line).append("\n");
      return true;
    }
  }
  parseEmail(email.toString());
  email = null;
  return true;
}
………

RecordReader 的nextKeyValue()方法解析文件,并生成键-值对以供 Map 任务使用。 每个值具有由#字符分隔的每个电子邮件的、主题时间

以下代码片段显示了 Map 任务源代码:

public void map(Object key, Text value, Context context) …… {
  String[] tokens = value.toString().split("#");
  String from = tokens[0];
  String subject = tokens[1];
  String date = tokens[2].replaceAll(",", "");
  subject = subject.replaceAll("Re:", "");
  context.write(new Text(subject), new Text(date + "#" + from));
}

映射任务将存档文件中的每封电子邮件作为单独的键-值对接收。 它通过用#打断行来解析它,并发出subject作为键,timefrom作为值。

然后,Hadoop 收集所有键-值对,对它们进行排序,然后为每个键调用 Reducer 一次。 由于我们使用电子邮件主题作为键,因此每次 Reduce 函数调用都将接收有关单个电子邮件线程的所有信息。 然后,Reduce 功能将分析一个线程的所有电子邮件,并找出谁发送了第一封电子邮件,每个邮件线程收到了多少回复,如下所示:

public void reduce(Text key, Iterable<Text> values, …{
  TreeMap<Long, String>replyData = new TreeMap<Long, String>();

  for (Text val : values) {
    String[] tokens = val.toString().split("#");
    if(tokens.length != 2)
    throw new IOException("Unexpected token "+ val.toString());

    String from = tokens[1];
    Date date = dateFormatter.parse(tokens[0]);
    replyData.put(date.getTime(), from);
  }

  String owner = replyData.get(replyData.firstKey());
  Int replyCount = replyData.size();

  Int selfReplies = 0;
  for(String from: replyData.values()){
    if(owner.equals(from)){
      selfReplies++;
    }
  }
  replyCount = replyCount - selfReplies;
  context.write(new Text(owner),
  new Text(replyCount+"#" + selfReplies));
}

还有更多...

有关实现自定义 InputFormats 的更多信息,请参考第 4 章开发复杂 Hadoop MapReduce 应用添加对新输入数据格式的支持-实现自定义 InputFormat配方。

使用 MapReduce 连接两个数据集

正如我们已经观察到的,Hadoop 在读取数据集和计算分析方面非常擅长。 但是,我们经常需要合并两个数据集来分析数据。 本食谱将解释如何使用 Hadoop 连接两个数据集。

例如,本食谱将使用 Tomcat 开发人员档案数据集。 开放源码社区中的一个共同信念是,开发人员参与社区的程度越高(例如,通过回复项目邮件列表中的电子邮件线程和帮助他人等等),他们就会越快地收到对他们的查询的响应。 在本食谱中,我们将使用 Tomcat 开发人员邮件列表来验证这一假设。

为了验证这一假设,我们将运行 MapReduce 作业,如下图所述:

Joining two datasets using MapReduce

我们将使用 Mbox 格式的电子邮件存档,并使用前面菜谱中解释的自定义 InputFormat 和 RecordReader 来解析它们。 映射任务将接收电子邮件发件人(发件人)、电子邮件主题和电子邮件发送时间作为输入。

  1. 在第一个作业中,map函数将发出主题作为键,发送者的电子邮件地址和时间作为值。 然后,Reducer 步骤将接收具有相同主题的所有值,并输出主题作为键,所有者和回复计数作为值。 我们在上一份食谱中执行了这项工作。
  2. 在第二个作业中,map函数发出发送者的电子邮件地址作为键,one作为值。 然后,Reducer 步骤将接收从同一地址发送到同一 Reducer 的所有电子邮件。 使用此数据,每个 Reducer 将发出电子邮件地址作为关键字,并将从该电子邮件地址发送的电子邮件数量作为值。
  3. 最后,第三个作业读取前两个作业的输出,连接结果,并发出每个电子邮件地址发送的电子邮件数和每个电子邮件地址接收的回复数作为输出。

做好准备

本食谱假设您已经安装了可以正常工作的 Hadoop。 遵循使用 Hadoop配方解析复杂数据集。 我们将在以下步骤中使用该配方的输入数据和输出数据。

怎么做……

以下步骤显示如何使用 MapReduce 连接两个数据集:

  1. 按照使用 Hadoop配方解析复杂数据集来运行CountReceivedRepliesMapReduce计算。

  2. 使用以下命令运行第二个 MapReduce 作业:

    $ hadoop jar hcb-c5-samples.jar \
    chapter5.mbox.CountSentRepliesMapReduce \
    data/mbox data/count-emails-out
    
    
  3. 使用以下命令检查结果:

    $ hdfs dfs -cat data/count-emails-out/part*
    
    
  4. 创建一个新文件夹join-input,并将早期作业的两个结果复制到 HDFS 中的该文件夹:

    $ hdfs dfs -mkdir data/join-input
    $ hdfs dfs -cp \
    data/count-replies-out/part-r-00000 \
    data/join-input/1.data
    $ hdfs dfs -cp \
    data/count-emails-out/part-r-00000 \
    data/join-input/2.data
    
    
  5. 使用以下命令运行第三个 MapReduce 作业:

    $ hadoop jar hcb-c5-samples.jar \
    chapter5.mbox.JoinSentReceivedReplies \
    data/join-input data/join-out
    
    
  6. 通过运行以下命令将步骤 5 的结果下载到本地计算机:

    $ hdfs dfs -copyToLocal data/join-out/part-r-00000 8.data
    
    
  7. chapter5/plots文件夹中的所有*.plot文件复制到下载数据的位置。

  8. 通过运行以下命令生成绘图:

    $ gnuplot sendvsreceive.plot
    
    
  9. It will generate a file called sendreceive.png, which will look like the following:

    How to do it...

图证实了我们的假设,和前面的一样,数据近似遵循幂律分布。

它是如何工作的.

您可以在chapter5/src/chapter5/mbox/CountSentRepliesMapReduce.javachapter5/src/chapter5/mbox/JoinSentReceivedReplies.java中找到此食谱的源代码。 我们已经讨论了前面食谱中的第一项工作。

下面的代码片段显示了第二个作业的map函数。 它接收发件人的电子邮件、主题和时间(以#分隔)作为输入,解析输入并输出发件人的电子邮件作为关键字,将电子邮件发送的时间作为值:

public void map(Object key, Text value, Context context) ……{
  String[] tokens = value.toString().split("#");
  String from = tokens[0];
  String date = tokens[2];
  context.write(new Text(from), new Text(date));
}

下面的代码片段显示了第二个作业的reduce函数。 每个reduce函数调用接收一个发送者发送的所有电子邮件的时间。 Reducer 计算每个发送者发送的回复数,输出发送者的名称作为关键字,并输出发送的回复数作为值:

public void reduce(Text key, Iterable<Text> values, ……{
  int sum = 0;
  for (Text val : values) {
    sum = sum +1;
  }
  context.write(key, new IntWritable(sum));
}

下面的代码片段显示了第三个作业的map函数。 它读取第一个和第二个作业的输出,并将它们输出为键-值对:

public void map(Object key, Text value, …… {
  String[] tokens = value.toString().split("\\s");
  String from = tokens[0];
  String replyData = tokens[1];
  context.write(new Text(from), new Text(replyData));
}

下面的代码片段显示了第三个作业的reduce函数。 由于第一个和第二个作业的输出具有相同的键,因此给定用户发送的回复数和接收的回复数将由同一 Reducer 处理。 reduce函数删除自回复,并将发送的回复数和接收的回复数作为键和值输出,从而连接两个数据集:

public void reduce(Text key, Iterable<Text> values, …… {
  StringBuffer buf = new StringBuffer("[");
  try {
    int sendReplyCount = 0;
    int receiveReplyCount = 0;
    for (Text val : values) {
      String strVal = val.toString();
      if(strVal.contains("#")){
        String[] tokens = strVal.split("#");
        int repliesOnThisThread =Integer.parseInt(tokens[0]);
        int selfRepliesOnThisThread = Integer.parseInt(tokens[1]);
        receiveReplyCount = receiveReplyCount + repliesOnThisThread;
        sendReplyCount = sendReplyCount–selfRepliesOnThisThread;
      }else{
        sendReplyCount = sendReplyCount + Integer.parseInt(strVal);
      }
    }

    context.write(new IntWritable(sendReplyCount),
    new IntWritable(receiveReplyCount));
    buf.append("]");
  } …
}

最后一个作业是使用 MapReduce 连接两个数据集的示例。 其思想是将需要在同一关键字下联接的所有值发送到同一 Reducer,并在那里联接数据。

六、Hadoop 生态系统——ApacheHive

在本章中,我们将介绍以下食谱:

  • Apache 配置单元入门
  • 使用配置单元 CLI 创建数据库和表
  • 使用 Apache 配置单元进行简单的 SQL 样式数据查询
  • 使用配置单元查询结果创建和填充配置单元表和视图
  • 在配置单元中使用不同的存储格式-使用 ORC 文件存储表数据
  • 使用配置单元内置函数
  • 配置单元批处理模式-使用查询文件
  • 执行与配置单元的联接
  • 创建分区配置单元表
  • 编写配置单元用户定义函数(UDF)
  • HCatalog-对映射到配置单元表的数据执行 Java MapReduce 计算
  • HCatalog-将数据从 Java MapReduce 计算写入配置单元表

简介

Hadoop 有一系列项目,这些项目要么构建在 Hadoop 之上,要么与 Hadoop 紧密合作。 这些项目已经形成了一个专注于大规模数据处理的生态系统,通常,用户可以组合使用这些项目中的几个来解决他们的用例。 本章介绍 Apache Have,它在 HDFS 中存储的数据之上提供数据仓库功能。 第 7 章Hadoop 生态系统 II--Pig、HBase、Mahout 和 Sqoop介绍了 Hadoop 生态系统中的其他几个关键项目。

Apache Have 提供了另一个高级语言层,用于使用 Hadoop 执行大规模数据分析。 HIVE 允许用户将 HDFS 中存储的数据映射到表格模型,并使用 HiveQL(类似 SQL 的语言层)对其进行处理,以使用 Hadoop 查询非常大的数据集。 HiveQL 可用于执行数据集的即席查询以及数据汇总和执行数据分析。 由于其类似 SQL 的语言,对于使用关系数据库进行数据仓库存储经验丰富的用户来说,hive 是一个自然而然的选择。

HIVE 将 HiveQL 查询转换为执行实际工作的一个或多个 MapReduce 计算。 配置单元允许我们使用表架构在现有数据集上定义结构。 但是,配置单元仅在读取和处理数据时才将此结构强加于数据(读取时的架构)。

HIVE 非常适合分析非常大的数据集,因为它可以利用 Hadoop 集群的并行性实现巨大的聚合吞吐量。 但是,由于运行 MapReduce 计算的延迟相对较高,所以配置单元不适合分析较小的数据集或交互式查询。 通过在底层使用 MapReduce 和 HDFS,HIVE 提供了良好的可扩展性和容错能力。 配置单元不支持数据的事务或行级更新。

本章还介绍了 HCatalog,它是配置单元的一个组件,为存储在 HDFS 中的数据提供元数据抽象层,使 Hadoop 生态系统的不同组件能够轻松处理这些数据。 HCatalog 抽象基于表格模型,并增加了 HDFS 数据的结构、位置和其他元数据信息。 使用 HCatalog,我们可以使用 Pig、Java MapReduce 等数据处理工具,而不必担心数据的结构、存储格式或存储位置。

提示

示例代码

本书的示例代码和数据文件可以在 giHub 上的https://github.com/thilg/hcb-v2获得。 代码库的chapter6文件夹包含本章的示例代码。

可以通过在代码库的chapter6文件夹中发出gradle build命令来编译和构建示例代码。 Eclipse IDE 的项目文件可以通过在代码库的主文件夹中运行gradle eclipse命令来生成。 IntelliJ IDEA IDE 的项目文件可以通过在代码存储库的主文件夹中运行gradle idea命令来生成。

在本章中,我们使用 Book Crossing 数据集作为样本数据。 该数据集由 Cai-Nicolas Ziegler 编制,包含图书、用户和评分列表。 本章的源库的Resources文件夹包含数据集的示例。 您可以从http://www2.informatik.uni-freiburg.de/~cziegler/BX/获取完整的数据集。

Apache 配置单元入门

为了安装配置单元,我们建议您使用免费的商业 Hadoop 发行版,如第 1 章《Hadoop v2 入门》中所述。 另一种选择是使用 Apache bigtop 安装配置单元。 有关使用 Apache bigtop 发行版安装配置单元的步骤,请参阅第 1 章《Hadoop v2 入门》中与 Bigtop 相关的食谱。

怎么做……

本节介绍如何开始使用配置单元。

  1. 如果您已经安装了可用的配置单元,请通过在命令提示符下执行hive来启动配置单元命令行界面(CLI),然后跳到步骤 4:

    $ hive
    
    
  2. 在情况下,如果您没有正在运行的配置单元和 Hadoop 安装,下面几个步骤将指导您如何使用 MapReduce 本地模式安装配置单元。 建议仅用于学习和测试目的。 从http://hive.apache.org/releases.html下载并解压最新配置单元版本:

    $ tar -zxvf hive-0.14.0.tar.gz 
    
    
  3. 通过从the 提取的配置单元文件夹运行以下命令启动配置单元:

    $ cd hive-0.14.0
    $ bin/hive
    
    
  4. 或者,您可以设置以下配置单元属性,以使配置单元在显示配置单元 CLI 中的查询结果时打印标题:

    hive> SET hive.cli.print.header=true;
    
    
  5. 或者,在您的主目录中创建一个.hiverc文件。 每当您启动配置单元 CLI 时,配置单元 CLI 都会将其作为初始化脚本加载。 您可以设置任何自定义属性,例如在此文件中启用打印标题(步骤 4)。 此文件的其他常见用法包括切换到配置单元数据库以及注册您将经常使用的任何库和自定义 UDF(请参阅编写配置单元用户定义函数食谱以了解有关 UDF 的更多信息)。

另请参阅

有关配置单元提供的配置属性的完整列表,请参阅https://cwiki.apache.org/confluence/display/Hive/Configuration+Properties

使用配置单元 CLI 创建数据库和表

此配方将指导您完成命令,以使用配置单元 CLI 创建配置单元数据库和表。 配置单元表用于定义结构(架构)和其他元数据信息,例如 HDFS 中存储的数据集的位置和存储格式。 这些表定义支持使用配置单元查询语言进行数据处理和分析。 正如我们在引言中所讨论的,配置单元遵循一种“读取时架构”方法,它仅在读取和处理数据时采用这种结构。

做好准备

对于本食谱,您需要一个正常工作的 Hive 安装。

怎么做……

本节介绍如何创建配置单元表以及如何对配置单元表执行简单查询:

  1. 通过运行以下命令启动配置单元 CLI:

    $ hive
    
    
  2. 执行以下命令,为简介中提到的图书交叉数据集创建和使用配置单元数据库:

    hive> CREATE DATABASE bookcrossing;
    hive> USE bookcrossing;
    
    
  3. 使用以下命令查看数据库的详细信息和文件系统的位置:

    hive> describe database bookcrossing;
    OK
    bookcrossing    hdfs://……/user/hive/warehouse/bookcrossing.db
    
    
  4. 让我们通过在配置单元 CLI 中运行以下命令来创建一个表来映射用户信息数据。 将在图书交叉数据库中创建一个表:

    CREATE TABLE IF NOT EXISTS users 
     (user_id INT, 
     location STRING, 
     age INT) 
    COMMENT 'Book Crossing users cleaned' 
    ROW FORMAT DELIMITED 
    FIELDS TERMINATED BY '\073' 
    STORED AS TEXTFILE;
    
    
  5. 让我们使用LOAD命令将数据加载到表中。 LOAD命令将文件复制到 HDFS 中的配置单元仓库位置。 在此步骤中,HIVE 不执行任何数据验证或数据解析。 请注意,LOAD 命令中的OVERWRITE子句将覆盖并删除表中已有的所有旧数据:

    hive> LOAD DATA LOCAL INPATH 'BX-Users-prepro.txt' OVERWRITE INTO TABLE users;
    Copying data from file:/home/tgunarathne/Downloads/BX-Users-prepro.txt
    Copying file: file:/home/tgunarathne/Downloads/BX-Users-prepro.txt
    Loading data to table bookcrossing.users
    Deleted /user/hive/warehouse/bookcrossing.db/users
    Table bookcrossing.users stats: [num_partitions: 0, num_files: 1, num_rows: 0, total_size: 10388093, raw_data_size: 0]
    OK
    Time taken: 1.018 seconds
    
    
  6. 现在,我们可以运行一个简单的查询来检查创建的表中的数据。 此时,配置单元使用表定义中定义的格式解析数据,并执行查询指定的处理:

    hive> SELECT * FROM users LIMIT 10;
    OK
    1  nyc, new york, usa  NULL
    2  stockton, california, usa  18
    3  moscow, yukon territory, russia  NULL
    ………
    10  albacete, wisconsin, spain  26
    Time taken: 0.225 seconds, Fetched: 10 row(s)
    
    
  7. 使用以下命令查看配置单元表格的列:

    hive> describe users; 
    OK
    user_id               int                   None 
    location              string                None 
    age                   int                   None 
    Time taken: 0.317 seconds, Fetched: 3 row(s)
    
    

它是如何工作的.

当我们运行配置单元时,我们首先定义一个表结构,并将文件中的数据加载到配置单元表中。 值得注意的是,表定义必须与输入数据的结构相匹配。 任何类型不匹配都将导致个空值,并且任何未定义的列都将被配置单元截断。 LOAD命令将文件复制到配置单元仓库位置,不做任何更改,这些文件将由配置单元管理。 只有在配置单元读取数据进行处理时,才会对数据实施表架构:

CREATE TABLE IF NOT EXISTS users 
 (user_id INT, 
 location STRING, 
 age INT) 
COMMENT 'Book Crossing users cleaned' 
ROW FORMAT DELIMITED 
FIELDS TERMINATED BY '\073' 
STORED AS TEXTFILE;

请注意,配置单元表名和列名不区分大小写。 前面的表将在bookcrossing数据库中创建,因为我们在发出 CREATE TABLE 命令之前发出了use bookcrossing命令。 或者,您也可以使用数据库名称bookcrossing.users来限定表名。 ROW FORMAT DELIMITED指示配置单元将本机SerDe(序列化程序和反序列化程序类,由配置单元用来序列化和反序列化数据)与分隔字段一起使用。 在前表使用的数据集中,字段使用;字符分隔,该字符是使用\073指定的,因为它是配置单元中的保留字符。 最后,我们指示配置单元数据文件是文本文件。 有关配置单元支持的不同存储格式选项的更多信息,请参阅在配置单元中使用不同的存储格式-使用 ORC 文件存储表数据配方。

还有更多...

在本节中,我们将探讨配置单元数据类型、配置单元外部表、集合数据类型以及有关describe命令的更多信息。

配置单元数据类型

可用于定义表列的配置单元数据类型列表可在https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Types中找到。 其中包括简单数据类型,如 TINYINT(1 字节有符号整数)、INT(4 字节有符号整数)、BIGINT(8 字节有符号整数)、DOUBLE(8 字节双精度浮点)、TIMESTAMP、DATE、STRING、Boolean 以及其他几种。

HIVE 还支持几种复杂的集合数据类型,如数组和映射作为表列数据类型。 配置单元包含几个用于操作数组和映射的内置函数。 一个例子是explode()函数,它将数组或映射的项输出为单独的行。 有关如何使用配置单元函数的更多信息,请参阅使用配置单元内置函数配方。

Hive 外部表

配置单元外部表允许我们将 HDFS 中的数据集映射到配置单元表,而不让配置单元管理数据集。 外部表的数据集不会移动到配置单元默认仓库位置。

此外,删除外部表不会导致删除基础数据集,而不是删除常规配置单元表,在常规配置单元表中,数据集将被删除。 当您想要防止意外删除数据时,这是一个有用的功能:

  1. 将 bx-Books-prepro.txt 文件复制到 HDFS 中的目录:

    $ hdfs dfs -mkdir book-crossing
    $ hdfs dfs -mkdir book-crossing/books
    $ hdfs dfs -copyFromLocal BX-Books-prepro.txt book-crossing/books
    
    
  2. 通过运行以下命令启动配置单元 CLI,然后使用图书交叉数据库:

    $ hive
    Hive> use bookcrossing;
    
    
  3. 通过在配置单元 CLI 中运行以下命令,创建外部表以映射图书信息数据:

    CREATE EXTERNAL TABLE IF NOT EXISTS books
     (isbn INT, 
     title STRING, 
     author STRING, 
     year INT, 
     publisher STRING, 
     image_s STRING, 
     image_m STRING, 
     image_l STRING) 
     COMMENT 'Book crossing books list cleaned'
     ROW FORMAT DELIMITED
     FIELDS TERMINATED BY '\073' 
     STORED AS TEXTFILE
     LOCATION '/user/<username>/book-crossing/books';
    
    
  4. 使用下面的查询检查新创建的表的数据:

    hive> select * from books limit 10;
    OK
    195153448  Classical Mythology  Mark P. O. Morford  2002  Oxford University Press  http://images.amazon.com/images/P/0195153448.01.THUMBZZZ.jpg  http://images.amazon.com/images/P/0195153448.01.MZZZZZZZ.jpg  http://images.amazon.com/images/P/0195153448.01.LZZZZZZZ.jpg
    
    
  5. 使用以下命令删除该表:

    hive> drop table books;
    OK
    Time taken: 0.213 seconds
    
    
  6. 使用以下命令检查 HDFS 中的数据文件。 即使表被删除,数据文件仍然存在:

    $ hdfs dfs -ls book-crossing/books
    Found 1 items
    -rw-r--r--   1 tgunarathne supergroup   73402860 2014-06-19 18:49 /user/tgunarathne/book-crossing/books/BX-Books-prepro.txt
    
    

使用 DESCRIBE FORMACTED 命令检查配置单元表元数据

您可以使用describe命令检查配置单元表的基本元数据。 describe extended命令将打印其他元数据信息,包括数据位置、输入格式、创建时间等。 describe formatted命令以更加用户友好的方式呈现此元数据信息:

hive> describe formatted users;
OK
# col_name              data_type             comment 
user_id               int                   None 
location              string                None 
age                   int                   None 

# Detailed Table Information 
Database:             bookcrossing 
Owner:                tgunarathne 
CreateTime:           Mon Jun 16 02:19:26 EDT 2014 
LastAccessTime:       UNKNOWN 
Protect Mode:         None 
Retention:            0 
Location:             hdfs://localhost:8020/user/hive/warehouse/bookcrossing.db/users 
Table Type:           MANAGED_TABLE 
Table Parameters: 
 comment               Book Crossing users cleaned
 numFiles              1 
 numPartitions         0 
 numRows               0 
 rawDataSize           0 
 totalSize             10388093 
 transient_lastDdlTime  1402900035 

# Storage Information 
SerDe Library:        org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe 
……
Time taken: 0.448 seconds, Fetched: 35 row(s)

使用 Apache 配置单元进行简单的 SQL 风格的数据查询

我们可以使用 HiveQL 查询已经映射到 hive 表的数据集,这类似于 SQL。 这些查询可以是简单的数据探索操作,如计数、orderby, and group by以及复杂的联接、汇总和分析操作。 在本食谱中,我们将探索简单的数据探索配置单元查询。 本章后面的食谱将介绍一些高级查询用例。

做好准备

安装配置单元并按照前面的步骤使用配置单元 CLI创建数据库和表。

怎么做……

本节演示如何使用配置单元执行简单的 SQL 样式查询。

  1. 通过发出以下命令启动配置单元:

    $ hive
    
    
  2. 在配置单元 CLI 中发出以下查询以检查年龄在 18 岁到 34 岁之间的用户。 配置单元在后台使用 MapReduce 作业执行此数据筛选操作:

    hive> SELECT user_id, location, age FROM users WHERE age>18 and age <34 limit 10; 
    Total MapReduce jobs = 1
    Launching Job 1 out of 1
    ……
    10  albacete, wisconsin, spain  26
    13  barcelona, barcelona, spain  26
    ….
    Time taken: 34.485 seconds, Fetched: 10 row(s)
    
    
  3. 在配置单元 CLI 中发出以下查询,统计满足上述条件(即年龄在 18 到 34 岁之间)的用户总数。 配置单元将此查询转换为 MapReduce 计算以计算结果:

    hive> SELECT count(*) FROM users WHERE age>18 and age <34; 
    Total MapReduce jobs = 1
    Launching Job 1 out of 1
    …………
    2014-06-16 22:53:07,778 Stage-1 map = 100%,  reduce = 100%, 
    …………
    Job 0: Map: 1  Reduce: 1   Cumulative CPU: 5.09 sec   HDFS Read: 10388330 HDFS Write: 6 SUCCESS
    Total MapReduce CPU Time Spent: 5 seconds 90 msec
    OK
    74339
    Time taken: 53.671 seconds, Fetched: 1 row(s)
    
    
  4. 以下查询统计按年龄分组的用户数:

    hive> SELECT  age, count(*) FROM users GROUP BY age; 
    Total MapReduce jobs = 1
    ………
    Job 0: Map: 1  Reduce: 1   Cumulative CPU: 3.8 sec   HDFS Read: 10388330 HDFS Write: 1099 SUCCESS
    Total MapReduce CPU Time Spent: 3 seconds 800 msec
    OK
    ….
    10  84
    11  121
    12  192
    13  885
    14  1961
    15  2376
    
    
  5. 下面的查询按用户年龄计算用户数,并按用户数的降序对结果进行排序:

    hive> SELECT  age, count(*) as c FROM users GROUP BY age ORDER BY c DESC;
    Total MapReduce jobs = 2
    …..
    Job 0: Map: 1  Reduce: 1   Cumulative CPU: 5.8 sec   HDFS Read: 10388330 HDFS Write: 3443 SUCCESS
    Job 1: Map: 1  Reduce: 1   Cumulative CPU: 2.15 sec   HDFS Read: 3804 HDFS Write: 1099 SUCCESS
    Total MapReduce CPU Time Spent: 7 seconds 950 msec
    OK
    NULL  110885
    24  5683
    25  5614
    26  5547
    23  5450
    27  5373
    28  5346
    29  5289
    32  4778
    
    

它是如何工作的.

您可以使用explain命令查看配置单元查询的执行计划。 这对于识别大规模查询的瓶颈并对其进行优化非常有用。 以下是我们在前面的菜谱中使用的一个查询的执行计划。 如您所见,该查询产生了一个 MapReduce 计算,然后是一个数据输出阶段:

hive> EXPLAIN SELECT user_id, location, age FROM users WHERE age>18 and age <34 limit 10;
OK
ABSTRACT SYNTAX TREE:
 …

STAGE PLANS:
 Stage: Stage-1
 Map Reduce
 Alias -> Map Operator Tree:
 users 
 TableScan
 alias: users
 Filter Operator
 predicate:
 expr: ((age > 18) and (age < 34))
 type: boolean
 Select Operator
 expressions:
 expr: user_id
 type: int
 expr: location
 type: string
 expr: age
 type: int
 outputColumnNames: _col0, _col1, _col2
 Limit
 File Output Operator
 compressed: false
 GlobalTableId: 0
 table:
 input format: org.apache.hadoop.mapred.TextInputFormat
 output format: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat

 Stage: Stage-0
 Fetch Operator
 limit: 10

还有更多...

HIVE 提供了几个用于对查询结果进行排序的运算符,这些运算符具有细微的差异和性能权衡:

  • ORDER BY:这个保证使用单个减法器对数据进行全局排序。 但是,对于任何非平凡数量的结果数据,使用单个减少器都会显著降低计算速度。
  • 排序依据:这个保证每个 Reduce 任务输出的数据的本地排序。 但是,Reduce 任务将包含重叠的数据区域。
  • CLUSTER BY:这将分发数据以减少任务,避免任何范围重叠,并且每个减少任务将以排序的顺序输出数据。 这确保了数据的全局排序,即使结果将存储在多个文件中。

有关上述运算符区别的详细说明,请参阅[http://stackoverflow.com/questions/13715044/hive-cluster-by-vs-order-by-vs-sort-by](http://stackoverflow.com/questions/13715044/hive-cluster-by -vs-order-by-vs-sort-by)。

使用 Apache Tez 作为配置单元的执行引擎

TEZ 是一个新的执行框架,它构建在 Yarn 之上,提供了比 MapReduce 更低级别的 API(有向无环图)。 TEZ 比 MapReduce 更灵活、更强大。 TEZ 允许应用通过使用比 MapReduce 模式更具表现力的执行模式来提高性能。 配置单元支持 TEZ 执行引擎作为后台 MapReduce 计算的替代,在后台 MapReduce 计算中,配置单元会将配置单元查询转换为 TEZ 执行图,从而大大提高了性能。 您可以执行以下步骤:

  • 通过设置以下配置单元属性,可以指示配置单元使用 TEZ 作为执行引擎:

    hive> set hive.execution.engine=tez;
    
    
  • 您可以切换回 MapReduce 作为执行引擎,如下所示:

    hive> set hive.execution.engine=mr;
    
    

另请参阅

有关配置单元select语句支持的子句和功能的列表,请参阅https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select

使用配置单元查询结果创建和填充配置单元表和视图

配置单元允许我们通过创建新的配置单元表来保存配置单元查询的输出数据。 我们还可以将配置单元查询的结果数据插入到另一个现有的表中。

做好准备

安装配置单元并按照使用配置单元 CLI配方创建数据库和表。

怎么做……

以下步骤显示如何将配置单元查询的结果存储到新的配置单元表中:

  1. 发出以下查询,将前面配方的步骤 3 的查询输出保存到名为tmp_users的表中:

    hive> CREATE TABLE tmp_users AS SELECT user_id, location, age FROM users WHERE age>18 and age <34;
    …
    Table bookcrossing.tmp_users stats: [num_partitions: 0, num_files: 1, num_rows: 0, total_size: 2778948, raw_data_size: 0]
    74339 Rows loaded to hdfs://localhost:8020/tmp/hive-root/hive_2014-07-08_02-57-18_301_5868823709587194356/-ext-10000
    
    
  2. 使用以下命令检查新创建的表的数据:

    hive> select * from tmp_users limit 10;
    OK
    10  albacete, wisconsin, spain  26
    13  barcelona, barcelona, spain  26
    18  rio de janeiro, rio de janeiro, brazil  25
    
    
  3. 配置单元还允许我们将配置单元查询的结果插入到现有表中,如下所示。 发出以下查询以将以下查询的输出数据加载到tmp_users配置单元表:

    hive> INSERT INTO TABLE tmp_users SELECT user_id, location, age FROM users WHERE age>33 and age <51;
    Total MapReduce jobs = 3
    Launching Job 1 out of 3
    …….
    Loading data to table bookcrossing.tmp_users
    Table bookcrossing.tmp_users stats: [num_partitions: 0, num_files: 2, num_rows: 0, total_size: 4717819, raw_data_size: 0]
    52002 Rows loaded to tmp_users
    
    
  4. 您还可以使用查询在现有表中创建视图,如下所示。 该视图可以用作用于查询的常规表,但视图的内容将仅按配置单元

    hive> CREATE VIEW tmp_users_view AS SELECT user_id, location, age FROM users WHERE age>18 and age <34;
    
    

    按需进行计算

在配置单元中使用不同的存储格式-使用 ORC 文件存储表数据

除了简单的文本文件外,配置单元还支持几种可用于存储表底层数据的其他二进制存储格式。 其中包括基于行的存储格式(如 Hadoop SequenceFiles 和 Avro 文件)以及基于列(列)的存储格式(如 ORC 文件和 Parquet)。

列存储格式逐列存储数据,其中列的所有值将一起存储,而不是在基于行的存储中以逐行的方式存储。 例如,如果我们将前一个配方中的users表存储在列数据库中,则所有用户 ID 将存储在一起,所有位置将存储在一起。 列存储提供了更好的数据压缩,因为很容易压缩存储在一起的相同类型的相似数据。 列存储还为配置单元查询提供了几个性能改进。 列存储允许处理引擎跳过从特定计算不需要的列加载数据,还可以更快地执行列级分析查询(例如,计算用户的最大年龄)。

在此配方中,我们将使用配置单元 CLI 配方创建数据库和表的users表中的数据存储到以 ORC 文件格式存储的配置单元表中。

做好准备

安装配置单元并按照使用配置单元 CLI配方创建数据库和表。

怎么做……

以下步骤显示如何创建使用 ORC 文件格式存储的配置单元表格:

  1. 在配置单元 CLI 中执行以下查询以创建使用 ORC 文件格式存储的用户表:

    hive> USE bookcrossing;
    
    hive> CREATE TABLE IF NOT EXISTS users_orc 
     (user_id INT, 
     location STRING, 
     age INT) 
    COMMENT 'Book Crossing users table ORC format' 
    STORED AS ORC;
    
    
  2. 执行以下命令将数据插入到新创建的表中。 我们必须使用之前创建的表填充数据,因为我们不能将文本文件直接加载到 ORC 文件或其他存储格式表:

    hive> INSERT INTO TABLE users_orc 
     SELECT * 
     FROM users;
    
    
  3. 执行以下查询以检查users_orc表中的数据:

    Hive> select * from users_orc limit 10;
    
    

它是如何工作的.

以下命令中的STORED AS ORC短语通知配置单元此表的数据将使用 ORC 文件存储。 您可以使用STORED AS PARQUET使用拼图格式存储表格数据,使用 Avro 文件使用STORED AS AVRO存储数据:

CREATE TABLE IF NOT EXISTS users_orc 
 (user_id INT, 
 location STRING, 
 age INT) 
STORED AS ORC;

使用配置单元内置函数

HIVE 提供了许多内置函数来帮助我们处理和查询数据。 这些函数提供的一些功能包括字符串操作、日期操作、类型转换、条件运算符、数学函数等等。

做好准备

此配方假定已执行了较早的配方。 如果您尚未安装配置单元,请按照之前的配方进行安装。

怎么做……

本节演示如何使用 parse_url 配置单元函数解析 URL 的内容:

  1. 通过运行以下命令启动配置单元 CLI:

    $ hive
    
    
  2. 发出以下命令以获取与每本书关联的小图像的FILE部分:

    hive> select isbn, parse_url(image_s, 'FILE') from books limit 10; 
    Total MapReduce jobs = 1
    …..
    OK
    0195153448  /images/P/0195153448.01.THUMBZZZ.jpg
    0002005018  /images/P/0002005018.01.THUMBZZZ.jpg
    0060973129  /images/P/0060973129.01.THUMBZZZ.jpg
    ……
    Time taken: 17.183 seconds, Fetched: 10 row(s)
    
    

它是如何工作的.

为前面查询选择的每个数据记录调用parse_url函数:

parse_url(string urlString, string partToExtract)

parse_url函数解析由urlString参数提供的 URL,并支持 HOST、PATH、QUERY、REF、PROTOCOL、AUTHORITY、FILE 和 USERINFO 作为partToExtract参数。

还有更多...

您可以在配置单元 CLI 中发出以下命令,以查看配置单元安装支持的函数列表:

hive> show functions;

您可以使用配置单元 CLI 中的describe <function_name>describe extended <function_name>命令访问每个功能的帮助和用法,如下所示。 例如:

hive> describe function extended parse_url;
OK
parse_url(url, partToExtract[, key]) - extracts a part from a URL
Parts: HOST, PATH, QUERY, REF, PROTOCOL, AUTHORITY, FILE, USERINFO
key specifies which query to extract
Example:
 > SELECT parse_url('http://facebook.com/path/p1.php?query=1', 'HOST') FROM src LIMIT 1;
 'facebook.com'
 > SELECT parse_url('http://facebook.com/path/p1.php?query=1', 'QUERY') FROM src LIMIT 1;
 'query=1'
 > SELECT parse_url('http://facebook.com/path/p1.php?query=1', 'QUERY', 'query') FROM src LIMIT 1;
 '1'

另请参阅

HIVE 提供了许多不同类别的函数,包括数学、日期操作、字符串操作等等。 有关配置单元提供的功能的完整列表,请参阅https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF

有关编写自己的函数以用于配置单元查询的信息,请参阅编写配置单元用户定义函数(UDF)配方。

配置单元批处理模式-使用查询文件

除了配置单元交互式 CLI 之外,在中,配置单元还允许我们使用脚本文件以批处理模式执行查询。 在本食谱中,我们使用配置单元脚本文件创建图书交叉数据集的图书、用户和评级表,并将数据加载到新创建的表中。

怎么做……

本节演示如何使用配置单元脚本文件创建表和加载数据。 继续执行以下步骤:

  1. 解压本章源库提供的数据包:

    $ tar –zxvf chapter6-bookcrossing-data.tar.gz
    
    
  2. 在本章来源资料库的配置单元脚本文件夹中找到create-book-crossing.hql配置单元查询文件。 通过为DATA_DIR参数提供提取的数据包的位置,按如下方式执行此配置单元脚本文件。 请注意,执行以下脚本文件将覆盖图书交叉数据库的 USERS、BOOK 和 RASTING 表(如果这些表预先存在)中的任何现有数据:

    $ hive \
     -hiveconf DATA_DIR=…/hcb-v2/chapter6/data/ \
     -f create-book-crossing.hql 
    ……
    Copying data from file:……/hcb-v2/chapter6/data/BX-Books-Prepro.txt
    ……
    Table bookcrossing.books stats: [num_partitions: 0, num_files: 1, num_rows: 0, total_size: 73402860, raw_data_size: 0]
    OK
    ……
    OK
    Time taken: 0.685 seconds
    
    
  3. 启动配置单元 CLI 并发出以下命令以检查由前面的脚本创建的表:

    $ hive
    hive> use bookcrossing; 
    ……
    hive> show tables; 
    OK
    books
    ratings
    users
    Time taken: 0.416 seconds, Fetched: 3 row(s)
    hive> select * from ratings limit 10;
    OK
    276725  034545104X  0
    276726  0155061224  5
    276727  0446520802  0
    
    

它是如何工作的.

hive –f <filename>选项以批处理模式执行给定文件中包含的 HiveQL 查询。 使用最新的配置单元版本,您甚至可以在 HDFS 中指定一个文件作为此命令的脚本文件。

Create-book-cross sing.hql 脚本包含用于创建 Book-Crossing 数据库以及创建数据并将数据加载到 USERS、BOOK 和 RATS 表的命令:

CREATE DATABASE IF NOT EXISTS bookcrossing;
USE bookcrossing;

CREATE TABLE IF NOT EXISTS books
  (isbn STRING, 
  title STRING, 
  author STRING, 
  year INT, 
  publisher STRING, 
  image_s STRING, 
  image_m STRING, 
  image_l STRING) 
COMMENT 'Book crossing books list cleaned'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\073' 
STORED AS TEXTFILE;

LOAD DATA LOCAL INPATH '${hiveconf:DATA_DIR}/BX-Books-Prepro.txt' OVERWRITE INTO TABLE books;

调用配置单元命令时,可以使用–hiveconf <property-name>=<property-value>选项设置属性并将参数传递给配置单元脚本文件。 您可以使用${hiveconf:<property-name>}在脚本中引用这些属性。 在查询执行之前,配置单元查询内的此类属性用法将被该属性的值替代。 在当前的配方中可以看到一个这样的例子,我们使用DATA_DIR属性将数据文件的位置传递给配置单元脚本。 在脚本中,我们通过${hiveconf:DATA_DIR}使用此属性的值。

–hiveconf选项也可用于设置配置单元配置变量。

还有更多...

您可以使用hive –e '<query>'选项直接从命令行运行批处理模式配置单元查询。 以下是这种用法的示例:

$ hive -e 'select * from bookcrossing.users limit 10'

另请参阅

有关配置单元 https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Cli 支持的选项的更多信息,请参阅CLI

执行与配置单元的联接

本配方将指导您如何使用配置单元跨两个数据集执行联接。 第一个数据集是 Book-Crossing 数据库的图书详细信息数据集,第二个数据集是这些图书的审阅者评分。 这个食谱将使用 Hive 来寻找收视率超过 3 颗星的作者。

做好准备

遵循前面的配置单元批处理模式-使用查询文件配方。

怎么做……

本节演示如何使用配置单元执行联接。 继续执行以下步骤:

  1. 启动配置单元 CLI 并使用图书交叉数据库:

    $ hive
    hive > USE bookcrossing;
    
    
  2. 在使用查询文件配方引用前面的*配置单元批处理模式命令后,通过执行create-book-crossing.hql配置单元查询文件来创建图书和图书评级表。 使用以下命令验证Book-Crossing数据库中是否存在这些表:

    hive > SELECT * FROM books LIMIT 10;
    ….
    hive > SELECT * FROM RATINGS LIMIT 10;
    ….
    
    ```* 
    
  3. 现在,我们可以使用配置单元的类似 SQL 的join命令连接这两个表:

    SELECT
     b.author AS author, 
     count(*) AS count 
    FROM 
     books b 
    JOIN
     ratings r 
    ON (b.isbn=r.isbn) and r.rating>3 
    GROUP BY b.author 
    ORDER BY count DESC 
    LIMIT 100;
    
    
  4. 如果成功,它将把以下内容和连同结果一起打印到控制台:

    Total MapReduce jobs = 4
    ...
    2014-07-07 08:09:53  Starting to launch local task to process map join;  maximum memory = 1013645312
    ...
    Launching Job 2 out of 4
    ....
    Launching Job 3 out of 4
    ...
    2014-07-07 20:11:02,795 Stage-2 map = 100%,  reduce = 100%, Cumulative CPU 8.18 sec
    MapReduce Total cumulative CPU time: 8 seconds 180 msec
    Ended Job = job_1404665955853_0013
    Launching Job 4 out of 4
    ....
    Total MapReduce CPU Time Spent: 21 seconds 360 msec
    OK
    Stephen King  4564
    Nora Roberts  2909
    John Grisham  2505
    James Patterson  2334
    J. K. Rowling  1742
    ...
    Time taken: 116.424 seconds, Fetched: 100 row(s)
    
    

它是如何工作的.

执行时,配置单元首先将 JOIN 命令转换为组 MapReduce 计算。 这些 MapReduce 计算将首先根据给定的架构加载和解析这两个数据集。 然后,根据给定的连接条件使用 MapReduce 计算连接数据。

配置单元支持内部联接以及左、右和完全外部联接。 目前,配置单元只支持基于相等的条件作为联接条件。 HIVE 能够根据数据集的性质和大小执行多项优化,以优化联接的性能。

另请参阅

创建分区配置单元表

本配方将展示如何使用分区表在配置单元中存储数据。 分区表允许我们存储按一个或多个数据列分区的数据集,以实现高效的查询。 实际数据将驻留在单独的目录中,这些目录的名称将构成分区列的值。 分区表可以通过在使用适当的where谓词时仅读取选择分区来减少配置单元必须处理的数据量,从而提高某些查询的性能。 一个常见的示例是存储按日期分区的事务性数据集(或其他带有时间戳的数据集,如 Web 日志)。 当配置单元表按日期分区时,我们可以查询属于单个日期或日期范围的数据,只读取属于这些日期的数据。 在未分区的表中,这将导致全表扫描,读取该表中的所有数据,当您将 TB 级的数据映射到配置单元表时效率可能会非常低。

做好准备

本配方假定已经执行了较早的配置单元批处理模式-使用查询文件配方。 如果您还没有安装 Hive,请按照该食谱进行安装。

怎么做……

本部分演示如何在配置单元中动态创建分区表。 继续执行以下步骤:

  1. 启动配置单元 CLI。

  2. 运行以下命令以启用配置单元中的动态分区创建:

    hive> set hive.exec.dynamic.partition=true; 
    hive> set hive.exec.dynamic.partition.mode=nonstrict;
    hive> set hive.exec.max.dynamic.partitions.pernode=2000; 
    
    
  3. 执行以下查询以使用 SELECT 语句的结果创建新的分区表。 在本例中,我们使用图书的出版年份对表进行分区。 通常,年份和日期可以作为跨时间数据(例如,日志数据)的良好分区列。 向分区表动态插入数据时,分区列应该是 INSERT 语句中的最后一列:

    hive> INSERT INTO TABLE books_partitioned
     > partition (year)
     > SELECT 
     >   isbn,
     >   title,
     >   author,
     >   publisher,
     >   image_s,
     >   image_m,
     >   image_l,
     >   year
     > FROM books;
    Total MapReduce jobs = 3
    Launching Job 1 out of 3
    …… 
    Loading data to table bookcrossing.books_partitioned partition (year=null)
     Loading partition {year=1927}
     Loading partition {year=1941}
     Loading partition {year=1984}
    ……. 
    Partition bookcrossing.books_partitioned{year=1982} stats: [num_files: 1, num_rows: 0, total_size: 1067082, raw_data_size: 0]
    …
    
    
  4. 执行以下查询。 由于使用了year分区列,该查询将只查看配置单元表 1982 年的数据分区中存储的数据。 如果不是分区,此查询将需要处理整个数据集的 MapReduce 计算:

    hive> select * from books_partitioned where year=1982 limit 10;
    OK
    156047624  All the King's Men  Robert Penn Warren  Harvest Books  http://images.amazon.com/images/P/0156047624.01.THUMBZZZ.jpg  http://images.amazon.com/images/P/0156047624.01.MZZZZZZZ.jpg  http://images.amazon.com/images/P/0156047624.01.LZZZZZZZ.jpg  1982
    
    
  5. 退出配置单元 CLI 并在命令提示符下执行以下命令。 您可以看到配置单元创建的分区目录:

    $ hdfs dfs -ls /user/hive/warehouse/bookcrossing.db/books_partitioned
    Found 116 items
    drwxr-xr-x   - root hive          0 2014-07-08 20:24 /user/hive/warehouse/bookcrossing.db/books_partitioned/year=0
    drwxr-xr-x   - root hive          0 2014-07-08 20:24 /user/hive/warehouse/bookcrossing.db/books_partitioned/year=1376
    drwxr-xr-x   - root hive          0 2014-07-08 20:24 /user/hive/warehouse/bookcrossing.db/books_partitioned/year=1378
    ….
    
    

编写配置单元用户定义函数(UDF)

正如在使用配置单元内置函数配方中提到的,配置单元支持许多用于数据操作和分析的内置函数。 配置单元还允许我们编写自己的自定义函数,以便与配置单元查询一起使用。 这些函数称为用户定义函数,本菜谱将向您展示如何为配置单元编写一个简单的用户定义函数(UDF)。 配置单元 UDF 允许我们扩展配置单元的功能以满足我们定制的需求,而不必从头开始实现 Java MapReduce 程序。

做好准备

此配方假定已执行了较早的配方。 如果您尚未安装配置单元,请按照之前的配方进行安装。

确保您的系统中安装了 Apache Ant。

怎么做……

本节演示如何实现简单的配置单元 UDF。 执行以下步骤:

  1. 使用本章源代码存储库中的 Gradle 构建文件构建用户定义的函数 JAR 文件:

    $ gradle build
    
    
  2. 启动配置单元 CLI:

    $ hive
    hive > USE bookcrossing;
    
    
  3. 使用步骤 1 中创建的 JAR 文件的完整路径将新创建的 JAR 文件添加到环境中:

    hive> ADD JAR /home/../ hcb-c6-samples.jar;
    
    
  4. 使用以下命令定义配置单元内部的新 UDF:

    hive> CREATE TEMPORARY FUNCTION filename_from_url as 'chapter6.udf. ExtractFilenameFromURL';
    
    
  5. 发出以下命令,使用我们新定义的 UDF 获取与每本书关联的小图像的文件名部分:

    hive> select isbn, filename_from_url(image_s, 'FILE') from books limit 10; 
    
    

它是如何工作的.

配置单元 UDF 应该扩展配置单元的 UDF 类,并实现evaluate方法来执行您需要执行的自定义计算。 需要使用适当的 Hadoop Writable 类型提供evaluate方法的输入和输出参数,该类型对应于您要处理并从 UDF 接收的配置单元数据类型:

public class ExtractFilenameFromURL extends UDF {
  public Text evaluate(Text input) throws MalformedURLException {
    URL url = new URL(input.toString());
    Text fileNameText = new Text(FilenameUtils.getName(url.getPath()));
    return fileNameText;
  }
}

我们可以使用如下所示的注释向 UDF 添加说明。 如果您从配置单元 CLI 向此 UDF 发出describe命令,则会发出这些命令:

@UDFType(deterministic = true)
@Description(
    name = "filename_from_url", 
    value = "Extracts and return the filename part of a URL.", 
    extended = "Extracts and return the filename part of a URL. "
        + "filename_from_url('http://test.org/temp/test.jpg?key=value') returns 'test.jpg'."
)

HCatalog-对映射到配置单元表的数据执行 Java MapReduce 计算

HCatalog 是存储在 HDFS 中的文件的元数据抽象层,它使得不同的组件很容易处理存储在 HDFS 中的数据。 HCatalog 抽象基于表格表模型,为 HDFS 中存储的数据集增加了结构、位置、存储格式和其他元数据信息。 有了 HCatalog,我们就可以使用 Pig、Java MapReduce 等数据处理工具对 Hive 表进行读写操作,而不必担心数据的结构、存储格式或存储位置。 当您想要对使用二进制数据格式(如 ORCFiles)存储在配置单元中的数据集执行 Java MapReduce 作业或 Pig 脚本时,HCatalog 非常有用。 拓扑如下所示:

HCatalog – performing Java MapReduce computations on data mapped to Hive tables

HCatalog 通过提供到配置单元元存储的接口来实现此功能,从而使其他应用能够利用配置单元表元数据信息。 我们可以使用 HCatalog 命令行界面(CLI)查询 HCatalog 中的表信息。 HCatalog CLI 基于配置单元 CLI,支持配置单元数据定义语言(DDL)语句,但需要运行 MapReduce 查询的语句除外。 HCatalog 还公开了一个名为 WebHCat 的 REST API。

在本食谱中,我们将通过利用 HCatalog 提供的元数据,在配置单元表中存储的数据之上使用 Java MapReduce 计算。 HCatalog 提供了 HCatInputFormat 类来从配置单元表格中检索数据。

做好准备

确保 HCatalog 与配置单元一起安装在您的系统中。

怎么做……

本节演示如何使用 MapReduce 计算处理配置单元表数据。 执行以下步骤:

  1. 使用本章的查询文件配方按照配置单元批处理模式命令创建并填充我们将在本配方中使用的bookcrossing.user配置单元表。

  2. 通过从源代码存储库的chapter6文件夹运行以下gradle命令,编译本章的示例源代码。

    $ gradle clean build uberjar
    
  3. 使用以下命令运行 MapReduce 作业。 第一个参数是数据库名,第二个参数是输入表名称,第三个参数是输出路径。 此作业统计年龄在 18 岁到 34 岁之间的用户数,按年份分组:

    $ hadoop jar build/libs/hcb-c6-samples-uber.jar \
    chapter7.hcat.HCatReadMapReduce \
    bookcrossing users hcat_read_out 
    
    
  4. 通过运行以下命令检查此计算的结果:

    $ hdfs dfs -cat hcat-read-out/part*
    
    

它是如何工作的.

您可以从本章源代码文件夹中的chapter6/hcat/``HCatReadMapReduce.java文件中找到本食谱的源代码。

run()函数中的以下几行将HCatalogInputFormat指定为计算的InputFormat,并使用输入的数据库名称和表名对其进行配置。

// Set HCatalog as the InputFormat
job.setInputFormatClass(HCatInputFormat.class);
HCatInputFormat.setInput(job, dbName, tableName);

map()函数从配置单元表格接收记录作为HCatRecord值,而map()输入键不包含任何有意义的数据。 HCatRecord 包含根据配置单元表的列结构解析的数据字段,我们可以在map函数中从HCatRecord中提取字段,如下所示:

public void map( WritableComparable key,HCatRecord value,…)
        throws IOException, InterruptedException {
  HCatSchema schema = HCatBaseInputFormat.getTableSchema(context.getConfiguration());
  // to avoid the "null" values in the age field 
  Object ageObject = value.get("age", schema);
  if (ageObject instanceof Integer) {
    int age = ((Integer) ageObject).intValue();
    // emit age and one for count
    context.write(new IntWritable(age), ONE);
    }
     }
  }

Hadoop 类路径中需要 HCatalogjar、hive jar 及其依赖项才能执行 HCatalog MapReduce 程序。 在调用 Hadoop jar 命令时,我们还需要通过在命令行使用libjars参数指定依赖库,从而为 Map 和 Reduce 任务提供这些 JAR。 同时解决 Hadoop 类路径和libjars需求的另一种方法是将所有依赖 JAR 打包到单个 FAT-JAR 中,并使用它提交 MapReduce 程序。

在此示例中,我们使用第二种方法,并使用 Gradle 构建创建一个 FAT-JAR(hcb-c6-samples-uber.jar),如下所示:

task uberjar(type: Jar) {
  archiveName = "hcb-c6-samples-uber.jar"
  from files(sourceSets.main.output.classesDir)
  from {configurations.compile.collect {zipTree(it)}} {
      exclude "META-INF/*.SF"
      exclude "META-INF/*.DSA"
      exclude "META-INF/*.RSA"
  }
}

HCatalog-将数据从 Java MapReduce 计算写入配置单元表

HCatalog 还允许我们使用HCatOutputFormat将数据从 Java MapReduce 计算写入配置单元表。 在本食谱中,我们将了解如何使用 Java MapReduce 计算将数据写入配置单元表。 此配方通过添加表写入功能扩展了前面HCatalog 配方的计算-在映射到配置单元表格的数据上执行 Java MapReduce 计算。

做好准备

确保 HCatalog 与配置单元一起安装在您的系统中。

怎么做……

本节演示如何使用 MapReduce 计算将数据写入配置单元表。 执行以下步骤:

  1. 遵循配置单元批处理模式-使用本章的查询文件配方创建并填充我们将在本配方中使用的用户配置单元表。

  2. 通过从源代码库的chapter6文件夹运行以下gradle 命令,编译本章的示例源代码:

    $ gradle clean build uberjar
    
    
  3. 使用配置单元 CLI 创建配置单元表以存储计算结果。

    hive> create table hcat_out(age int, count int);
    
    
  4. 使用以下命令运行 MapReduce 作业。 第一个参数是数据库名,第二个参数是输入表名称,第三个参数是输出表名称。 此作业统计按年份分组的 18 到 34 岁的用户数,并将结果写入我们在步骤 3:

    $ hadoop jar hcb-c6-samples-uber.jar \ 
    chapter6.hcat.HCatWriteMapReduce \
    bookcrossing users hcat_out
    
    

    中创建的hcat_out

  5. 通过在配置单元 CLI 中运行以下命令读取结果:

    hive> select * from bookcrossing.hcat_out limit 10;
    OK
    hcat_out.age    hcat_out.count
    19    3941
    20    4047
    21    4426
    22    4709
    23    5450
    24    5683
    
    

它是如何工作的.

您可以从chapter6/src/chapter6/hcat/``HCatWriteMapReduce.java文件中找到配方的来源。

除了我们在前面的HCatalog-在映射到配置单元表格的数据上执行 Java MapReduce 计算配方中讨论的配置之外,我们还指定HCatalogOutputFormat 作为run()函数中的计算的OutputFormat,如下所示。 我们还配置了输出数据库和表名称:

job.setOutputFormatClass(HCatOutputFormat.class);

HCatOutputFormat.setOutput(job,
    OutputJobInfo.create(dbName, outTableName, null));

将数据写入配置单元表格时,我们必须使用DefaultHCatRecord作为作业输出值:

job.setOutputKeyClass(WritableComparable.class);
job.setOutputValueClass(DefaultHCatRecord.class);

我们为输出表设置模式,如下所示:

HCatSchema schema = HCatOutputFormat.getTableSchema(job
                          .getConfiguration());
HCatOutputFormat.setSchema(job, schema);

reduce()函数将数据输出为HCatRecord值。 HCatOutputFormat忽略任何输出键:

public void reduce(IntWritable key, Iterable<IntWritable> values,
        Context context) … {
  if (key.get() < 34 & key.get() > 18) {
     int count = 0;
     for (IntWritable val : values) {
    count += val.get();
     }

      HCatRecord record = new DefaultHCatRecord(2);
     record.set(0, key.get());
     record.set(1, count);
     context.write(null, record);
  }
}

七、Hadoop 生态系统 II——Pig、HBase、Mahout 和 Sqoop

在本章中,我们将介绍以下主题:

  • Apache Pig 入门
  • 使用 Pig 连接两个数据集
  • 使用 HCatalog 访问 Pig 中的 Hive 表数据
  • Apache HBase 入门
  • 使用 Java 客户端 API 随机访问数据
  • 在 HBase 上运行 MapReduce 作业
  • 使用配置单元将数据插入 HBase 表
  • Apache Mahout 入门
  • 使用 Mahout 运行 K-Means
  • 使用 Apache Sqoop 将数据从关系数据库导入 HDFS
  • 使用 Apache Sqoop 将数据从 HDFS 导出到关系数据库

简介

Hadoop 生态系统有一系列项目,这些项目要么构建在 Hadoop 之上,要么与 Hadoop 紧密合作。 这些项目催生了一个专注于大规模数据处理的生态系统,用户往往可以组合使用几个这样的项目来解决他们的大数据问题。

本章介绍 Hadoop 生态系统中的几个关键项目,并展示如何开始使用每个项目。

重点抓好以下四个项目:

  • Pig:一种数据流样式的数据处理语言,用于大规模处理存储在 HDFS 中的数据
  • HBase:一种 NoSQL 风格的高度可扩展的数据存储,它在 HDFS 之上提供低延迟、随机访问和高度可扩展的数据存储
  • Mahout:机器学习和数据挖掘工具的工具包
  • Sqoop:用于在 Apache Hadoop 生态系统和关系数据库之间高效传输批量数据的数据移动工具

备注

本章的一些 HBase 和 Mahout 配方基于本书前一版Hadoop MapReduce Cookbook章 5Hadoop 生态系统章。 这些食谱最初是由斯里纳特·佩雷拉(Srinath Perera)撰写的。

备注

示例代码

本书的示例代码和数据文件可以在 giHub 的https://github.com/thilg/hcb-v2中找到。 代码库的chapter7文件夹包含本章的示例代码。

通过在代码库的chapter7文件夹中发出 Gradle build 命令,可以编译和构建示例代码。 Eclipse IDE 和 IntelliJ IDEA IDE 的项目文件可以通过分别在代码存储库的主文件夹中运行gradle eclipsegradle idea命令来生成。

本章的一些食谱使用图书交叉数据集作为样本数据。 该数据集由 Cai-Nicolas Ziegler 编制,包括图书列表、用户列表和评级列表。 源资料库的chapter6文件夹包含此数据集的清理样本。 您可以从http://www2.informatik.uni-freiburg.de/~cziegler/BX/获取完整的数据集。

Apache Pig 入门

Apache Pig 是 Hadoop 的高级语言框架,它使得分析存储在 HDFS 中的非常大的数据集变得容易,而不必实现复杂的 Java MapReduce 应用。 Pig 的语言被称为 Pig 拉丁语,是一种数据流语言。 虽然 Pig 和 Have 框架的目标相似,但这两个框架的语言层通过分别提供过程性语言和声明性语言来相辅相成。

PIG 在后台将 Pig 拉丁语查询转换为一系列一个或多个 MapReduce 作业。

为了安装 Pig,我们建议您使用免费提供的商业 Hadoop 发行版之一,如第 1 章《Hadoop v2 入门》中所述。 另一种选择是使用 Apache bigtop 安装 Pig。 有关使用 Apache bigtop 发行版安装 Pig 的步骤,请参阅第 1 章《Hadoop v2 入门》中与 bigtop 相关的食谱。

备注

如果您没有正在运行的 Pig 和 Hadoop 安装,下面的步骤将向您展示如何使用本地文件系统作为数据存储,在 MapReduce 本地模式下安装 Pig。 建议仅用于学习和测试目的。

下载并从http://pig.apache.org/releases.html解压最新版本的 PIG。 将解压缩文件夹的bin目录添加到 PATH 环境变量,如下所示:

$ export PATH=pig-0.13.0/bin:$PATH

使用带有local标志的pig命令启动 Grunt shell,如下所示:

$ pig -x local
grunt>

这个配方演示了如何使用 Pig 查询来处理 HDFS 中的数据。 我们将在这个食谱中使用图书交叉数据集。 本食谱将使用 Pig 处理图书交叉用户数据集,并选择年龄在 18 岁到 34 岁之间的用户列表(按年龄排序)。

做好准备

此配方需要将 Working Pig 安装与 Hadoop Yarn 集群集成。 您也可以使用 Pig 本地模式运行这些示例。 但是,在这种情况下,您必须使用本地文件系统而不是 HDFS 来加载数据。

怎么做……

本节介绍如何使用 Pig 拉丁语查询从图书交叉用户数据集中查找按年龄排序的 18 到 34 岁之间的用户。 继续执行以下步骤:

  1. 从代码存储库的chapter6文件夹复制并解压缩 BookCross 示例数据集(chapter6-bookcrossing-data.tar.gz)。

  2. 在 HDFS 中创建一个目录,并将书签用户数据集复制到该目录中,如下所示:

    $ hdfs dfs –mkdir book-crossing
    $ hdfs dfs -copyFromLocal \
    chapter6/data/BX-Users-Prepro.txt book-crossing
    
    
  3. 启动 Pig Grunt shell 并发出以下 Pig 命令:

    $ pig
    grunt> A = LOAD 'book-crossing/BX-Users-Prepro.txt' USING PigStorage(';')  AS (userid:int, location:chararray, age:int);
    grunt> B = FILTER A BY age > 18 AND age < 34 ;
    grunt> C = ORDER B BY age;
    
    
  4. Print the output of the processing flow by using the DUMP operator in the same grunt shell. The queries we issued in step 3 get executed only after we issue the following command (or any other data output command). You should notice a series of MapReduce jobs after issuing the following two commands:

    grunt> D = LIMIT C 10;
    grunt> DUMP D;
    
    

    前面命令的输出如下所示:

    How to do it...

  5. You can also use the ILLUSTRATE operator to test your queries. The Illustrate operator retrieves a small sample of data from your input data and runs your queries on that data, giving faster turnaround times to review and test the Pig queries:

    grunt> ILLUSTRATE B;
    
    

    前面命令的输出如下所示:

    How to do it...

它是如何工作的.

当我们发出 Pig 查询时,Pig 会在内部将它们转换为一组 MapReduce 作业,并在 Hadoop 集群中执行它们以获得所需的结果。 对于几乎所有的数据查询,Pig 查询比 MapReduce 应用更容易编写和管理。

以下行指示 Pig 将数据文件加载到名为A的关系中。 我们可以为load命令提供单个文件或目录。 使用PigStorage(';')指示 Pig 使用默认加载函数加载数据,并以;作为分隔符。 在执行 MapReduce 作业时,Pig 的 Load 函数解析输入数据,并将其分配给 AS 子句中描述的模式的字段。 任何不符合给定模式的数据点都会在执行时导致错误或NULL值:

grunt> A = LOAD 'book-crossing/BX-Users-Prepro.txt' USING PigStorage(';')  AS (userid:int, location:chararray, age:int);

FILTER运算符根据给定条件从关系中选择数据。 在下面的代码行中,我们选择数据点,其中用户的年龄在 18 到 34 岁之间:

grunt> B = FILTER A BY age > 18 AND age < 34;

ORDER BY运算符根据一个或多个数据字段对关系中的数据进行排序。 在下面的查询中,我们按用户的年龄对关系 B 进行排序:

grunt> C = ORDER B BY age;

运算符LIMIT使用给定的数量限制关系中的数据点(元组)的数量。 在下面的查询中,我们将关系 C 限制为只有 10 个元组。 以下步骤使使用DUMP运算符检查数据变得容易:

grunt> D = LIMIT C 10;

还有更多...

PIG 拉丁语还包含大量内置函数,这些函数提供数学、字符串处理、数据时间处理、基本统计、数据加载和存储等领域的功能。 PIG 的内置函数列表可以在http://pig.apache.org/docs/r0.13.0/func.html中找到。 您还可以实现 PigUser Defined Functions来执行所需的任何自定义处理。

另请参阅

使用 Pig 连接两个数据集

本食谱解释了如何使用 Pig 连接两个数据集。 我们将使用本食谱的书本交叉数据集。 本食谱将使用 Pig 将 Books 数据集与 Book-Ratings 数据集连接起来,并查找关于作者的高评级(评级为>3)的分布。

怎么做……

本节介绍如何使用 Pig 拉丁文脚本通过将图书数据集与评级数据集相结合来查找作者的评论评级分布:

  1. 从代码存储库的chapter6文件夹中提取书签示例数据集(chapter6-bookcrossing-data.tar.gz)。

  2. 在 HDFS 中创建一个目录,并将 BookCrossBooks 数据集和 Book-Ratings 数据集复制到该目录,如下所示:

    $ hdfs dfs –mkdir book-crossing
    $ hdfs dfs -copyFromLocal \
    chapter6/data/BX-Books-Prepro.txt book-crossing
    $ hdfs dfs -copyFromLocal \
    BX-Book-Ratings-Prepro.txt book-crossing
    
    
  3. 查看chapter7/pig-scripts/book-ratings-join.pig脚本。

  4. Execute the preceding Pig Latin script using the following command:

    $ pig –f pig-scripts/book-ratings-join.pig
    
    

    前面命令的输出如下所示:

    How to do it...

它是如何工作的.

下面的 Pig 命令将数据加载到 Books 和 BookRatings 关系中。 如前面的配方中所述,PigStorage(';')指示 Pig 使用';'作为字段分隔符:

Books = LOAD 'book-crossing/BX-Books-Prepro.txt' 
USING PigStorage(';')  AS (
    isbn:chararray, 
    title:chararray, 
    author:chararray, 
    year:int, 
    publisher:chararray, 
    image_s:chararray, 
    image_m:chararray, 
    image_l:chararray);
Ratings = LOAD 'book-crossing/BX-Book-Ratings-Prepro.txt' 
  USING PigStorage(';')  AS (
    userid:int, 
    isbn:chararray, 
    ratings:int);

我们使用以下FILTER操作只选择评分良好的评论:

GoodRatings = FILTER R BY ratings > 3;

然后,我们使用 ISBN 作为公共标准加入图书和好评关系。 这是个内部等值连接,并生成由连接条件过滤的所有记录的笛卡尔乘积。 换句话说,结果关系包含每个匹配图书的记录和图书评级(匹配图书的数量 X 良好评级的数量):

J = JOIN Books BY isbn, GoodRatings by isbn;

下面的语句按作者对联接结果进行分组。 现在,每个组都包含属于某个作者的所有记录。 假设我们对每个好的评价都有一本相匹配的书,那么一个组中的记录数就是该组的作者收到的好评数。

JA = GROUP J BY author;

下面的语句统计每组关系 JA 中的记录数,并输出作者姓名和该作者所写书籍的好评计数:

JB = FORACH JA GENERATE group, COUNT(J);
OA = LIMIT JB 100;
DUMP OA;

您可以在 Pig Grunt shell 中手动发出上述命令,以更详细地了解数据流。 在执行此操作时,您可以使用LIMITDUMP运算符来了解每一步的结果。

还有更多...

PIG 也支持外连接。 然而,目前 Pig 只支持 equi 联接,其中联接条件必须基于相等。

使用 HCatalog 访问 Pig 中的 Hive 表数据

可能会有希望从 Hive 和 Pig 访问同一数据集的情况。 还可能存在这样的场景,我们希望处理使用 Pig 映射到配置单元表的配置单元查询的结果。 在这种情况下,我们可以利用 Pig 中的 HCatalog 集成从 Pig 访问 HCatalog 管理的 Hive 表,而不必担心数据定义、数据存储格式或存储位置。

做好准备

遵循配置单元批处理模式-使用第 6 章Hadoop 生态系统中的查询文件配方-Apache 配置单元创建我们将在本配方中使用的配置单元表。

怎么做……

本部分演示如何从 Pig 访问 Hive 表。 继续执行以下步骤:

  1. 使用-useHCatalog标志启动 Pig‘s Grunt 外壳,如下所示。 这将加载访问配置单元

    $ pig -useHCatalog
    
    

    中的 HCatalog 托管表所需的 HCatalog JAR

  2. 在 Grunt shell 中使用以下命令将users表从bookcrossing配置单元数据库加载到名为users的 Pig 关系中。 HCatLoader 便于从 HCatalog 管理表读取数据:

    grunt> users = LOAD 'bookcrossing.users' USING org.apache.hive.hcatalog.pig.HCatLoader();
    
    
  3. 按如下方式使用describe运算符检查users关系的架构:

    grunt> DESCRIBE users;
    users: {user_id: int,location: chararray,age: int}
    
    
  4. Inspect the data of the users relation by issuing the following command in the Pig Grunt shell. The relations loaded through Hive can be used similarly to any other relation in Pig:

    grunt> ILLUSTRATE users; 
    
    

    前面命令的输出如下所示:

    How to do it...

还有更多...

您还可以使用 HCatStorer 接口将数据写入 HCatalog 管理的表格中,如所示,从 Pig 将数据存储在配置单元表格中:

grunt> STORE r INTO 'database.table' 
 USING org.apache.hcatalog.pig.HCatStorer();

另请参阅

HCatalog-在映射到配置单元表的数据上执行 Java MapReduce 计算-从 Java MapReduce 计算配方将数据写入配置单元表第 6 章Hadoop 生态系统-Apache 配置单元

Apache HBase 入门

HBase 是一个高度可伸缩的分布式 NoSQL 数据存储,支持列式数据存储。 HBase 是以谷歌的 Bigtable 为蓝本的。 HBase 使用 HDFS 进行数据存储,并允许随机访问数据,这在 HDFS 中是不可能的。

可以将 HBase 表数据模型可视化为一个非常大的多维排序映射。 HBase 表由行组成,每个行都有一个唯一的行键,后跟一个列列表。 每行可以有任意数量的列,并且不必遵循固定的架构。 每个数据单元格(特定行中的列)可以基于时间戳具有多个值,从而形成三维表(行、列、时间戳)。 HBase 按排序顺序存储所有行和列,从而可以随机访问数据。

尽管数据模型与关系数据模型有一些相似之处,但与关系表不同,HBase 数据模型中的不同行可能有不同的列。 例如,第二行可能包含与第一行完全不同的名称-值对。 HBase 也不支持跨行的事务或原子性。 您可以在 Google 的 Bigtable 文章http://research.google.com/archive/bigtable.html中找到有关该数据模型的更多详细信息。

HBase 支持超大型数据集的存储,并提供低延迟、高吞吐量的读写。 HBase 为一些要求非常苛刻的实时数据处理系统提供动力,比如在线广告代理;它也为 Facebook Messenger 提供动力。 存储在 HBase 中的数据也可以使用 MapReduce 进行处理。

HBase 集群架构由一个或多个主节点和一组区域服务器组成。 HBase 表水平拆分成区域,由区域服务器提供服务和管理。 面域按柱族进一步垂直细分,并作为文件保存在 HDFS 中。 列族是表中列的逻辑分组,其结果是在存储层对列进行物理分组。

要获得 HBase 的最高性能,需要仔细设计表,同时考虑到它的分布式特性。 RowKey 在性能中起着重要作用,因为区域分布和任何查询都是基于 RowKey 的。 本书中的食谱并没有关注这样的优化。

为了安装 HBase,我们建议您使用免费提供的商业 Hadoop 发行版之一,如第 1 章《Hadoop v2 入门》中所述。 另一种选择是在 Amazon 云环境中使用 HBase 集群,如第 2 章云部署中所述--在云环境上使用 Hadoop Yarn

做好准备

本食谱要求 Apache HBase 安装与 Hadoop Yarn 集群集成。 在开始之前,请确保启动所有已配置的 HBase Master 和 RegionServer 进程。

怎么做……

本节演示如何开始使用 Apache HBase。 我们将创建一个简单的 HBase 表,并使用 HBase shell 向该表插入一行数据。 继续执行以下步骤:

  1. 通过执行以下命令启动 HBase shell:

    $ hbase shell
    ……
    hbase(main):001:0> 
    
    
  2. 在 HBase shell 中发出以下命令以检查版本:

    hbase(main):002:0> version
    0.98.4.2.2.0.0-2041-hadoop2, r18e3e58ae6ca5ef5e9c60e3129a1089a8656f91d, Wed Nov 19 15:10:28 EST 2014
    
    
  3. 创建名为testtable 的 HBase 表。 列出所有表以验证test表的创建,如下所示:

    hbase(main):003:0> create 'test', 'cf'
    0 row(s) in 0.4210 seconds
    => Hbase::Table - test 
    
    hbase(main):004:0> list
    TABLE 
    SYSTEM.CATALOG 
    SYSTEM.SEQUENCE 
    SYSTEM.STATS 
    test
    4 row(s) in 0.0360 seconds
    
    
  4. 现在,使用 HBaseput命令向test表插入一行,如下所示。 使用row1作为行键,使用cf:a作为列名,使用 10 作为值

    hbase(main):005:0> put 'test', 'row1', 'cf:a', '10'
    0 row(s) in 0.0080 seconds
    
    
  5. 使用以下命令扫描test表,该命令将打印表中的所有数据:

    hbase(main):006:0> scan 'test'
    ROW      COLUMN+CELL 
    row1column=cf:a, timestamp=1338485017447, value=10 
    1 row(s) in 0.0320 seconds
    
    
  6. 使用以下命令从表中检索值,方法是将test指定为表名,将row1指定为 RowKey:

    hbase(main):007:0> get 'test', 'row1'
    COLUMN    CELL 
    cf:atimestamp=1338485017447, value=10 
    1 row(s) in 0.0130 seconds
    
    
  7. 使用disabledrop命令禁用并删除测试表,如下所示:

    hbase(main):014:0> disable 'test'
    0 row(s) in 11.3290 seconds
    
    hbase(main):015:0> drop 'test'
    0 row(s) in 0.4500 seconds
    
    

还有更多...

除了本章接下来的几个菜谱外,本书中的以下菜谱也使用了 HBase,并提供了更多关于 HBase 的用例:

  • 将大型数据集加载到 Apache HBase 数据存储--第 10 章海量文本数据处理中的 Importtsv 和 BulkLoad配方
  • 创建用于第 10 章海量文本数据处理的文本数据配方的 TF 和 TF-IDF 向量
  • 生成第 8 章搜索和索引的爬行网页的内链图配方
  • 在 Amazon EC2 上使用云部署的 EMR配方部署 Apache HBase 集群-在云环境上使用 Hadoop Yarn

另请参阅

使用 Java 客户端 API 随机访问数据

前面的配方引入了 HBase 的命令行界面。 本食谱演示了如何使用 Java API 与 HBase 交互。

做好准备

本食谱要求 Apache HBase 安装与 Hadoop Yarn 集群集成。 在开始之前,请确保启动所有已配置的 HBase Master 和 RegionServer 进程。

怎么做……

以下步骤执行 HBase Java 客户端以从 HBase 表存储和检索数据。

通过从示例源资料库的chapter 7文件夹运行以下命令来运行HBaseClientJava 程序:

$ gradle execute HBaseClient

它是如何工作的.

上述 Java 程序的源代码位于源代码存储库的chapter7/src/chapter7/hbase/HBaseClient.java文件中。 以下代码创建一个 HBase 配置对象,然后创建到testHBase 表的连接。 此步骤使用 ZooKeeper 获取 HBase 主机名和端口。 在高吞吐量生产场景下,建议使用HConnection实例连接 HBase 表。

Configuration conf = HBaseConfiguration.create();
HTable table = new HTable(conf, "test");

下面的命令将向 HBase 表添加一个数据行:

Put put = new Put("row1".getBytes());
put.add("cf".getBytes(), "b".getBytes(), "val2".getBytes());
table.put(put);

通过执行扫描搜索数据,如下所示:

Scan s = new Scan();
s.addFamily(Bytes.toBytes("cf")); 
ResultScanner results = table.getScanner(s);

在 HBase 上运行 MapReduce 作业

这个配方解释了如何运行 MapReduce 作业,该作业直接从 HBase 存储读取数据和从 HBase 存储写入数据。

HBase 提供抽象的映射器和减少器实现,用户可以扩展到直接从 HBase 读取和写入。 本食谱解释了如何使用这些映射器和减少器编写示例 MapReduce 应用。

我们将使用世界银行的《人类发展报告》(HDR)数据,其中显示每个国家的人均国民总收入(GNI)。 数据集可在http://hdr.undp.org/en/statistics/data/找到。 示例源代码存储库中的chapter7/resources/hdi-data.csv文件中提供了该数据集的示例。 使用 MapReduce,我们将按国家计算人均国民总收入的平均值。

做好准备

本食谱要求 Apache HBase 安装与 Hadoop Yarn 集群集成。 在开始之前,请确保启动所有已配置的 HBase Master 和 RegionServer 进程。

怎么做……

本节演示如何对存储在 HBase 中的数据运行 MapReduce 作业。 继续执行以下步骤:

  1. 从源代码库的chapter7文件夹执行命令编译源代码,如下所示:

    $ gradle build 
    
    
  2. chapter7文件夹运行以下命令,将示例数据上传到 HBase。 此命令使用chapter7/src/chapter7/hbase/HDIDataUploader上载数据:

    $ gradle executeHDIDataUpload
    
    
  3. 通过从HADOOP_HOME运行以下命令来运行 MapReduce 作业:

    $ hadoop jar hcb-c7-samples.jar \
     chapter7.hbase.AverageGINByCountryCalcualtor
    
    
  4. 通过从 HBase shell 运行以下命令在 HBase 中查看结果:

    $ hbase shell
    hbase(main):009:0> scan  'HDIResult'
    
    

它是如何工作的.

您可以在chapter7/src/chapter7/hbase/AverageGINByCountryCalcualtor.java中找到 Java HBase MapReduce 示例。 由于我们将使用 HBase 来读取输入和写入输出,因此我们使用 HBaseTableMapperTableReducer帮助器类来实现 MapReduce 应用。 我们使用TableMapReduceUtil类中给出的实用程序方法配置TableMapperTableReducer。 对象Scan用于指定映射器在从 HBase 数据存储读取输入数据时要使用的条件。

使用配置单元将数据插入 HBase 表

配置单元-HBase 集成使我们能够使用配置单元查询语言(HQL)查询 HBase 表。 配置单元-HBase 集成支持将现有的 HBase 表映射到配置单元表,以及使用 HQL 创建新的 HBase 表。 HQL 支持从 HBase 表中读取数据和向 HBase 表中插入数据,包括执行配置单元映射的 HBase 表和传统配置单元表之间的连接。

下面的配方使用 HQL 创建一个 HBase 表来存储bookcrossing数据集的books表,并使用示例数据填充该表。

做好准备

遵循配置单元批处理模式-使用第 6 章Hadoop 生态系统的查询文件配方-Apache 配置单元创建我们将在本配方中使用的配置单元表。

怎么做……

本节演示如何从 Pig 访问 Hive 表。 继续执行以下步骤:

  1. 使用以下命令启动配置单元外壳:

    $ hive
    
    
  2. 在配置单元 shell 中发出以下命令以创建 HBase 表。 HBaseStorageHandler类负责与 HBase 的数据通信。 我们必须指定hbase.column.mapping属性来指示配置单元如何将 HBase 表的列映射到相应的配置单元表:

    CREATE TABLE IF NOT EXISTS books_hbase
     (key STRING,
     title STRING,
     author STRING,
     year INT,
     publisher STRING,
     image_s STRING,
     image_m STRING,
     image_l STRING)
    STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
    WITH SERDEPROPERTIES ('hbase.columns.mapping' =   ':key,f:title,f:author,f:year,f:publisher,img:image_s,img:image_m,img:image_l')
    TBLPROPERTIES ('hbase.table.name' = 'bx-books');
    
    
  3. 发出以下配置单元查询,将数据插入到新创建的 HBase 表中。 HBase 表中的行键必须是唯一的。 当有多行具有重复的行键时,HBase 只存储其中的一行,而丢弃其他行。 使用图书 ISBN(对于每本图书都是唯一的)作为以下示例中的 RowKey:

    hive> insert into table books_hbase select * from bookcrossing.books;
    ….
    Total MapReduce CPU Time Spent: 23 seconds 810 msec
    OK
    books.isbn    books.title    books.author    books.year    books.publisher    books.image_s    books.image_m    books.image_l
    Time taken: 37.817 seconds
    
    
  4. 使用以下命令检查插入配置单元映射 HBase 表的数据:

    hive> select * from books_hbase limit 10;
    
    
  5. 我们还可以在刚刚创建的表上执行配置单元函数,如count,如下所示:

    hive> select count(*) from books_hbase; 
    ...
    Total MapReduce CPU Time Spent: 22 seconds 510 msec
    OK
    _c0
    271379
    
    
  6. 启动 HBase shell 并发出list命令查看 HBase 中的表列表,如下所示:

    $ hbase shell
    hbase(main):001:0> list
    TABLE
    …… 
    SYSTEM.STATS
    bx-books
    …… 
    8 row(s) in 1.4260 seconds
    
    
  7. Inspect the data of the bx-books HBase table using the following command:

    hbase(main):003:0> scan 'bx-books', {'LIMIT' => 5}
    
    

    前面命令的输出如下所示:

    How to do it...

另请参阅

  • HCatalog-在映射到配置单元表的数据上执行 Java MapReduce 计算-从 Java MapReduce 计算配方将数据写入配置单元表第 6 章Hadoop 生态系统-Apache 配置单元

Apache Mahout 入门

Mahout 是一种使用 Hadoop MapReduce 框架实现众所周知的机器学习数据挖掘算法的努力。 用户可以在他们的数据处理应用中使用 Mahout 算法实现,而无需经历使用 Hadoop MapReduce 从头开始实现这些算法的复杂性。

这个食谱解释了如何开始使用 Mahout。

为了安装 Mahout,我们建议您使用可免费获得的商业 Hadoop 发行版之一,如章 1《Hadoopv2 入门》中所述。 另一种选择是使用 Apache bigtop 安装 Mahout。 有关使用 Apache bigtop 发行版安装 Mahout 的步骤,请参阅第 1 章《Hadoop v2 入门》中与 bigtop 相关的食谱。

怎么做……

本节演示如何通过运行示例 KMeans 集群计算来开始使用 Mahout。 您可以通过执行以下步骤来运行和验证 Mahout 安装:

  1. http://archive.ics.uci.edu/ml/databases/synthetic_control/synthetic_control.data下载输入数据如下:

    $ wget http://archive.ics.uci.edu/ml/databases/synthetic_control/synthetic_control.data 
    
    
  2. 创建名为testdata的 HDFS 目录,并使用以下命令将下载的文件复制到该目录:

    $ hdfs dfs –mkdir testdata
    $ hdfs dfs –copyFromLocal synthetic_control.data  testdata
    
    
  3. 通过运行以下命令运行 K 均值示例:

    $ mahout org.apache.mahout.clustering.syntheticcontrol.kmeans.Job
    
    
  4. 如果一切正常,它将处理并打印出群集:

    12/06/19 21:18:15 INFO kmeans.Job: Running with default arguments
    12/06/19 21:18:15 INFO kmeans.Job: Preparing Input
    12/06/19 21:18:15 WARN mapred.JobClient: Use GenericOptionsParser for parsing the arguments. Applications should implement Tool for the same.
    .....
    2/06/19 21:19:38 INFO clustering.ClusterDumper: Wrote 6 clusters
    12/06/19 21:19:38 INFO driver.MahoutDriver: Program took 83559 ms (Minutes: 1.39265)
    
    

它是如何工作的.

Mahout 是 MapReduce 作业的集合,您可以使用mahout命令运行它们。 前面的说明通过运行 Mahout 发行版附带的K-Means示例安装并验证了 Mahout。

还有更多...

除了本章的下一个配方外,本书第 10 章海量文本数据处理中的以下配方也使用 Mahout:

  • 创建用于第 10 章海量文本数据处理的文本数据配方的 TF 和 TF-IDF 向量
  • 第 10 章海量文本数据处理中的使用 Apache Mahout配方对文本数据进行聚类
  • 使用潜在狄利克雷分配(LDA)配方的主题发现第 10 章海量文本数据处理**
  • 使用 Mahout 朴素贝叶斯分类器的文档分类配方第 10 章海量文本数据处理

使用 Mahout 运行 K-Means

K-Means 是一种聚类算法。 聚类算法采用在和N 维空间中定义的数据点,并通过考虑这些数据点之间的距离将它们分组到多个中。 群集是一组数据点,使得群集内的数据点之间的距离远远小于群集内的数据点到群集外的数据点的距离。 关于 K-Means 聚类的更多细节可以在 Google 的集群计算和 MapReduce系列讲座的第 4 讲(http://www.youtube.com/watch?v=1ZDybXl212Q)中找到。

在这个配方中,我们将使用一个数据集,其中包括按国家/地区列出的人类发展报告(HDR)。 人类发展报告根据几项人类发展指标描述了不同的国家。 您可以在http://hdr.undp.org/en/statistics/data/找到该数据集。 示例源代码存储库中的chapter7/resources/hdi-data.csv文件中提供了该数据集的示例。 本食谱将使用 K-Means 根据 HDR 维度对国家进行分类。

做好准备

这个食谱需要一个 Mahout 安装。 如果您还没有安装 Mahout,请按照前面的方法安装。

怎么做……

本节演示如何使用 Mahout K-Means 算法处理数据集。 继续执行以下步骤:

  1. 使用以下 Gradle 命令编译示例:

    $ gradle build 
    
    
  2. 将文件chapter7/resources/countries4Kmean.data复制到 HDFS 中的testdata目录。 创建testdata目录。

  3. 通过运行以下命令运行示例:

    $ gradle executeKMeans
    
    

它是如何工作的.

前面的示例显示了如何配置和使用 Java 中的 K-Means 实现。 您可以在chapter7/src/chapter7/KMeansSample.java文件中找到此示例的来源。 当我们运行代码时,它会初始化 K-Means MapReduce 作业,并使用 MapReduce 框架执行它。

使用 Apache Sqoop 将数据从关系数据库导入 HDFS

ApacheSqoop 是一个支持在 Apache Hadoop 生态系统和关系数据存储之间高效地批量传输数据的项目。 Sqoop 可用于自动执行从 RDBMS(如 MySQL、PostgreSQL、Oracle 等)导入数据或将数据导出到 RDBMS 的过程。 Sqoop 还支持 Netezza 和 Teradata 等数据库设备。 它支持使用多个 Map 任务并行导入/导出数据,还支持节流以减少外部 RDBMS 的负载。

在本食谱中,我们将使用 Sqoop2 将数据从 PostgreSQL 数据库导入到 HDFS。 我们还包括了 Sqoop 1.4.x 的说明,因为该 Sqoop 版本在当前 Hadoop 发行版中有广泛的可用性和用途。

我们建议您使用第 1 章《Hadoop v2 入门》中描述的免费商业 Hadoop 发行版之一来安装 Apache Sqoop2 或 Sqoop 1.4.x。 另一种选择是使用 Apache bigtop 安装 Apache Sqoop2。

做好准备

本食谱需要一个安装了 Sqoop2 或 Sqoop 1.4.x 的工作 Hadoop2 集群。

我们将使用 PostgreSQL 数据库。 您也可以使用另一个 RDBMS 来实现此目的,但是必须相应地更改以下配方中的某些步骤。

怎么做……

本节演示如何使用 SQOOP 将数据从 PostgreSQL 数据库导入到 HDFS。 继续执行以下步骤:

  1. Download the appropriate PostgreSQL JDBC driver from http://jdbc.postgresql.org/download.html and copy it to the lib directory of the SQOOP web app using the following command and restart the SQOOP server:

    $ cp postgresql-XXXX.jdbcX.jar \
    /usr/lib/sqoop/webapps/sqoop/WEB-INF/lib/
    
    

    备注

    对于 Sqoop 1.4.x,将 PostgreSQL JDBC 驱动程序 JAR 复制到 Sqoop 安装的 lib 文件夹。

  2. 在 PostgreSQL 中创建用户和数据库,如下所示。 也可以使用您的操作系统用户名作为 PostgreSQL 数据库中的用户。 对于本食谱,您可以使用现有的 PostgreSQL 用户和现有的数据库:

    $ sudo su - postgres
    $ psql
    postgres=# CREATE USER aluck WITH PASSWORD 'xxx123';
    CREATE ROLE
    postgres=# CREATE DATABASE test;
    CREATE DATABASE
    postgres=# GRANT ALL PRIVILEGES ON DATABASE test TO aluck;
    GRANT
    postgres=# \q
    
    
  3. 登录到新创建的数据库。 使用 PostgreSQL 外壳中的以下语句创建模式和数据库表:

    $ psql test
    
    test=> CREATE SCHEMA bookcrossing;
    CREATE SCHEMA
    test=> CREATE TABLE bookcrossing.ratings 
     (user_id INT, 
     isbn TEXT, 
     rating TEXT);
    CREATE TABLE
    
    
  4. Load the book-ratings.txt dataset in the chapter7 folder of the Git repository into the table we just created, using the following command:

    test=> \COPY bookcrossing.ratings FROM '…/chapter7/book-ratings.txt' DELIMITER ';'
    test=# select * from bookcrossing.ratings limit 10;
    
     user_id |    isbn    | rating 
    ---------+------------+--------
     276725 | 034545104X | 0
     276726 | 0155061224 | 5
     276727 | 0446520802 | 0
     276729 | 052165615X | 3
     276729 | 0521795028 | 6
     276733 | 2080674722 | 0
     276736 | 3257224281 | 8
     276737 | 0600570967 | 6
     276744 | 038550120X | 7
     276745 | 342310538  | 10
    (10 rows)
    
    

    备注

    以下步骤(6 到 9)适用于 Sqoop2。 有关 Sqoop 1.4.x 的说明,请跳至步骤 10。

  5. 在 SQOOP 命令行客户端中使用以下命令创建 SQOOP 连接,并回答提示的问题:

    $ sqoop
    sqoop:000> create connection --cid 1 
    Creating connection for connector with id 1
    Please fill following values to create new connection object
    Name: t2
    
    Connection configuration
    
    JDBC Driver Class: org.postgresql.Driver 
    JDBC Connection String: jdbc:postgresql://localhost:5432/test
    Username: testuser
    Password: ****
    JDBC Connection Properties: 
    There are currently 0 values in the map:
    …
    New connection was successfully created with validation status FINE and persistent id 3
    
    
  6. 创建 SQOOP 作业以将数据导入 HDFS,如下所示:

    sqoop:000> create job --xid 1 --type import
    Creating job for connection with id 1
    Please fill following values to create new job object
    Name: importest 
    Database configuration
    Schema name: bookcrossing
    Table name: ratings
    Table SQL statement: 
    Table column names: 
    Partition column name: user_id
    Boundary query: 
    
    Output configuration
    Storage type: 
     0 : HDFS
    Choose: 0
    Output format: 
     0 : TEXT_FILE
     1 : SEQUENCE_FILE
    Choose: 0
    Output directory: /user/test/book_ratings_import
    New job was successfully created with validation status FINE  and persistent id 8
    
    
  7. 使用以下命令提交 Sqoop 作业:

    sqoop:000> submission start --jid 8 
    Submission details
    Job id: 8
    Status: BOOTING 
    Creation date: 2014-10-15 00:01:20 EDT
    
    
  8. 使用以下命令监视作业状态:

    sqoop:000> submission status --jid 8
    Submission details
    Job id: 8
    Status: SUCCEEDED
    Creation date: 2014-10-15 00:01:20 EDT
    
    
  9. 检查 HDFS 目录中的数据。 您可以将此数据映射到配置单元表以供进一步查询。 接下来的两步只适用于 Sqoop 1.4.x。 如果您使用的是 Sqoop 2,请跳过它们。

  10. 发出以下 Sqoop 命令,将数据从 PostgreSQL 直接导入配置单元表。 相应地替换 PostgreSQL 数据库 IP 地址(或主机名)、数据库端口和数据库用户名。 成功执行以下命令后,将在 HDFS 主目录中创建一个名为‘Rating’的文件夹,其中包含从 PostgreSQL 导入的数据:

```scala
$ sqoop import \
--connect jdbc:postgresql://<ip_address>:5432/test \
--table ratings \
--username aluck -P \
--direct -- --schema bookcrossing

```
  1. 发出以下 Sqoop 命令,将数据从 PostgreSQL 导入到 HDFS 主目录。 相应地替换 PostgreSQL 数据库 IP 地址(或主机名)、数据库端口和数据库用户名。 成功执行以下命令后,将在当前配置单元数据库中创建一个名为‘Rating’的配置单元表,其中包含从 PostgreSQL 导入的数据:
```scala
$ sqoop import \
--connect jdbc:postgresql://<ip_address>:5432/test \
--table ratings \
--username aluck -P \
--hive-import \
--direct -- --schema bookcrossing

```

使用 Apache Sqoop 将数据从 HDFS 导出到关系数据库

在这个配方中,我们将使用 Sqoop2 或 Sqoop 1.4.x 将数据从 HDFS 导出到 PostgreSQL 数据库。

做好准备

本食谱需要一个安装了 Sqoop2 或 Sqoop 1.4.x 的工作 Hadoop2 集群。

我们将使用 PostgreSQL 数据库。 您也可以使用另一个 RDBMS 来实现此目的,但是必须相应地更改以下配方步骤。

遵循前面的配方,使用 Apache Sqoop将数据从关系数据库导入 HDFS。

怎么做……

本节演示如何使用 SQOOP 将数据从 HDFS 导出到 PostgreSQL 数据库。 继续执行以下步骤:

  1. 按照前面中的步骤 1 使用 Apache Sqoop配方将数据从关系数据库导入 HDFS,在 PostgreSQL 数据库中创建一个用户和一个数据库。

  2. Create a database table using the following statements in the PostgreSQL shell:

    $ psql test
    test=> CREATE TABLE bookcrossing.ratings_copy
     (user_id INT,
     isbn TEXT, 
     rating TEXT);
    
    

    备注

    以下步骤(3 到 5)适用于 Sqoop2。 有关 Sqoop 1.4.x 的说明,请跳至步骤 6。

  3. 创建 SQOOP 作业以从 HDFS 导出数据,如下所示:

    sqoop:000> create job --xid 1 --type export
    Creating job for connection with id 1
    Please fill following values to create new job object
    Name: exporttest
    
    Database configuration
    Schema name: bookcrossing
    Table name: ratings_copy
    Table SQL statement: 
    Table column names: 
    Input configuration
    Input directory: /user/test/book_ratings_import
    Throttling resources
    Extractors: 
    Loaders: 
    New job was successfully created with validation status FINE  and persistent id 13
    
    
  4. 使用以下命令提交 Sqoop 作业:

    sqoop:000> submission start --jid 13 
    Submission details
    Job id: 13
    Status: BOOTING 
     …..
    
    
  5. 使用此命令监视作业状态。 跳到步骤 7:

    sqoop:000> submission status --jid 13
    Submission details
    Job id: 13
    Status: SUCCEEDED
    
    
  6. 此步骤仅适用于 Sqoop 1.4.x。 使用 Apache Sqoop 配方重新执行前面*步骤中的步骤 11,将数据从关系数据库导入 HDFS,以确保 HDFS 主目录中有包含导入数据的“Rating”文件夹。 发出以下 Sqoop 命令将数据从 HDFS 直接导出到 PostgreSQL 表中。 替换 PostgreSQL 数据库 IP 地址(或主机名)、数据库端口、数据库用户名,相应导出数据源目录。 执行此步骤将导致 Hadoop MapReduce 作业:

    $ sqoop export \
    --connect jdbc:postgresql://<ip_address>:5432/test \
    --table ratings_copy \
    --username aluck -P \
    --export-dir /user/aluck/ratings
    --input-fields-terminated-by ','
    --lines-terminated-by '\n'
    -- --schema bookcrossing
    
    ```* 
    
  7. 登录 PostgreSQL shell 并检查导入的数据:

    test=# select * from bookcrossing.ratings_copy limit 10;
     user_id |    isbn    | rating 
    ---------+------------+--------
     276725 | 034545104X | 0
     276726 | 0155061224 | 5
     276727 | 0446520802 | 0
     276729 | 052165615X | 3
     276729 | 0521795028 | 6
    
    

八、搜索和索引

在本章中,我们将介绍以下食谱:

  • 使用 Hadoop MapReduce 生成倒排索引
  • 使用 Apache Nutch 进行域内 Web 爬行
  • 使用 Apache Solr 对 Web 文档进行索引和搜索
  • 将 Apache HBase 配置为 Apache Nutch 的后端数据存储
  • 使用 Hadoop/HBase 群集使用 Apache Nutch 进行全 Web 爬行
  • 用于索引和搜索的 ElasticSearch
  • 为爬行网页生成内链接图

简介

MapReduce 框架非常适合大规模搜索和索引应用。 事实上,谷歌提出了最初的 MapReduce 框架,专门用来简化网络搜索涉及的各种操作。 Apache Hadoop 项目也是作为Apache Nutch搜索引擎的子项目开始的,然后作为单独的顶级项目衍生出来。

网络搜索由获取、索引、排序和检索组成。 考虑到数据量非常大,所有这些操作都需要可伸缩。 此外,检索的延迟也应该很低。 通常,获取是通过 Web 爬行执行的,其中爬行器获取获取队列中的一组页面,从获取的页面中提取链接,将提取的链接添加回获取队列,并重复此过程多次。 索引以快速高效的方式解析、组织和存储获取的数据,以便于查询和检索。 搜索引擎基于诸如 PageRank 之类的算法对文档进行离线排名,并基于查询参数对结果进行实时排名。

在本章中,我们将介绍几个可用于 Apache Hadoop 来执行大规模搜索和索引的工具。

提示

示例代码

本书的示例代码文件位于 gihub 的https://github.com/thilg/hcb-v2chapter8文件夹代码库包含本章的示例代码。

可以通过在代码库的chapter8文件夹中发出gradle build命令来编译和构建示例代码。 Eclipse IDE 的项目文件可以通过在代码库的主文件夹中运行gradle eclipse命令来生成。 IntelliJ IDEA IDE 的项目文件可以通过在代码存储库的主文件夹中运行gradle idea命令来生成。

使用 Hadoop MapReduce 生成倒排索引

Simple 文本搜索系统依靠倒排索引来查找包含给定词或术语的文档集。 在本食谱中,我们实现了一个简单的倒排索引构建应用,该应用计算文档中的术语列表、包含每个术语的文档集以及每个文档中的术语频率。 从倒排索引检索结果可以像返回包含给定项的文档集一样简单,也可以涉及复杂得多的操作,例如返回基于特定排名排序的文档集。

做好准备

您必须配置并安装 Apache Hadoopv2 才能遵循本指南。 编译和构建源代码需要 Gradle。

怎么做……

在以下步骤中,我们将使用 MapReduce 程序为文本数据集构建倒排索引:

  1. Create a directory in HDFS and upload a text dataset. This dataset should consist of one or more text files.

    $ hdfs dfs -mkdir input_dir
    $ hdfs dfs -put *.txt input_dir
    
    

    备注

    您可以按照http://www.gutenberg.org/wiki/Gutenberg:Information_About_Robot_Access_to_our_Pages给出的说明下载古腾堡计划书籍的文本版本。 请确保提供下载请求的filetypes查询参数为txt。 解压缩下载的文件。 您可以使用解压缩的文本文件作为此食谱的文本数据集。

  2. 通过从源资料库的chapter 8文件夹运行gradle build命令,编译源。

  3. 使用以下命令运行倒排索引 MapReduce 作业。 提供在步骤 2 中上载输入数据的 HDFS 目录作为第一个参数,并提供 HDFS 路径以存储输出作为第二个参数:

    $ hadoop jar hcb-c8-samples.jar \
     chapter8.invertindex.TextOutInvertedIndexMapReduce \
     input_dir output_dir
    
    
  4. 通过运行以下命令检查输出目录中的结果。 此程序的输出将由术语后跟逗号分隔的文件名和频率列表组成:

    $ hdfs dfs -cat output_dir/*
    ARE three.txt:1,one.txt:1,four.txt:1,two.txt:1,
    AS three.txt:2,one.txt:2,four.txt:2,two.txt:2,
    AUGUSTA three.txt:1,
    About three.txt:1,two.txt:1,
    Abroad three.txt:2,
    
    
  5. 为了更清楚地理解算法,我们在步骤 3 中使用了文本输出倒排索引 MapReduce 程序。 chapter8存储库的源文件夹中的chapter8/invertindex/InvertedIndexMapReduce.javaMapReduce 程序使用 HadoopSequenceFilesMapWritable类输出倒排索引。 该索引对机器处理更友好,存储效率更高。 您可以通过将步骤 3 中的命令替换为以下命令来运行此版本的程序:

    $ hadoop jar hcb-c8-samples.jar \
     chapter8.invertindex.InvertedIndexMapReduce \
     input_dir seq_output_dir
    
    

它是如何工作的.

Map 函数接收输入文档的一大块作为输入,并为每个单词输出术语和<docid,1>对。 在 Map 函数中,我们首先替换输入文本值中的所有非字母数字字符,然后再对其进行标记,如下所示:

public void map(Object key, Text value, ……… {
  String valString = value.toString().replaceAll("[^a-zA-Z0-9]+"," ");
  StringTokenizer itr = new StringTokenizer(valString);
   StringTokenizer(value.toString());

  FileSplit fileSplit = (FileSplit) context.getInputSplit();
  String fileName = fileSplit.getPath().getName();
  while (itr.hasMoreTokens()) {
    term.set(itr.nextToken());
    docFrequency.set(fileName, 1);
    context.write(term, docFrequency);
  }
}

我们使用MapContextgetInputSplit()方法来获取对分配给当前 Map 任务的InputSplit的引用。 此计算的InputSplits类是FileSplit的实例,这是因为使用了基于FileInputFormatInputFormat。 然后,我们使用FileSplitgetPath()方法获取包含当前拆分的文件的路径,并从中提取文件名。 在构建倒排索引时,我们使用这个提取的文件名作为文档 ID。

Reduce函数接收包含术语(键)作为输入的所有文档的 ID 和频率。 然后,Reduce函数将该术语和文档 ID 列表以及该术语在每个文档中出现的次数作为输出输出:

public void reduce(Text key, Iterable<TermFrequencyWritable> values,Context context) …………{

  HashMap<Text, IntWritable> map = new HashMap<Text, IntWritable>();
  for (TermFrequencyWritable val : values) {
    Text docID = new Text(val.getDocumentID());
    int freq = val.getFreq().get();
    if (map.get(docID) != null) {
      map.put(docID, new IntWritable(map.get(docID).get() + freq));
    } else {
      map.put(docID, new IntWritable(freq));
    }
  }
  MapWritable outputMap = new MapWritable();
  outputMap.putAll(map);
  context.write(key, outputMap);
}

在前面的模型中,我们为每个单词输出一条记录,在 Map 任务和 Reduce 任务之间生成大量中间数据。 我们使用以下合并器聚合 Map 任务发出的术语,从而减少需要在 Map 和 Reduce 任务之间传输的中间数据量:

public void reduce(Text key, Iterable<TermFrequencyWritable> values …… {
  int count = 0;
  String id = "";
  for (TermFrequencyWritable val : values) {
    count++;
    if (count == 1) {
      id = val.getDocumentID().toString();
    }
  }
  TermFrequencyWritable writable = new TermFrequencyWritable();
  writable.set(id, count);
  context.write(key, writable);
}

在驱动程序中,我们设置了 Mapper、Reducer 和 Combiner 类。 此外,当我们为 Map 任务和 Reduce 任务使用不同的值类型时,我们同时指定了 Output Value 和 MapOutput Value 属性。

…
job.setMapperClass(IndexingMapper.class);
job.setReducerClass(IndexingReducer.class);
job.setCombinerClass(IndexingCombiner.class);
…
job.setMapOutputValueClass(TermFrequencyWritable.class);
job.setOutputValueClass(MapWritable.class);
job.setOutputFormatClass(SequenceFileOutputFormat.class);

还有更多...

我们可以通过执行诸如过滤停用词、用词干替换词、存储更多关于词的上下文的信息等优化来改进该索引程序,从而使索引成为一个复杂得多的问题。 幸运的是,有几个开源索引框架可以用于索引目的。 本章后面的食谱将探索使用 Apache Solr 和 Elasticsearch 进行索引,它们基于 Apache Lucene 索引引擎。

下一节将介绍如何使用MapFileOutputFormat以索引随机访问的方式存储InvertedIndex

输出随机可访问索引 InvertedIndex

Apache Hadoop 支持一种名为MapFile的文件格式,可用于将索引存储到 SequenceFiles 中存储的数据中。 当我们需要随机访问存储在大型 SequenceFile 中的记录时,MapFile 非常有用。 您可以使用MapFileOutputFormat格式来输出 MapFiles,它将由一个包含实际数据的 SequenceFile 和另一个包含 SequenceFile 索引的文件组成。

chapter8源文件夹中的chapter8/invertindex/MapFileOutInvertedIndexMR.javaMapReduce 程序利用 MapFiles 将二级索引存储到倒排索引中。 您可以使用以下命令执行该程序。 第三个参数(sample_lookup_term)应该是输入数据集中的单词:

$ hadoop jar hcb-c8-samples.jar \
 chapter8.invertindex.MapFileOutInvertedIndexMR \
 input_dir indexed_output_dir sample_lookup_term

如果您选中indexed_output_dir,您将能够看到名为part-r-xxxxx的文件夹,每个文件夹包含一个data和一个index文件。 我们可以将这些索引加载到 MapFileOutputFormat,并对数据执行随机查找。 在MapFileOutInvertedIndexMR.java程序中给出了一个使用此方法的简单查找示例,如下所示:

MapFile.Reader[] indexReaders = MapFileOutputFormat.getReaders(new Path(args[1]), getConf());
MapWritable value = new MapWritable();
Text lookupKey = new Text(args[2]);
// Performing the lookup for the values if the lookupKey
Writable map = MapFileOutputFormat.getEntry(indexReaders, new HashPartitioner<Text, MapWritable>(), lookupKey, value);

为了使用此功能,您需要通过设置以下属性来确保禁止 Hadoop 在output文件夹中写入 _SUCCESS文件。 使用 MapFileOutputFormat 查找索引中的值时,_SUCCESS文件的存在可能会导致错误:

job.getConfiguration().setBoolean("mapreduce.fileoutputcommitter.marksuccessfuljobs", false);

另请参阅

  • 第 10 章海量文本数据处理中,为文本数据配方创建 TF 和 TF-IDF 矢量。

使用 Apache Nutch 进行域内网络爬行

网络爬行是访问和下载 Internet 上的所有或部分网页的过程。 虽然爬行和实现简单爬行器的概念听起来很简单,但构建一个成熟的爬行器需要大量的工作。 一个成熟的爬行器需要是分布式的,必须遵守最佳实践,如不使服务器过载并遵守robots.txt、执行定期爬行、确定要爬行的页面的优先顺序、识别多种格式的文档等。 Apache Nutch 是一个开源搜索引擎,它提供了一个高度可伸缩的爬行器。 Apache Nutch 提供了礼貌、健壮性和可伸缩性等特性。

在本食谱中,我们将在独立模式下使用 Apache Nutch 进行小规模的域内 Web 爬行。 几乎所有的 Nutch 命令都是作为 Hadoop MapReduce 应用实现的,正如您在执行本菜谱的步骤 10 到 18 时会注意到的那样。 Nutch Standalone 在本地模式下使用 Hadoop 执行这些应用。

本食谱以http://wiki.apache.org/nutch/NutchTutorial给出的说明为基础。

做好准备

设置JAVA_HOME环境变量。 安装 Apache Ant 并将其添加到PATH环境变量。

怎么做……

以下步骤显示了如何在独立模式下使用 Apache Nutch 进行小规模 Web 爬网:

  1. Apache Nutch 独立模式使用 HyperSQL 数据库作为默认数据存储。 从http://sourceforge.net/projects/hsqldb/下载 HyperSQL。 解压缩发行版并转到数据目录:

    $ cd hsqldb-2.3.2/hsqldb
    
    
  2. 使用以下命令启动 HyperSQL 数据库。 以下数据库使用data/nutchdb.*作为数据库文件,并使用nutchdb作为数据库别名。 在步骤 7:

    $ java -cp lib/hsqldb.jar \
    org.hsqldb.server.Server \
    --database.0 file:data/nutchdb \
    --dbname.0 nutchtest
    ......
    [Server@79616c7]: Database [index=0, id=0, db=file:data/nutchdb, alias=nutchdb] opened sucessfully in 523 ms.
    ......
    
    

    中,我们将在gora.sqlstore.jdbc.url属性中使用此数据库别名

  3. http://nutch.apache.org/下载 Apache Nutch 2.2.1 并解压缩。

  4. Go to the extracted directory, which we will refer as NUTCH_HOME. Change the gora-core dependency version to 0.2.1 and uncomment the gora-sql dependency by modifying the Gora artifacts section of the ivy/ivy.xml file as follows:

    <!--================-->
    <!-- Gora artifacts -->
    <!--================-->
    <dependency org="org.apache.gora" name="gora-core" rev="0.2.1" conf="*->default"/>
    
    <dependency org="org.apache.gora" name="gora-sql" rev="0.1.1-incubating" conf="*->default" />
    

    备注

    您还可以通过更新conf/gora.properties文件的Default SqlStore properties部分中的必要数据库配置,将 MySQL 数据库用作 Nutch 独立模式 Web 爬网的后端数据库。 您还必须取消注释ivy/ivy.xml文件的Gora artifacts部分中的mysql-connector-java依赖项。

  5. 使用以下命令构建 Apache Nutch:

    $ ant runtime
    
    
  6. 确保在NUTCH_HOME/runtime/local/conf/gora.properties文件中包含以下内容。 提供步骤 2 中使用的数据库别名:

    ###############################
    # Default SqlStore properties #
    ###############################
    gora.sqlstore.jdbc.driver=org.hsqldb.jdbc.JDBCDriver
    gora.sqlstore.jdbc.url=jdbc:hsqldb:hsql://localhost/nutchtest
    gora.sqlstore.jdbc.user=sa
    
  7. 转到runtime/local目录并运行bin/nutch命令以验证 Nutch 安装。 成功的安装将打印出如下的 Nutch 命令列表:

    $ cd runtime/local
    $ bin/nutch 
    Usage: nutch COMMAND
    where COMMAND is one of:…..
    
    
  8. 将以下内容添加到NUTCH_HOME/runtime/local/conf/nutch-site.xml。 您可以为指定http.agent.name

    <configuration>
    <property>
      <name>storage.data.store.class</name>
      <value>org.apache.gora.sql.store.SqlStore</value>
    </property>
    <property>
      <name>http.agent.name</name>
      <value>NutchCrawler</value>
    </property>
    <property>
      <name>http.robots.agents</name>
      <value>NutchCrawler,*</value>
    </property>
    </configuration>
    

    的值的任何名称

  9. 您可以通过编辑位于NUTCH_HOME/runtime/local/conf/regex-urlfiler.txt文件来限制要爬网的域名。 例如,为了将域限制为http://apache.org,请在NUTCH_HOME/runtime/local/conf/regex-urlfilter.txt处替换以下行:

    # accept anything else
    +.
    
  10. 使用以下正则表达式:

```scala
+^http://([a-z0-9]*\.)*apache.org/
```
  1. 创建名为urls的目录,并在该目录内创建名为seed.txt的文件。 将您的种子 URL 添加到此文件。 种子 URL 用于开始爬网,并且将是最先爬网的页面。 在下面的示例中,我们使用http://apache.org作为种子 URL:
```scala
$ mkdir urls
$ echo http://apache.org/ > urls/seed.txt

```
  1. 使用以下命令将种子 URL 注入 Nutch 数据库:
```scala
$ bin/nutch inject urls/
InjectorJob: starting
InjectorJob: urlDir: urls
……
Injector: finished

```
  1. 使用以下命令验证种子是否注入到 Nutch 数据库。 此命令打印的TOTAL urls应与您的seed.txt文件中的 URL 数量相匹配。 您还可以在后面的周期中使用以下命令来了解数据库中的网页条目数量:
```scala
$ bin/nutch readdb  -stats
WebTable statistics start
Statistics for WebTable: 
min score:  1.0
....
TOTAL urls:  1

```
  1. 使用以下命令从注入的种子 URL 生成获取列表。 这将准备在爬行的第一个周期中要获取的网页列表。 生成将为当前生成的获取列表分配一个批处理 ID,该获取列表可在后续命令中使用:
```scala
$ bin/nutch generate –topN 1
GeneratorJob: Selecting best-scoring urls due for fetch.
GeneratorJob: starting
GeneratorJob: filtering: true
GeneratorJob: done
GeneratorJob: generated batch id: 1350617353-1356796157

```
  1. 使用以下命令获取在步骤 12 中准备的页面列表。此步骤执行网页的实际获取。 参数–all用于通知 Nutch 获取所有生成的批次:
```scala
$ bin/nutch fetch -all
FetcherJob: starting
FetcherJob: fetching all
FetcherJob: threads: 10
......

fetching http://apache.org/
......

-activeThreads=0
FetcherJob: done

```
  1. 使用下面的命令从抓取的网页中解析并提取有用的数据,如页面的文本内容、页面的元数据、从抓取的页面链接的页面集合等。 我们将从获取的页面链接的一组页面称为该特定获取的页面的外部链接。 外部链接数据将用于发现要获取的新页面,以及使用链接分析算法(如 PageRank:
```scala
$ bin/nutch parse -all
ParserJob: starting
......
ParserJob: success

```

)对页面进行排名
  1. 执行以下命令,使用上一步中提取的数据更新 Nutch 数据库。 此步骤包括更新获取的页面的内容以及添加通过获取的页面中包含的链接发现的页面的新条目。
```scala
$ bin/nutch updatedb
DbUpdaterJob: starting
……
DbUpdaterJob: done

```
  1. 执行以下命令,使用先前获取的数据中的信息生成新的获取列表。 topN参数限制为下一个提取周期生成的 URL 数量:
```scala
$ bin/nutch generate -topN 100
GeneratorJob: Selecting best-scoring urls due for fetch.
GeneratorJob: starting
......
GeneratorJob: done
GeneratorJob: generated batch id: 1350618261-1660124671

```
  1. 获取新列表,对其进行解析,然后更新数据库。
```scala
$ bin/nutch fetch –all
......
$ bin/nutch parse -all 
......
$ bin/nutch updatedb
......

```
  1. 重复步骤 17 和 18,直到从起始 URL 获得所需页数或所需深度。

另请参阅

使用 Apache Solr 索引和搜索 Web 文档

Apache Solr是一个开源搜索平台,是Apache Lucene项目的一部分。 它支持强大的全文搜索、分面搜索、动态集群、数据库集成、丰富的文档(例如 Word 和 PDF)处理和地理空间搜索。 在本食谱中,我们将为 Apache Nutch 抓取的网页编制索引,以供 Apache Solr 使用,并使用 Apache Solr 搜索这些网页。

做好准备

  1. 按照使用 Apache Nutch 配方的域内 Web 爬网,使用 Apache Nutch 爬网一组网页
  2. Solr 4.8 及更高版本需要 JDK 1.7

怎么做……

以下步骤显示如何索引和搜索已爬网的网页数据集:

  1. http://lucene.apache.org/solr/下载并提取 Apache Solr。 对于本章中的示例,我们使用 Apache Solr 4.10.3。 从这里开始,我们将提取的目录称为$SOLR_HOME

  2. 使用位于$NUTCH_HOME/runtime/local/conf/下的 schema.solr4.xml文件替换位于$SOLR_HOME/examples/solr/collection1/conf/下的schema.xml文件,如下所示:

    $ cp $NUTCH_HOME/conf/schema-solr4.xml \
     $SOLR_HOME/example/solr/collection1/conf/schema.xml
    
    
  3. 将以下配置添加到<fields>标记下的$SOLR_HOME/examples/solr/collection1/conf/schema.xml

    <fields>
      <field name="_version_" type="long" indexed="true" stored="true"/>
    ……
    </fields>
    
  4. 通过从$SOLR_HOME/下的example目录执行以下命令启动 Solr:

    $ java -jar start.jar
    
    
  5. 转到 URLhttp://localhost:8983/solr以验证 Apache Solr 安装。

  6. 通过从$NUTCH_HOME/runtime/local目录发出以下命令,将使用 Apache Nutch 获取的数据索引到 Apache Solr 中。 此命令通过 Solr Web 服务接口将 Nutch 抓取的数据推送到 Solr 中:

    $ bin/nutch solrindex http://127.0.0.1:8983/solr/ -reindex 
    
    
  7. Go to Apache Solr search UI at http://localhost:8983/solr/#/collection1/query. Enter a search term in the q textbox and click on Execute Query, as shown in the following screenshot:

    How to do it...

  8. 您还可以使用 HTTP get 请求直接发出搜索查询。 将http://localhost:8983/solr/collection1/select?q=hadoop&start=5&rows=5&wt=xmlURL 粘贴到您的浏览器。

它是如何工作的.

Apache Solr 是使用 Apache Lucene 文本搜索库构建的。 Apache Solr 在 Apache Lucene 之上添加了许多功能,并提供了一个开箱即用的文本搜索 Web 应用。 前面的步骤部署 Apache Solr,并将 Nutch 抓取的数据导入到部署的 Solr 实例中。

我们计划使用 Solr 索引和搜索的文档的元数据需要通过 Solrschema.xml文件指定。 Solr 模式文件应该定义文档中的数据字段,以及 Solr 应该如何处理这些数据字段。 我们使用随 Nutch($NUTCH_HOME/conf/schema-solr4.xml)提供的模式文件,该文件定义了 Nutch 抓取的网页的模式,作为本食谱的 Solr 模式文件。 有关 Solr 模式文件的更多信息可以在http://wiki.apache.org/solr/SchemaXml中找到。

另请参阅

将 Apache HBase 配置为 Apache Nutch 的后端数据存储

Apache Nutch 集成了 Apache Gora 以添加对不同后端数据存储的支持。 在本食谱中,我们将 Apache HBase 配置为 Apache Nutch 的后端数据存储。 同样,可以通过 Gora 插入数据存储,如 RDBMS 数据库、Cassandra 和其他。

本食谱以http://wiki.apache.org/nutch/Nutch2Tutorial给出的说明为基础。

备注

从 Apache Nutch 2.2.1 版本开始,Nutch 项目还没有正式迁移到 Hadoop 2.x,整个网络爬行仍然依赖 Hadoop 1.x。 但是,可以使用 Hadoop 2.x 集群执行 Nutch 作业,该集群利用 Hadoop 的向后兼容性特性。

Nutch HBaseStore 集成进一步依赖于 HBase 0.90.6,而 HBase 0.90.6 不支持 Hadoop2。因此,此配方仅适用于 Hadoop 1.x 集群。 我们期待着一个完全支持 Hadoop2.x 的新 Nutch 版本。

做好准备

  1. 安装 ApacheAnt 并将其添加到PATH环境变量。

怎么做……

以下步骤说明如何将 Apache HBase 本地模式配置为 Apache Nutch 的后端数据存储,以存储已爬网的数据:

  1. 安装 Apache HBase。 Apache Nutch 2.2.1 和 Apache Gora 0.3 推荐使用 HBase 0.90.6 版本。

  2. Create two directories to store the HDFS data and Zookeeper data. Add the following to the hbase-site.xml file under $HBASE_HOME/conf/ replacing the values with the paths to the two directories. Start HBase:

    <configuration>
    <property>
        <name>hbase.rootdir</name>
        <value>file:///u/software/hbase-0.90.6/hbase-data</value>
      </property>
    <property>
        <name>hbase.zookeeper.property.dataDir</name>
        <value>file:///u/software/hbase-0.90.6/zookeeper-data</value>
      </property>
    </configuration>
    

    提示

    在继续之前,请使用 HBase Shell 测试您的 HBase 安装。

  3. 如果您没有为本章前面的食谱下载 Apache Nutch,请从http://nutch.apache.org下载 Nutch 并解压缩。

  4. 将以下内容添加到$NUTCH_HOME/conf/下的nutch-site.xml 文件中:

    <property>
     <name>storage.data.store.class</name>
     <value>org.apache.gora.hbase.store.HBaseStore</value>
     <description>Default class for storing data</description>
    </property>
    <property>
     <name>http.agent.name</name>
     <value>NutchCrawler</value>
    </property>
    <property>
      <name>http.robots.agents</name>
      <value>NutchCrawler,*</value>
    </property>
    
  5. 取消注释$NUTCH_HOME/ivy/ivy.xml 文件的Gora artifacts部分中后面的。 恢复您对早期配方中的 ivy/ivy.xml 文件所做的更改,并确保gora-core依赖项版本为 0.3。 此外,请确保注释gora-sql依赖项:

    <dependency org="org.apache.gora" name="gora-hbase" rev="0.3" conf="*->default" />
    
  6. 将以下内容添加到$NUTCH_HOME/conf/下的gora.properties文件中,以将 HBase 存储设置为默认的 GORA 数据存储:

    gora.datastore.default=org.apache.gora.hbase.store.HBaseStore
    
  7. $NUTCH_HOME目录中执行以下命令,以 HBase 作为后端数据存储构建 Apache Nutch:

    $ ant clean
    $ ant runtime
    
    
  8. 按照使用 Apache Solr 配方的域内 Web 爬网步骤 9 到 19 进行操作。

  9. 启动 HBase shell 并发出以下命令以查看获取的数据:

    $ hbase shell
    HBase Shell; enter 'help<RETURN>' for list of supported commands.
    Type "exit<RETURN>" to leave the HBase Shell
    Version 0.90.6, r1295128, Wed Feb 29 14:29:21 UTC 2012
    hbase(main):001:0> list
    TABLE 
    webpage 
    1 row(s) in 0.4970 seconds
    
    hbase(main):002:0> count 'webpage'
    Current count: 1000, row: org.apache.bval:http/release-management.html 
    Current count: 2000, row: org.apache.james:http/jspf/index.html 
    Current count: 3000, row: org.apache.sqoop:http/team-list.html 
    Current count: 4000, row: org.onesocialweb:http/ 
    4065 row(s) in 1.2870 seconds
    
    hbase(main):005:0> scan 'webpage',{STARTROW => 'org.apache.nutch:http/', LIMIT=>10}
    ROW                                   COLUMN+CELL
     org.apache.nutch:http/               column=f:bas, timestamp=1350800142780, value=http://nutch.apache.org/
     org.apache.nutch:http/               column=f:cnt, timestamp=1350800142780, value=<....
    ......
    10 row(s) in 0.5160 seconds
    
    
  10. 遵循索引**中的步骤,使用 Apache Solr配方搜索 Web 文档,并使用 Apache Solr 搜索获取的数据。

它是如何工作的.

以上步骤使用 Apache HBase 作为存储后端配置并运行 Apache Nutch。 配置后,Nutch 将获取的网页数据和其他元数据存储在 HBase 表中。 在本食谱中,我们使用独立的 HBase 部署。 然而,正如使用 Hadoop/HBase 集群的 Apache Nutch 进行全网爬行食谱所示,Nutch 也可以与分布式 HBase 部署一起使用。 使用 HBase 作为后端数据存储为 Nutch 爬行提供了更高的可伸缩性和性能。

另请参阅

使用 Hadoop/HBase 群集使用 Apache Nutch 进行全 Web 爬行

通过利用 MapReduce 集群的强大功能,可以高效地爬行大量的个 Web 文档。

备注

从 Apache Nutch 2.2.1 版本开始,Nutch 项目还没有正式迁移到 Hadoop 2.x,整个网络爬行仍然依赖 Hadoop 1.x。 但是,可以使用 Hadoop 2.x 集群执行 Nutch 作业,该集群利用 Hadoop 的向后兼容性特性。

Nutch HBaseStore 集成进一步依赖于 HBase 0.90.6,而 HBase 0.90.6 不支持 Hadoop2。因此,此配方仅适用于 Hadoop 1.x 集群。 我们期待着一个完全支持 Hadoop2.x 的新 Nutch 版本。

做好准备

我们假设您已经部署了 Hadoop 1.x 和 HBase 集群。

怎么做……

以下步骤说明如何将 Apache Nutch 与 Hadoop MapReduce 集群和 HBase 数据存储配合使用,以执行大规模 Web 爬行:

  1. 确保可以从命令行访问hadoop命令。 如果没有,请将$HADOOP_HOME/bin目录添加到计算机的PATH环境变量中,如下所示:

    $ export PATH=$PATH:$HADOOP_HOME/bin/
    
    
  2. 按照将 Apache HBase 配置为 Apache Nutch的后端数据存储食谱中的步骤 3 到 7 进行操作。 如果您已经按照该食谱操作,则可以跳过此步骤。

  3. 在 HDFS 中创建一个目录以上载种子 URL。

    $ hadoop dfs -mkdir urls
    
    
  4. Create a text file with the seed URLs for the crawl. Upload the seed URLs file to the directory created in the preceding step.

    $ hadoop dfs -put seed.txt urls
    
    

    备注

    您可以使用 Open Directory 项目 RDF 转储(http://rdf.dmoz.org/)来创建种子 URL。 Nutch 提供了一个实用程序类,用于从提取的 DMOZ RDF 数据中选择 URL 子集,如bin/nutch org.apache.nutch.tools.DmozParser content.rdf.u8 -subset 5000 > dmoz/urls

  5. $NUTCH_HOME/runtime/deploy发出下面的命令,将种子 URL 注入到 Nutch 数据库,并生成初始获取列表:

    $ bin/nutch inject urls
    $ bin/nutch generate
    
    
  6. $NUTCH_HOME/runtime/deploy发出以下命令:

    $ bin/nutch fetch -all
    14/10/22 03:56:39 INFO fetcher.FetcherJob: FetcherJob: starting
    14/10/22 03:56:39 INFO fetcher.FetcherJob: FetcherJob: fetching all
    ......
    
    $ bin/nutch parse -all
    14/10/22 03:48:51 INFO parse.ParserJob: ParserJob: starting
    ......
    
    14/10/22 03:50:44 INFO parse.ParserJob: ParserJob: success
    
    $ bin/nutch updatedb
    14/10/22 03:53:10 INFO crawl.DbUpdaterJob: DbUpdaterJob: starting
    ....
    14/10/22 03:53:50 INFO crawl.DbUpdaterJob: DbUpdaterJob: done
    
    $ bin/nutch generate -topN 10
    14/10/22 03:51:09 INFO crawl.GeneratorJob: GeneratorJob: Selecting best-scoring urls due for fetch.
    14/10/22 03:51:09 INFO crawl.GeneratorJob: GeneratorJob: starting
    ....
    14/10/22 03:51:46 INFO crawl.GeneratorJob: GeneratorJob: done
    14/10/22 03:51:46 INFO crawl.GeneratorJob: GeneratorJob: generated batch id: 1350892269-603479705
    
    
  7. 根据需要重复步骤 6 中的命令,以爬网所需的页数或深度。

  8. 遵循使用 Apache Solr索引和搜索 Web 文档的食谱,使用 Apache Solr 为获取的数据编制索引。

它是如何工作的.

我们在本配方中使用的所有 Nutch 操作,包括获取和解析,都是作为 MapReduce 程序实现的。 这些 MapReduce 程序利用 Hadoop 集群以分布式方式执行 Nutch 操作,并使用 HBase 跨 HDFS 集群存储数据。 您可以通过 Hadoop 集群的监控 UI 监控这些 MapReduce 计算。

Apache Nutch Ant Build 创建一个 Hadoop 作业文件,其中包含$NUTCH_HOME/runtime/deploy文件夹中的所有依赖项。 bin/nutch脚本使用此作业文件将 MapReduce 计算提交给 Hadoop 集群。

另请参阅

  • 使用 Apache Nutch 配方的域内 Web 爬行。

用于索引和搜索的 Elasticsearch

Elasticsearch(http://www.elasticsearch.org/)是一个 Apache2.0 许可的开源搜索解决方案,构建在 Apache Lucene 的之上。 ElasticSearch 是一个分布式、多租户和面向文档的搜索引擎。 ElasticSearch 通过将索引分解为碎片并跨集群中的节点分布碎片来支持分布式部署。 Elasticsearch 和 Apache Solr 都使用 Apache Lucene 作为核心搜索引擎,而 Elasticsearch 的目标是提供比 Apache Solr 更适合云环境的可伸缩性和分布式解决方案。

做好准备

安装 Apache Nutch 并按照域内 Web 爬网使用 Apache Nutch或使用 Hadoop/HBase 群集配方使用 Apache Nutch 爬网爬网一些网页。 确保 Nutch 的后端 HBase(或 HyperSQL)数据存储仍然可用。

怎么做……

以下步骤说明如何使用 Elasticsearch 索引和搜索 Nutch 搜索到的数据:

  1. 下载并从http://www.elasticsearch.org/download/解压弹性搜索。

  2. 转到解压的 Elasticsearch 目录,执行以下命令在前台启动 Elasticsearch 服务器:

    $ bin/elasticsearch
    
    
  3. 在新控制台中运行以下命令以验证您的安装:

    > curl localhost:9200
    {
     "status" : 200,
     "name" : "Talisman",
     "cluster_name" : "elasticsearch",
     "version" : {
     "number" : "1.4.2",
     ……
     "lucene_version" : "4.10.2"
     },
     "tagline" : "You Know, for Search"
    }
    
    
  4. 转到$NUTCH_HOME/runtime/deploy(如果您在本地模式下运行 Nutch,则转到$NUTCH_HOME/runtime/local)目录。 执行以下命令,将 Nutch 抓取的数据索引到 Elasticsearch 服务器:

    $ bin/nutch elasticindex elasticsearch -all 
    14/11/01 06:11:07 INFO elastic.ElasticIndexerJob: Starting
    …...
    
    
  5. 发出以下命令执行搜索:

    $ curl -XGET 'http://localhost:9200/_search?q=hadoop'
    ....
    {"took":3,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":36,"max_score":0.44754887,"hits":[{"_index":"index","_type":"doc","_id": 100 30551  100  30551 "org.apache.hadoop:http/","_score":0.44754887, ....
    
    

它是如何工作的.

与 Apache Solr 类似,Elasticsearch 也是使用 Apache Lucene 文本搜索库构建的。 在前面的步骤中,我们将 Nutch 爬行的数据导出到 Elasticsearch 实例中,以用于索引和搜索。

您也可以将 Elasticsearch 安装为一项服务。 有关将 http://www.elasticsearch.org/guide/reference/setup/installation.html 作为服务安装的更多详细信息,请参阅Elasticsearch

我们使用 Nutch 的 ElasticIndex 作业将 Nutch 抓取的数据导入到 Elasticsearch 服务器。 弹性索引命令的用法如下:

bin/nutch  elasticindex  <elastic cluster name> \
 (<batchId> | -all | -reindex) [-crawlId <id>]

弹性集群名称恢复为默认值 ElasticSearch。 您可以通过编辑config/下的elasticsearch.yml文件中的cluster.name属性来更改群集名称。 群集名称用于自动发现,并且对于单个网络中的每个 Elasticsearch 部署都应该是唯一的。

另请参阅

  • 使用 Apache Solr配方索引和搜索 Web 文档。

为抓取的网页生成内链接图

从其他页面到特定网页的个链接的数量,即内链接的数量,被广泛认为是衡量一个网页的受欢迎程度或重要性的一个很好的度量标准。 事实上,网页内链接的数量和这些链接来源的重要性已经成为大多数流行的链接分析算法(如 Google 引入的 PageRank)不可或缺的组成部分。

在本食谱中,我们将从 Apache Nutch 获取并存储在 Apache HBase 后端数据存储中的一组网页中提取内链接信息。 在我们的 MapReduce 程序中,我们首先检索存储在 Nutch HBase 数据库中的那组网页的外部链接信息,然后使用该信息计算这组网页的内部链接图。 所计算的内链接图将仅包含来自获取的 web 图的子集的链接信息。

做好准备

遵循使用 Hadoop/HBase 群集配方使用 Apache Nutch 进行全网爬行,或者将 Apache HBase 配置为 Apache Nutch配方的后端数据存储,并使用 Apache Nutch 将一组网页爬行到后端 HBase 数据存储。

怎么做……

以下步骤显示如何从存储在 Nutch HBase 数据存储中的网页中提取出链接图,以及如何使用提取出的出链图计算入链接图:

  1. 启动 HBase 外壳:

    $ hbase shell
    
    
  2. 创建名为linkdata的 HBase 表和名为il的列族。 退出 HBase 外壳:

    hbase(main):002:0> create 'linkdata','il'
    0 row(s) in 1.8360 seconds
    hbase(main):002:0> quit
    
    
  3. 解压缩本章的源包,并通过从chapter8源目录执行gradle build来编译它。

  4. 通过发出以下命令运行 Hadoop 程序:

    $ hadoop jar hcb-c8-samples.jar \
    chapter8.InLinkGraphExtractor
    
    
  5. 启动 HBase shell 并使用以下命令扫描linkdata表,以检查 MapReduce 程序的输出:

    $ hbase shell
    hbase(main):005:0> scan 'linkdata',{COLUMNS=>'il',LIMIT=>10}
    ROW                            COLUMN+CELL 
    ....
    
    

它是如何工作的.

由于我们将使用 HBase 来读取输入以及写入输出,因此我们使用 HBaseTableMapperTableReducer帮助器类来实现 MapReduce 应用。 我们使用TableMapReduceUtil类中给出的实用程序方法配置TableMapperTableReducer类。 对象Scan用于指定从 HBase 数据存储读取输入数据时映射器要使用的条件:

Configuration conf = HBaseConfiguration.create();
Job job = new Job(conf, "InLinkGraphExtractor");
job.setJarByClass(InLinkGraphExtractor.class);
Scan scan = new Scan();
scan.addFamily("ol".getBytes());
TableMapReduceUtil.initTableMapperJob("webpage", scan, ……);
TableMapReduceUtil.initTableReducerJob("linkdata",……);

MAP 实现接收 HBase 行作为输入记录。 在我们的实现中,每行对应一个获取的网页。 Map函数的输入键由网页 URL 组成,值由从该特定网页链接的网页组成。 Map函数为每个链接网页发出一条记录,其中Map输出记录的关键字是链接页面的 URL,Map输出记录的值是Map函数的输入键(当前处理网页的 URL):

public void map(ImmutableBytesWritable row, Result values,……){
  List<KeyValue> results = values.list();      
  for (KeyValue keyValue : results) {
    ImmutableBytesWritable userKey = new     ImmutableBytesWritable(keyValue.getQualifier());
    try {
      context.write(userKey, row);
    } catch (InterruptedException e) {
      throw new IOException(e);
    }
  }
}

Reduce 实现接收网页 URL 作为关键字,并接收包含到该网页的链接(在关键字中提供)的网页列表作为值。 Reduce 函数将该数据存储到 HBase 表中:

public void reduce(ImmutableBytesWritable key,
  Iterable<ImmutableBytesWritable> values, ……{

Put put = new Put(key.get());
  for (ImmutableBytesWritable immutableBytesWritable :values)   {
    put.add(Bytes.toBytes("il"), Bytes.toBytes("link"),
            immutableBytesWritable.get());
  }
  context.write(key, put);
}

另请参阅

  • 第 7 章,Hadoop 生态系统 II 中的在 HBase配方上运行 MapReduce 作业-Pig、HBase、Mahout 和 Sqoop

九、分类、推荐和查找关系

在本章中,我们将介绍:

  • 执行基于内容的推荐
  • 利用朴素贝叶斯分类器进行分类
  • 使用 Adword Balance 算法将广告分配给关键字

简介

本章讨论如何将 Hadoop 用于更复杂的用例,如对数据集进行分类和提出建议。

以下是一些此类场景的几个实例:

  • 根据产品之间的相似性(例如,如果一个用户喜欢一本关于历史的书,他/她可能喜欢另一本关于同一主题的书)或基于用户行为模式(例如,如果两个用户相似,他们可能喜欢另一个人读过的书)向用户推荐产品
  • 对数据集进行聚类以标识相似实体;例如,标识具有相似兴趣的用户
  • 根据历史数据将数据分类为几组

在本食谱中,我们将使用 MapReduce 应用这些技术和其他技术。 对于本章中的食谱,我们将使用亚马逊产品联合采购网络元数据数据集,可从http://snap.stanford.edu/data/amazon-meta.html获取。

备注

本章的内容基于本书前一版Hadoop MapReduce Cookbook分类、建议和查找关系。 这一章是由合著者斯里纳特·佩雷拉(Srinath Perera)贡献的。

提示

示例代码

本书的示例代码和数据文件位于 GitHub 的https://github.com/thilg/hcb-v2中。 代码库的chapter9文件夹包含本章的示例源代码文件。 通过在代码库的chapter9文件夹中发出gradle build命令,可以编译和构建示例代码。 Eclipse IDE 的项目文件可以通过在代码存储库的主文件夹中运行gradle eclipse命令来生成。 IntelliJ IDEA IDE 的项目文件可以通过在代码库的主文件夹中运行gradle idea命令来生成。

执行基于内容的推荐

推荐是对某人可能感兴趣的事情的建议。 例如,你会把一本好书推荐给一位你知道和你有相似兴趣的朋友。 我们经常在网上零售中找到推荐的用例。 例如,当你浏览一款产品时,亚马逊会推荐购买该特定商品的用户也购买的其他产品。

像亚马逊这样的在线零售网站有非常多的商品。 虽然书籍被分成几个类别,但通常每个类别都有太多的书,不能一个接一个地浏览。 推荐使用户的生活变得更轻松,帮助他找到最适合自己口味的产品,同时增加销售。

提出建议的方式有很多种:

  • 基于内容的推荐:可以使用关于该产品的信息来识别类似的产品。 例如,您可以使用类别、内容相似性等来识别相似的产品,并将它们推荐给已经购买了特定产品的用户。
  • 协作过滤:另一个选项是使用用户行为来识别产品之间的相似性。 例如,如果同一用户给两个产品打了很高的分数,那么这两个产品之间就有一些相似之处。

这个食谱使用从亚马逊收集的关于产品的数据集来提供基于内容的推荐。 在数据集中,每个产品都有一个由 Amazon 预先确定的提供给用户的类似商品列表。 在本食谱中,我们将使用这些数据来提出建议。

怎么做……

  1. Download the dataset from the Amazon product co-purchasing network metadata available at http://snap.stanford.edu/data/amazon-meta.html and unzip it. We call this directory as DATA_DIR.

    通过运行以下命令将数据上传到 HDFS。 如果数据目录已经存在,请将其清理。

    $ hdfs dfs -mkdir data
    $ hdfs dfs -mkdir data/input1
    $ hdfs dfs -put <DATA_DIR>/amazon-meta.txt data/input1
    
    
  2. 通过从源代码存储库的chapter9目录运行gradle build命令编译源代码,并获得hcb-c9-samples.jar文件。

  3. 使用以下命令运行最频繁的用户查找程序 MapReduce 作业:

    $ hadoop jar hcb-c9-samples.jar \
     chapter9.MostFrequentUserFinder \
     data/input1 data/output1
    
    
  4. 通过运行以下命令读取结果:

    $ hdfs dfs -cat data/output1/*
    
    
  5. 您将看到 MapReduce 作业已经从每个客户提取了购买数据,结果将如下所示:

    customerID=A1002VY75YRZYF,review=ASIN=0375812253#title=Really Useful Engines (Railway Series)#salesrank=623218#group=Book #rating=4#similar=0434804622|0434804614|0434804630|0679894780|0375827439|,review=ASIN=B000002BMD#title=EverythingMustGo#salesrank=77939#group=Music#rating=4#similar=B00000J5ZX|B000024J5H|B00005AWNW|B000025KKX|B000008I2Z
    
    
  6. 使用以下命令运行建议 MapReduce 作业:

    $ hadoop jar hcb-c9-samples.jar \
    chapter9.ContentBasedRecommendation \
    data/output1 data/output2
    
    
  7. 通过运行以下命令读取结果:

    $ hdfs dfs -cat data/output2/*
    
    

您将看到它将按如下方式打印结果。 结果的每一行都包含客户 ID 和针对该客户的产品推荐列表。

A10003PM9DTGHQ  [0446611867, 0446613436, 0446608955, 0446606812, 0446691798, 0446611867, 0446613436, 0446608955, 0446606812, 0446691798]

它是如何工作的.

下面的清单显示了数据集中一个产品的条目。 这里,每个数据条目包括 ID、标题、分类、与该项目类似的项目以及有关已审阅该项目的用户的信息。 在本例中,我们假设查看了该商品的客户已经购买了该商品。

Id:   13
ASIN: 0313230269
  title: Clockwork Worlds : Mechanized Environments in SF (Contributions to the Study of Science Fiction and Fantasy)
  group: Book
  salesrank: 2895088
  similar: 2  1559360968  1559361247
  categories: 3
   |Books[283155]|Subjects[1000]|Literature & Fiction[17]|History & Criticism[10204]|Criticism & Theory[10207]|General[10213]
   |Books[283155]|Subjects[1000]|Science Fiction & Fantasy[25]|Fantasy[16190]|History & Criticism[16203]
   |Books[283155]|Subjects[1000]|Science Fiction & Fantasy[25]|Science Fiction[16272]|History & Criticism[16288]
  reviews: total: 2  downloaded: 2  avg rating: 5
    2002-8-5  cutomer: A14OJS0VWMOSWO  rating: 5  votes:   2  helpful:   1
    2003-3-21  cutomer:  A2C27IQUH9N1Z  rating: 5  votes:   4  helpful:   4

我们已经编写了 Hadoop InputFormat 来解析 Amazon 产品数据;数据格式类似于我们使用第 5 章Analytics的 MapReduce 配方在简单分析中编写的格式。 源文件src/chapter9/amazondata/AmazonDataReader.javasrc/chapter9/amazondata/AmazonDataFormat.java包含 Amazon 数据格式化程序的代码。

Amazon 数据格式化程序将解析数据集,并将有关每个 Amazon 产品的数据作为键-值对发送给map函数。 关于每个 Amazon 产品的数据表示为一个字符串,AmazonCustomer.java类包括解析和写出有关 Amazon 客户的数据的代码。

此配方包括两个 MapReduce 计算。 这些任务的来源可以从src/chapter9/MostFrequentUserFinder.javasrc/chapter9/ ContentBasedRecommendation.java中找到。 第一个 MapReduce 作业的 Map 任务在日志文件中以不同的键-值对接收有关每个产品的数据。

当 Map 任务接收到产品数据时,它将发出客户 ID 作为关键字,并发出产品信息作为购买产品的每个客户的值。

public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
    List<AmazonCustomer> customerList = AmazonCustomer.parseAItemLine(value.toString());
    for(AmazonCustomer customer: customerList){
        context.write(new Text(customer.customerID),
        new Text(customer.toString()));
    }
}

然后,Hadoop 收集键的所有值,并为每个键调用一次 Reducer。 每个客户都有一个reduce函数调用,每个调用都将接收客户购买的所有产品。 Reducer 发出每个客户购买的商品列表,从而构建客户配置文件。 每个项目也包含相似项目的列表。 为了限制数据集的大小,Reducer 将只发出购买了五种以上产品的客户的详细信息。

public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
    AmazonCustomer  customer = new AmazonCustomer();
    customer.customerID = key.toString();

    for(Text value: values){
        Set<ItemData> itemsBought =new AmazonCustomer(
        value.toString()).itemsBought;
        for(ItemData itemData: itemsBrought){
            customer.itemsBought.add(itemData);
        }
    }
    if(customer.itemsBought.size() > 5){
        context.write(
        new IntWritable(customer.itemsBrought.size()),
        new Text(customer.toString()));
    }
}

第二个 MapReduce 作业使用第一个 MapReduce 任务生成的数据为每个客户提供建议。 Map 任务接收有关每个客户的数据作为输入,MapReduce 作业使用以下三个步骤提出建议:

  1. 来自亚马逊的每个产品(项目)数据都包括与该项目类似的项目。 给定一个客户,map函数为该客户购买的每个项目创建一个所有类似项目的列表。

  2. 然后,map函数从相似项目列表中删除客户已经购买的任何项目。

  3. 最后,map函数选择 10 个项目作为推荐。

    public void map(Object key, Text value, Context context)
    throws IOException, InterruptedException {
      AmazonCustomer amazonCustomer =
      new AmazonCustomer(value.toString()
      .replaceAll("[0-9]+\\s+", ""));
    
      List<String> recommendations = new ArrayList<String>();
      for (ItemData itemData : amazonCustomer.itemsBrought) {
        recommendations.addAll(itemData.similarItems);
      }
    
      for (ItemData itemData : amazonCustomer.itemsBrought) {
        recommendations.remove(itemData.itemID);
      }
    
      ArrayList<String> finalRecommendations =
      new ArrayList<String>();
      for (int i = 0;
      i < Math.min(10, recommendations.size());i++) {
        finalRecommendations.add(recommendations.get(i));
      }
      context.write(new Text(amazonCustomer.customerID),
      new Text(finalRecommendations.toString()));
    }
    

还有更多...

您可以从 Anand Rajaraman 和 Jeffrey David Ullman 的推荐系统挖掘海量数据集剑桥大学出版社一书的推荐系统中了解更多基于内容的推荐。

Apache Mahout 在第 7 章Hadoop 生态系统 II-Pig、HBase、Mahout 和 Sqoop中介绍了,并在第 10 章海量文本数据处理中使用,包含几个推荐实现。 以下文章将为您提供有关在 Mahout 中使用基于用户和基于项目的推荐器的信息:

使用朴素贝叶斯分类器进行分类

分类器根据输入的某些属性(也称为特征)将输入分配到N类之一。 分类器有着广泛的应用,例如电子邮件垃圾邮件过滤、查找最有前途的产品、选择客户进行更密切的交互以及在机器学习的情况下做出决策。 让我们探索如何使用大型数据集实现分类器。 例如,垃圾邮件过滤器会将每个电子邮件分配给两个群集之一:垃圾邮件或非垃圾邮件。

分类算法有很多种。 最简单但有效的算法之一是朴素的贝叶斯分类器,它使用涉及条件概率的贝叶斯定理。

在本食谱中,我们还将一如既往地关注 Amazon 元数据数据集。 我们将查看产品的几个功能,例如收到的评论数量、正面评分和已知的类似项目,以确定有潜力进入前 10,000 个销售排名的产品。 我们将使用朴素贝叶斯分类器进行分类。

备注

您可以在http://en.wikipedia.org/wiki/Naive_Bayes_classifier了解更多关于朴素拜耳分类器的信息。

怎么做……

  1. 从亚马逊产品的联合购买网络元数据(可从http://snap.stanford.edu/data/amazon-meta.html下载)下载数据集并解压缩。 我们将此目录命名为DATA_DIR

  2. 通过运行以下命令将数据上传到 HDFS。 如果数据目录已经存在,请将其清理。

    $ hdfs dfs -mkdir data
    $ hdfs dfs -mkdir data/input1
    $ hdfs dfs -put <DATA_DIR>/amazon-meta.txt data/input1
    
    
  3. 通过从源代码存储库的chapter9目录运行gradle build命令编译源代码,并获得hcb-c9-samples.jar文件。

  4. 使用以下命令运行 MapReduce 作业:

    $ hadoop jar hcb-c9-samples.jar \ 
    chapter9.NaiveBayesProductClassifier \
    data/input1 data/output5
    
    
  5. 通过运行以下命令读取结果:

    $ hdfs dfs -cat data/output5/*
    
    
  6. 您将看到它将打印以下结果。 您可以将这些值与贝叶斯分类器一起使用来对输入进行分类:

    postiveReviews>30       0.635593220338983
    reviewCount>60  0.62890625
    similarItemCount>150    0.5720620842572062
    
    

它是如何工作的.

分类器使用以下特征作为指示符,表示该产品可以落入前 10,000 个产品内:

  • 对给定产品的评论次数
  • 对给定产品的正面评论数量
  • 给定产品的类似产品数量

我们首先运行 MapReduce 任务来计算以下概率,然后将这些概率与前面的公式一起使用来对给定产品进行分类:

  • P1:如果某项评论超过 60 条,则该条目在前 10,000 个产品中的概率
  • P2:如果某个项目的正面评价超过 30 条,则该项目在前 10,000 个产品中的概率
  • P3:如果某一项目有超过 150 个类似项目,则该项目在前 10,000 个产品内的概率

您可以在文件src/chapter9/``NaiveBayesProductClassifier.java中找到分类器的源。 Mapper 函数如下所示:

public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
  List<AmazonCustomer> customerList = AmazonCustomer.parseAItemLine(value.toString());
  int salesRank = -1;
  int reviewCount = 0;
  int postiveReviews = 0;
  int similarItemCount = 0;

  for (AmazonCustomer customer : customerList) {
    ItemData itemData =  customer.itemsBrought.iterator().next();
    reviewCount++;
    if (itemData.rating > 3) {
      postiveReviews++;
    }
    similarItemCount = similarItemCount +
      itemData.similarItems.size();
    if (salesRank == -1) {
      salesRank = itemData.salesrank;
    }
  }

  boolean isInFirst10k = (salesRank <= 10000);
  context.write(new Text("total"),
  new BooleanWritable(isInFirst10k));
  if (reviewCount > 60) {
    context.write(new Text("reviewCount>60"),
    new BooleanWritable(isInFirst10k));
  }
  if (postiveReviews > 30) {
    context.write(new Text("postiveReviews>30"),
    new BooleanWritable(isInFirst10k));
  }
  if (similarItemCount > 150) {
    context.write(new Text("similarItemCount>150"),
    new BooleanWritable(isInFirst10k));
  }
}

Mapper 函数遍历每个产品,评估其特性。 如果功能评估为 true,它将发出功能名称作为关键字,并发出产品是否在前 10,000 个产品内作为值。

MapReduce 为每个功能调用一次 Reducer。 然后,每个 Reduce 作业接收特征为真的所有值,并计算产品在销售排名中的前 10,000 个产品的概率(假设特征为真)。

public void reduce(Text key, Iterable<BooleanWritable> values, Context context) throws IOException,
  InterruptedException {
    int total = 0;
    int matches = 0;
    for (BooleanWritable value : values) {
      total++;
      if (value.get()) {
        matches++;
      }
    }
  context.write(new Text(key), new DoubleWritable((double) matches / total));
  }

在给定产品的情况下,我们将检查并决定以下事项:

  • 它有 60 多条评论吗?
  • 它有 30 多条正面评论吗?
  • 有 150 多种类似的商品吗?

我们可以决定事件 A、B 和 C 的概率,我们可以使用贝叶斯定理计算给定项目在前 10,000 个产品中的概率。 以下代码实现此逻辑:

public static boolean classifyItem(int similarItemCount, int reviewCount, int postiveReviews){
  double reviewCountGT60 = 0.8;
  double postiveReviewsGT30 = 0.9;
  double similarItemCountGT150 = 0.7;
  double a , b, c;

  if (reviewCount > 60) {
    a = reviewCountGT60;
  }else{
    a= 1 - reviewCountGT60;
  }
  if (postiveReviews > 30) {
    b = postiveReviewsGT30;
  }else{
    b = 1- postiveReviewsGT30;
  }
  if (similarItemCount > 150) {
    c = similarItemCountGT150;
  }else{
    c = 1- similarItemCountGT150;
  }
  double p = a*b*c/ (a*b*c + (1-a)*(1-b)*(1-c));
  return p > 0.5;
}

当您运行分类器测试逻辑时,它将加载 MapReduce 作业生成的数据,并对随机选择的 1,000 种产品进行分类。

使用 Adword Balance 算法将广告分配给关键字

广告已经成为网络的主要收入媒介。 这是一项价值 10 亿美元的业务,也是硅谷大多数领先公司的收入来源。 此外,它还使谷歌、Facebook、雅虎和 Twitter 等公司可以免费运营其主要服务,同时通过广告收取收入。

AdWords 允许人们竞标关键词。 例如,广告商A可以 2 美元竞标关键字 Hadoop support,并提供最高 100 美元的预算。 广告商B可以以 1.50 美元的价格竞标关键字 Hadoop support,并提供最高 200 美元的预算。 当用户搜索具有给定关键字的文档时,系统将从对这些关键字的出价中选择一个或多个广告。 只有当用户点击广告时,广告商才会付费。

我们的目标是选择广告,使它们的收入最大化。 在设计这样的解决方案时,有几个因素在起作用:

  • 我们想展示更有可能经常被点击的广告,因为很多时候,只有点击,而不是展示广告,才能让我们赚到钱。 我们用广告被点击的次数来衡量这一点,而不是广告被播放的次数。 我们把这个关键字的点击率称为点击率。
  • 我们希望显示属于预算较高的广告商的广告,而不是预算较低的广告商的广告。

在本配方中,我们将实现一个可用于此类情况的 Adword 平衡算法的简化版本。 为简单起见,我们假设广告商只对单个单词出价。 此外,由于我们找不到真正的 BID 数据集,因此我们将生成一个样本 BID 数据集。 请看下图:

Assigning advertisements to keywords using the Adwords balance algorithm

假设您要使用 Amazon 数据集支持基于关键字的广告。 食谱的工作原理如下:

  1. 第一个 MapReduce 作业将使用 Amazon 销售指数近似计算关键字的点击率。 这里,我们假设在销售排名较高的产品标题中找到的关键字的点击率会更高。

  2. 然后,我们将运行一个 Java 程序来生成投标数据集。

  3. 现在,第二个 MapReduce 任务将把同一产品的投标组合在一起,并创建一个可供广告分配程序使用的输出。

  4. 最后,我们将使用广告分配程序为广告商分配关键字。 我们将使用以下公式来实现 Adword 平衡算法。 该公式根据每个广告商未花费预算的比例、投标值和点击率来分配优先级:

    Measure = bid value * click-through rate * (1-e^(-1*current budget/ initial budget))
    

怎么做……

  1. 从亚马逊产品联购网络元数据(可从http://snap.stanford.edu/data/amazon-meta.html下载)下载数据集并解压缩。 我们将此目录命名为DATA_DIR

  2. 通过运行以下命令将数据上传到 HDFS。 如果数据目录已经存在,请将其清理。

    $ hdfs dfs -mkdir data
    $ hdfs dfs -mkdir data/input1
    $ hdfs dfs -put <DATA_DIR>/amazon-meta.txt data/input1
    
    
  3. 通过从源代码存储库的chapter9目录运行gradle build命令编译源代码,并获得hcb-c9-samples.jar文件。

  4. 使用以下命令运行 MapReduce 作业:

    $ hadoop jar hcb-c9-samples.jar \ 
    chapter9.adwords.ClickRateApproximator \
    data/input1 data/output6
    
    
  5. 通过运行以下命令将结果下载到您的计算机:

    $ hdfs dfs -get data/output6/part-r-* clickrate.data
    
    
  6. 您将看到它将打印以下结果。 您可以将这些值与贝叶斯分类器一起使用来对输入进行分类:

    keyword:(Annals 74
    keyword:(Audio  153
    keyword:(BET    95
    keyword:(Beat   98
    keyword:(Beginners)     429
    keyword:(Beginning      110
    
    
  7. 通过运行以下命令生成 BID 数据集。 您可以在biddata.data文件中找到结果。

    $ java -cp hcb-c9-samples.jar \
     chapter9.adwords.AdwordsBidGenerator \
     clickrate.data
    
    
  8. 创建名为data/input2的目录,并使用以下命令将 BID 数据集和早期 MapReduce 任务的结果上传到 HDFS 的data/input2目录:

    $ hdfs dfs -put clickrate.data data/input2
    $ hdfs dfs -put biddata.data data/input2
    
    
  9. 按如下方式运行第二个 MapReduce 作业:

    $ hadoop jar hcb-c9-samples.jar \
     chapter9.adwords.AdwordsBalanceAlgorithmDataGenerator \
     data/input2 data/output7
    
    
  10. 通过运行以下命令将结果下载到您的计算机:

```scala
$ hdfs dfs -get data/output7/part-r-* adwords.data

```
  1. 检查结果:
```scala
(Animated       client23,773.0,5.0,97.0|
(Animated)      client33,310.0,8.0,90.0|
(Annals         client76,443.0,13.0,74.0|
client51,1951.0,4.0,74.0|
(Beginners)     client86, 210.0,6.0,429.0|
 client6,236.0,5.0,429.0|
(Beginning      client31,23.0,10.0,110.0|

```
  1. 通过运行以下命令对随机的组关键字执行匹配:
```scala
$ java jar hcb-c9-samples.jar \
 chapter9.adwords.AdwordsAssigner adwords.data

```

它是如何工作的.

正如我们所讨论的,该配方由两个 MapReduce 作业组成。 您可以从文件src/chapter9/adwords/ClickRateApproximator.java中找到第一个 MapReduce 作业的源。

Mapper 函数使用 Amazon 数据格式解析 Amazon 数据集,对于每个产品标题中的每个单词,它都会发出该产品的单词和销售排名。 该函数如下所示:

public void map(Object key, Text value, Context context) {
......
    String[] tokens = itemData.title.split("\\s");
    for(String token: tokens){
        if(token.length() > 3){
            context.write(new Text(token), new IntWritable(itemData.salesrank));
        }
    }
}

然后,MapReduce 框架按键对发出的键-值对进行排序,并为每个键调用一次 Reducer。 如下所示,减少器使用针对键发出的销售排名来计算点击率的近似值:

public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
    double clickrate = 0;
    for(IntWritable val: values){
        if(val.get() > 1){
            clickrate = clickrate + 1000/Math.log(val.get());
        }else{
            clickrate = clickrate + 1000;
        }
    }
    context.write(new Text("keyword:" +key.toString()),
    new IntWritable((int)clickrate));
}

没有公开可用的投标数据集。 因此,我们将使用AdwordsBidGenerator程序为我们的食谱生成一个随机投标数据集。 它将读取由前面的配方生成的关键字,并生成随机出价数据集。

然后,我们将使用第二个 MapReduce 作业将投标数据集与点击率合并,并生成一个具有根据关键字排序的投标信息的数据集。 您可以从文件src/chapter9/adwords/AdwordsBalanceAlgorithmDataGenerator.java中找到第二个 MapReduce 任务的源。 Mapper 函数如下所示:

public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
  String[] keyVal = value.toString().split("\\s");
  if (keyVal[0].startsWith("keyword:")) {
    context.write(
    new Text(keyVal[0].replace("keyword:", "")),
    new Text(keyVal[1]));
  } else if (keyVal[0].startsWith("client")) {
    List<String[]> bids = new ArrayList<String[]>();
    double budget = 0;
    String clientid = keyVal[0];
    String[] tokens = keyVal[1].split(",");
    for (String token : tokens) {
      String[] kp = token.split("=");
      if (kp[0].equals("budget")) {
        budget = Double.parseDouble(kp[1]);
      } else if (kp[0].equals("bid")) {
        String[] bidData = kp[1].split("\\|");
        bids.add(bidData);
      }
    }

    for (String[] bid : bids) {
      String keyword = bid[0];
      String bidValue = bid[1];
      Double.parseDouble(bidValue);
      context.write(new Text(keyword),
      new Text(new StringBuffer()
      .append(clientid).append(",")
      .append(budget).append(",")
      .append(bidValue).toString()));
    }
  }
}

Mapper 函数读取投标数据集和点击率数据集,并根据关键字发出这两种类型的数据。 然后,每个 Reducer 接收所有出价和每个关键字的相关点击数据。 接下来,Reducer 合并数据并发出针对每个关键字的出价列表。

public void reduce(Text key, Iterable<Text> values,
Context context) throws IOException, InterruptedException {
  String clientid = null;
  String budget = null;
  String bid = null;
  String clickRate = null;

  List<String> bids = new ArrayList<String>();
  for (Text val : values) {
    if (val.toString().indexOf(",") > 0) {
      bids.add(val.toString());
    } else {
      clickRate = val.toString();
    }
  }
  StringBuffer buf = new StringBuffer();
  for (String bidData : bids) {
    String[] vals = bidData.split(",");
    clientid = vals[0];
    budget = vals[1];
    bid = vals[2];
    buf.append(clientid).append(",")
    .append(budget).append(",")
    .append(Double.valueOf(bid)).append(",")
    .append(Math.max(1, Double.valueOf(clickRate)));
    buf.append("|");
  }
  if (bids.size() > 0) {
    context.write(key, new Text(buf.toString()));
  }
}

最后,Adword 分配器加载投标数据,并将其与关键字相对应地存储到存储器中。 在给定关键字的情况下,Adword 分配器查找具有以下等式的最大值的出价,并从所有出价中选择用于广告的出价:

Measure = bid value * click-through rate * (1-e^(-1*current budget/ initial budget))

还有更多...

前面的配方假设 Adword 分配器可以将所有数据加载到内存中,以做出广告分配决策。 实际上,由于广告竞价系统所需的毫秒级响应时间和大数据集,这些计算由大型集群进行实时决策,这些集群结合了 Apache Storm 等流媒体技术和 HBase 等高吞吐量数据库。

这个菜谱假定用户只对单个单词出价。 然而,为了支持多个关键字竞价,我们需要合并点击率,算法的其余部分可以像前面一样进行。

有关在线广告的更多信息可以在 Anand Rajaraman 和 Jeffrey David Ullman 所著的《挖掘海量数据集剑桥大学出版社》一书中找到。

十、海量文本数据处理

在本章中,我们将介绍以下主题:

  • 使用 Hadoop Streaming 和 Python 进行数据预处理(提取、清理和格式转换)
  • 使用 Hadoop 流消除重复数据
  • 将大型数据集加载到 Apache HBase 数据存储-import 和 Bulkload
  • 为文本数据创建 TF 和 TF-IDF 矢量
  • 使用 Apache Mahout 对文本数据进行聚类
  • 基于潜在狄利克雷分配(LDA)的主题发现
  • 基于 Mahout 朴素贝叶斯分类器的文档分类

简介

Hadoop MapReduce 与支持项目集一起,使其成为处理大型文本数据集和执行提取-转换-加载(ETL)类型操作的理想框架。

在本章中,我们将探讨如何使用 Hadoop Streaming 执行数据提取、格式转换和重复数据删除等数据预处理操作。 我们还将使用 HBase 作为数据存储来存储数据,并探索以最小的开销将大量数据加载到 HBase 的机制。 最后,我们将研究如何使用 Apache Mahout 算法执行文本分析。

我们将对本章中的食谱使用以下示例数据集:

提示

示例代码

本书的示例代码文件位于 gihub 的https://github.com/thilg/hcb-v2。 代码库的chapter10文件夹包含本章的示例代码。

使用 Hadoop Streaming 和 Python 进行数据预处理

数据预处理是数据分析中重要的,通常也是必需的组件。 当使用从多个不同来源生成的非结构化文本数据时,Data 预处理变得更加重要。 数据预处理步骤包括清理数据、从数据中提取重要特征、从数据集中删除重复项、转换数据格式等操作。

Hadoop MapReduce 提供了在处理海量数据集时并行执行这些任务的理想环境。 除了使用 Java MapReduce 程序或 Pig 脚本或 Have 脚本对数据进行预处理外,Hadoop 还包含一些其他工具和功能,这些工具和功能可用于执行这些数据预处理操作。 其中一个特性是InputFormats,它为我们提供了通过实现自定义InputFormat来支持自定义数据格式的能力。另一个特性是 Hadoop 流支持,它允许我们使用我们最喜欢的脚本语言来执行实际的数据清理和提取,而 Hadoop 将并行计算到数百个计算和存储资源。

在本食谱中,我们将使用 Hadoop Streaming 和基于 Python 脚本的 Mapper 来执行数据提取和格式转换。

做好准备

  • 检查 Hadoop 工作节点上是否已经安装了 Python。 如果没有,请在所有 Hadoop 工作节点上安装 Python。

怎么做……

以下步骤显示如何从 20news 数据集中清理和提取数据,并将数据存储为制表符分隔的文件:

  1. http://qwone.com/~jason/20Newsgroups/20news-19997.tar.gz

    $ wget http://qwone.com/~jason/20Newsgroups/20news-19997.tar.gz
    $ tar –xzf 20news-19997.tar.gz
    
    

    下载并解压 20news 数据集

  2. 将提取的数据上传到 HDFS。 在中,为了节省计算时间和资源,您只能使用数据集的子集:

    $ hdfs dfs -mkdir 20news-all
    $ hdfs dfs –put  <extracted_folder> 20news-all
    
    
  3. 解压本章的资源包并找到MailPreProcessor.pyPython 脚本。

  4. 在您的计算机中找到 Hadoop 安装的hadoop-streaming.jarJAR 文件。 使用该 JAR 运行以下 Hadoop 流命令。 /usr/lib/hadoop-mapreduce/是基于 bigtop 的 Hadoop 安装的hadoop-streamingjar 文件的位置:

    $ hadoop jar \
    /usr/lib/hadoop-mapreduce/hadoop-streaming.jar \
    -input 20news-all/*/* \
    -output 20news-cleaned \
    -mapper MailPreProcessor.py \
    -file MailPreProcessor.py
    
    
  5. 使用以下命令检查结果:

    > hdfs dfs –cat 20news-cleaned/part-* | more
    
    

它是如何工作的.

Hadoop 使用默认的TextInputFormat作为上一次计算的输入规范。 使用TextInputFormat将为输入数据集中的每个文件生成一个 Map 任务,并为每行生成一条 Map 输入记录。 Hadoop 流通过标准输入向地图应用提供输入:

line =  sys.stdin.readline();
while line:
….
  if (doneHeaders):
    list.append( line )
  elif line.find( "Message-ID:" ) != -1:
    messageID = line[ len("Message-ID:"):]
  ….
  elif line == "":
    doneHeaders = True

   line = sys.stdin.readline();

前面的 Python 代码从标准输入读取输入行,直到它到达文件末尾。 我们解析新闻组文件的标题,直到遇到分隔标题和消息内容的空行。 消息内容将逐行读入列表:

value = ' '.join( list )
value = fromAddress + "\t" ……"\t" + value
print '%s\t%s' % (messageID, value)

前面的代码段将消息内容合并为单个字符串,并将流应用的输出值构造为一组以制表符分隔的所选头,后跟消息内容。 输出键值是从输入文件中提取的Message-ID头。 通过使用制表符来分隔键和值,将输出写入标准输出。

还有更多...

通过将SequenceFileOutputFormat指定为流计算的OutputFormat ,我们可以生成 HadoopSequenceFile格式的上一次计算的输出:

$ hadoop jar \
/usr/lib/Hadoop-mapreduce/hadoop-streaming.jar \
-input 20news-all/*/* \
-output 20news-cleaned \
-mapper MailPreProcessor.py \
-file MailPreProcessor.py \
-outputformat \
 org.apache.hadoop.mapred.SequenceFileOutputFormat \
-file MailPreProcessor.py

在第一次传递输入数据之后,最好将数据存储为SequenceFiles(或其他 Hadoop 二进制文件格式,如 avro),因为SequenceFiles占用的空间较少,并且支持压缩。 您可以使用hdfs dfs -text <path_to_sequencefile>SequenceFile的内容输出为文本:

$ hdfs dfs –text 20news-seq/part-* | more

但是,要使前面的命令起作用,SequenceFile中使用的任何可写类都应该在 Hadoop 类路径中可用。

另请参阅

  • 请参阅第 4 章开发复杂 Hadoop MapReduce 应用的中的将 Hadoop 与遗留应用结合使用-Hadoop Streaming添加对新输入数据格式的支持-实现自定义 InputFormat食谱。**

使用 Hadoop 流消除重复数据

通常,数据集包含需要消除的重复项,以确保结果的准确性。 在本食谱中,我们使用 Hadoop 删除 20news 数据集中的重复邮件记录。 这些重复记录是由于用户将同一消息交叉发布到多个新闻板造成的。

做好准备

  • 确保您的 Hadoop 计算节点上安装了 Python。

怎么做……

以下步骤显示了如何从 20news 数据集中删除由于跨列表交叉发布而导致的重复邮件:

  1. http://qwone.com/~jason/20Newsgroups/20news-19997.tar.gz

    $ wget http://qwone.com/~jason/20Newsgroups/20news-19997.tar.gz
    $ tar –xzf 20news-19997.tar.gz
    
    

    下载并解压 20news 数据集

  2. 将提取的数据上传到 HDFS。 为了节省计算时间和资源,您只能使用数据集的子集:

    $ hdfs dfs -mkdir 20news-all
    $ hdfs dfs –put  <extracted_folder> 20news-all
    
    
  3. 我们将使用前面配方中的MailPreProcessor.pyPython 脚本,使用 Hadoop 流进行数据预处理,并使用 Python作为映射器。 在本章的源代码存储库中找到MailPreProcessorReduce.py文件。

  4. 执行以下命令:

    $ hadoop jar \
    /usr/lib/hadoop-mapreduce/hadoop-streaming.jar \
    -input 20news-all/*/* \
    -output 20news-dedup\
    -mapper MailPreProcessor.py \
    -reducer MailPreProcessorReduce.py \
    -file MailPreProcessor.py\
    -file MailPreProcessorReduce.py
    
    
  5. 使用以下命令检查结果:

    $ hdfs dfs –cat 20news-dedup/part-00000 | more
    
    

它是如何工作的.

Mapper Python 脚本输出 MessageID 作为键。 我们使用 MessageID 来标识跨不同新闻组交叉发布的重复邮件。

Hadoop Streaming 通过标准输入将每个键组的 Reducer 输入记录逐行提供给 Streaming Reducer 应用。 但是,Hadoop 流没有区分新键值组的机制。 当 Hadoop 开始向进程提供新密钥的记录时,流减少器应用需要跟踪输入键以识别新组。 由于我们使用 MessageID 输出 Mapper 结果,因此 Reducer 输入将按 MessageID 分组。 每个 MessageID 包含多个值(也称为一条消息)的任何组都包含重复项。 在下面的脚本中,我们只使用记录组的第一个值(Message),并丢弃其他值(即重复的消息):

#!/usr/bin/env python
import sys;

currentKey = ""

for line in sys.stdin:
  line = line.strip()
  key, value = line.split('\t',1)
  if currentKey == key :
    continue
  print '%s\t%s' % (key, value)

另请参阅

  • 在遗留应用中使用 Hadoop 的-本章第 4 章、开发复杂 Hadoop MapReduce 应用的 Hadoop Streaming配方,以及本章的使用 Hadoop Streaming 和 Python配方的数据预处理。

将大型数据集加载到 Apache HBase 数据存储-import 和 Bulkload

当以半结构化方式存储大规模数据时,Apache HBase data 存储非常有用,这样它就可以用于使用 Hadoop MapReduce 程序进行进一步处理,或者为客户端应用提供随机访问数据存储。 在本食谱中,我们将使用importtsvbulkload工具将大型文本数据集导入 HBase。

做好准备

  1. 在 Hadoop 群集中安装和部署 Apache HBase。
  2. 确保您的 Hadoop 计算节点中安装了 Python。

How to Do It…

以下步骤显示如何将 TSV(制表符分隔值)转换后的 20news 数据集加载到 HBase 表中:

  1. 遵循数据预处理(使用 Hadoop Streaming 和 Python配方)来执行此配方的数据预处理。 我们假设该配方的以下步骤 4 的输出存储在名为“20news-cleaned”的 HDFS 文件夹中:

    $ hadoop jar \
     /usr/lib/hadoop-mapreduce/hadoop-streaming.jar \
     -input 20news-all/*/* \
     -output 20news-cleaned \
     -mapper MailPreProcessor.py \
     -file MailPreProcessor.py
    
    
  2. 启动 HBase 外壳:

    $ hbase shell
    
    
  3. 通过在 HBase shell 中执行以下命令创建名为 20news-data 的表。 旧版本的importtsv命令(在下一步中使用)只能处理单个列族。 因此,在创建 HBase 表时,我们仅使用单个列族:

    hbase(main):001:0> create '20news-data','h'
    
    
  4. 执行以下命令以将预处理数据导入到前面创建的 HBase 表中:

    $ hbase \
     org.apache.hadoop.hbase.mapreduce.ImportTsv \
     -Dimporttsv.columns=HBASE_ROW_KEY,h:from,h:group,h:subj,h:msg \
     20news-data 20news-cleaned
    
    
  5. 启动 HBase Shell 并使用 HBase Shell 的 COUNT 和 SCAN 命令验证表的内容:

    hbase(main):010:0> count '20news-data'
     12xxx row(s) in 0.0250 seconds
    
    hbase(main):010:0> scan '20news-data', {LIMIT => 10}
     ROW                                       COLUMN+CELL 
     <1993Apr29.103624.1383@cronkite.ocis.te column=h:c1,    timestamp=1354028803355, value= katop@astro.ocis.temple.edu (Chris Katopis)>
     <1993Apr29.103624.1383@cronkite.ocis.te column=h:c2,  timestamp=1354028803355, value= sci.electronics 
    ......
    
    

以下是使用bulkload功能将20news数据集加载到 HBase 表的步骤:

  1. 按照步骤 1 到 3 操作,但使用不同的名称创建表:

    hbase(main):001:0> create '20news-bulk','h'
    
    
  2. 使用以下命令生成 HBasebulkload数据文件:

    $ hbase \
     org.apache.hadoop.hbase.mapreduce.ImportTsv \
    -Dimporttsv.columns=HBASE_ROW_KEY,h:from,h:group,h:subj,h:msg\
    -Dimporttsv.bulk.output=hbaseloaddir \
    20news-bulk–source 20news-cleaned
    
    
  3. 列出文件以验证是否生成了bulkload数据文件:

    $ hadoop fs -ls 20news-bulk-source
    ......
    drwxr-xr-x   - thilina supergroup          0 2014-04-27 10:06 /user/thilina/20news-bulk-source/h
    
    $ hadoop fs -ls 20news-bulk-source/h
    -rw-r--r--   1 thilina supergroup      19110 2014-04-27 10:06 /user/thilina/20news-bulk-source/h/4796511868534757870
    
    
  4. 以下命令通过将输出文件移动到正确位置将数据加载到 HBase 表中:

    $ hbase \
     org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles \
     20news-bulk-source 20news-bulk
    ......
    14/04/27 10:10:00 INFO mapreduce.LoadIncrementalHFiles: Trying to load hfile=hdfs://127.0.0.1:9000/user/thilina/20news-bulk-source/h/4796511868534757870 first= <1993Apr29.103624.1383@cronkite.ocis.temple.edu>last= <stephens.736002130@ngis>
    ......
    
    
  5. 启动 HBase Shell 和使用 HBase Shell 的countscan命令验证表的内容:

    hbase(main):010:0> count '20news-bulk' 
    hbase(main):010:0> scan '20news-bulk', {LIMIT => 10}
    
    

它是如何工作的.

MailPreProcessor.pyPython 脚本从新闻板消息中提取一组选定的数据字段,并将其作为制表符分隔的数据集输出:

value = fromAddress + "\t" + newsgroup 
+"\t" + subject +"\t" + value
print '%s\t%s' % (messageID, value)

我们使用importtsv工具将流式 MapReduce 计算生成的以制表符分隔的数据集导入 HBase。 importtsv工具要求数据除了分隔数据字段的制表符之外,不能有其他制表符。 因此,我们使用以下 Python 脚本片段删除输入数据中可能存在的任何制表符:

line = line.strip()
line = re.sub('\t',' ',line)

importtsv工具支持直接使用Put操作以及通过生成 HBase 内部HFiles将数据加载到 HBase 中。 以下命令使用Put操作直接将数据加载到 HBase。 我们生成的数据集在值中包含一个键和四个字段。 我们使用-Dimporttsv.columns参数将数据字段指定到数据集的表列名称映射。 此映射包括按照输入数据集中以制表符分隔的数据字段的顺序列出各自的表列名称:

$ hbase \
 org.apache.hadoop.hbase.mapreduce.ImportTsv \
 -Dimporttsv.columns=<data field to table column mappings> \ 
 <HBase tablename> <HDFS input directory>

我们可以使用命令后面的命令为数据集生成 HBase HFiles。 这些 HFile 无需通过 HBase API 即可直接加载到 HBase,从而减少了所需的 CPU 和网络资源量:

$ hbase \
 org.apache.hadoop.hbase.mapreduce.ImportTsv \
 -Dimporttsv.columns=<filed to column mappings> \ 
 -Dimporttsv.bulk.output=<path for hfile output> \
 <HBase tablename> <HDFS input directory>

只需将文件移动到正确的位置,就可以将这些生成的 HFile 加载到 HBase 表中。 此移动可通过使用completebulkload命令执行:

$ hbase \org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles \
 <HDFS path for hfiles> <table name>

还有更多...

您也可以使用importtsv工具,该工具具有带有其他数据字段分隔符字符的数据集,方法是指定‘-Dimport tsv.Separator’参数。 以下是使用逗号作为分隔符将逗号分隔的数据集导入到 HBase 表的示例:

$ hbase \
 org.apache.hadoop.hbase.mapreduce.ImportTsv \
 '-Dimporttsv.separator=,' \
 -Dimporttsv.columns=<data field to table column mappings> \ 
 <HBase tablename> <HDFS input directory>

在 MapReduce 作业控制台输出或 Hadoop 监控控制台中查找中的Bad Lines。 使用Bad Lines的原因之一是使用不需要的分隔符。 我们在数据清理步骤中使用的 Python 脚本删除了消息中的任何额外选项卡:

14/03/27 00:38:10 INFO mapred.JobClient:   ImportTsv
14/03/27 00:38:10 INFO mapred.JobClient:     Bad Lines=2

使用 HBase 进行重复数据消除

HBase 支持为每条记录存储多个版本的列值。 查询时,除非我们特别提到时间段,否则 HBase 会返回最新版本的值。 通过确保对重复值使用相同的 RowKey,可以使用 HBase 的此功能执行自动重复数据消除。 在我们的 20news 示例中,我们使用 MessageID 作为记录的 RowKey,确保重复的消息将显示为同一数据记录的不同版本。

HBase 允许我们配置每个列系列的最大或最小版本数。 将最大版本数设置为较低值将通过丢弃旧版本来减少数据使用量。 有关设置最大或最小版本数的详细信息,请参阅http://hbase.apache.org/book/schema.versions.html

另请参阅

为文本数据创建 TF 和 TF-IDF 矢量

大多数文本分析数据挖掘算法对矢量数据进行操作。 我们可以使用向量空间模型将文本数据表示为一组向量。 例如,我们可以通过获取数据集中出现的所有术语的集合并为术语集中的每个术语分配索引来构建向量空间模型。 术语集中的项的数量是结果向量的维度,并且向量的每个维度对应于一个项。 对于每个文档,向量包含每个术语在分配给该特定术语的索引位置的出现次数。 这将使用每个文档中的个词频创建向量空间模型,这与我们在使用第 8 章搜索和索引的 Hadoop MapReduce 配方生成倒排索引的中执行的计算结果类似。

向量可以如下所示:

Creating TF and TF-IDF vectors for the text data

词频和结果文档向量

然而,使用前面的术语计数模型创建向量时,许多文档中频繁出现的术语(例如,the、is、a、are、was、who 等等)会被赋予很高的权重,尽管这些频繁出现的术语在定义文档含义方面的贡献非常小()。 术语频率-逆文档频率(TF-IDF)模型通过利用倒置文档频率(IDF)来缩放术语频率(TF)来解决该问题。 IDF 通常为,计算方法是:首先对出现该术语的文档数(Df)进行计数,将其倒置(1/df),然后将其与文档数相乘,并使用所得值的对数进行归一化,大致如以下公式所示:

Creating TF and TF-IDF vectors for the text data

在本食谱中,我们将使用 Apache Mahout 的内置实用工具从文本数据集创建 TF-IDF 向量。

做好准备

使用 Hadoop 发行版在您的计算机上安装 Apache Mahout,或手动安装最新版本的 Apache Mahout。

How to Do It…

下面的步骤向您展示了如何从到构建 20news 数据集的矢量模型:

  1. http://qwone.com/~jason/20Newsgroups/20news-19997.tar.gz

    $ wget http://qwone.com/~jason/20Newsgroups/20news-19997.tar.gz
    $ tar –xzf 20news-19997.tar.gz
    
    

    下载并解压 20news 数据集

  2. 将提取的数据上传到 HDFS。 为了节省计算时间和资源,您可以只使用数据集的一个子集:

    $ hdfs dfs -mkdir 20news-all
    $ hdfs dfs –put  <extracted_folder> 20news-all
    
    
  3. 转到MAHOUT_HOME。 从上传的文本数据生成 Hadoop 序列文件:

    $ mahout seqdirectory -i 20news-all -o 20news-seq
    
    
  4. Generate TF and TF-IDF sparse vector models from the text data in the sequence files:

    $ mahout seq2sparse -i 20news-seq  -o 20news-vector 
    
    

    前面的命令启动一系列 MapReduce 计算。 等待以下计算完成:

    How to do it…

  5. 使用以下命令检查输出目录。 tfidf-vectors文件夹包含 TF-IDF 模型向量,tf-vectors文件夹包含术语计数模型向量,dictionary.file-0包含术语到术语索引的映射:

    $ hdfs dfs -ls 20news-vector
    
    Found 7 items
    drwxr-xr-x   - u supergroup          0 2012-11-27 16:53 /user/u/20news-vector /df-count
    -rw-r--r--   1 u supergroup       7627 2012-11-27 16:51 /user/u/20news-vector/dictionary.file-0
    -rw-r--r--   1 u supergroup       8053 2012-11-27 16:53 /user/u/20news-vector/frequency.file-0
    drwxr-xr-x   - u supergroup          0 2012-11-27 16:52 /user/u/20news-vector/tf-vectors
    drwxr-xr-x   - u supergroup          0 2012-11-27 16:54 /user/u/20news-vector/tfidf-vectors
    drwxr-xr-x   - u supergroup          0 2012-11-27 16:50 /user/u/20news-vector/tokenized-documents
    drwxr-xr-x   - u supergroup          0 2012-11-27 16:51 /user/u/20news-vector/wordcount
    
    
  6. 或者,可以使用以下命令将 TF-IDF 矢量转储为文本。 关键字是文件名,矢量内容的格式为<term index>:<TF-IDF value>

    $ mahout seqdumper -i 20news-vector/tfidf-vectors/part-r-00000
    
    ……
    Key class: class org.apache.hadoop.io.Text Value Class: class org.apache.mahout.math.VectorWritable
    Key: /54492: Value: {225:3.374729871749878,400:1.5389964580535889,321:1.0,324:2.386294364929199,326:2.386294364929199,315:1.0,144:2.0986123085021973,11:1.0870113372802734,187:2.652313232421875,134:2.386294364929199,132:2.0986123085021973,......}
    ……
    
    

…的工作原理

Hadoop SequenceFiles 将数据存储为二进制键-值对,并支持数据压缩。 Mahout 的seqdirectory命令将文本文件转换为 Hadoop SequenceFile,方法是使用文本文件的文件名作为键,将文本文件的内容用作值。 seqdirectory命令将所有文本内容存储在单个 SequenceFile 中。 但是,我们可以指定块大小来控制 HDFS 中 SequenceFile 数据块的实际存储。 以下是seqdirectory命令的一组选定选项:

mahout seqdirectory –i <HDFS path to text files> -o <HDFS output directory for sequence file> 
 -ow                   If present, overwrite the output directory 
 -chunk <chunk size>   In MegaBytes. Defaults to 64mb 
 -prefix <key prefix>  The prefix to be prepended to the key 

seq2sparse命令是一个 Apache Mahout 工具,支持从包含文本数据的 SequenceFiles 生成稀疏向量。 它支持生成 TF 和 TF-IDF 矢量模型。 此命令作为一系列 MapReduce 计算执行。 以下是为seq2sparse命令选择的一组选项:

mahout seq2sparse -i <HDFS path to the text sequence file> -o <HDFS output directory>
 -wt {tf|tfidf} 
 -chunk <max dictionary chunk size in mb to keep in memory> 
 --minSupport <minimum support>
 --minDF <minimum document frequency>
 --maxDFPercent <MAX PERCENTAGE OF DOCS FOR DF

minSupport命令是将单词视为特征的最低频率。 minDF是 Word 需要包含的最小文档数。 maxDFPercent是表达式的最大值(单词的文档频率/文档总数),以便将该单词视为文档中的良好特征。 这有助于删除诸如停用词等高频特征。

您可以使用 Mahoutseqdumper命令将使用 Mahout 可写数据类型的 SequenceFile 的内容转储为纯文本:

mahout seqdumper -i <HDFS path to the sequence file>
 -o <output directory>
 --count         Output only the number of key value pairs.
 --numItems      Max number of key value pairs to output
 --facets        Output the counts per key.

另请参阅

使用 Apache Mahout 对文本数据进行群集

聚类在数据挖掘计算中起着不可或缺的作用。 基于用例,使用数据项的一个或多个特征将数据集的相似项分组在一起。 文档聚类被用于许多文本挖掘操作,如文档组织、主题识别、信息表示等。 文档聚类与传统的数据聚类机制有许多相同的机制和算法。 然而,在确定用于聚类的特征以及构建表示文本文档的向量空间模型时,文档聚类有其独特的挑战。

第 7 章,Hadoop 生态系统 II-Pig、HBase、Mahout 和 SqoopRunning K-Means with Mahout配方侧重于使用 Mahout KMeansClusters 来集群统计数据。 本书前一版的分类、推荐和查找关系中的聚类亚马逊销售数据集配方侧重于使用聚类来识别具有相似兴趣的客户。 这两个食谱总体上提供了对使用集群算法的更深入的理解。 本食谱重点介绍 Apache Mahout 中可用于文档集群的几种集群算法中的两种。

做好准备

  • 使用 Hadoop 发行版在您的计算机上安装 Apache Mahout,或在您的计算机上手动安装最新的 Apache Mahout 版本。

怎么做……

以下步骤使用 Apache Mahout KmeansClusters 算法对 20news 数据集进行聚类:

  1. 请参阅本章中的为文本数据配方创建 TF 和 TF-IDF 矢量,并为 20news 数据集生成 TF-IDF 矢量。 我们假设 TF-IDF 矢量位于 HDFS 的20news-vector/tfidf-vectors文件夹中。

  2. 执行以下命令以运行 Mahout KMeansClusters 计算:

    $ mahout kmeans \
     --input 20news-vector/tfidf-vectors \
     --clusters 20news-seed/clusters
     --output 20news-km-clusters\
     --distanceMeasure \
    org.apache.mahout.common.distance.SquaredEuclideanDistanceMeasure-k 10 --maxIter 20 --clustering
    Execute the following command to convert the clusters to text:
    $ mahout clusterdump \
     -i 20news-km-clusters/clusters-*-final\
     -o 20news-clusters-dump \
     -d 20news-vector/dictionary.file-0 \
     -dt sequencefile \
     --pointsDir 20news-km-clusters/clusteredPoints
    
    $ cat 20news-clusters-dump
    
    

它是如何工作的.

下面的代码显示了 Mahout KMeans 算法的用法:

mahout kmeans 
 --input <tfidf vector input>
 --clusters <seed clusters>
 --output <HDFS path for output>
 --distanceMeasure <distance measure>-k <number of clusters>--maxIter <maximum number of iterations>--clustering

当为--clusters选项提供空的 HDFS 目录路径时,mahout 将生成随机种子群集。 Manhout 支持几种不同的距离计算方法,如欧几里得、余弦和曼哈顿。

以下是 Mahoutclusterdump命令的用法:

mahout clusterdump -i <HDFS path to clusters>-o <local path for text output>
 -d <dictionary mapping for the vector data points>
 -dt <dictionary file type (sequencefile or text)>
 --pointsDir <directory containing the input vectors to       clusters mapping>

另请参阅

  • 第 7 章,Hadoop 生态系统 II 的Running K-Means with Mahout配方--Pig、HBase、Mahout 和 Sqoop

基于潜在 Dirichlet 分配(LDA)的主题发现

我们可以使用潜在 Dirichlet 分配(LDA)将给定的词集聚集成主题,将一组文档聚集成主题的组合。 LDA 在基于上下文识别文档或单词的含义时非常有用,而不是完全依赖于单词的数量或确切的单词。 LDA 从原始文本匹配向语义分析迈进了一步。 LDA 可用于在诸如搜索引擎的系统中识别意图并解决歧义词。 LDA 的其他一些示例用例包括针对特定主题识别有影响力的推特用户,以及 Twahpicc(http://twahpic.cloudapp.net)应用使用 LDA 识别推特上使用的主题。

LDA 使用 TF 向量空间模型,而不是 TF-IDF 模型,因为它需要考虑单词的共现和相关性。

做好准备

使用 Hadoop 发行版在您的计算机上安装 Apache Mahout,或手动安装最新版本的 Apache Mahout。

How to Do It…

以下步骤向您展示了如何在 20news 数据集的子集上运行 Mahout LDA 算法:

  1. http://qwone.com/~jason/20Newsgroups/20news-19997.tar.gz

    $ wget http://qwone.com/~jason/20Newsgroups/20news-19997.tar.gz
    $ tar –xzf 20news-19997.tar.gz
    
    

    下载并解压 20news 数据集

  2. 将提取的数据上传到 HDFS。 为了节省计算时间和资源,您可以只使用数据集的一个子集:

    $ hdfs dfs -mkdir 20news-all
    $ hdfs dfs –put  <extracted_folder> 20news-all
    
    
  3. 从上传的文本数据生成序列文件:

    $ mahout seqdirectory -i 20news-all -o 20news-seq 
    
    
  4. 从序列文件中的文本数据生成稀疏向量:

    $ mahout seq2sparse \
    –i 20news-seq  -o 20news-tf \
    -wt tf -a org.apache.lucene.analysis.WhitespaceAnalyzer
    
    
  5. 将 TF 矢量从 SequenceFile转换为 SequenceFile

    $ mahout rowid -i 20news-tf/tf-vectors -o 20news-tf-int
    
    ```</intwritable></text> 
    
  6. 运行以下命令以执行 LDA 计算:

    $ mahout cvb \
    -i 20news-tf-int/matrix -o lda-out \
    -k 10  -x 20  \
    -dict 20news-tf/dictionary.file-0 \
    -dt lda-topics \
    -mt lda-topic-model
    
    
  7. 转储并检查 LDA 计算的结果:

    $ mahout seqdumper -i lda-topics/part-m-00000
    
    Input Path: lda-topics5/part-m-00000
    Key class: class org.apache.hadoop.io.IntWritable Value Class: class org.apache.mahout.math.VectorWritable
    Key: 0: Value: {0:0.12492744375758073,1:0.03875953927132082,2:0.1228639250669511,3:0.15074522974495433,4:0.10512715697420276,5:0.10130565323653766,6:0.061169131590630275,7:0.14501579630233746,8:0.07872957132697946,9:0.07135655272850545}
    .....
    
    
  8. 将输出向量与术语到术语索引的字典映射连接起来:

    $ mahout vectordump \
    -i lda-topics/part-m-00000 \
    --dictionary 20news-tf/dictionary.file-0 \
    --vectorSize 10  -dt sequencefile 
    
    ......
    
    {"Fluxgate:0.12492744375758073,&:0.03875953927132082,(140.220.1.1):0.1228639250669511,(Babak:0.15074522974495433,(Bill:0.10512715697420276,(Gerrit:0.10130565323653766,(Michael:0.061169131590630275,(Scott:0.14501579630233746,(Usenet:0.07872957132697946,(continued):0.07135655272850545}
    {"Fluxgate:0.13130952097888746,&:0.05207587369196414,(140.220.1.1):0.12533225607394424,(Babak:0.08607740024552457,(Bill:0.20218284543514245,(Gerrit:0.07318295757631627,(Michael:0.08766888242201039,(Scott:0.08858421220476514,(Usenet:0.09201906604666685,(continued):0.06156698532477829}
    .......
    
    

…的工作原理

LDA 的 Mahout CVB 版本使用迭代 MapReduce 方法实现折叠变量贝叶斯推理算法:

mahout cvb \
-i 20news-tf-int/matrix \
-o lda-out -k 10  -x 20 \
-dict 20news-tf/dictionary.file-0 \
-dt lda-topics \
-mt lda-topic-model

-i参数提供输入路径,而-o参数提供存储输出的路径。 参数-k指定要学习的主题数,–x指定计算的最大迭代次数。 -dict参数指向包含术语到术语索引的映射的字典。 –dt参数中给出的路径存储训练主题分布。 –mt中给出的路径用作存储中间模型的临时位置。

通过调用help选项可以查询cvb命令的所有命令行选项,如下所示:

mahout  cvb  --help

将主题数量设置为非常小的值将显示极高级别的主题。 大量的主题会产生更多描述性的主题,但需要更长的处理时间。 可以使用maxDFPercent选项删除常用单词,从而加快处理速度。

另请参阅

基于 Mahout 朴素贝叶斯分类器的文档分类

分类将文档或数据项分配给一组已知的具有已知属性的类。 当我们需要将文档分配到一个或多个类别时,可以使用文档分类。 这是信息检索和图书馆学中的常见用例。

第 9 章分类、建议和查找关系中的分类使用朴素贝叶斯分类器配方提供了关于分类用例的更详细描述,还概述了使用朴素贝叶斯分类器算法。 本食谱重点介绍 Apache Mahout 中对文本文档的分类支持。

做好准备

  • 使用 Hadoop 发行版在您的计算机上安装 Apache Mahout,或手动安装最新版本的 Apache Mahout。

怎么做……

以下个步骤使用 ApacheMahout 朴素贝叶斯算法对 20news 数据集进行聚类:

  1. 请参阅本章中的为文本数据配方创建 TF 和 TF-IDF 矢量,并为 20news 数据集生成 TF-IDF 矢量。 我们假设 TF-IDF 矢量位于 HDFS 的20news-vector/tfidf-vectors文件夹中。

  2. 将数据拆分为训练和测试数据集:

    $ mahout split \
     -i 20news-vectors/tfidf-vectors \
     --trainingOutput /20news-train-vectors \
     --testOutput /20news-test-vectors  \
     --randomSelectionPct 40 \
    --overwrite --sequenceFiles 
    
    
  3. 训练朴素贝叶斯模型:

    $ mahout trainnb \
     -i 20news-train-vectors -el \
     -o  model \
     -li labelindex 
    
    
  4. 测试数据集上的分类:

    $ mahout testnb \
     -i 20news-train-vectors \
     -m model \
     -l labelindex \
     -o 20news-testing 
    
    

它是如何工作的.

Mahout 的split命令可用于将数据集分割为训练数据集和测试数据集。 此命令适用于文本数据集以及 Hadoop SequenceFile 数据集。 以下是 Mahoutdata-splitting命令的用法。 您可以将--help选项与split命令一起使用,以打印出所有选项:

mahout split \
 -i <input data directory> \
 --trainingOutput <HDFS path to store the training dataset> \
 --testOutput <HDFS path to store the test dataset>  \
 --randomSelectionPct <percentage to be selected as test data> \ 
 --sequenceFiles 

sequenceFiles选项指定输入数据集在 Hadoop SequenceFiles 中。

以下是 Mahout naive Bayes 分类器训练命令的用法。 --el选项通知 Mahout 从输入数据集中提取标签:

mahout trainnb \
 -i <HDFS path to the training data set> \
 -el \
 -o <HDFS path to store the trained classifier model> \
 -li <Path to store the label index> \

以下是 Mahout naive Bayes 分类器测试命令的用法:

mahout testnb \
 -i <HDFS path to the test data set>
 -m <HDFS path to the classifier model>\
 -l <Path to the label index> \
 -o <path to store the test result>

另请参阅

  • 使用第 9 章分类、建议和查找关系的朴素贝叶斯分类器配方的分类
posted @ 2025-10-01 11:29  绝不原创的飞龙  阅读(1)  评论(0)    收藏  举报