Hadoop-现代大数据处理指南-全-

Hadoop 现代大数据处理指南(全)

原文:zh.annas-archive.org/md5/66e2cecfbe4e4c8497841d32d56ad097

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

当今数据的复杂结构需要高级解决方案来进行数据转换及其语义表示,以便使信息更容易为用户获取。Apache Hadoop 以及众多其他大数据工具,使您能够相对轻松地构建此类解决方案。本书列出了一些独特思想和技巧,使您能够克服成为大数据架构专家道路上的不同数据处理和分析挑战。

本书首先迅速阐述了企业数据架构的原则,并展示了它们与 Apache Hadoop 生态系统的关系。您将全面了解使用 Hadoop 的数据生命周期管理,随后在 Hadoop 中建模结构化和非结构化数据。本书还将向您展示如何利用 Apache Spark 等工具设计实时流管道,以及如何使用 Elasticsearch 等工具构建高效的搜索引擎解决方案。您将在 Hadoop 上构建企业级分析解决方案,并学习如何使用 Tableau 和 Python 等工具可视化您的数据。

本书还涵盖了在本地和云上部署大数据解决方案的技术,以及管理和管理您的 Hadoop 集群的专家技术。

在本书结束时,您将拥有构建满足任何数据或洞察需求的大数据系统的全部知识,利用现代大数据框架和工具的全套功能。您将具备成为真正的数据专家所需的技能和知识。

这本书面向谁

本书面向希望快速进入 Hadoop 行业并成为大数据架构专家的大数据专业人士。项目经理和希望在大数据和 Hadoop 领域建立职业生涯的主机专业人员也将发现本书很有用。为了从本书中获得最佳效果,需要对 Hadoop 有一定的了解。

这本书涵盖的内容

第一章,企业数据架构原则,展示了如何在 Hadoop 集群中存储和建模数据。

第二章,Hadoop 生命周期管理,涵盖了各种数据生命周期阶段,包括数据创建、共享、维护、归档、保留和删除。它还进一步详细介绍了数据安全工具和模式。

第三章,Hadoop 设计考虑因素,涵盖了关键数据架构原则和实践。读者将了解现代数据架构师如何适应大数据架构用例。

第四章,数据移动技术,涵盖了将数据传输到和从我们的 Hadoop 集群的不同方法,以充分利用其实力。

第五章,Hadoop 中的数据建模,展示了如何使用云基础设施构建企业应用程序。

第六章,设计实时流数据管道,涵盖了设计实时数据分析的不同工具和技术。

第七章,大规模数据处理框架,描述了企业数据架构原则以及管理和保护这些数据的重要性。

第八章,构建企业搜索平台,提供了使用 Elasticsearch 构建搜索解决方案的详细架构设计。

第九章,设计数据可视化解决方案,展示了如何使用 Apache Ambari 部署您的 Hadoop 集群。

第十章,使用云开发应用程序,涵盖了可视化数据的不同方法和选择正确可视化方法所涉及的因素。

第十一章,生产 Hadoop 集群部署,涵盖了从我们的数据中提取价值的不同数据处理解决方案。

为了充分利用本书

如果 Hadoop 的安装如前几章所述完成得很好,那就太好了。对 Hadoop 的详细或基本了解将是一个额外的优势。

下载示例代码文件

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

您可以通过以下步骤下载代码文件:

  1. www.packtpub.com登录或注册。

  2. 选择 SUPPORT 标签。

  3. 点击代码下载与勘误。

  4. 在搜索框中输入本书的名称,并遵循屏幕上的说明。

下载文件后,请确保您使用最新版本解压缩或提取文件夹:

  • Windows 上的 WinRAR/7-Zip

  • Mac 上的 Zipeg/iZip/UnRarX

  • Linux 上的 7-Zip/PeaZip

本书代码包也托管在 GitHub 上,地址为github.com/PacktPublishing/Modern-Big-Data-Processing-with-Hadoop。如果代码有更新,它将在现有的 GitHub 仓库中更新。

我们还有其他来自我们丰富图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!

下载彩色图像

我们还提供了一个包含本书中使用的截图/图表彩色图像的 PDF 文件。您可以从这里下载:www.packtpub.com/sites/default/files/downloads/ModernBigDataProcessingwithHadoop_ColorImages.pdf

使用的约定

本书使用了多种文本约定。

CodeInText:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账号。以下是一个示例:“将下载的 WebStorm-10*.dmg 磁盘镜像文件挂载为系统中的另一个磁盘。”

代码块应如下设置:

export HADOOP_CONF_DIR="${HADOOP_CONF_DIR:-$YARN_HOME/etc/hadoop}"
export HADOOP_COMMON_HOME="${HADOOP_COMMON_HOME:-$YARN_HOME}"
export HADOOP_HDFS_HOME="${HADOOP_HDFS_HOME:-$YARN_HOME}"  

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

$ hadoop fs -cat /tmp/output-7/part*
 NewDelhi, 440
 Kolkata, 390
 Bangalore, 270

任何命令行输入或输出都应如下编写:

useradd hadoop
passwd hadoop1 

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中如下显示。以下是一个示例:“从管理面板中选择系统信息。”

警告或重要提示将如下所示。

小贴士和技巧将如下所示。

联系我们

我们始终欢迎读者的反馈。

一般反馈:请发送电子邮件至 feedback@packtpub.com,并在邮件主题中提及书籍标题。如果您对本书的任何方面有疑问,请通过 questions@packtpub.com 发送电子邮件给我们。

勘误:尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告此错误。请访问 www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。

盗版:如果您在互联网上发现我们作品的任何非法副本,我们将不胜感激,如果您能提供位置地址或网站名称,我们将不胜感激。请通过 copyright@packtpub.com 联系我们,并提供材料的链接。

如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问 authors.packtpub.com

评论

请留下评论。一旦您阅读并使用了这本书,为何不在购买它的网站上留下评论?潜在读者可以查看并使用您的客观意见来做出购买决定,Packt 可以了解您对我们产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!

如需了解 Packt 的更多信息,请访问 packtpub.com.

第一章:企业数据架构原则:

传统上,企业采用数据仓库来存储、处理和访问大量数据。这些仓库通常是大型关系型数据库管理系统(RDBMS),能够存储各种大规模数据集。随着数据复杂性、体积和访问模式的增加,许多企业开始采用大数据作为模型来重新设计他们的数据组织,并围绕它制定必要的政策。

此图展示了典型企业在企业中的数据仓库外观:

图片

由于企业有许多不同的部门、组织和地理区域,每个部门往往拥有自己的仓库,这对整个企业提出了各种挑战。例如:

  • 数据的多个来源和目的地:

  • 数据重复和冗余:

  • 数据访问监管问题:

  • 企业中非标准的数据定义。

  • 软件和硬件的可扩展性和可靠性问题:

  • 数据移动和审计:

  • 仓库之间的集成:

由于技术进步,如:

  • 每 TB 的成本:

  • 每纳米的计算能力:

  • 吉比特网络带宽:

  • 云:

随着全球化,市场已经全球化,消费者也是全球化的。这极大地增加了覆盖范围。这些进步也给企业带来了许多挑战,例如:

  • 人力资本管理:

  • 仓库管理:

  • 物流管理:

  • 数据隐私和安全:

  • 销售和账单管理:

  • 理解需求和供应:

为了跟上市场的需求,企业开始收集越来越多的关于自己的指标;因此,在当前情况下,数据所涉及的维度有所增加。

在本章中,我们将学习:

  • 数据架构原则:

  • 元数据的重要性:

  • 数据治理:

  • 数据安全:

  • 数据即服务:

  • 使用 Hadoop 的数据架构演变:

数据架构原则:

当前状态的数据可以从以下四个维度(四个 V)来定义。

体积:

数据量是设计大数据系统所需的重要度量标准。这是决定企业必须投资多少以满足当前和未来存储需求的一个重要因素。

企业中的不同类型的数据需要不同的存储、归档和处理能力。今天,企业中非常常见的 PB 级存储系统,在几十年前几乎是不可想象的。

速度:

这是数据的另一个维度,决定了数据的流动性。组织内部存在各种数据,属于以下类别:

  • 流数据:

    • 实时/近实时数据:
  • 静态数据:

    • 不可变数据:

    • 可变数据:

这个维度对企业使用的网络架构有影响,用于消费和处理数据。

多样性

这个维度讨论数据的形态和形状。我们可以进一步将其分类为以下类别:

  • 流数据:

    • 线上数据格式(例如,JSON、MPEG 和 Avro)
  • 静态数据:

    • 不可变数据(例如,媒体文件和客户发票)

    • 可变数据(例如,客户详情、产品库存和员工数据)

  • 应用数据:

    • 配置文件、机密信息、密码等

对于一个组织来说,拥抱非常少的科技以减少数据的多样性非常重要。拥有许多不同类型的数据对企业在管理和消费方面构成了非常大的挑战。

真实性

这个维度讨论数据的准确性。如果没有对每个系统在保证数据安全、可用和可靠方面所提供保证的稳固理解,那么理解由此数据生成的分析以及进一步生成洞察就变得非常困难。

应该实施必要的审计,以确保通过系统的数据通过所有质量检查,并最终通过大数据系统。

让我们看看典型的大数据系统是如何看的:

图片

正如你所见,许多不同类型的应用程序正在与大数据系统交互,以存储、处理和生成分析。

元数据的重要性

在我们试图理解元数据的重要性之前,让我们先尝试理解什么是元数据。元数据简单地说就是关于数据的数据。这听起来有些令人困惑,因为我们以一种递归的方式定义了定义。

在典型的大数据系统中,我们有这三个垂直层面的水平:

  • 将数据写入大数据系统的应用程序

  • 在大数据系统中组织数据

  • 消费大数据系统数据的应用程序

当我们谈论数百万(甚至数十亿)存储在大数据系统中的数据文件/段时,这引发了一些挑战。我们应该能够正确识别这些数据文件在整个企业中的所有权和使用情况。

让我们以一个拥有电视频道的电视广播公司为例;它制作电视节目并通过有线电缆网络、卫星网络、互联网等向所有目标受众广播。如果我们仔细观察,内容的来源只有一个。但它通过所有可能的媒介,最终到达用户的观看位置,在电视、移动电话、平板电脑等设备上观看。

由于观众正在各种设备上访问这些电视内容,运行在这些设备上的应用程序可以生成多个消息来指示各种用户行为和偏好,并将它们发送回应用程序服务器。这些数据量相当大,并存储在大数据系统中。

根据大数据系统内部数据的组织方式,外部应用程序或对等应用程序几乎不可能了解系统内部存储的不同类型的数据。为了使这个过程更容易,我们需要描述和定义大数据系统内部数据组织的方式。这将帮助我们更好地理解大数据系统中的数据组织和访问。

让我们进一步扩展这个例子,并假设还有一个应用程序从大数据系统中读取数据,以了解在特定电视节目中广告的最佳时间。这个应用程序应该更好地理解大数据系统中可用的所有其他数据。因此,如果没有一个明确定义的元数据系统,很难做到以下事情:

  • 理解存储、访问和处理的数据多样性

  • 在不同类型的数据集之间构建接口

  • 从安全角度正确标记数据,将其归类为高度敏感数据或非敏感数据

  • 在大数据生态系统中连接给定系统集之间的联系

  • 审计和排查可能因数据不一致而产生的问题

数据治理

拥有大量数据并不足以做出对业务成功产生积极影响的高质量决策。确保只收集、保存和维护高质量数据非常重要。数据收集过程也会随着需要收集的新类型数据而演变。在这个过程中,我们可能会破坏一些读取上一代数据的接口。如果没有一个明确的过程和人员,处理数据对所有规模的组织都成为一个巨大的挑战。

要在数据管理方面表现出色,我们应该考虑以下品质:

  • 良好的政策和流程

  • 责任制

  • 正式的决策结构

  • 管理规则的实施

这些品质的实施被称为数据治理。在较高层次上,我们将数据治理定义为得到良好管理的数据。这个定义也有助于我们明确数据管理和数据治理不是同一回事。数据管理涉及使用数据来做出良好的商业决策,并最终运营组织。数据治理涉及我们在整个组织中使用纪律行为来管理数据的程度。

这是一个重要的区别。那么底线是什么?大多数组织管理数据,但远没有多少组织能够很好地管理这些管理技术。

数据治理的基本原理

让我们尝试理解数据治理的基本原理:

  • 责任制

  • 标准化

  • 透明度

透明度确保组织内部和外部的所有员工在与与组织相关的数据互动时了解他们的角色。这将确保以下事项:

  • 建立信任

  • 避免意外

责任制确保有权访问数据的团队和员工描述他们可以使用和不能使用数据的方式。

标准化处理数据的正确标记、描述和分类方式。例如,如何为组织内部的员工生成电子邮件地址。一种方式是使用 firstname-lastname@company.com,或者这些组合中的任何其他组合。这将确保所有有权访问这些电子邮件地址的人都能理解哪个是首字母,哪个是尾字母,而无需任何人亲自解释。

标准化提高了数据质量,并为多个数据维度带来了秩序。

数据安全

安全性不是一个新概念。它自早期的 UNIX 分时操作系统设计以来就被采用。在最近,由于大量数据泄露导致组织损失了大量收入,个人和组织在这个安全领域的意识有所提高。

安全性作为一个一般概念,可以应用于许多不同的事物。当涉及到数据安全时,我们需要理解以下基本问题:

  • 存在哪些类型的数据?

  • 谁拥有数据?

  • 谁有权访问数据?

  • 数据何时退出系统?

  • 数据是否物理安全?

让我们看看一个简单的大数据系统,并试图更详细地理解这些问题。系统的规模使得安全性对每个人来说都是一个噩梦。因此,我们应该制定适当的政策,确保每个人都处于同一页面上:

图片

在这个例子中,我们有以下组件:

  • 在全球多个地理区域内运行的不同类型的应用程序。

  • 应用程序产生了大量和多样化的输入数据。

  • 所有数据都被摄入到大数据系统中。

  • ETL/ELT 应用程序从大数据系统中消耗数据,并将可消费的结果放入 RDBMS(这是可选的)。

  • 商业智能应用程序从这个存储中读取数据,并进一步对数据进行深入分析。这些是领导团队决策的驱动力。

你可以想象通过这个系统流动的数据的规模和数量。我们还可以看到,参与整个生态系统的服务器、应用程序和员工数量非常大。如果我们没有制定适当的政策,保护这样一个复杂的系统将不是一项容易的任务。

此外,如果攻击者使用社会工程学手段获取对系统的访问权限,我们应确保数据访问仅限于最低可能的级别。当实施较差的安全措施时,攻击者可以几乎访问所有商业机密,这可能对业务造成严重损失。

仅举一个例子,一家初创公司正在构建下一代计算设备,计划将其所有数据托管在云端,并且没有建立适当的安全策略。当攻击者破坏云上服务器的安全时,他们可以轻易地了解这家初创公司在构建什么,并可以窃取情报。一旦情报被窃取,我们可以想象黑客如何利用这些信息为自己谋取私利。

在理解了安全的重要性之后,让我们定义需要保护的内容。

应用程序安全

应用程序是产品型组织的最前线,因为消费者使用这些应用程序与产品和服务进行交互。我们必须确保在编写这些应用程序接口时遵循适当的安全标准。

由于这些应用程序向后端系统生成数据,我们应该确保在防火墙方面只允许适当的访问机制。

此外,这些应用程序与许多其他后端系统进行交互,我们必须确保显示与用户相关的正确数据。这归结为实施适当的认证和授权,不仅针对用户,也针对应用程序在访问组织资源的不同类型时。

如果没有适当的审计措施,分析应用程序的数据访问模式将非常困难。所有日志都应该收集在远离应用服务器的中央位置,并可以进一步被集成到大数据系统中。

输入数据

一旦应用程序生成多个指标,它们可以临时存储在本地,由周期性进程进一步消费,或者进一步推送到像 Kafka 这样的流式系统。

在这种情况下,我们应该仔细思考和设计数据存储的位置以及哪些用户可以访问这些数据。如果我们进一步将数据写入像 Kafka 或 MQ 这样的系统,我们必须确保有进一步的认证、授权和访问控制措施。

在这里,我们可以利用操作系统提供的诸如进程用户 ID、进程组 ID、文件系统用户 ID、组 ID 等安全措施,以及高级系统(如 SELinux)来进一步限制对输入数据的访问。

大数据安全

根据选择的数据仓库解决方案,我们必须确保授权的应用程序和用户可以写入和读取数据仓库。必须建立适当的安全策略和审计措施,以确保大规模数据不会轻易被所有人访问。

为了实施所有这些访问策略,我们可以使用操作系统提供的机制,如文件访问控制和访问控制。由于我们讨论的是地理上分布的大数据系统,我们必须思考和设计集中的认证系统,以便员工在与这些大数据系统交互时获得无缝体验。

RDBMS 安全

许多 RDBMS 非常安全,可以为用户提供以下访问级别:

  • 数据库

  • 表格

  • 使用模式

它们还内置了审计机制,可以告知哪些用户在何时访问了哪些类型的数据。这些数据对于保持系统安全至关重要,并且应该实施适当的监控,以监视这些系统的健康和安全。

BI 安全

这些可以是针对公司特定需求内部构建的应用程序,或者可以提供业务团队所需见解的外部应用程序。这些应用程序也应通过实施单点登录、基于角色的访问控制和基于网络的访问控制来得到适当的保护。

由于这些应用程序提供的见解对于组织的成功至关重要,因此应采取适当的安全措施来保护它们。

到目前为止,我们已经看到了企业系统的不同部分,并了解了可以遵循哪些事项来提高整体企业数据设计的安全性。让我们谈谈一些可以在数据设计中普遍应用的事项。

物理安全

这涉及到物理设备访问、数据中心访问、服务器访问和网络访问。如果未经授权的人员获得了企业设备的访问权限,他们可以访问其中所有存在的数据。

如前几节所述,当操作系统运行时,我们可以通过利用操作系统的安全功能来保护资源。当入侵者获得对设备的物理访问权限(或甚至退役的服务器)时,他们可以将这些设备连接到他们控制的另一个操作系统,并访问我们服务器上所有存在的数据。

当我们退役服务器时,必须小心谨慎,因为即使在这些设备上(即使格式化后)写入的数据也有可能被恢复。因此,我们应该遵循行业标准的数据擦除技术,以正确清理企业拥有的所有数据。

为了防止这些,我们应该考虑加密数据。

数据加密

加密数据将确保即使授权人员访问了设备,他们也无法恢复数据。这是由于数据和企业员工的流动性增加而如今遵循的标准做法。许多大型企业加密了笔记本电脑和手机上的硬盘。

安全密钥管理

如果你曾与需要身份验证的应用程序合作过,你将使用用户名和密码的组合来访问服务。通常这些秘密存储在源代码本身中。这对非编译程序构成了挑战,因为攻击者可以轻易地访问用户名和密码,从而获得访问我们资源的权限。

许多企业开始采用集中式密钥管理,通过它应用程序可以查询这些服务以获取访问受认证保护的资源的权限。所有这些访问模式都由 KMS(密钥管理系统)进行适当的审计。

员工也应该使用自己的凭证访问这些系统以获取资源。这确保了密钥是受保护的,并且只能由授权应用程序访问。

数据即服务

数据即服务DaaS)是一个由于云的采用增加而在最近变得流行起来的概念。当涉及到数据时,可能会有些困惑,数据如何添加到服务模型中?

DaaS(数据即服务)为服务的用户提供极大的灵活性,无需担心服务运行在的基础设施的规模、性能和维护。基础设施会自动为我们处理这些,但鉴于我们处理的是云模型,我们享有云的所有好处,如按需付费、容量规划等。这将减轻数据管理的负担。

如果我们仔细理解,我们只是在单独提取数据管理部分。但数据治理也应该在这里得到很好的定义,否则我们将失去服务模型的所有好处。

到目前为止,我们一直在谈论云中的服务概念。这意味着我们无法在企业或更小的组织中使用它吗? 答案是。因为这是一个通用概念,它告诉我们以下事情。

当我们谈论服务模型时,我们应该记住以下几点,否则将会出现混乱:

  • 认证

  • 授权

  • 审计

这将保证只有定义良好的用户、IP 地址和服务可以访问作为服务公开的数据。

让我们以一个拥有以下数据的组织为例:

  • 员工

  • 服务器和数据中心

  • 应用程序

  • 内部文档网站

如您所见,所有这些都是独立的数据集。但是,作为一个整体,当我们希望组织成功时,有很多重叠,我们应该尝试接受 DaaS 模型,这样所有这些对数据进行权威管理的应用程序仍然会管理数据。但对于其他应用程序,它们通过 REST API 以简单服务的形式公开;因此,这增加了组织内的协作并促进了创新。

让我们进一步举例说明这是如何可能的:

  • 管理所有员工数据的团队可以提供一个简单的数据服务。所有其他应用程序都可以使用这个数据集,无需担心存储这些员工数据的底层基础设施:

    • 这将使数据服务的消费者得到解放,使得消费者:

      • 不必担心底层基础设施

      • 不必担心用于与这些数据服务器通信的协议

      • 只需专注于 REST 模型来设计应用程序

    • 典型的例子包括:

      • 将员工数据存储在类似LDAPMicrosoft Active目录的数据库中
  • 管理整个组织基础设施的团队可以设计自己的系统,以避免整个组织的硬件库存,并且可以提供一个简单的数据服务。组织的其他部分可以使用这个服务来构建他们感兴趣的应用程序:

    • 这将使企业更具敏捷性

    • 确保关于整个组织硬件的数据有一个单一的真实来源

    • 它提高了数据信任度,并增加了构建在此数据之上的应用程序的信心

  • 组织中的每个团队可能都会使用不同的技术来构建和部署他们的应用程序到服务器上。随后,他们还需要构建一个数据存储库,以跟踪服务器上部署的软件的活跃版本。拥有这样的数据源可以帮助组织以以下方式:

    • 使用这些数据构建的服务可以持续监控并看到软件部署发生得更频繁的地方

    • 服务还可以确定哪些应用程序容易受到攻击并且正在生产中积极部署,以便可以采取进一步行动来修复漏洞,无论是通过升级操作系统还是软件

    • 理解整体软件部署生命周期中的挑战

    • 为整个组织提供一个单一平台,以标准化的方式做事,这促进了所有权感

  • 文档对于一个组织来说非常重要。与其运行自己的基础设施,使用 DaaS 模型,组织和团队可以专注于与他们公司相关的文档,并且只为这些文档付费。在这里,像 Google Docs 和 Microsoft Office Online 这样的服务非常受欢迎,因为它们让我们可以按需付费,最重要的是,不必担心构建这些所需的技术。

    • 拥有这种数据服务模型将帮助我们做到以下事情:

      • 只为使用的服务付费

      • 根据需要增加或减少存储规模

      • 如果服务在云端并且连接到互联网,可以从任何地方访问数据

      • 当通过企业政策决定通过 VPN 连接时,可以访问企业资源

在前面的例子中,我们看到了在企业中使用的大量应用程序,以及数据作为模型如何以各种方式帮助企业实现协作、创新和信任。

但是,当涉及到大数据时,DaaS 能做什么呢?

就像所有其他数据片段一样,大数据也可以适应 DaaS 模型,并提供我们之前看到的相同灵活性:

  • 不必担心底层硬件和技术

  • 根据需要扩展基础设施

  • 只为属于企业的数据付费

  • 运营和维护挑战被移除

  • 数据可以地理上可用以实现高可用性

  • 集成备份和恢复以满足灾难恢复需求

带着这些优点,企业可以更加灵活,并构建可以利用这些数据作为服务的应用程序。

使用 Hadoop 的演进数据架构

Hadoop 是一种帮助实现可扩展和分布式计算的软件。在 Hadoop 出现之前,许多技术被行业用来满足他们的数据需求。让我们对这些存储机制进行分类:

  • 层次化数据库

  • 网络数据库

  • 关系型数据库

让我们了解这些数据架构是什么。

层次化数据库架构

这种存储企业数据的方式是在 20 世纪 60 年代初由 IBM 发明的,并用于他们的应用程序。层次化数据库的基本概念是数据以根树的形式组织。根节点是树的开始,然后所有的子节点只与一个父节点链接。这是一种非常独特的存储和检索方式。

如果你有一些计算机科学的背景,树是存储数据的一种独特方式,这样它就有一些相互关系(如父子和关系)。

这张图片展示了典型 HDBMS 中数据的组织方式:

如我们所见,根节点是组织本身,与组织相关的所有数据都遵循树结构,这描绘了多种关系。这些关系可以这样理解:

  • 员工拥有笔记本电脑移动电话工作站iMac

  • 员工属于组织

  • 许多供应商提供不同的需求:

    • 计算机供应商提供iMac工作站
  • 餐饮服务在印度和美国都有;两家供应商,The Best CaterersBay Area Caterers,为这些地区提供服务

尽管我们在这一庞大的数据存储中表达了多种类型的关系,但我们可以看出数据被重复,并且针对不同类型需求查询数据成为了一个挑战。

让我们以一个简单的问题为例:哪个供应商为 Employee-391 拥有的 iMac 提供了服务?

为了做到这一点,我们需要遍历树并从两个不同的子树中获取信息。

网络数据库架构

网络数据库管理系统也源于计算机科学:图论,其中有许多不同类型的节点和关系将它们连接在一起。在这个结构中没有特定的根节点。它是在 20 世纪 70 年代初发明的:

如我们所见,在这个结构中,有一些核心数据集,还有一些其他数据集与核心数据集相关联。

这就是我们如何理解它的方法:

  • 主要医院被定义

  • 它有许多子医院

  • 子医院位于印度和美国

  • 印度医院使用患者数据

  • 美国医院使用患者数据

  • 患者存储与主医院相连

  • 员工属于医院,并与其他组织相连

在这个结构中,根据我们的设计,数据被表示为元素的网络。

关系型数据库架构

该系统在 20 世纪 80 年代初再次在 IBM 开发,被认为是迄今为止最著名的数据库系统之一。采用这种风格的软件的一些显著例子是 Oracle 和 MySQL。

在这个模型中,数据以记录的形式存储,每个记录又包含几个属性。所有记录集合都存储在表中。数据属性在表之间存在关系。相关表的集合存储在数据库中。

让我们看看 RDBMS 表的典型示例:

网络结构图

我们正在定义以下类型的表和关系

员工

  • 该表包含所有员工记录

  • 每条记录由以下定义:

    • 员工唯一标识符

    • 员工姓名

    • 员工出生日期

    • 员工地址

    • 员工电话

    • 员工手机

设备

  • 该表包含所有由员工拥有的设备

  • 每个所有权记录都由以下定义:

    • 设备所有权标识符

    • 设备型号

    • 设备制造商

    • 设备所有权日期

    • 设备唯一编号

    • 员工 ID

部门名称

包含组织中所有部门的表:

  • 唯一部门 ID

  • 唯一部门名称

部门和员工映射表

这是一个特殊的表,只包含部门和员工之间的唯一标识符关系:

  • 唯一部门 ID

  • 唯一员工 ID

Hadoop 数据架构

到目前为止,我们已经探讨了企业使用过的几种数据架构类型。在本节中,我们将了解如何在 Hadoop 中构建数据架构。

简单介绍一下,Hadoop 有多个组件:

  • 数据

  • 数据管理

  • 在数据上运行作业的平台

数据层

这是所有数据以文件形式存储的层。这些文件由 Hadoop 系统内部分割成多个部分,并在服务器之间复制以提高可用性。

由于我们正在谈论以文件形式存储的数据,因此了解这些文件的组织方式对于更好的治理非常重要。

下一个图表显示了数据如何在 Hadoop 存储层之一中组织。数据的内容可以是任何形式,因为 Hadoop 不强制它们具有特定的结构。因此,我们可以安全地将蓝光™电影、CSV逗号分隔值)文件、AVRO 编码文件等存储在这个数据层中。

你可能想知道为什么我们不在这里使用“HDFS”(Hadoop 分布式文件系统)这个词。这是因为 Hadoop 被设计为可以在任何分布式文件系统之上运行。

组件图

数据管理层

这一层负责跟踪给定文件或路径(以服务器、偏移量等术语)的数据存储位置。由于这只是一个账务层,因此确保这一层的内含物以高可靠性和持久性得到保护非常重要。这一层数据的任何损坏都可能导致整个数据文件永远丢失。

在 Hadoop 术语中,这也被称为NameNode

作业执行层

一旦我们解决了数据问题,接下来就是读取和写入数据的程序。当我们谈论单个服务器或笔记本电脑上的数据时,我们很清楚数据的位置,因此我们可以编写程序将数据读取和写入相应的位置。

类似地,Hadoop 存储层使得应用程序在计算过程中将文件路径提供给存储以读取和写入数据变得非常容易。这对编程社区来说是一个很大的胜利,因为他们不需要担心数据在分布式 Hadoop 集群中物理存储的底层语义。

由于 Hadoop 推广了“计算靠近数据”模型,这提供了非常高的性能和吞吐量,因此运行的程序可以由 Hadoop 引擎在集群中数据所在的位置进行调度和执行。整个数据传输和软件执行移动都由 Hadoop 负责处理。

因此,Hadoop 的最终用户将系统视为一个具有巨大计算能力和存储能力的简单系统。这种抽象满足了所有人的需求,并已成为今天大数据计算的标准。

摘要

在本章中,我们看到了许多组织如何采用数据仓库来存储、处理和访问他们拥有的大量数据。我们学习了数据架构原则、它们的治理和安全。在下一章中,我们将探讨数据预处理的一些概念。

第二章:Hadoop 生命周期管理

在本章中,我们将了解以下主题:

  • 数据清洗

  • 数据掩码

  • 数据安全

数据清洗

如果你有一些处理某种类型数据的经验,你将记得大多数时候数据需要预处理,这样我们才能将其作为更大分析的一部分进一步使用。这个过程被称为数据清洗

让我们看看这个过程中的典型流程是什么样的:

  • 数据获取

  • 数据结构分析

  • 信息提取

  • 无用数据移除

  • 数据转换

  • 数据标准化

让我们尝试详细了解这些内容。

数据获取

虽然这不是数据清洗的一部分,但这个阶段涉及从某处获取数据的过程。通常,所有数据都是在中央位置生成和存储的,或者位于某些共享存储上的文件中。

理解这一步有助于我们构建接口或使用现有的库从获取的数据源位置拉取数据。

数据结构分析

数据获取后,我们必须理解数据的结构。记住,我们得到的数据可以是以下任何一种形式:

  • 文本数据:

    • 结构化数据

    • 非结构化数据

  • 二进制数据

这是我们需要某些工具来帮助我们理解数据结构的地方。

一旦我们对所处理的数据有了彻底的了解,下一个任务是理解我们需要从这个结构中提取的各个部分。有时,根据我们处理的数据的复杂性和大小,我们可能需要时间来真正找到并提取我们所需的信息。

一旦我们知道了我们要找什么,并且对数据的结构有了坚实的理解,我们就能更容易地提出简单的算法来从输入数据中提取所需的信息。

信息提取

在这个阶段,我们感兴趣的是从输入数据中提取必要的细节。在前一个阶段,我们已经确定了对我们感兴趣的有必要部分。这里我们可以采用以下技术进行信息提取:

  • 识别并定位文本所在的位置

    • 分析并找出最佳的信息提取方法:

    • 分词并提取信息

    • 前往偏移量并提取信息

    • 基于正则表达式的信息提取

    • 基于复杂算法的信息提取

根据数据的复杂性,我们可能需要采用上述一种或多种技术来从目标数据中提取信息。

无用数据移除

这个阶段可以在信息提取步骤之前或之后发生。这取决于哪个更容易(缩短文本或提取信息)。这是分析师可以做出的设计选择。

在这个阶段,我们正在从信息或输入数据中删除不需要的数据,以便数据进一步提炼,并可以轻松地满足我们的业务需求。

数据转换

这也是一个非常重要的阶段,我们强制执行企业定义的标准来定义最终的数据输出。例如,一个组织可以建议所有国家代码应采用 ISO 3166-1 alpha-2 格式。为了遵守这一标准,我们可能需要对包含国家全名的输入数据进行转换。因此,需要进行映射和转换。

可以对输入数据进行许多其他转换,以便以定义良好的形式和符合组织标准的方式,使组织中的任何人都能够消费最终数据。

此步骤还重视拥有企业级标准以提高协作。

数据标准化

一旦信息提取完成并且进行了任何必要的清理,我们需要决定如何保存此过程的结果。通常,我们可以使用简单的CSV逗号分隔值)格式来保存这些数据。如果我们处理的是复杂输出格式,我们可以选择XML可扩展标记语言)或JSONJavaScript 对象表示法)格式。

这些格式非常标准化,我们今天拥有的几乎所有技术都能非常容易地理解这些格式。但为了使事情简单起见,最好从 CSV 格式开始。

数据掩码

处理客户数据的业务必须确保这些客户的PII个人可识别信息)在整个数据管道中不会自由流动。这一标准不仅适用于客户数据,也适用于任何其他被视为机密的数据,如 GDPR、SOX 等标准。为了确保我们保护客户、员工、承包商和供应商的隐私,我们需要采取必要的预防措施,确保当数据通过多个管道时,数据使用者只能看到匿名数据。我们进行的匿名化程度取决于公司遵守的标准以及所在国家的现行标准。

因此,数据掩码可以称为隐藏/转换原始数据部分为其他数据的过程,同时不丢失意义或上下文。

在本节中,我们将了解可用于完成此任务的各种技术:

  • 替换:

    • 静态

    • 动态:

      • 加密

      • 哈希

  • 隐藏

  • 删除

  • 截断

  • 方差

  • 洗牌

替换

替换是将数据部分替换为计算数据的过程。它可以数学上定义为:

图片

其中 x 是源,y 是该函数的输出。

为了选择正确的替换机制,我们需要了解这些数据将如何被使用,目标受众以及数据流环境。让我们看看各种可用的替换机制。

静态

在此方法中,我们有一个查找表;它包含给定输入集的所有可能的替换。此查找表可以可视化如下:

源文本 (y) 替换文本 (y)
史蒂夫·乔布斯 AAPL-1
123456789
网球 板球

此表说明了如何构建查找表以替换源文本为不同的文本。当有预定义数量的替换可用时,此方法可扩展性良好。

这种基于查找表替换的另一个例子是我们遵循国家代码的命名标准,例如,ISO-8661:

源文本 (x) 替换文本 (y)
埃及 EG
印度 IN
圣文森特和格林纳丁斯 VN
英国 GB
美利坚合众国 US

动态

当有大量可能性并且我们想使用某些算法更改数据时,这些替换技术是有用的。这些方法可以分为两种类型。

加密

这是通过使用某种形式的密钥将给定文本转换为其他形式的过程。这些是数学上定义的函数:

如您所见,这些函数接受输入和密钥,并生成可以使用相同密钥和输出解密的数据:

VSNN4EtlgZi3/

如果我们仔细观察,这里起着重要作用的是密钥。在密码学中,根据这个密钥有两种类型的算法可用。这些算法的使用取决于具体情况和密钥传输的挑战。

不深入探讨密码学,让我们尝试理解这些方法:

  • 对称密钥加密

  • 非对称密钥加密

这两种方法的基本区别在于,在前一种中,我们使用相同的密钥进行加密和解密。但在后一种中,我们使用两个不同的密钥进行加密和解密。

让我们看看几个对称密钥加密的实际例子:

算法 输入数据 输出数据 方法
ROT13 hello uryyb 加密
uryyb hello 解密
DES hello yOYffF4rl8lxCQ4HS2fpMg== 加密(密钥是 hello
yOYffF4rl8lxCQ4HS2fpMg== hello 解密(密钥是 hello

| RIJNDAEL-256 | hello | v8QbYPszQX/TFeYKbSfPL/ rNJDywBIQKtxzOzWhBm16/

iPqJZpCiXXzDu0sKmKSl6IxbBKhYw== | 加密(密钥是 hello)|

| | v8QbYPszQX/TFeYKbSfPL/ rNJDywBIQKtxzOzWhBm16/

VSNN4EtlgZi3/

iPqJZpCiXXzDu0sKmKSl6IxbBKhYw== | hello | 加密(密钥是 hello)|

如您所见,生成的数据在复杂性和长度上因我们使用的加密算法而异。它还取决于用于加密的秘密密钥。

加密提出了更多计算要求和存储空间的问题。如果我们想将加密作为屏蔽过程中的方法之一,我们需要相应地规划我们的系统。

哈希

这也是一种基于密码学的技术,其中原始数据被转换为不可逆的形式。让我们看看这个数学形式:

在这里,与加密的情况不同,我们不能使用输出发现输入是什么。

让我们通过一些例子来更好地理解这一点:

输入 输出 方法
10-point 7d862a9dc7b743737e39dd0ea3522e9f MD5
10th 8d9407b7f819b7f25b9cfab0fe20d5b3 MD5
10-point c10154e1bdb6ea88e5c424ee63185d2c1541efe1bc3d4656a4c3c99122ba9256 SHA256
10th 5b6e8e1fcd052d6a73f3f0f99ced4bd54b5b22fd4f13892eaa3013ca65f4e2b5 SHA256

我们可以看到,根据我们使用的加密算法,输出大小会有所不同。另一个需要注意的事情是,给定的哈希函数无论输入大小如何,都会产生相同大小的输出。

隐藏

在这种方法中,数据被认为甚至对原始所有者来说都过于敏感,以至于不能透露。因此,为了保护数据的机密性,某些文本部分被预定义的字符(例如 X 或任何其他字符)屏蔽,这样只有了解这些片段的人才能提取必要的信息。

示例:信用卡信息被认为是高度机密的,绝不应该透露给任何人。如果您在亚马逊等网站上购买过在线商品,您就会看到您的完整信用卡信息不会显示;只显示最后四位数字。由于我是这种信用卡的真正持卡人,我可以轻松地识别它并继续交易。

同样,当需要让数据的一部分被分析师看到时,屏蔽重要的数据部分很重要,这样最终用户就不会看到完整的画面,但仍然可以同时使用这些数据来进行他们正在进行的任何分析。

让我们通过一些例子来更好地理解这一点:

数据类型 输入 输出 网络
信用卡 4485 4769 3682 9843 4485 XXXX XXXX 9843 维萨卡
信用卡 5402 1324 5087 3314 5402 XXXX XXXX 3314 万事达卡
信用卡 3772 951960 72673 3772 XXXXXX 72673 美国运通卡

在前面的例子中,这些数字遵循一个预定义的算法和大小。因此,在固定位置屏蔽数字的简单技术可以更有效。

让我们再举一个隐藏电子邮件地址部分,这些部分在大小和复杂性上都有所不同的例子。在这种情况下,我们必须遵循不同的技术来隐藏字符,以防止泄露完整信息:

数据类型 输入 输出 方法
电子邮件 hello@world.com h.l.o@w.r.d.com 偶数隐藏
simple@book.com .i.p.e@.o.k.c.m 奇数隐藏
something@something.com s...th.ng@..me...com 复杂隐藏

这些技术可以非常简单:

  • 偶数隐藏:在这个技术中,我们隐藏所有位于偶数位置的字符

  • 奇数隐藏:我们在输入数据中隐藏每个奇数字符

  • 复杂隐藏:在这个技术中,我们使用自然语言处理(NLP)来理解我们正在处理的数据,然后尝试应用一个不会泄露太多信息,从而允许任何有智能的人解码的算法

删除

如其名所示,当应用于输入数据时,这会导致数据丢失。根据我们处理的数据的重要性,我们需要应用这种技术。这种技术的典型例子是为列中的所有记录设置 NULL 值。由于这些空数据无法用来推断任何有意义的信息,这种技术有助于确保机密数据不会被发送到数据处理的其他阶段。

让我们来看几个删除的例子:

输入数据 输出数据 被删除的内容
NULL 每月赚取 1000 INR 拉维每月赚取 NULL 薪水和姓名
NULL 手机号码是 0123456789 拉维的手机号码是 NULL 手机号码和姓名

从例子中,你可能想知道:为什么我们要使这些值无效?当我们对 PII 不感兴趣,而只对数据库/输入中薪资记录或手机号码记录的总结感兴趣时,这种技术很有用。

这个概念也可以扩展到其他用例。

截断

另一种删除的变体是截断,其中我们将所有输入数据调整为统一的大小。当我们相当确信在管道的进一步处理中可以接受信息损失时,这很有用。

这也可以是一种智能截断,其中我们了解我们正在处理的数据。让我们看看以下电子邮件地址的例子:

输入 输出 被截断的内容
alice@localhost.com alice @localhost.com
bob@localhost.com bob @localhost.com
rob@localhost.com rob @localhost.com

从前面的例子中,我们可以看到,所有来自电子邮件的域名部分都被截断,因为它们都属于同一个域名。这种技术节省了存储空间。

变化

这种技术适用于本质上是数字的数据类型。它也可以应用于日期/时间值。

这遵循一种统计方法,我们试图通过算法以+/- X 百分比的因子改变输入数据。X 的值纯粹取决于我们进行的分析,并且不应对理解业务数据产生整体影响。

让我们看看几个例子:

输入数据 输出数据 方法 说明
100 110 固定方差 增加 10%
-100 90 固定方差 减少 10%
1-Jan-2000 1-Feb-2000 固定方差 增加 1 个月
1-Aug-2000 1-Jul-2000 固定方差 减少一个月
100 101 动态方差 增加 1%到 5%或减少 1%到 5%
100 105 动态 增加 1%到 5%或减少 1%到 5%

洗牌

这也被认为是实现数据匿名化的一种标准技术。这个过程在拥有具有多个属性(在数据库术语中称为列)的数据记录时更为适用。在这个技术中,记录中的数据会在某一列周围进行洗牌,以确保记录级别的信息发生变化。但从统计学的角度来看,该列中的数据值保持不变。

示例:在进行一个组织薪资范围的分析时,我们实际上可以对整个薪资列进行洗牌,其中所有员工的薪资都不会与现实相符。但我们可以使用这些数据来分析薪资范围。

在这个情况下,也可以采用复杂的方法,我们可以根据其他字段(如资历、地理位置等)进行洗牌。这种技术的最终目标是保留数据的含义,同时使发现这些属性原始所有者变得不可能。

让我们用一些示例数据来看看:

有五个带有薪资信息的员工样本记录。上面的表格包含原始薪资详情,下面的表格包含洗牌后的薪资记录。仔细观察数据,你就会明白。记住,在洗牌时,可以应用随机算法来增加发现真相的复杂性。

数据安全

在做出非常关键的决定时,数据已经成为企业的重要资产。由于生成和使用这些数据的基础设施复杂性很高,因此对数据的访问模式进行一些控制非常重要。在 Hadoop 生态系统中,我们有 Apache Ranger,这是另一个开源项目,有助于管理大数据的安全性。

什么是 Apache Ranger?

Apache Ranger 是一个应用程序,它使数据架构师能够在大数据生态系统中实施安全策略。这个项目的目标是提供一个统一的方式,让所有 Hadoop 应用程序都能遵守定义的安全指南。

这里是 Apache Ranger 的一些特性:

  • 集中管理

  • 细粒度授权

  • 标准化授权

  • 多种授权方法

  • 集中审计

使用 Ambari 安装 Apache Ranger

在本节中,我们将使用 Apache Ambari 安装 Ranger。本节假设已经有一个运行的 Ambari 实例。

Ambari 管理 UI

打开运行在主节点上的 Ambari Web 界面;然后点击“添加服务”,如图所示:

图片

这将打开一个模态窗口,“添加服务向导”,它将引导我们完成 Apache Ambari 的完整安装。

添加服务

一旦模态窗口可见,从列表中选择 Apache Ranger 服务,并在屏幕上点击“下一步”。

这在以下截图中显示:

图片

服务放置

一旦选择了服务,我们就会在 UI 中看到下一步,我们需要选择该服务将要安装和运行的服务器。

我已选择 node-3 作为 Ranger(见绿色标签):

图片

屏幕截图显示如何选择将要安装和运行此服务的服务器

然后,选择页面底部的“下一步”。

服务客户端放置

在此步骤中,我们可以选择此服务的客户端可以安装的位置。使用复选框标记您的偏好。

它们看起来像这样:

图片

在做出选择后,请点击“下一步”。

主节点上的数据库创建

我们已在主节点上安装了 MySQL 数据库服务器。在继续到 Ambari 向导的下一步之前,我们必须创建一个新的数据库并分配一些权限:

图片

我们还必须使用ambari-server setup命令注册 JDBC 驱动程序:

bash-$ sudo ambari-server setup --jdbc-db=mysql --jdbc-driver=/usr/share/java/mysql-connector-java.jar
Using python /usr/bin/python
Setup ambari-server
Copying /usr/share/java/mysql-connector-java.jar to /var/lib/ambari-server/resources
If you are updating existing jdbc driver jar for mysql with mysql-connector-java.jar. Please remove the old driver jar, from all hosts. Restarting services that need the driver, will automatically copy the new jar to the hosts.
JDBC driver was successfully initialized.
Ambari Server 'setup' completed successfully.

在此步骤之后,我们可以返回到 Ambari 向导。

Ranger 数据库配置

在向导中,我们将被提示输入数据库名称、用户名和密码。请根据我们在上一步中做出的选择填写它们:

图片

一旦添加了设置,请点击“测试连接”。这将节省很多时间。

如果有任何错误,请返回上一步;检查是否有拼写错误,并重新运行那些步骤。

完成更改后,请点击“下一步”。

配置更改

由于我们正在添加 Ranger 服务,Ambari 显示了 Ranger 正确工作所需的配置更改列表。大多数情况下,请保留这些为默认值。

这些更改看起来如下截图所示。一旦更改看起来不错,请点击“确定”继续:

图片

配置审查

在此步骤中,我们看到了在向导中迄今为止所做的更改列表,并显示了打印更改摘要和部署 Ranger 的选择。

只有当我们点击“部署”时,Ranger 软件才会被安装。在此之前,所有内容都保存在浏览器缓存中。

屏幕看起来像这样:

图片

部署进度

一旦 Ranger 的安装开始,它应该看起来像截图中的那样。不应该有任何失败,因为我们已经正确设置了所有配置。如果有任何失败,请检查日志并通过点击“后退”按钮检查配置:

图片

应用程序重启

一旦部署完成,我们需要重启所有受影响的 Hadoop 组件,如下面的截图所示:

图片

一旦所有组件都已重启,Ambari 仪表板看起来相当健康,我们就完成了 Apache Ranger 的安装。

在下一步中,我们将看到如何使用 Apache Ranger 来处理我们的数据安全。

Apache Ranger 用户指南

一旦 Apache Ranger 的部署完成,我们可以使用 Apache Ranger 提供的 Web 界面管理整个 Hadoop 基础设施的安全。

登录到 UI

如果您没有更改默认设置,Ranger 默认在非 SSL 模式下运行在端口6080。在安装了它的服务器上打开端口6080的 Web 浏览器(http://<服务器 IP>:6080),您将看到一个类似这样的屏幕:

图片

使用默认用户名admin和密码admin登录(请登录后首次更改密码,出于安全原因)。

一旦登录成功,我们将被带到访问管理器部分。

访问管理器

访问管理器允许我们根据服务和标签定义策略。此截图显示了默认的服务列表和配置的策略:

图片

如您所见,由于它们已经在 Ambari 设置中安装,因此已经为 HDFS 服务和 KAFKA 服务定义了策略。

当我们想要定义一个新的服务时,我们可以点击加号图标并定义服务详情。

服务详情

在我们开始为服务定义授权规则之前,我们需要定义一个服务,然后向服务添加授权策略。这些是从 UI 定义服务所需的必填属性:

UI 元素名称 描述
服务名称 在代理配置中定义的服务名称
用户名 服务用户的名称
密码 服务用户的密码
Namenode URL Namenode 的 URL

通过点击应用程序下方的加号图标(例如,HDFSKafka等)可以定义新的服务。

之后,服务定义屏幕看起来像这样:

图片

定义新服务后服务定义屏幕的截图

我们需要填写服务定义所需的所有必要值并保存。稍后,我们需要向此服务添加策略以进行访问控制和审计。

HDFS 的策略定义和审计

对于 Ranger 中的每个服务,我们都可以将不同的策略与服务中的资源关联起来。在 HDFS 的情况下,资源将是文件/目录路径。

在本节中,我们将为三个用户定义一个新的针对 HDFS 路径名为 projects 的策略:hdfs-alicehdfs-bobhdfs-tom。其中只有 hdfs-alice 被允许所有权限,其余用户只有读取权限。

一旦策略实施,我们将看到 Ranger 如何执行访问限制。

让我们看看策略创建的屏幕:

图片

屏幕截图显示 Ranger 如何执行访问限制

一旦我们点击添加按钮,此策略就会被注册并添加到当前服务下。

现在,让我们回到 Unix 终端,看看 Ranger 如何执行策略。

此屏幕显示 hdfshdfs-alice 用户被允许创建 /projects/projects/1 目录,但对于 hdfs-tom 来说这是被拒绝的:

图片

Apache Ranger 在网页界面中还有一个审计部分,我们可以看到这些访问模式。

此屏幕显示 hdfs-tom 被拒绝访问,而 hdfs-alice 被策略允许访问:

图片

屏幕截图显示策略拒绝 hdfs-tom 访问并允许 hdfs-alice 访问

就像这样,我们可以定义自己的策略并自定义 hdfs 应该允许/拒绝访问哪些资源。

Ranger 的强大和灵活性来自于其可配置性。访问控制要发挥重要作用,无需任何配置文件和应用重启。

摘要

在本章中,我们学习了关于不同数据生命周期阶段的内容,包括数据创建、共享、维护、归档、保留和删除。

本章详细介绍了大数据的管理方法,考虑到它要么是非结构化的,要么是半结构化的,并且具有快速到达率和大量数据。

随着业务组织中生成和使用数据的基础设施复杂性急剧增加,确保数据安全变得至关重要。本章进一步介绍了数据安全工具,如 Apache Ranger,以及帮助我们了解如何控制数据访问模式的模式。

在下一章中,我们将探讨 Hadoop 的安装、其架构和关键组件。

第三章:Hadoop 设计考虑因素

大数据并不一定意味着大量数据。如果数据集很小,分析它非常容易。我们可以将其加载到 Excel 电子表格中,并进行所需的计算。但是,随着数据量的增加,我们必须找到其他替代方案来处理它。我们可能需要将其加载到 RDMBS 表中,并运行 SQL 查询以在给定的结构上找到趋势和模式。此外,如果数据集格式变为类似电子邮件的格式,那么将其加载到 RDBMS 中将是一个巨大的挑战。更复杂的是,如果数据速度变为类似实时,使用传统的基于 RDBMS 的工具分析给定的数据集几乎是不可能的。在现代社会,大数据这个术语可以用五个最著名的 V 来表达。以下是对每个 V 的简要说明。

图片

在本章中,我们将涵盖以下主题:

  • 数据结构原则

  • 安装 Hadoop 集群

  • 探索 Hadoop 架构

  • 介绍 YARN

  • Hadoop 集群组成

  • Hadoop 文件格式

理解数据结构原则

让我们回顾一些重要的数据架构原则:

  • 数据是企业的一项资产:数据具有可衡量的价值。它为企业提供了实际的价值。在现代社会,数据被视为真正的黄金。

  • 数据是全企业共享的:数据只被捕获一次,然后被多次使用和分析。多个用户可以访问相同的数据,用于不同的用例和需求。

  • 数据治理:数据得到治理以确保数据质量。

  • 数据管理:需要管理数据以实现企业目标。

  • 数据访问:所有用户都应有权访问数据。

  • 数据安全:数据应该得到适当的保护和保护。

  • 数据定义:数据中的每个属性都需要在企业范围内保持一致的定义。

既然我们已经了解了大数据及其原理的基础,让我们开始一些实际操作。

安装 Hadoop 集群

需要执行以下步骤来安装 Hadoop 集群。在撰写本书时,Hadoop 版本 2.7.3 是一个稳定的版本。我们将安装它。

  1. 使用以下命令检查 Java 版本:
Java -version
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
You need to have Java 1.6 onwards 
  1. 在所有服务器上创建 Hadoop 用户账户,包括所有 NameNodes 和 DataNodes,可以使用以下命令:
useradd hadoop
passwd hadoop1 

假设我们拥有四台服务器,我们必须使用这四台服务器创建一个 Hadoop 集群。这四台服务器的 IP 地址如下:192.168.11.1192.168.11.2192.168.11.3192.168.11.4。在这四台服务器中,我们将首先使用一台服务器作为主服务器(NameNode),其余所有服务器将用作从服务器(DataNodes)。

  1. 在两个服务器上,NameNode 和 DataNodes,使用以下命令更改 /etc/hosts 文件:
vi /etc/hosts--   
  1. 然后将以下内容添加到所有服务器上的所有文件中:
NameNode 192.168.11.1
DataNode1 192.168.11.2
DataNode2 192.168.11.3
DataNode3 192.168.11.4 
  1. 现在,在 NameNodes 和 DataNodes 上设置 SSH:
su - hadoop
ssh-keygen -t rsa
ssh-copy-id -i ~/.ssh/id_rsa.pub hadoop@namenode
ssh-copy-id -i ~/.ssh/id_rsa.pub hadoop@datanode1
ssh-copy-id -i ~/.ssh/id_rsa.pub hadoop@datanode2
ssh-copy-id -i ~/.ssh/id_rsa.pub hadoop@datanode3
chmod 0600 ~/.ssh/authorized_keys
exit
  1. 在 NameNode 和所有 DataNodes 上下载并安装 Hadoop:
mkdir /opt/hadoop
cd /opt/hadoop
wget http://www-eu.apache.org/dist/hadoop/common/hadoop-2.7.3/hadoop-2.7.3.tar.gz
tar -xvf hadoop-2.7.3.tar.gz 
mv Hadoop-2.7.3 hadoop
chown -R hadoop /opt/hadoop
cd /opt/hadoop/Hadoop

在 NameNode 上配置 Hadoop

登录到 NameNode:

cd /opt/Hadoop/conf

vi core-site.xml  

找到并更改以下属性为这些值:

Filename 属性名称 属性值
core-site.xml fs.default.name hdfs://namenode:9000/
dfs.permissions False
hdfs-site.xml dfs.data.dir /opt/hadoop/hadoop/dfs/namenode/data
dfs.name.dir /opt/hadoop/hadoop/dfs/namenode
dfs.replication 1
mapred-site.xml mapred.job.tracker namenode:9001 
    vi masters
    namenode

    vi slaves
    datanode1
    datanode2
    datanode3

格式化 NameNode

以下代码用于格式化 NameNode:

 cd /opt/Hadoop/Hadoop/bin

    hadoop -namenode  -format 

启动所有服务

我们使用以下代码行启动所有服务:

    ./start-all.sh

关于如何设置 Hadoop 单节点和多节点集群的详细信息,请使用以下链接: hadoop.apache.org/docs/r2.7.0/hadoop-project-dist/hadoop-common/ClusterSetup.html.

探索 HDFS 架构

HDFS 架构基于主从模式。NameNode 是主节点,所有 DataNode 都是从节点。以下是一些关于这两个节点的重要注意事项。

定义 NameNode

NameNode 是 Hadoop 集群中所有 DataNode 的主节点。它仅以树形结构存储文件的元数据。重要的是 NameNode 永远不会存储除元数据以外的任何其他数据。NameNode 以块的形式跟踪写入 DataNode 的所有数据。默认块大小为 256 MB(可配置)。没有 NameNode,DataNode 文件系统上的数据无法读取。元数据存储在 NameNode 上,使用两个文件——文件系统命名空间镜像文件 FSImage 和编辑日志。FSImage 是 NameNode 编辑日志开始时的文件系统快照——自 NameNode 启动以来文件系统的所有更改,当 NameNode 启动时,它读取 FSImage 文件和编辑日志文件。所有事务(编辑)都合并到 FSImage 文件中。FSImage 文件写入磁盘,并创建一个新的空编辑日志文件以记录所有编辑。由于 NameNode 不经常重启,编辑日志文件变得非常大且难以管理。当 NameNode 重启时,由于所有编辑都需要应用到 FSImage 文件,因此重启需要非常长的时间。在 NameNode 崩溃的情况下,编辑日志文件中的所有元数据将不会写入 FSImage 文件,并将丢失。

次要 NameNode

次要 NameNode 的名称令人困惑。它并不充当 NameNode。其主要功能是定期从 NameNode 获取文件系统更改并将其合并到 NameNode 的 FSImage 中。将编辑日志文件更改写入 FSImage 的操作称为提交。定期的提交有助于减少 NameNode 的启动时间。次要 NameNode 也被称为提交节点。

NameNode 安全模式

这是 HDFS 集群的只读模式。客户端不允许对文件系统或块进行任何修改。在启动时,NameNode 自动以安全模式启动,应用对 FSImage 的编辑,自动禁用安全模式,并以正常模式重新启动。

DataNode

DataNodes 是 Hadoop 集群的功臣。它们的主要功能是以块的形式存储和检索数据。它们总是以心跳的形式向 NameNode 报告其状态。这就是 NameNode 如何跟踪任何 DataNodes,无论它们是活着还是死了。DataNodes 保持三个已知块的副本和复制因子。DataNodes 与其他 DataNodes 通信,以复制数据块来维护数据复制。

数据复制

HDFS 架构支持在集群的机器上放置非常大的文件。每个文件都存储为一系列块。为了确保容错性,每个块被复制三次到三台不同的机器上。这被称为复制因子,可以在集群级别或单个文件级别进行更改。是 NameNode 做出所有与块复制相关的决策。NameNode 从每个 DataNode 获取心跳和块报告。心跳确保 DataNode 是活着的。块报告包含一个 DataNode 上所有块的列表。

机架感知

HDFS 块放置将使用机架感知来提高容错性,将一个块副本放置在不同的机架上,如下图所示:

让我们详细理解这个图:

  • 第一个副本被放置在发起请求的 DataNode 所在的同一机架上,例如,机架 1 和 DataNode 1

  • 第二个副本被放置在另一个机架上的任意 DataNode 上,例如,机架 2,DataNode 2

  • 第三个副本被放置在相同机架上的任意 DataNode 上,例如,机架 2,DataNode 3

可以使用 Unix shell、Java 或 Python 开发一个自定义的机架拓扑脚本,该脚本包含一个选择适当 DataNodes 的算法。它可以通过更改Core-site.xml文件中的topology.script.file.name参数在集群中激活。

HDFS WebUI

下表显示了 HDFS WebUI 中的服务:

服务 协议 端口 URL
NameNode WebUI HTTP 50070 http://namenode:50070/
DataNode WebUI HTTP 50075 http://datanode:50075/
Secondary NameNode HTTP 50090 http://Snamenode:50090/

介绍 YARN

YARN(Yet Another Resource Negotiator)将资源管理、调度和处理组件分离。它有助于实现集群资源的 100%利用率。YARN 根据 Hadoop 调度策略管理集群的 CPU 和内存。YARN 支持任何类型的应用程序,而不仅限于 MapReduce。它支持用任何类型的语言编写的应用程序,前提是可以在 Hadoop 集群上安装二进制文件。

YARN 架构

在接下来的几节中,我们将详细理解 YARN 架构。

资源管理器

资源管理器负责跟踪集群中的资源并调度应用程序。资源管理器有两个主要组件:调度器和应用程序管理器。

节点管理器

节点管理器负责在节点上启动和管理容器。容器执行应用程序主指定的任务。它作为资源管理器的从属。每个节点管理器跟踪其 SlaveNode 上可用的数据处理资源,并向资源管理器发送定期报告。Hadoop 集群中的处理资源以称为容器的字节大小块消耗。

YARN 配置

您可以执行以下步骤来配置 YARN:

  1. 启动 Hadoop NameNode、辅助 NameNode 和 DataNode

  2. 修改 yarn-env.sh

根据您的 Hadoop 安装查找相应的 XML 文件。

  1. YARN_CONF_DIR 的定义下添加以下内容:
export HADOOP_CONF_DIR="${HADOOP_CONF_DIR:-$YARN_HOME/etc/hadoop}"
export HADOOP_COMMON_HOME="${HADOOP_COMMON_HOME:-$YARN_HOME}"
export HADOOP_HDFS_HOME="${HADOOP_HDFS_HOME:-$YARN_HOME}"  
  1. 修改 yarn-site.xml
<?xml version="1.0"?>
<configuration>
  <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>
</configuration> 
  1. 修改 mapred-site.xml
<?xml version="1.0"?>
<?xml-stylesheet href="configuration.xsl"?>
<configuration>
  <property>
    <name>mapreduce.framework.name </name>
    <value>yarn</value>
  </property>
</configuration>  
  1. 启动 YARN 服务:
yarn resourcemanager
yarn nodemanager 

配置 HDFS 高可用性

让我们看看 Hadoop 随时间带来的变化。

在 Hadoop 1.x 期间

Hadoop 1.x 从单个 NameNode 架构开始。所有 DataNode 都会将它们的块报告发送到该单个 NameNode。架构中有一个辅助 NameNode,但它的唯一责任是合并所有对 FSImage 的编辑。在这种架构中,NameNode 成为了单点故障(SPOF)。由于它拥有 Hadoop 集群中所有 DataNode 的元数据,在 NameNode 崩溃的情况下,Hadoop 集群将无法使用,直到 NameNode 重新启动并修复。如果 NameNode 无法恢复,那么所有 DataNode 中的所有数据都将完全丢失。在计划维护时关闭 NameNode,HDFS 将无法用于正常使用。因此,有必要通过频繁备份 NameNode 文件系统来保护现有的 NameNode,以最大限度地减少数据丢失。

从 Hadoop 2.x 及以后版本开始

为了克服 HDFS 高可用性(HA)问题并使 NameNode 成为 SPOF,架构已经改变。新的架构提供了在同一集群中运行两个冗余 NameNode 的能力,采用活动/被动配置和热备用。这允许在机器崩溃的情况下快速切换到新的 NameNode,或者为了计划维护而进行的优雅的由管理员启动的故障转移。以下提供了两种 HDFS HA 架构选项:

  • 使用共享存储

  • 使用法定多数日志管理器

使用 NFS 的 HDFS HA 集群

以下图展示了使用 NFS 作为共享存储的 HDFS HA 集群,该存储是 NameNodes 架构所需的:

图片

重要架构要点

在使用共享存储架构的 HDFS 高可用性(HA)中,以下是一些需要记住的重要要点:

  • 在集群中,有两台独立的机器:活动状态 NameNode 和备用状态 NameNode。

  • 在任何给定时间点,只有一个 NameNode 处于活动状态,另一个处于备用状态。

  • 活动 NameNode 管理集群中所有客户端 DataNode 的请求,而备用节点保持为从属状态。

  • 所有 DataNode 都配置为向活动 NameNode 和备用 NameNode 发送其块报告和心跳。

  • 备用 NameNode 保持其状态与活动 NameNode 同步。

  • 活动节点和备用节点都可以访问共享存储设备上的文件系统(例如,从 NAS 的 NFS 挂载)

  • 当客户端进行任何文件系统更改时,活动 NameNode 会将相应的更改(编辑)应用到位于网络共享目录上的编辑日志文件中。

  • 备用 NameNode 对其自己的命名空间进行所有相应的更改。这样,它就与活动 NameNode 保持同步。

  • 在活动 NameNode 不可用的情况下,备用 NameNode 确保它从共享网络目录吸收所有更改(编辑),并将其提升为活动 NameNode。

  • Hadoop 管理员应将隔离方法应用于共享存储,以避免同时使两个 NameNode 处于活动状态的场景。在故障转移的情况下,隔离方法切断对先前活动 NameNode 的访问,以确保对共享存储的更改顺利转移到备用 NameNode。之后,备用 NameNode 成为活动 NameNode。

配置具有共享存储的 HA NameNode

将以下属性添加到hdfs-site.xml中:

属性
dfs.nameservices cluster_name
dfs.ha.namenodes.cluster_name NN1, NN2
dfs.namenode.rpc-address.cluster_name.NN1 machine1:8020
dfs.namenode.rpc-address.cluster_name.NN2 machine2:8020
dfs.namenode.http-address.cluster_name.NN1 machine1:50070
dfs.namenode.http-address.cluster_name.NN2 machine2:50070
dfs.namenode.shared.edits.dir file:///mnt/filer1/dfs/ha-name-dir-shared
dfs.client.failover.proxy.provider.cluster_name org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider
dfs.ha.fencing.methods sshfence
dfs.ha.fencing.ssh.private-key-files /home/myuser/.ssh/id_rsa
dfs.ha.fencing.methods sshfence([[username][:port]])
dfs.ha.fencing.ssh.connect-timeout 30000

将以下属性添加到core-site.xml中:

属性
fs.defaultFS hdfs://cluster_name

使用 Quorum Journal Manager 的 HDFS HA 集群

以下图展示了Quorum Journal ManagerQJM)架构,用于在活动 NameNode 和备用 NameNode 之间共享编辑日志:

重要架构点

以下是关于使用 QJM 架构的 HDFS HA 的一些重要要点:

  • 在集群中,有两个独立的机器——活动状态的 NameNode 和待机状态的 NameNode。

  • 在任何时刻,恰好有一个 NameNode 处于活动状态,另一个处于待机状态。

  • 活动 NameNode 管理集群中所有客户端 DataNode 的请求,而待机节点保持为从属状态。

  • 所有 DataNode 都配置为向活动 NameNode 和待机 NameNode 发送它们的块报告和心跳。

  • 活动和待机 NameNode 通过与一组称为JournalNodesJNs)的独立守护进程通信,保持彼此的同步。

  • 当客户端进行任何文件系统更改时,活动状态的 NameNode 会持久地将修改记录日志记录到大多数 JNs 中。

  • 待机节点通过与 JNs 通信,立即将其更改应用到自己的命名空间中。

  • 在活动 NameNode 不可用的情况下,待机 NameNode 确保从 JNs 吸收所有更改(编辑),并提升自己为活动 NameNode。

  • 为了避免同时使两个 NameNode 处于活动状态的场景,JNs 将只允许一个 NameNode 在某一时刻成为写者。这允许新的活动 NameNode 安全地进行故障转移。

配置使用 QJM 的 HA NameNode

将以下属性添加到hdfs-site.xml中:

属性
dfs.nameservices cluster_name
dfs.ha.namenodes.cluster_name NN1, NN2
dfs.namenode.rpc-address.cluster_name.NN1 machine1:8020
dfs.namenode.rpc-address.cluster_name.NN2 machine2:8020
dfs.namenode.http-address.cluster_name.NN1 machine1:50070
dfs.namenode.http-address.cluster_name.NN2 machine2:50070
dfs.namenode.shared.edits.dir qjournal://node1:8485;node2:8485;node3:8485/cluster_name
dfs.client.failover.proxy.provider.cluster_name org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider
dfs.ha.fencing.methods sshfence
dfs.ha.fencing.ssh.private-key-files /home/myuser/.ssh/id_rsa
dfs.ha.fencing.methods sshfence([[用户名][:端口]])
dfs.ha.fencing.ssh.connect-timeout 30000

将以下属性添加到core-site.xml中:

属性
fs.defaultFS hdfs://cluster_name
dfs.journalnode.edits.dir /path/to/journal/node/local/datat

自动故障转移

非常重要的是要知道上述两种架构仅支持手动故障转移。为了实现自动故障转移,我们必须引入两个额外的组件:一个 ZooKeeper 集群和一个ZKFailoverControllerZKFC)进程,以及更多的配置更改。

重要架构点

  • 每个 NameNode,无论是活动状态还是待机状态,都会运行 ZKFC 进程。

  • NameNode 的状态由 ZKFC 监控和管理。

  • ZKFC 定期 ping 本地 NameNode,以确保 NameNode 是活跃的。如果它没有收到 ping 回复,它将标记该 NameNode 为不健康。

  • 健康的 NameNode 持有一个特殊的锁。如果 NameNode 变得不健康,该锁将自动删除。

  • 如果本地 NameNode 健康且 ZKFC 看到锁当前未被任何其他 NameNode 持有,它将尝试获取该锁。如果它成功获取锁,那么它赢得了选举。现在,这个 NameNode 负责运行故障转移,使其本地 NameNode 激活。

配置自动故障转移

将以下属性添加到 hdfs-site.xml 以配置自动故障转移:

属性
dfs.ha.automatic-failover.enabled true
ha.zookeeper.quorum zk1:2181, zk2:2181, zk3:2181

Hadoop 集群组成

如我们所知,一个 Hadoop 集群由主服务器和从服务器组成:主节点——管理基础设施,从节点——分布式数据存储和处理。边缘节点不是 Hadoop 集群的一部分。这台机器用于与 Hadoop 集群交互。用户没有权限直接登录到任何主节点和数据节点,但他们可以登录到边缘节点以在 Hadoop 集群上运行任何作业。没有应用程序数据存储在边缘节点上。数据始终存储在 Hadoop 集群的数据节点上。根据在 Hadoop 集群上运行作业的用户数量,可能会有多个边缘节点。如果硬件足够,始终最好将每个主节点和数据节点托管在不同的机器上。但在典型的 Hadoop 集群中,有三个主节点。

请注意,我们假设在集群中使用 HBase 作为 NoSQL 数据存储。

典型的 Hadoop 集群

Hadoop 集群的组成将如下所示:

以下是一些需要考虑的硬件规格:

  • NameNode 和备用 NameNode。

  • 内存需求取决于要创建的文件和块副本的数量。通常,建议 NameNodes 至少有 64 GB - 96 GB 的内存。

  • NameNodes 需要可靠的存储来托管 FSImage 和编辑日志。建议这些主节点至少拥有 4 TB - 6 TB 的 SAS 存储。为 NameNodes 配置 RAID 5 - 6 存储是一个好主意。如果集群是高可用集群,那么请这样规划您的 Hadoop 集群,即 JNs 应该配置在主节点上。

就处理器而言,建议至少有 2 个运行在 2 GHz 的四核 CPU,以处理主节点的消息流量。

  • 数据节点/从节点每个节点至少应有 64 GB RAM。通常,每个 Hadoop 守护进程(如数据节点、节点管理器 ZooKeeper 等)需要 2 GB - 3 GB 内存;操作系统和其他服务需要 5 GB;每个 MapReduce 作业需要 5 GB - 8 GB。

  • DataNodes 可能配备至少 8 TB - 10 TB 的磁盘存储,使用 7200 RPM SATA 驱动器。硬盘配置应该是Just a Bunch Of DisksJBOD)。

  • 建议所有 DataNodes 至少配备 8 个处理器——2.5 GHz 核心和 24 核心 CPU。

  • 建议每个机架内部具有 1 GbE 到 10 GbE 的网络连接。对于所有从节点,建议 1 GB 网络带宽,对于主节点,建议 10 GB 带宽。

  • 如果您计划在未来扩展您的 Hadoop 集群,您也可以添加额外的机器。

请阅读以下来自 Hortonworks 和 Cloudera 的文章以获取更多信息:

Hadoop 部署最佳实践

以下是一些在 Hadoop 部署中应遵循的最佳实践:

  • 从小开始:与其他软件项目一样,Hadoop 的实施也涉及风险和成本。最好设置一个由四个节点组成的小型 Hadoop 集群。这个小集群可以作为一个概念验证POC)。在使用任何 Hadoop 组件之前,它可以添加到现有的 Hadoop POC 集群中作为技术验证POT)。这允许基础设施和开发团队了解大数据项目需求。在 POC 和 POT 成功完成后,可以添加额外的节点到现有集群。

  • Hadoop 集群监控:为了了解集群的健康状况,需要对 NameNode 和所有 DataNode 进行适当的监控。这有助于在节点出现问题时采取纠正措施。如果某个服务崩溃,及时的行动可以帮助避免未来出现大问题。设置 Gangalia 和 Nagios 是配置警报和监控的流行选择。在 Hortonworks 集群的情况下,Ambari 监控,以及 Cloudera 集群,Cloudera (CDH) 管理器监控可以是一个简单的设置。

  • 自动化部署:使用 Puppet 或 Chef 等工具对于 Hadoop 部署至关重要。使用自动化工具而不是手动部署来部署 Hadoop 集群变得超级简单和高效。重视使用可用的工具/组件进行数据分析和处理。在解决问题时,优先考虑使用 Hive 或 Pig 脚本而不是编写重量级的自定义 MapReduce 代码。目标应该是开发更少,分析更多。

  • 高可用性(HA)的实施:在决定 HA 基础设施和架构时,应仔细考虑任何需求增加和数据增长。在任何故障或崩溃发生时,系统应能够自我恢复或故障转移到另一个数据中心/站点。

  • 安全:需要通过创建用户和组,并将用户映射到组中来保护数据。设置适当的权限并强制执行强密码应该锁定每个用户组。

  • 数据保护:在将敏感数据移动到 Hadoop 集群之前,识别敏感数据至关重要。了解隐私政策和政府法规对于更好地识别和缓解合规风险暴露至关重要。

Hadoop 文件格式

在 Hadoop 中,有许多文件格式可供选择。用户可以根据用例选择任何格式。每种格式在存储和性能方面都有特殊功能。让我们详细讨论每种文件格式。

文本/CSV 文件

文本和 CSV 文件在 Hadoop 数据处理算法中非常常见。文件中的每一行都被视为一个新的记录。通常,每一行以 n 字符结束。这些文件不支持列标题。因此,在处理时,总是需要额外的代码行来删除列标题。CSV 文件通常使用 GZIP 编解码器进行压缩,因为它们不支持块级压缩;这会增加更多的处理成本。不用说,它们也不支持模式演变。

JSON

JSON 格式在所有现代编程语言中变得越来越受欢迎。这些文件是名称/值对的集合。JSON 格式通常用于数据交换应用程序,并被视为对象、记录、结构或数组。这些文件是文本文件,并支持模式演变。从 JSON 文件中添加或删除属性非常容易。与文本/CSV 文件一样,JSON 文件不支持块级压缩。

序列文件

序列文件是一个由二进制键/值对组成的平面文件。它们在 MapReduce (wiki.apache.org/hadoop/MapReduce) 作为输入/输出格式中被广泛使用。它们主要用于 MapReduce 作业序列中的中间数据存储。序列文件作为小文件的容器工作得很好。如果 HDFS 中有太多小文件,它们可以被打包成一个序列文件,以提高文件处理效率。序列文件有三种格式:未压缩、记录压缩和块压缩键/值记录。序列文件支持块级压缩,但不支持模式演变。

Avro

Avro 是 Hadoop 社区中广泛使用的文件类型。它之所以受欢迎,是因为它有助于模式演变。它包含以二进制格式序列化的数据。Avro 文件是可分割的,并支持块压缩。它包含数据和元数据。它使用一个单独的 JSON 文件来定义模式格式。当 Avro 数据存储在文件中时,其模式也会与其一起存储,以便文件可以在以后由任何程序处理。如果读取数据的程序期望不同的模式,这可以很容易地解决,因为两种模式都存在。

Parquet

Parquet 以扁平的列式格式存储嵌套数据结构。与任何行级文件格式相比,Parquet 在存储和性能方面更高效。Parquet 以列导向的方式存储二进制数据。在 Parquet 格式中,新列被添加到结构的末尾。Cloudera 主要支持这种格式用于 Impala 实现,但最近正迅速变得流行。这种格式适合 SQL 查询,因为它从具有许多列的宽表中读取特定列,从而只读取选择性列以减少 I/O 成本。

ORC

ORC 文件是优化的记录列式文件格式,是 RC 文件的扩展版本。这些文件非常适合压缩,并且最适合 Hive SQL 性能,当 Hive 读取、写入和处理数据以减少访问时间和存储空间时。这些文件不支持真正的模式演变。它们主要得到 Hortonworks 的支持,并且不适合 Impala SQL 处理。

哪种文件格式更好?

答案是:这取决于您的用例。通常,选择文件格式的标准基于查询读取和查询写入性能。此外,这也取决于您正在使用的 Hadoop 发行版。使用 Hortonworks 发行版时,ORC 文件格式是 Hive 和 Tez 的最佳选择,而对于 Cloudera Impala 实现,建议使用 parquet 文件格式。对于涉及模式演变的用例,Avro 文件格式最为合适。如果您想使用 Sqoop 从 RDBMS 导入数据,text/CSV 文件格式是更好的选择。对于存储映射中间输出,序列文件是最终的选择。

摘要

在本章中,主要目标是了解各种 Hadoop 设计替代方案。当我们谈到 Hadoop 集群及其在典型生产环境中的最佳部署实践时,我们学到了很多。我们从对 Hadoop 的基本理解开始,然后继续到 Hadoop 配置、安装和 HDFS 架构。我们还学习了实现 HDFS 高可用性的各种技术。我们还研究了 YARN 架构。最后,我们探讨了各种文件格式以及如何根据您的用例选择一个。

在下一章中,我们将了解如何将数据导入到一个新创建的 Hadoop 集群中。

第四章:数据移动技术

在上一章中,我们学习了如何创建和配置 Hadoop 集群、HDFS 架构、各种文件格式以及 Hadoop 集群的最佳实践。我们还学习了 Hadoop 的高可用性技术。

由于我们现在已经知道如何创建和配置一个 Hadoop 集群,在本章中,我们将学习各种数据导入 Hadoop 集群的技术。我们了解 Hadoop 的优势,但现在,我们需要在我们的 Hadoop 集群中有数据来利用其真正的力量。

数据导入被认为是 Hadoop 数据生命周期中的第一步。数据可以以批处理或(实时)记录流的形式导入 Hadoop。Hadoop 是一个完整的生态系统,MapReduce 是 Hadoop 的批处理生态系统。

以下图表显示了各种数据导入工具:

在接下来的几节中,我们将详细了解每个工具。

在本章中,我们将介绍以下将数据传输到和从我们的 Hadoop 集群的方法:

  • Apache Sqoop

  • Apache Flume

  • Apache NiFi

  • Apache Kafka Connect

批处理与实时处理

在我们深入探讨不同的数据导入技术之前,让我们讨论一下批处理和实时(流)处理之间的区别。以下解释了这两个生态系统的区别。

批处理

以下要点描述了批处理系统:

  • 在处理大量数据方面非常高效。

  • 所有数据处理步骤(即数据收集、数据导入、数据处理和结果展示)都作为一个单独的批处理作业完成。

  • 吞吐量比延迟更重要。延迟总是超过一分钟。

  • 吞吐量直接取决于数据的大小和可用的计算系统资源。

  • 可用的工具包括 Apache Sqoop、MapReduce 作业、Spark 作业、Hadoop DistCp 工具等。

实时处理

以下要点描述了实时处理与批处理的不同之处:

  • 延迟非常重要,例如,不到一秒

  • 计算相对简单

  • 数据作为独立的单元进行处理

  • 可用的工具包括 Apache Storm、Spark Streaming、Apache Fink、Apache Kafka 等

Apache Sqoop

Apache Sqoop 是一个工具,旨在高效地在 Hadoop 集群和结构化数据存储(如关系数据库)之间传输大量数据。在典型的用例中,例如数据湖,总是需要将基于 RDBMS 的数据仓库存储中的数据导入到 Hadoop 集群中。在数据导入和数据聚合之后,需要将数据导回到 RDBMS。Sqoop 允许轻松地从结构化数据存储(如 RDBMS、企业数据仓库和 NoSQL 系统)导入和导出数据。借助 Sqoop,可以从外部系统将数据配置到 Hadoop 集群中,并在 Hive 和 HBase 中填充表。Sqoop 使用基于连接器的架构,支持提供外部系统连接性的插件。内部,Sqoop 使用 MapReduce 算法导入和导出数据。默认情况下,所有 Sqoop 作业运行四个 map 作业。我们将在接下来的几节中详细看到 Sqoop 的导入和导出功能。

Sqoop 导入

以下图显示了 Sqoop 导入 功能,用于将 RDBMS 表中的数据导入到 Hadoop 集群中:

导入到 HDFS

以下是将数据导入 HDFS 的示例命令:

$sqoop import -connect jdbc:mysql://localhost/dbname -table <table_name>   --username <username> --password >password> -m 4

导入分为两个步骤,如下所述。

  1. Sqoop 扫描数据库并收集要导入的表元数据

  2. Sqoop 提交一个仅 map 作业并使用必要的元数据传输实际数据

导入的数据存储在 HDFS 文件夹中。用户可以指定替代文件夹。导入的数据根据导入的表存储在 HDFS 的一个目录中。与 Sqoop 操作的大部分方面一样,用户可以指定任何替代目录,以便填充文件。您可以通过明确指定字段分隔符和记录终止符字符来轻松覆盖数据复制的格式。用户可以使用不同的格式,如 Avro、ORC、Parquet、序列文件、文本文件等,将文件存储到 HDFS 上,例如,将 MySQL 表导入到 HDFS。以下是将 MySQL 表导入 HDFS 的示例:

$ mysql>  create database  sales;
$ mysql>  use sales;
$  mysql>   create table customer 
 (cust_num int not null,cust_fname  varchar(30),cust_lname varchar     (30),cust_address  varchar (30),cust_city varchar (20),cust_state      varchar (3), cust_zip  varchar (6),primary key (cust_num));
$ ctrl-C   -- to exit from MySQL

在命令提示符下,运行以下 sqoop 命令以导入 MySQL 销售数据库表 customer

$  sqoop import --connect jdbc:mysql://127.0.0.1:3306/sales --username root --password hadoop --table customer  --fields-terminated-by ","  --driver com.mysql.jdbc.Driver --target-dir /user/data/customer

按如下方式验证 HDFS 上的 customer 文件夹:

$ hadoop fs -ls /user/data/customerFound 5 items-rw-r--r--   1 root hdfs          0 2017-04-28 23:35 /user/data/customer/_SUCCESS-rw-r--r--   1 root hdfs        154 2017-04-28 23:35 /user/data/customer/part-m-00000-rw-r--r--   1 root hdfs         95 2017-04-28 23:35 /user/data/customer/part-m-00001-rw-r--r--   1 root hdfs         96 2017-04-28 23:35 /user/data/customer/part-m-00002-rw-r--r--   1 root hdfs        161 2017-04-28 23:35 /user/data/customer/part-m-00003

让我们创建一个外部 Hive 表来验证记录,如下面的片段所示:

$ hive$hive > CREATE EXTERNAL TABLE customer_H 
(cust_num int,cust_fname  string,cust_lname  string,cust_address string,  cust_city  string,cust_state  string,cust_zip   string) 
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','LINES TERMINATED BY 'n'LOCATION '/user/data/customer';
$hive> select * from customer_H; 
Custnum Cust Fname Cust Lname Cust address City State Zip
1 James Butt 6649 N Blue Gum St New Orleans LA 70116
2 Art Venere 8 W Cerritos Ave #54 Bridgeport NJ 8014
3 Lenna Paprocki 639 Main St Anchorage AK 99501
4 Donette Foller 34 Center St Hamilton OH 45011
5 Simona Morasca 3 Mcauley Dr Ashland OH 44805
6 Mitsue Tollner 7 Eads St Chicago IL 60632
7 Leota Dilliard 7 W Jackson Blvd San Jose CA 95111
8 Sage Wieser 5 Boston Ave #88 Sioux Falls SD 57105
9 Kris Marrier 228 Runamuck Pl #2808 Baltimore MD 21224
10 Minna Amigon 2371 Jerrold Ave Kulpsville PA 19443

以下是将 MySQL 表导入 Hive 的示例:

$ sqoop import --connect jdbc:mysql://127.0.0.1:3306/sales --username root --password hadoop --table customer  --driver com.mysql.jdbc.Driver --m 1 --hive-import  --hive-table customor_H

验证 Hive 表:

$hive$use default;
$ show tables;

你会看到customer_H表在默认数据库下创建。如果你想将customer_H表创建在不同的数据库下,例如,销售数据库,你必须事先创建销售数据库。此外,你必须将-hive-table参数更改为--hive-table sales cutomer_H增量加载(仅插入)。这是一个典型仅加载数据源表中发生增量变化的数据加载需求。让我们假设一个新的客户11被插入到源customer MySQL 表中:

insert into customer values (11,'Abel','Maclead','25 E 75th St #69','Los Angeles','CA','90034');

为了仅适应新的记录(即客户 11),我们不得不在我们的原始sqoop命令中添加一些额外的参数。新的sqoop命令如下:

sqoop import --connect jdbc:mysql://127.0.0.1:3306/sales --username root --password hadoop --table customer  --driver com.mysql.jdbc.Driver --incremental append --check-column cust_num 
      --last-value 10 
    --m 1 --split-by cust_state --target-dir /user/data/customer

执行此命令后,Sqoop 将仅获取新行(即cust_num,其值为11):

$hive> select * from  customer_H;
客户编号 客户名 客户姓 客户地址 城市 邮编
1 James Butt 6649 N Blue Gum St New Orleans LA 70116
2 Art Venere 8 W Cerritos Ave #54 Bridgeport NJ 8014
3 Lenna Paprocki 639 Main St Anchorage AK 99501
4 Donette Foller 34 Center St Hamilton OH 45011
5 Simona Morasca 3 Mcauley Dr Ashland OH 44805
6 Mitsue Tollner 7 Eads St Chicago IL 60632
7 Leota Dilliard 7 W Jackson Blvd San Jose CA 95111
8 Sage Wieser 5 Boston Ave #88 Sioux Falls SD 57105
9 Kris Marrier 228 Runamuck Pl #2808 Baltimore MD 21224
10 Minna Amigon 2371 Jerrold Ave Kulpsville PA 19443
11 Abel Maclead 25 E 75th St #69 Los Angeles CA 90034

对于增量加载,我们无法直接使用 Sqoop 导入来更新数据。

请按照给定链接中的步骤进行行级更新:hortonworks.com/blog/four-step-strategy-incremental-updates-hive/ 现在,让我们看看一个将 MySQL 表的子集导入 Hive 的示例。以下命令显示了如何将 MySQL 中customer表的子集仅导入 Hive。例如,我们只导入了State = "OH"的客户数据:

$ sqoop import --connect jdbc:mysql://127.0.0.1:3306/sales --username root --password hadoop --table sales.customer  --driver com.mysql.jdbc.Driver --m 1 --where "city = 'OH' --hive-import  --hive-table customer_H_1$ hive> select * from customer_H_1;
客户编号 客户名 客户姓 客户地址 城市 邮编
4 Donette Foller 34 Center St Hamilton OH 45011
5 Simona Morasca 3 Mcauley Dr Ashland OH 44805

将 MySQL 表导入 HBase 表

以下是一个将数据导入 HBase 表的示例命令:

$sqoop import -connect jdbc:mysql://localhost/dbname -table <table_name>  --username <username> --password >password>  --hive-import -m 4                                    --hbase-create-table --hbase-table <table_name>--column-family <col family name>

Sqoop 将数据导入 HBase 表的列族。数据被转换为 UTF-8 字节格式。

Sqoop 导出

以下图示展示了Sqoop 导出功能,用于从 Hadoop 集群导出数据:

在类似数据湖的使用案例中处理的数据可能需要用于额外的业务功能。Sqoop 可以用于将那些数据从 HDFS 或 Hive 表导出到 RDBMS。在将数据导回到 RDBMS 表的情况下,目标表必须存在于 MySQL 数据库中。HDFS 文件中的行或 Hive 表中的记录作为sqoop命令的输入,并被称为目标表中的行。这些记录被读取并解析成一组记录,并以用户指定的分隔符分隔。

以下是从 HDFS 导出到 MySQL 表的命令。让我们在 MySQL 中创建一个表来存储从 HDFS 导出的数据:

$ mysql>  use sales;$  mysql>   create table customer_export (      cust_num int not null,      cust_fname  varchar(30),      cust_lname varchar (30),      cust_address  varchar (30),      cust_city varchar (20),      cust_state  varchar (3),      cust_zip  varchar (6),      primary key (cust_num));

$  sqoop export --connect jdbc:mysql://127.0.0.1:3306/sales --driver com.mysql.jdbc.Driver --username root --password hadoop --table customer_exported  --export-dir /user/data/customer

--table参数指定了将被填充的表。Sqoop 将数据拆分,并使用单独的 map 任务将拆分推入数据库。每个 map 任务执行实际的数据传输。--export-dir <directory h>是数据将从中导出的目录:

$ mysql>  use sales;$ mysql>  select * from customer_exported;
客户编号 客户名 客户姓 客户地址 城市 邮编
1 James Butt 6649 N Blue Gum St 新奥尔良 LA 70116
2 Art Venere 8 W Cerritos Ave #54 桥港 NJ 8014
3 Lenna Paprocki 639 Main St 安克雷奇 AK 99501
4 Donette Foller 34 Center St Hamilton OH 45011
5 Simona Morasca 3 Mcauley Dr Ashland OH 44805
6 Mitsue Tollner 7 Eads St 芝加哥 IL 60632
7 Leota Dilliard 7 W Jackson Blvd 圣何塞 CA 95111
8 Sage Wieser 5 Boston Ave #88 硅谷 SD 57105
9 Kris Marrier 228 Runamuck Pl #2808 Baltimore MD 21224
10 Minna Amigon 2371 Jerrold Ave Kulpsville PA 19443

Flume

Flume 是一个可靠、可用且分布式的服务,用于高效地收集、聚合和传输大量日志数据。它具有基于流数据流的灵活和简单架构。Apache Flume 的当前版本是 1.7.0,于 2016 年 10 月发布。

Apache Flume 架构

以下图示描绘了 Apache Flume 的架构:

让我们更详细地看看 Apache Flume 架构的组件:

  • 事件:事件是一个带有可选字符串头部的字节有效负载。它代表 Flume 可以从其来源传输到目的地的数据单元。

  • :从来源到目的地的事件传输被视为数据流,或简称流。

  • 代理:它是一个独立进程,承载 Flume 的组件,如来源、通道和接收器。因此,它具有接收、存储并将事件转发到其下一跳目的地的能力。

  • 来源:来源是一个接口实现。它具有通过特定机制消费发送给它的事件的 capability。

  • 通道:它是一个存储库,事件通过在代理内运行的源传递到通道。放置在通道中的事件将保留在那里,直到汇入端将其取出以进行进一步传输。通道在确保这一点方面发挥着重要作用。

  • 汇入端(Sink):它是一个接口实现,就像源一样。它可以从通道中移除事件并将它们传输到流程中的下一个代理或最终目的地。

  • 拦截器(Interceptors):它们有助于在传输过程中更改事件。事件可以根据选择的准则被移除或修改。拦截器是实现 org.apache.flume.interceptor.Interceptor 接口的类。

使用 Flume 的数据流

整个 Flume 代理都在 JVM 进程中运行,包括所有组件(源、通道和汇入端)。Flume 源从外部源接收事件,如 Web 服务器、外部文件等。源将事件推送到通道,通道将其存储,直到汇入端取走。通道根据源的类型将有效载荷(消息流)存储在本地文件系统或内存中。例如,如果源是文件,则有效载荷将本地存储。汇入端从通道中提取有效载荷并将其推送到外部数据存储。代理内的源和汇入端异步运行。有时,汇入端可能可以将有效载荷推送到另一个 Flume 代理。我们将在下一节中讨论这种情况。

Flume 复杂数据流架构

在以下架构中,有三个源(服务器)。为了从这些服务器上存储的日志文件中提取数据,我们必须在每个服务器上安装 Flume 软件。安装后,需要将文件名添加到 flume.conf 文件中。Flume 收集所有文件数据并通过通道将其推送到相应的汇入端。上述架构中有多个汇入端;Hive HDFS,以及连接到另一个服务器上安装的另一个 Flume 代理的另一个汇入端。它从汇入端将数据推送到源,并将数据写入 Cassandra 数据存储。

请注意,这不是一个好的架构,但我提到它是为了解释 Flume 汇入端和 Flume 源如何连接。

下面的图示展示了涉及多个代理的复杂数据流:

Flume 设置

Flume 代理配置存储在本地文本文件中。请参阅本书代码库中的示例 Flume 代理配置文件。Flume 1.7.0 支持各种源和汇入端。广泛使用的 Flume 源(摘要)如下:

描述
Avro 源 监听 Avro 端口并从外部 Avro 客户端流接收事件
执行源 运行给定的 Unix 命令并期望该进程持续在标准输出上产生数据
存储目录源 从磁盘上的文件中摄取数据
Taildir 源 在检测到文件中的新行后,近实时地跟踪文件
Kafka 源 从 Kafka 主题中读取消息
Syslog 源 读取 syslog 数据(支持 syslog-TCP 和 syslog-UDP)
HTTP 源 通过 HTTP POSTGET 接受 Flume 事件

广泛使用的 Flume 输出端可以总结如下:

输出端 描述
Avro 输出端 将事件转换为 Avro 事件并发送到配置的主机名/端口号
HDFS 输出端 将事件写入 HDFS
Hive 输出端 将文本或 JSON 数据写入 Hive 表
HBase 输出端 将数据写入 HBase
Morphline Solr 输出端 在近实时中将数据加载到 Apache Solr 服务器
Elasticsearch 输出端 将数据写入 Elasticsearch 集群
Kafka 输出端 将数据写入 Kafka 主题

广泛使用的 Flume 通道(总结)如下:

通道 描述
JDBC 通道 事件存储在数据库支持的存储中
Kafka 通道 事件存储在 Kafka 集群中
文件通道 事件存储在文件中
可溢出内存通道 事件存储在内存中;如果内存满了,则存储在磁盘上

广泛使用的 Flume 拦截器可以总结如下:

拦截器 描述
时间戳拦截器 将事件的处理时间添加到事件头中
主机拦截器 添加代理的主机名
搜索和替换拦截器 支持 Java 正则表达式
正则表达式过滤拦截器 对事件进行正则表达式过滤
正则表达式提取拦截器 从事件中提取并附加匹配的正则表达式组作为事件头

日志聚合用例

在日常业务场景中,我们总是需要获取日志文件并对其进行分析。例如,我们总是需要从不同的应用程序和服务器中获取日志,并将它们合并在一起以找到趋势和模式。让我进一步扩展这个例子。假设我们有五个部署在五个不同服务器上的 Web 服务器。我们想要获取所有五个 Web 服务器的日志并将它们合并/聚合在一起,通过在 HDFS 上存储一份副本,并将另一份副本发送到 Kafka 主题进行实时分析。问题是我们是如何设计基于 Flume 的日志聚合架构的。以下是我们 Web 服务器日志聚合场景的 Flume 架构:

让我们详细地了解架构:总共有五个 Web 服务器。每个 Web 服务器生成一个日志文件并将其本地存储。Flume 代理安装在每个 Web 服务器上。Flume 代理实际上是一个(JVM)进程,它通过宿主事件从外部源流向下一个目的地(跳转)的组件。每个 Flume 代理根据 flume.conf 的本地配置访问日志文件。每个 Flume 代理读取日志文件并将数据推送到 Flume 收集器。日志文件的每一行都被视为一条消息(有效负载)。Flume 收集器从所有 Web 服务器、适配器接收消息,聚合所有消息,并将这些消息推送到数据存储。以下是 Flume 代理的示例 flume.conf 和收集器代理的 flume.conf

## Sample Flume Agent Configuration  
## This conf file should deploy on each webserver 
##   

a1.sources = apache 
a1.sources.apache.type = exec 
a1.sources.apache.command = gtail -F /var/log/httpd/access_log 
a1.sources.apache.batchSize = 1 
a1.sources.apache.channels = memoryChannel 

a1.channels = memoryChannel 
a1.channels.memoryChannel.type = memory 
a1.channels.memoryChannel.capacity = 100 

## Collector Details 

a1.sinks = AvroSink 
a1.sinks.AvroSink.type = avro 
a1.sinks.AvroSink.channel = memoryChannel 
a1.sinks.AvroSink.hostname = 10.0.0.10 
a1.sinks.AvroSink.port = 6565 

收集器 flume.conf 文件如下:


## Collector get data from all agents 

collector.sources = AvroIn 
collector.sources.AvroIn.type = avro 
collector.sources.AvroIn.bind = 0.0.0.0 
collector.sources.AvroIn.port = 4545 
collector.sources.AvroIn.channels = mc1 mc2 

collector.channels = mc1 mc2 
collector.channels.mc1.type = memory 
collector.channels.mc1.capacity = 100 

collector.channels.mc2.type = memory 
collector.channels.mc2.capacity = 100 

## Write copy to Local Filesystem (Debugging) 
# http://flume.apache.org/FlumeUserGuide.html#file-roll-sink 
collector.sinks.LocalOut.type = file_roll 
collector.sinks.LocalOut.sink.directory = /var/log/flume 
collector.sinks.LocalOut.sink.rollInterval = 0 
collector.sinks.LocalOut.channel = mc1 

## Write to HDFS 
collector.sinks.HadoopOut.type = hdfs 
collector.sinks.HadoopOut.channel = mc2 
collector.sinks.HadoopOut.hdfs.path = /flume/events/%{log_type}/%{host}/%y-%m-%d 
collector.sinks.HadoopOut.hdfs.fileType = DataStream 
collector.sinks.HadoopOut.hdfs.writeFormat = Text 
collector.sinks.HadoopOut.hdfs.rollSize = 0 
collector.sinks.HadoopOut.hdfs.rollCount = 10000 
collector.sinks.HadoopOut.hdfs.rollInterval = 600 

Apache NiFi

什么是 Apache NiFi?在任何组织中,我们都知道存在各种系统。一些系统生成数据,而其他系统则消费这些数据。Apache NiFi 是为了自动化数据从系统到系统的流动而构建的。Apache NiFi 是一个带有 Web UI 的数据流管理系统,它可以帮助实时构建数据流。它支持基于流的编程。图编程包括一系列节点和边,数据通过这些节点和边移动。在 NiFi 中,这些节点被转换为处理器,而边被转换为连接器。数据存储在一个称为 FlowFile 的信息包中。这个 FlowFile 包括内容、属性和边。作为用户,您可以使用连接器将处理器连接起来,以定义数据应该如何处理。

Apache NiFi 的主要概念

下表描述了 Apache NiFi 的主要组件:

组件名称 描述
FlowFile 在系统中运行的数据包
FlowFile 处理器 执行数据路由、转换和数据移动的实际工作
连接 处理器之间的实际数据链接
流控制器 促进处理器之间的 FlowFile 交换
流程组 特定的数据输入和输出处理器组

Apache NiFi 架构

下图显示了 Apache NiFi 架构的组件(来源:nifi.apache.org/docs.html):

组件如下:

  • Web 服务器:这是 NiFi 的基于 HTTP 的 UI 的宿主

  • 文件控制器:这提供线程并管理扩展运行的调度

  • 扩展:扩展在 JVM 中运行和执行

  • FileFlow 存储库:这跟踪它所知道的关于当前在流程中活动的给定 FlowFile 的状态

  • 内容存储库:这是给定 FlowFile 的实际内容字节存储的地方

  • 来源存储库:这是所有来源事件数据存储的地方

关键特性

Apache NiFi 的以下是一些关键特性:

  • 保证交付:在数据量增加、电源故障、NiFi 中的网络和系统故障的情况下,确保数据的稳健交付变得必要。NiFi 确保在数据流系统内部,NiFi 与其接收到的数据点之间的交易性通信。

  • 带背压和压力释放的数据缓冲:在任何数据流中,都可能存在涉及系统的某些问题;一些可能已经关闭或运行缓慢。在这种情况下,数据缓冲变得非常关键,以应对进入或离开数据流的数据。

当 NiFi 达到特定限制和数据年龄时,它支持所有队列的带背压缓冲。NiFi 以最大可能的吞吐量率进行,同时保持良好的响应时间。

  • 优先级队列:通常,数据队列保持自然顺序或插入顺序。但是,很多时候,当数据插入速率快于带宽时,您必须优先从队列中检索数据。默认情况下是先处理最旧的数据。但是,NiFi 支持基于大小、时间等优先级队列,以拉取数据,即先处理最大的或最新的数据。

  • 特定流的质量服务(QoS):有些情况下,我们必须在特定时间段内处理数据,例如,在一秒内等,否则数据会失去其价值。Apache NiFi 通过这些特定配置的细粒度流来启用这些关注点。

  • 数据溯源:NiFi 自动记录、索引并使系统中的对象流通过时的溯源数据可用——甚至包括扇入、扇出、转换等。这些信息在支持合规性、故障排除、优化和其他场景中变得极其关键。

  • 可视化命令和控制:Apache NiFi 允许用户对数据流进行交互式管理。它对数据流的每次更改都提供即时反馈。因此,用户可以理解和立即纠正他们数据流中的任何问题、错误或问题。基于数据流的分析结果,用户可以对其数据流进行更改,优先处理队列,添加更多数据流等。

  • 流模板:可以开发、设计和共享数据流。模板允许主题专家构建和发布他们的流设计,并允许其他人从中受益并协作。

  • 扩展:NiFi 允许我们扩展其关键组件。

  • 扩展点:处理器、控制器服务、报告任务、优先级排序器和客户 UI。

  • 多角色安全:多粒度、多角色安全可以应用于每个组件,这允许管理员用户拥有细粒度的访问控制级别。

  • 聚类:NiFi 通过将多个节点组合在一起进行扩展设计。这样,通过向集群添加更多节点,它可以处理更多数据。

要开始使用 Apache NiFi,请使用此链接: nifi.apache.org/docs/nifi-docs/html/getting-started.html

让我们设想一个场景。我有一个正在运行的日志文件。它实时更新。我想根据其内容捕获和监控该文件中的每一行,并将其发送到我的 Kafka 代理。我还想将所有错误记录发送到 HDFS 以进行归档和进一步分析。不同类型的行将被发送到不同的 Kafka 代理。例如,错误、信息和成功类型的行将被发送到三个不同的 Kafka 主题,即错误、信息和成功。为此,我开发了以下 NiFi 工作流程。以下表格详细说明了每个处理器的说明:

处理器 目的 属性
TailFile 尾随日志文件 要尾随的文件 /var/log/apache.log
SplitText 将日志条目拆分为行 行拆分计数 1
RouteOnContent 进行路由决策
PutHDFS 将错误发送到 HDFS HDFS 详细信息
PublishKafka 向 Kafka 主题发送数据 代理和主题名称 主机名:端口,主题面板

实时日志捕获数据流

以下示例工作流程展示了如何将日志文件数据推送到 HDFS,然后将其移动到 Kafka 代理:

Kafka Connect

Kafka Connect 是 Apache Kafka 的一部分。它是一个使用连接器从一种系统到另一种系统摄取数据的框架。有两种类型的连接器:源连接器和目标连接器。目标连接器从源系统导入数据并将其写入 Kafka 主题。目标连接器从 Kafka 主题读取数据并将其导出到目标系统。Kafka Connect 提供了各种内置的源和目标连接器。

Kafka Connect 的简要历史

Kafka Connect 主要在 2015 年 11 月的 Kafka 0.9.x 版本中引入。除了 Kafka 0.9.x 的各种功能外,Connect API 是一个全新的功能。然后,在 2016 年 5 月,发布了新版本 Kafka 0.10.0。在该版本中,Kafka Streams API 是一个新且令人兴奋的功能。但是,在 2017 年 3 月,Kafka 版本 0.10.2 是 Kafka Connect 获得真正动力的版本。作为 Kafka 0.10.2 的一部分,发布了改进的简化 Connect API 和单消息转换 API。

为什么选择 Kafka Connect?

Kafka Connect 有助于简化数据进出 Kafka 的过程。它提供大量开箱即用的连接器来完成这项工作。在我看来,这是像我这样的开发者最好的激励,因为我无需开发单独的代码来开发自己的连接器以导入和导出数据;我总是可以重用开箱即用的连接器。此外,如果我想,我总是可以使用 Kafka Connect API 开发自己的独特连接器。此外,所有连接器都是基于配置的。常见的源和目标包括数据库、搜索引擎、NoSQL 数据存储以及像 SAP、GoldenGate、Salesforce、HDFS、Elasticsearch 等应用程序。有关所有可用源和连接器的详细列表,请参阅 www.confluent.io/product/connectors/

Kafka Connect 特性

以下是一些 Kafka Connect 的特性:

  • 可扩展:这是一个在 Apache Kafka 和其他系统之间进行可扩展和可靠流数据传输的框架

  • 简单:这使得定义将大量数据移动到和从 Kafka 中移动的连接器变得简单

  • 偏移量管理:框架负责记录连接器的偏移量的大部分繁重工作

  • 易于操作:这提供了一个具有 RESTful API 的服务,用于管理和部署连接器

  • 分布式:框架可以集群化,并将连接器自动分布到集群中,确保连接器始终运行

  • 开箱即用的连接器:有关所有可用源和连接器的详细列表,请参阅 www.confluent.io/product/connectors/

Kafka Connect 架构

以下图表示 Kafka Connect 架构:

图片

Kafka 集群由 Kafka 代理组成:如图所示,有三个代理。源可以是任何类型,例如,数据库、NoSQL、Twitter 等。在源和 Kafka 集群之间,有一个 Kafka Connect 集群,由工作节点组成。Kafka Connect 的工作包括以下步骤:

  1. 工作节点根据配置从源中拉取数据

  2. 在获取数据后,连接器将数据推送到 Kafka 集群

  3. 如果需要使用流应用程序(如 Spark、Storm 等)对数据进行转换、过滤、连接或聚合,流 API 将改变 Kafka 中的数据

  4. 根据配置,连接器将从 Kafka 中拉取数据并写入到目标

一些 Kafka Connect 概念如下:

  • 源连接器从常见的数据源获取数据。

  • 目标连接器将数据发布到常见的数据源。

  • Kafka Connect 使得将数据可靠地快速导入 Kafka 变得容易。

  • 它是 ETL 管道的一部分。

  • 从小型管道扩展到公司级管道非常容易。

  • 代码是可重用的。

  • Kafka Connect 集群有多个加载的连接器。每个连接器是一段可重用的代码,(Java JARs)。有许多开源连接器可用,可以加以利用。

  • 每个连接器任务是由连接器类和配置的组合。任务链接到连接器配置。作业创建可能会创建多个任务。因此,如果您有一个连接器和配置,则可以创建两个或更多任务。

  • Kafka Connect 工作者和服务器执行任务。工作者是一个单一的 Java 进程。工作者可以是独立的或分布式的。

Kafka Connect 工作者模式

Kafka Connect 工作者有两种模式:

  • 独立模式

  • 分布式模式

独立模式

独立模式是一个运行所有连接器和任务的单一进程(工作者)。配置捆绑在一个进程中。它不具有容错性或可扩展性,并且很难监控。由于它易于设置,因此主要用于开发和测试。

分布式模式

在分布式模式下,多个工作者(进程)运行您的连接器和任务。配置通过 REST API 提交。它是可扩展的和容错的。如果任何工作者死亡,它会自动在集群上重新平衡所有任务。由于它可扩展且容错,因此主要用于生产环境。

Kafka Connect 集群分布式架构

以下是对 Kafka Connect 集群分布式架构细节的表示:

在前面的图中,我们可以看到以下细节:

  • 我们有一个带有三个任务的连接器 1任务 1任务 2任务 3。这三个任务分布在四个工作者之间:工作者 1工作者 3工作者 4

  • 我们还有一个带有两个任务的连接器 2任务 1任务 2。这两个任务分布在两个工作者之间:工作者 2工作者 3

  • 我们还有一个带有四个任务的连接器 3任务 1任务 2任务 3任务 4。这四个任务分布在四个工作者之间:工作者 1工作者 2工作者 3工作者 4

  • 现在,发生了一些事情,工作者 4 死机了,我们完全失去了这个工作者。

  • 作为容错的一部分,重新平衡活动启动。连接器 1任务 3工作者 4移动到工作者 2。同样,连接器 3任务 4连接器 4移动到连接器 1

以下图表示重新平衡后的 Kafka Connect 集群:

示例 1

在独立模式下,从源文件 Demo-Source.txt 流式传输的数据被移动到目标文件 Demo-Sink.txt,如下所示:

为了在独立模式下从源文件 Demo-Source.txt 流式传输数据到目标文件 Demo-Sink.txt,我们需要执行以下步骤:

  1. 启动 Kafka:
$ /bin/kafka-server-start.sh config/server.properties 
  1. 创建主题:
$ .bin/kafka-topics --create --topic demo-1-standalone --partitions 3 --replication-factor 1 --zookeeper 127.0.0.1:2181 
  1. 配置source-file-stream-standalone.properties文件:
name=source-file-stream-standalone 
connector.class=org.apache.kafka.connect.file.FileStreamSourceConnector 
tasks.max=1 
file=demo-source.txt 
topic=file-source-topic 
  1. 配置file-stream-standalone.properties文件:
name=sinkfile-stream-standalone 
connector.class=org.apache.kafka.file.FileStreamSourceConnector 
tasks.max=1 
file=demo-sink.txt 
topics=file-source-topic 
  1. 配置file-worker.properties文件:
bootstrap.servers=127.0.0.1:9092 
key.converter=org.apache.kafka.connect.json.JsonConverter 
key.converter.schemas.enable=false 
value.converter=org.apache.kafka.connect.json.JsonConverter 
value.converter.schemas.enable=false 
# we always leave the internal key to JsonConverter 
internal.key.converter=org.apache.kafka.connect.json.JsonConverter 
internal.key.converter.schemas.enable=false 
internal.value.converter=org.apache.kafka.connect.json.JsonConverter 
internal.value.converter.schemas.enable=false 
rest.port=8086 
rest.host.name=127.0.0.1 
# this config is only for standalone workers 
offset.storage.file.filename=standalone.offsets 
offset.flush.interval.ms=10000 
  1. 启动 Kafka Connect。打开另一个终端并运行以下命令:
$ .bin/connect-standalone config/file-worker.properties config/source-file-stream-standalone.properties config/ sink-file-stream-standalone.properties 
  1. demo-source.txt文件添加数据。打开另一个终端并运行以下命令:
$ touch demo-source.txt 

$ echo "Test Line 1 " >>  demo-source.txt 

$ echo "Test Line 2 " >>  demo-source.txt 

$ echo "Test Line 2 " >>  demo-source.txt 
  1. 读取demo-sink.txt文件:
$ cat demo-sink.file 

示例 2

从源文件Demo-Source.txt流式传输的数据在分布式模式下移动到目标文件Demo-Sink.txt。如果您想使用分布式模式运行前面的示例,您必须在步骤 3 和步骤 4 中的source-file-streamsink-file-stream中添加以下参数:

key.converter=org.apache.kafka.connect.json.JsonConverter 
key.converter.schemas.enable=true 
value.converter=org.apache.kafka.connect.json.JsonConverter 
value.converter.schemas.enable=true 

概述

在本章中,我们学习了在生产环境中使用的所有流行数据摄取工具。Sqoop 主要用于在关系型数据库管理系统(RDBMS)数据存储中导入和导出数据。Apache Flume 用于实时系统以导入数据,主要来自文件源。它支持广泛的源和目标。Apache NiFi 是一个相对较新的工具,最近非常受欢迎。它还支持基于 GUI 的 ETL 开发。Hortonworks 从他们的 HDP 2.4 版本开始支持这个工具。Apache Kafka Connect 是市场上另一个流行的工具。它也是 Confluent 数据平台的一部分。Kafka Connect 可以摄取整个数据库或从所有应用程序服务器收集指标到 Kafka 主题中,使数据能够以低延迟进行流处理。

由于我们到目前为止已经知道如何构建 Hadoop 集群以及如何在其中摄取数据,我们将在下一章学习数据建模技术。

第五章:Hadoop 中的数据建模

到目前为止,我们已经学习了如何创建 Hadoop 集群以及如何将数据加载到其中。在上一章中,我们学习了各种数据摄取工具和技术。正如我们所知,市场上有很多开源工具,但并没有一个能够应对所有用例的万能工具。每个数据摄取工具都有其独特的功能;在典型用例中,它们可以证明是非常高效和有用的。例如,Sqoop 在用于从关系型数据库管理系统(RDBMS)导入和导出 Hadoop 数据时更为有用。

在本章中,我们将学习如何在 Hadoop 集群中存储和建模数据。与数据摄取工具类似,有各种数据存储可供选择。这些数据存储支持不同的数据模型——即列式数据存储、键值对等;并且支持各种文件格式,如 ORC、Parquet 和 AVRO 等。目前,有一些非常流行的数据存储,在生产环境中被广泛使用,例如 Hive、HBase、Cassandra 等。我们将更深入地了解以下两个数据存储和数据建模技术:

  • Apache Hive

  • Apache HBase

首先,我们将从基本概念开始,然后我们将学习如何应用现代数据建模技术以实现更快的数据访问。简而言之,本章将涵盖以下主题:

  • Apache Hive 和关系型数据库管理系统(RDBMS)

  • 支持的数据类型

  • Hive 架构及其工作原理

Apache Hive

Hive 是 Hadoop 中的一个数据处理工具。正如我们在上一章所学的,数据摄取工具在 Hadoop 中加载数据并生成 HDFS 文件;我们需要根据业务需求查询这些数据。我们可以使用 MapReduce 编程来访问数据。但是,使用 MapReduce 访问数据非常慢。为了访问 HDFS 文件中的一小部分数据,我们必须编写单独的 mapper、reducer 和 driver 代码。因此,为了避免这种复杂性,Apache 引入了 Hive。Hive 支持类似 SQL 的接口,它可以帮助使用 SQL 命令访问相同的 HDFS 文件行。Hive 最初由 Facebook 开发,但后来被 Apache 接管。

Apache Hive 和 RDBMS

我提到 Hive 提供了一个类似 SQL 的接口。考虑到这一点,随之而来的问题是:Hive 是否与 Hadoop 上的 RDBMS 相同? 答案是不是。Hive 不是一个数据库。Hive 不存储任何数据。Hive 将表信息作为元数据的一部分存储,这被称为模式,并指向 HDFS 上的文件。Hive 使用一个称为HiveQLHQL)的类似 SQL 的接口来访问存储在 HDFS 文件上的数据。Hive 支持 SQL 命令来访问和修改 HDFS 中的数据。Hive 不是一个 OLTP 工具。它不提供任何行级别的插入、更新或删除。当前版本的 Hive(版本 0.14)支持具有完整 ACID 属性的插入、更新和删除,但这个功能效率不高。此外,这个功能不支持所有文件格式。例如,更新只支持 ORC 文件格式。基本上,Hive 是为批量处理设计的,不支持像 RDBMS 那样的事务处理。因此,Hive 更适合数据仓库应用,提供数据汇总、查询和分析。内部,Hive SQL 查询由其编译器转换为 MapReduce。用户无需担心编写任何复杂的 mapper 和 reducer 代码。Hive 只支持查询结构化数据。使用 Hive SQL 访问非结构化数据非常复杂。你可能需要为该功能编写自己的自定义函数。Hive 支持各种文件格式,如文本文件、序列文件、ORC 和 Parquet,这些格式提供了显著的数据压缩。

支持的数据类型

Hive 版本 0.14 支持以下数据类型:

数据类型组 数据类型 格式
字符串 STRING column_name STRING
VARCHAR column_name VARCHAR(max_length)
CHAR column_name CHAR(length)
数值 TINYINT column_name TINYINT
SMALLINT column_name SMALLINT
INT column_name INT
BIGINT column_name BIGINT
FLOAT column_name FLOAT
DOUBLE column_name DOUBLE
DECIMAL column_name DECIMAL[(precision[,scale])]
日期/时间类型 TIMESTAMP column_name TIMESTAMP
DATE column_name DATE
INTERVAL column_name INTERVAL year to month
杂项类型 BOOLEAN column_name BOOLEAN
BINARY column_name BINARY
复杂数据类型 ARRAY column_name ARRAY < type >
MAPS column_name MAP < primitive_type, type >
STRUCT column_name STRUCT < name : type [COMMENT 'comment_string'] >
UNION column_name UNIONTYPE <int, double, array, string>

Hive 的工作原理

Hive 数据库由表组成,这些表由分区构成。数据可以通过简单的查询语言访问,并且 Hive 支持数据的覆盖或追加。在特定数据库中,表中的数据是序列化的,每个表都有一个对应的 HDFS 目录。每个表可以进一步细分为分区,这些分区决定了数据如何在表目录的子目录中分布。分区内的数据可以进一步细分为桶。

Hive 架构

以下是对 Hive 架构的表示:

上述图示显示 Hive 架构分为三个部分——即客户端、服务和元存储。Hive SQL 的执行方式如下:

  • Hive SQL 查询:可以使用以下方式之一将 Hive 查询提交给 Hive 服务器:WebUI、JDBC/ODBC 应用程序和 Hive CLI。对于基于 thrift 的应用程序,它将提供一个 thrift 客户端用于通信。

  • 查询执行:一旦 Hive 服务器接收到查询,它就会被编译,转换为优化查询计划以提高性能,并转换为 MapReduce 作业。在这个过程中,Hive 服务器与元存储交互以获取查询元数据。

  • 作业执行:MapReduce 作业在 Hadoop 集群上执行。

Hive 数据模型管理

Hive 以以下四种方式处理数据:

  • Hive 表

  • Hive 表分区

  • Hive 分区桶

  • Hive 视图

我们将在接下来的几节中详细探讨每一个。

Hive 表

Hive 表与任何 RDBMS 表非常相似。表被分为行和列。每个列(字段)都使用适当的名称和数据类型进行定义。我们已经在支持的数据类型部分中看到了 Hive 中所有可用的数据类型。Hive 表分为两种类型:

  • 管理表

  • 外部表

我们将在接下来的几节中学习这两种类型。

管理表

以下是一个定义 Hive 管理表的示例命令:

Create Table < managed_table_name>  
   Column1 <data type>, 
   Column2 <data type>, 
   Column3 <data type> 
Row format delimited Fields Terminated by "t"; 

当执行前面的查询时,Hive 会创建表,并且元数据会相应地更新到元存储中。但是表是空的。因此,可以通过执行以下命令将数据加载到这个表中:

Load data inpath <hdfs_folder_name> into table <managed_table_name>; 

执行前面的命令后,数据将从<hdfs_folder_name>移动到 Hive 表的默认位置/user/hive/warehouse/<managed_table_name>。这个默认文件夹/user/hive/warehousehive-site.xml中定义,可以被更改为任何文件夹。现在,如果我们决定删除表,可以通过以下命令执行:

Drop table <managed_table_name>; 

/user/hive/warehouse/<managed_table_name>文件夹将被删除,并且存储在元存储中的元数据将被删除。

外部表

以下是一个定义 Hive 外部表的示例命令:

Create Table < external_table_name>  
   Column1 <data type>, 
   Column2 <data type>, 
   Column3 <data type> 
Row format delimited Fields Terminated by "t" 
Location <hdfs_folder_name>; 

当执行前面的查询时,Hive 将创建表,并在 metastore 中相应地更新元数据。但是,表仍然是空的。因此,可以通过执行以下命令将数据加载到该表中:

Load data inpath <hdfs_folder_name> into table <external_table_name>; 

此命令不会将任何文件移动到任何文件夹,而是创建一个指向文件夹位置的指针,并在 metastore 的元数据中进行更新。文件将保持在查询相同的位置(《hdfs_folder_name》)。现在,如果我们决定删除表,我们可以通过以下命令执行:

Drop table <managed_table_name>;  

文件夹/user/hive/warehouse/<managed_table_name>不会被删除,只会删除存储在 metastore 中的元数据。文件将保持在相同的位置——<hdfs_folder_name>

Hive 表分区

表分区意味着根据分区键的值将表划分为不同的部分。分区键可以是任何列,例如日期、部门、国家等。由于数据存储在部分中,查询响应时间会更快。分区不是扫描整个表,而是在主表文件夹内创建子文件夹。Hive 将根据查询的WHERE子句仅扫描表的具体部分或部分。Hive 表分区类似于任何 RDBMS 表分区。其目的也是相同的。随着我们不断向表中插入数据,表的数据量会越来越大。假设我们创建了一个如下所示的ORDERS表:

hive> create database if not exists ORDERS; 
OK 
Time taken: 0.036 seconds 

hive> use orders; 
OK 
Time taken: 0.262 seconds 

hive> CREATE TABLE if not exists ORDEERS_DATA 
    > (Ord_id INT, 
    > Ord_month INT, 
    > Ord_customer_id INT, 
    > Ord_city  STRING, 
    > Ord_zip   STRING, 
    > ORD_amt   FLOAT 
    > ) 
    > ROW FORMAT DELIMITED 
    > FIELDS TERMINATED BY  ',' 
    > ; 
OK 
Time taken: 0.426 seconds 
hive> 

我们将按照以下方式加载以下示例文件ORDERS_DATA表:

101,1,100,'Los Angeles','90001',1200 
102,2,200,'Los Angeles','90002',1800 
103,3,300,'Austin','78701',6500 
104,4,400,'Phoenix','85001',7800 
105,5,500,'Beverly Hills','90209',7822 
106,6,600,'Gaylord','49734',8900 
107,7,700,'Los Angeles','90001',7002 
108,8,800,'Los Angeles','90002',8088 
109,9,900,'Reno','89501',6700 
110,10,1000,'Los Angeles','90001',8500 
111,10,1000,'Logan','84321',2300 
112,10,1000,'Fremont','94539',9500 
113,10,1000,'Omaha','96245',7500 
114,11,2000,'New York','10001',6700 
115,12,3000,'Los Angeles','90003',1000 

然后将orders.txt加载到/tmp HDFS 文件夹中:

[root@sandbox order_data]# hadoop fs -put /root/order_data/orders.txt /tmp 

[root@sandbox order_data]# hadoop fs -ls /tmp 
Found 3 items 
-rw-r--r--   1 root      hdfs        530 2017-09-02 18:06 /tmp/orders.txt 

按照以下方式加载ORDERS_DATA表:

hive> load data inpath '/tmp/orders.txt' into table ORDERS_DATA; 
Loading data to table orders.orders_data 
Table orders.orders_data stats: [numFiles=1, numRows=0, totalSize=530, rawDataSize=0] 
OK 
Time taken: 0.913 seconds 

hive> select * from ORDERS_DATA; 
OK 
101      1     100   'Los Angeles'     '90001'     1200.0 
102      2     200   'Los Angeles'     '90002'     1800.0 
103      3     300   'Austin'    '78701'     6500.0 
104      4     400   'Phoenix'   '85001'     7800.0 
105      5     500   'Beverly Hills'   '90209'     7822.0 
106      6     600   'Gaylord'   '49734'     8900.0 
107      7     700   'Los Angeles'     '90001'     7002.0 
108      8     800   'Los Angeles'     '90002'     8088.0 
109      9     900   'Reno'      '89501'     6700.0 
110      10    1000  'Los Angeles'     '90001'     8500.0 
111      10    1000  'Logan'     '84321'     2300.0 
112      10    1000  'Fremont'   '94539'     9500.0 
113      10    1000  'Omaha'     '96245'     7500.0 
114      11    2000  'New York'  '10001'     6700.0 
115      12    3000  'Los Angeles'     '90003'     1000.0 
Time taken: 0.331 seconds, Fetched: 15 row(s) 

假设我们想在ORDERS_DATA表中插入城市数据。每个城市的订单数据大小为 1 TB。因此,ORDERS_DATA表的总数据大小将为 15 TB(表中共有 15 个城市)。现在,如果我们编写以下查询以获取在洛杉矶预订的所有订单:

hive>  select * from ORDERS where Ord_city = 'Los Angeles' ; 

查询将运行得非常慢,因为它必须扫描整个表。显然的想法是,我们可以为每个城市创建 10 个不同的orders表,并将orders数据存储在ORDERS_DATA表的相应城市中。但不是这样,我们可以按照以下方式对ORDERS_PART表进行分区:

hive> use orders; 

hive> CREATE TABLE orders_part 
    > (Ord_id INT, 
    > Ord_month INT, 
    > Ord_customer_id INT, 
    > Ord_zip   STRING, 
    > ORD_amt   FLOAT 
    > ) 
    > PARTITIONED BY  (Ord_city INT) 
    > ROW FORMAT DELIMITED 
    > FIELDS TERMINATED BY  ',' 
    > ; 
OK 
Time taken: 0.305 seconds 
hive> 

现在,Hive 根据列或分区键将表组织成分区,以便将相似类型的数据分组在一起。假设我们为每个城市有 10 个orders文件,即Orders1.txtOrders10.txt。以下示例显示了如何将每个月度文件加载到相应的分区中:

load data inpath '/tmp/orders.txt' into table orders_part partition(Ord_city='Los Angeles'); 
load data inpath '/tmp/orders.txt' into table orders_part partition(Ord_city='Austin'); 
load data inpath '/tmp/orders.txt' into table orders_part partition(Ord_city='Phoenix'); 
load data inpath '/tmp/orders.txt' into table orders_part partition(Ord_city='Beverly Hills'); 
load data inpath '/tmp/orders.txt' into table orders_part partition(Ord_city='Gaylord'); 
load data inpath '/tmp/orders.txt' into table orders_part partition(Ord_city=Reno'); 
load data inpath '/tmp/orders.txt' into table orders_part partition(Ord_city='Fremont'); 
load data inpath '/tmp/orders.txt' into table orders_part partition(Ord_city='Omaha'); 
load data inpath '/tmp/orders.txt' into table orders_part partition(Ord_city='New York'); 
load data inpath '/tmp/orders.txt' into table orders_part partition(Ord_city='Logan'); 

[root@sandbox order_data]# hadoop fs -ls /apps/hive/warehouse/orders.db/orders_part 
Found 10 items 
drwxrwxrwx   - root hdfs          0 2017-09-02 18:32 /apps/hive/warehouse/orders.db/orders_part/ord_city=Austin 
drwxrwxrwx   - root hdfs          0 2017-09-02 18:32 /apps/hive/warehouse/orders.db/orders_part/ord_city=Beverly Hills 
drwxrwxrwx   - root hdfs          0 2017-09-02 18:32 /apps/hive/warehouse/orders.db/orders_part/ord_city=Fremont 
drwxrwxrwx   - root hdfs          0 2017-09-02 18:32 /apps/hive/warehouse/orders.db/orders_part/ord_city=Gaylord 
drwxrwxrwx   - root hdfs          0 2017-09-02 18:33 /apps/hive/warehouse/orders.db/orders_part/ord_city=Logan 
drwxrwxrwx   - root hdfs          0 2017-09-02 18:32 /apps/hive/warehouse/orders.db/orders_part/ord_city=Los Angeles 
drwxrwxrwx   - root hdfs          0 2017-09-02 18:32 /apps/hive/warehouse/orders.db/orders_part/ord_city=New York 
drwxrwxrwx   - root hdfs          0 2017-09-02 18:32 /apps/hive/warehouse/orders.db/orders_part/ord_city=Omaha 
drwxrwxrwx   - root hdfs          0 2017-09-02 18:32 /apps/hive/warehouse/orders.db/orders_part/ord_city=Phoenix 
drwxrwxrwx   - root hdfs          0 2017-09-02 18:33 /apps/hive/warehouse/orders.db/orders_part/ord_city=Reno 
[root@sandbox order_data]  

数据分区可以显著提高查询性能,因为数据已经根据列值分离到不同的文件中,这可以减少映射器的数量,并大大减少 MapReduce 作业中数据的洗牌和排序量。

Hive 静态分区和动态分区

如果您想在 Hive 中使用静态分区,应设置以下属性:

set hive.mapred.mode = strict;  

在前面的例子中,我们已经看到我们必须将每个月的订单文件分别插入到每个静态分区中。与动态分区相比,静态分区在加载数据时可以节省时间。我们必须单独向表中添加一个分区并将文件移动到表的分区中。如果我们有很多分区,编写一个查询来加载数据到每个分区可能会变得繁琐。我们可以通过动态分区来克服这一点。在动态分区中,我们可以使用单个 SQL 语句将数据插入到分区表中,但仍可以加载数据到每个分区。与静态分区相比,动态分区在加载数据时花费更多时间。当你有一个表中有大量数据存储时,动态分区是合适的。如果你想要对多个列进行分区,但你不知道它们有多少列,那么动态分区也是合适的。以下是你应该允许的 hive 动态分区属性:

SET hive.exec.dynamic.partition = true;
SET hive.exec.dynamic.partition.mode = nonstrict;  

以下是一个动态分区的示例。假设我们想要从ORDERS_PART表加载数据到一个名为ORDERS_NEW的新表中:

hive> use orders; 
OK 
Time taken: 1.595 seconds 
hive> drop table orders_New; 
OK 
Time taken: 0.05 seconds 
hive> CREATE TABLE orders_New 
    > (Ord_id INT, 
    > Ord_month INT, 
    > Ord_customer_id INT, 
    > Ord_city  STRING, 
    > Ord_zip   STRING, 
    > ORD_amt   FLOAT 
    > ) 
    > ) 
    > PARTITIONED BY  (Ord_city STRING) 
    > ROW FORMAT DELIMITED 
    > FIELDS TERMINATED BY  ',' 
    > ; 
OK 
Time taken: 0.458 seconds 
hive> 

ORDERS_PART表加载数据到ORDER_NEW表。在这里,Hive 将动态地加载数据到ORDERS_NEW表的全部分区:

hive> SET hive.exec.dynamic.partition = true; 
hive> SET hive.exec.dynamic.partition.mode = nonstrict; 
hive>  
    > insert into table orders_new  partition(Ord_city) select * from orders_part; 
Query ID = root_20170902184354_2d409a56-7bfc-416e-913a-2323ea3b339a 
Total jobs = 1 
Launching Job 1 out of 1 
Status: Running (Executing on YARN cluster with App id application_1504299625945_0013) 

-------------------------------------------------------------------------------- 
        VERTICES      STATUS  TOTAL  COMPLETED  RUNNING  PENDING  FAILED  KILLED 
-------------------------------------------------------------------------------- 
Map 1 ..........   SUCCEEDED      1          1        0        0       0       0 
-------------------------------------------------------------------------------- 
VERTICES: 01/01  [==========================>>] 100%  ELAPSED TIME: 3.66 s      
-------------------------------------------------------------------------------- 
Loading data to table orders.orders_new partition (ord_city=null) 
    Time taken to load dynamic partitions: 2.69 seconds 
   Loading partition {ord_city=Logan} 
   Loading partition {ord_city=Los Angeles} 
   Loading partition {ord_city=Beverly Hills} 
   Loading partition {ord_city=Reno} 
   Loading partition {ord_city=Fremont} 
   Loading partition {ord_city=Gaylord} 
   Loading partition {ord_city=Omaha} 
   Loading partition {ord_city=Austin} 
   Loading partition {ord_city=New York} 
   Loading partition {ord_city=Phoenix} 
    Time taken for adding to write entity : 3 
Partition orders.orders_new{ord_city=Austin} stats: [numFiles=1, numRows=1, totalSize=13, rawDataSize=12] 
Partition orders.orders_new{ord_city=Beverly Hills} stats: [numFiles=1, numRows=1, totalSize=13, rawDataSize=12] 
Partition orders.orders_new{ord_city=Fremont} stats: [numFiles=1, numRows=1, totalSize=15, rawDataSize=14] 
Partition orders.orders_new{ord_city=Gaylord} stats: [numFiles=1, numRows=1, totalSize=13, rawDataSize=12] 
Partition orders.orders_new{ord_city=Logan} stats: [numFiles=1, numRows=1, totalSize=15, rawDataSize=14] 
Partition orders.orders_new{ord_city=Los Angeles} stats: [numFiles=1, numRows=6, totalSize=82, rawDataSize=76] 
Partition orders.orders_new{ord_city=New York} stats: [numFiles=1, numRows=1, totalSize=15, rawDataSize=14] 
Partition orders.orders_new{ord_city=Omaha} stats: [numFiles=1, numRows=1, totalSize=15, rawDataSize=14] 
Partition orders.orders_new{ord_city=Phoenix} stats: [numFiles=1, numRows=1, totalSize=13, rawDataSize=12] 
Partition orders.orders_new{ord_city=Reno} stats: [numFiles=1, numRows=1, totalSize=13, rawDataSize=12] 
OK 
Time taken: 10.493 seconds 
hive>  

让我们看看ORDERS_NEW中创建了多少个分区:

hive> show partitions ORDERS_NEW; 
OK 
ord_city=Austin 
ord_city=Beverly Hills 
ord_city=Fremont 
ord_city=Gaylord 
ord_city=Logan 
ord_city=Los Angeles 
ord_city=New York 
ord_city=Omaha 
ord_city=Phoenix 
ord_city=Reno 
Time taken: 0.59 seconds, Fetched: 10 row(s) 
hive>  

现在很清楚何时使用静态分区和动态分区。当在将数据加载到 Hive 表之前,分区列的值已知得很好时,可以使用静态分区。在动态分区的情况下,分区列的值仅在将数据加载到 Hive 表期间知道。

Hive 分区桶

桶分区是一种将大型数据集分解成更易管理的小组的技术。桶分区基于哈希函数。当一个表被桶分区时,具有相同列值的所有表记录将进入同一个桶。在物理上,每个桶是表文件夹中的一个文件,就像分区一样。在一个分区表中,Hive 可以在多个文件夹中分组数据。但是,当分区数量有限且数据在它们之间均匀分布时,分区证明是有效的。如果有大量分区,那么它们的使用效果就会降低。因此,在这种情况下,我们可以使用桶分区。我们可以在创建表时显式创建多个桶。

Hive 桶分区的工作原理

以下图表详细展示了 Hive 桶分区的原理:

如果我们决定在一个表中的列(在我们的例子中是Ord_city)有三个桶,那么 Hive 将创建三个编号为 0-2(n-1)的桶。在记录插入时,Hive 将对每个记录的Ord_city列应用哈希函数以决定哈希键。然后 Hive 将对每个哈希值应用模运算符。我们也可以在非分区表中使用桶分区。但是,当与分区表一起使用桶分区功能时,我们将获得最佳性能。桶分区有两个关键好处:

  • 改进查询性能:在相同分桶列上的连接操作中,我们可以明确指定桶的数量。由于每个桶的数据大小相等,因此分桶表上的 map-side 连接比非分桶表上的连接性能更好。在 map-side 连接中,左侧表桶将确切知道右侧桶中的数据集,以便高效地执行表连接。

  • 改进采样:因为数据已经被分割成更小的块。

让我们考虑我们的ORDERS_DATA表示例。它按CITY列进行分区。可能所有城市订单的分布并不均匀。一些城市可能比其他城市有更多的订单。在这种情况下,我们将有倾斜的分区。这将影响查询性能。对于订单较多的城市的查询将比订单较少的城市慢。我们可以通过分桶表来解决这个问题。表中的桶由表 DDL 中的CLUSTER子句定义。以下示例详细解释了分桶功能。

在非分区表中创建桶

首先,我们将创建一个ORDERS_BUCK_non_partition表:

SET hive.exec.dynamic.partition = true; 
SET hive.exec.dynamic.partition.mode = nonstrict; 
SET hive.exec.mx_dynamic.partition=20000; 
SET hive.exec.mx_dynamic.partition.pernode=20000; 
SET hive.enforce.bucketing = true; 

hive> use orders; 
OK 
Time taken: 0.221 seconds 
hive>  
    > CREATE TABLE ORDERS_BUCKT_non_partition 
    > (Ord_id INT, 
    > Ord_month INT, 
    > Ord_customer_id INT, 
    > Ord_city  STRING, 
    > Ord_zip   STRING, 
    > ORD_amt   FLOAT 
    > ) 
    > CLUSTERED BY (Ord_city) into 4 buckets stored as textfile; 
OK 
Time taken: 0.269 seconds 
hive>  

要引用所有 Hive SET配置参数,请使用此 URL:

cwiki.apache.org/confluence/display/Hive/Configuration+Properties.

加载新创建的非分区桶表:

hive> insert into ORDERS_BUCKT_non_partition select * from orders_data; 
Query ID = root_20170902190615_1f557644-48d6-4fa1-891d-2deb7729fa2a 
Total jobs = 1 
Launching Job 1 out of 1 
Tez session was closed. Reopening... 
Session re-established. 
Status: Running (Executing on YARN cluster with App id application_1504299625945_0014) 

-------------------------------------------------------------------------------- 
        VERTICES      STATUS  TOTAL  COMPLETED  RUNNING  PENDING  FAILED  KILLED 
-------------------------------------------------------------------------------- 
Map 1 ..........   SUCCEEDED      1          1        0        0       0       0 
Reducer 2 ......   SUCCEEDED      4          4        0        0       0       0 
-------------------------------------------------------------------------------- 
VERTICES: 02/02  [==========================>>] 100%  ELAPSED TIME: 9.58 s      
-------------------------------------------------------------------------------- 
Loading data to table orders.orders_buckt_non_partition 
Table orders.orders_buckt_non_partition stats: [numFiles=4, numRows=15, totalSize=560, rawDataSize=545] 
OK 
Time taken: 15.55 seconds 
hive> 

以下命令显示 Hive 在表中创建了四个桶(文件夹),00000[0-3]_0


[root@sandbox order_data]# hadoop fs -ls /apps/hive/warehouse/orders.db/orders_buckt_non_partition 
Found 4 items 
-rwxrwxrwx   1 root hdfs         32 2017-09-02 19:06 /apps/hive/warehouse/orders.db/orders_buckt_non_partition/000000_0 
-rwxrwxrwx   1 root hdfs        110 2017-09-02 19:06 /apps/hive/warehouse/orders.db/orders_buckt_non_partition/000001_0 
-rwxrwxrwx   1 root hdfs        104 2017-09-02 19:06 /apps/hive/warehouse/orders.db/orders_buckt_non_partition/000002_0 
-rwxrwxrwx   1 root hdfs        314 2017-09-02 19:06 /apps/hive/warehouse/orders.db/orders_buckt_non_partition/000003_0 
[root@sandbox order_data]# 

在分区表中创建桶

首先,我们将创建一个分桶分区表。在这里,表按Ord_city列分为四个桶,但按Ord_zip列进一步细分:

SET hive.exec.dynamic.partition = true; 
SET hive.exec.dynamic.partition.mode = nonstrict; 
SET hive.exec.mx_dynamic.partition=20000; 
SET hive.exec.mx_dynamic.partition.pernode=20000; 
SET hive.enforce.bucketing = true; 

hive> CREATE TABLE ORDERS_BUCKT_partition 
    > (Ord_id INT, 
    > Ord_month INT, 
    > Ord_customer_id INT, 
    > Ord_zip   STRING, 
    > ORD_amt   FLOAT 
    > ) 
    > PARTITIONED BY  (Ord_city STRING) 
    > CLUSTERED BY (Ord_zip) into 4 buckets stored as textfile; 
OK 
Time taken: 0.379 seconds 

使用动态分区将分桶分区表加载到另一个分桶分区表(ORDERS_PART)中:

hive> SET hive.exec.dynamic.partition = true; 
hive> SET hive.exec.dynamic.partition.mode = nonstrict; 
hive> SET hive.exec.mx_dynamic.partition=20000; 
Query returned non-zero code: 1, cause: hive configuration hive.exec.mx_dynamic.partition does not exists. 
hive> SET hive.exec.mx_dynamic.partition.pernode=20000; 
Query returned non-zero code: 1, cause: hive configuration hive.exec.mx_dynamic.partition.pernode does not exists. 
hive> SET hive.enforce.bucketing = true; 
hive> insert into ORDERS_BUCKT_partition partition(Ord_city) select * from orders_part; 
Query ID = root_20170902194343_dd6a2938-6aa1-49f8-a31e-54dafbe8d62b 
Total jobs = 1 
Launching Job 1 out of 1 
Status: Running (Executing on YARN cluster with App id application_1504299625945_0017) 

-------------------------------------------------------------------------------- 
        VERTICES      STATUS  TOTAL  COMPLETED  RUNNING  PENDING  FAILED  KILLED 
-------------------------------------------------------------------------------- 
Map 1 ..........   SUCCEEDED      1          1        0        0       0       0 
Reducer 2 ......   SUCCEEDED      4          4        0        0       0       0 
-------------------------------------------------------------------------------- 
VERTICES: 02/02  [==========================>>] 100%  ELAPSED TIME: 7.13 s      
-------------------------------------------------------------------------------- 
Loading data to table orders.orders_buckt_partition partition (ord_city=null) 
    Time taken to load dynamic partitions: 2.568 seconds 
   Loading partition {ord_city=Phoenix} 
   Loading partition {ord_city=Logan} 
   Loading partition {ord_city=Austin} 
   Loading partition {ord_city=Fremont} 
   Loading partition {ord_city=Beverly Hills} 
   Loading partition {ord_city=Los Angeles} 
   Loading partition {ord_city=New York} 
   Loading partition {ord_city=Omaha} 
   Loading partition {ord_city=Reno} 
   Loading partition {ord_city=Gaylord} 
    Time taken for adding to write entity : 3 
Partition orders.orders_buckt_partition{ord_city=Austin} stats: [numFiles=1, numRows=1, totalSize=22, rawDataSize=21] 
Partition orders.orders_buckt_partition{ord_city=Beverly Hills} stats: [numFiles=1, numRows=1, totalSize=29, rawDataSize=28] 
Partition orders.orders_buckt_partition{ord_city=Fremont} stats: [numFiles=1, numRows=1, totalSize=23, rawDataSize=22] 
Partition orders.orders_buckt_partition{ord_city=Gaylord} stats: [numFiles=1, numRows=1, totalSize=23, rawDataSize=22] 
Partition orders.orders_buckt_partition{ord_city=Logan} stats: [numFiles=1, numRows=1, totalSize=26, rawDataSize=25] 
Partition orders.orders_buckt_partition{ord_city=Los Angeles} stats: [numFiles=1, numRows=6, totalSize=166, rawDataSize=160] 
Partition orders.orders_buckt_partition{ord_city=New York} stats: [numFiles=1, numRows=1, totalSize=23, rawDataSize=22] 
Partition orders.orders_buckt_partition{ord_city=Omaha} stats: [numFiles=1, numRows=1, totalSize=25, rawDataSize=24] 
Partition orders.orders_buckt_partition{ord_city=Phoenix} stats: [numFiles=1, numRows=1, totalSize=23, rawDataSize=22] 
Partition orders.orders_buckt_partition{ord_city=Reno} stats: [numFiles=1, numRows=1, totalSize=20, rawDataSize=19] 
OK 
Time taken: 13.672 seconds 
hive>  

Hive 视图

Hive 视图是一个逻辑表。它就像任何 RDBMS 视图一样。概念是相同的。当创建视图时,Hive 不会将其中的任何数据存储进去。当创建视图时,Hive 会冻结元数据。Hive 不支持任何 RDBMS 的物化视图概念。视图的基本目的是隐藏查询复杂性。有时,HQL 包含复杂的连接、子查询或过滤器。借助视图,整个查询可以简化为一个虚拟表。

当在底层表上创建视图时,对该表的任何更改,甚至添加或删除该表,都会在视图中失效。此外,当创建视图时,它只更改元数据。但是,当查询访问该视图时,它将触发 MapReduce 作业。视图是一个纯粹的逻辑对象,没有关联的存储(Hive 中目前不支持物化视图)。当查询引用视图时,将评估视图的定义以生成一组行供查询进一步处理。(这是一个概念性描述。实际上,作为查询优化的部分,Hive 可能会将视图的定义与查询结合起来,例如,将查询中的过滤器推入视图。)

视图的模式在创建视图时被冻结;对底层表(例如,添加列)的后续更改不会反映在视图的模式中。如果底层表被删除或以不兼容的方式更改,后续尝试查询无效视图将失败。视图是只读的,不能用作LOAD/INSERT/ALTER更改元数据的目标。视图可能包含ORDER BYLIMIT子句。如果引用查询也包含这些子句,则查询级别的子句将在视图子句(以及查询中的任何其他操作)之后评估。例如,如果视图指定LIMIT 5,并且引用查询以(select * from v LIMIT 10)执行,则最多返回五行。

视图的语法

让我们看看几个视图的示例:

CREATE VIEW [IF NOT EXISTS] [db_name.]view_name [(column_name [COMMENT column_comment], ...) ] 
  [COMMENT view_comment] 
  [TBLPROPERTIES (property_name = property_value, ...)] 
  AS SELECT ...;

我将通过以下几个示例演示视图的优势。假设我们有两个表,Table_XTable_Y,具有以下模式:Table_XXCol_1字符串,XCol_2字符串,XCol_3字符串,Table_YYCol_1字符串,YCol_2字符串,YCol_3字符串,和YCol_4字符串。要创建与基础表完全相同的视图,请使用以下代码:

Create view table_x_view as select * from Table_X; 

要在基础表的选定列上创建视图,请使用以下语法:

Create view table_x_view as select xcol_1,xcol_3  from Table_X; 

要创建一个用于筛选基础表列值的视图,我们可以使用:

Create view table_x_view as select * from Table_X where XCol_3 > 40 and  XCol_2 is not null; 

要创建一个用于隐藏查询复杂性的视图:

create view table_union_view  as select XCol_1, XCol_2, XCol_3,Null from Table_X 
   where XCol_2  = "AAA" 
   union all 
   select YCol_1, YCol_2, YCol_3, YCol_4 from Table_Y 
   where YCol_3 = "BBB"; 

   create view table_join_view as select * from Table_X 
   join Table_Y on Table_X. XCol_1 = Table_Y. YCol_1; 

Hive 索引

索引的主要目的是便于快速搜索记录并加速查询。Hive 索引的目标是提高对表特定列的查询查找速度。如果没有索引,带有如WHERE tab1.col1 = 10这样的谓词的查询将加载整个表或分区并处理所有行。但是,如果为col1存在索引,则只需加载和处理文件的一部分。索引可以提供的查询速度提升是以创建索引的额外处理和存储索引的磁盘空间为代价的。有两种类型的索引:

  • 紧凑索引

  • 位图索引

主要区别在于存储不同块中行的映射值。

紧凑索引

在 HDFS 中,数据存储在块中。但是扫描存储在哪个块中的数据是耗时的。紧凑索引存储索引列的值及其 blockId。因此,查询将不会访问表。相反,查询将直接访问存储列值和 blockId 的紧凑索引。无需扫描所有块以找到数据!所以,在执行查询时,它将首先检查索引,然后直接进入该块。

位图索引

位图索引将索引列的值和行列表的组合存储为位图。位图索引通常用于具有不同值的列。让我们回顾几个示例:基础表,Table_XXCol_1 整数,XCol_2 字符串,XCol_3 整数,和 XCol_4 字符串。创建索引:

CREATE INDEX table_x_idx_1 ON TABLE table_x (xcol_1) AS 'COMPACT';  
SHOW INDEX ON table_x_idx;  
DROP INDEX table_x_idx ON table_x; 

CREATE INDEX table_x_idx_2 ON TABLE table_x (xcol_1) AS 'COMPACT' WITH DEFERRED REBUILD;  
ALTER INDEX table_x_idx_2 ON table_x REBUILD;  
SHOW FORMATTED INDEX ON table_x; 

前面的索引为空,因为它使用了 DEFERRED REBUILD 子句创建,无论表是否包含任何数据。在此索引创建后,需要使用 REBUILD 命令来构建索引结构。在索引创建后,如果基础表中的数据发生变化,必须使用 REBUILD 命令来更新索引。创建索引并将其存储在文本文件中:

CREATE INDEX table_x_idx_3 ON TABLE table_x (table_x) AS 'COMPACT' ROW FORMAT DELIMITED  
FIELDS TERMINATED BY 't'  
STORED AS TEXTFILE; 

创建位图索引:

CREATE INDEX table_x_bitmap_idx_4 ON TABLE table_x (table_x) AS 'BITMAP' WITH DEFERRED REBUILD;  
ALTER INDEX table_x_bitmap_idx_4 ON table03 REBUILD;  
SHOW FORMATTED INDEX ON table_x; 
DROP INDEX table_x_bitmap_idx_4 ON table_x; 

使用 Hive 的 JSON 文档

JSON 是结构化数据的最小可读格式。它主要用于在服务器和 Web 应用程序之间传输数据,作为 XML 的替代方案。JSON 建立在两种结构之上:

  • 名称/值对的集合。在不同的语言中,这通常实现为对象、记录、结构、字典、散列表、键列表或关联数组。

  • 值的有序列表。在大多数语言中,这通常实现为数组、向量、列表或序列。

请在以下网址了解更多关于 JSON 的信息:www.json.org/.

示例 1 – 使用 Hive 访问简单的 JSON 文档(Hive 0.14 及更高版本)

在本例中,我们将看到如何使用 HiveQL 查询简单的 JSON 文档。假设我们想访问以下 Sample-Json-simple.json 文件在 HiveSample-Json-simple.json

{"username":"abc","tweet":"Sun shine is bright.","time1": "1366150681" } 
{"username":"xyz","tweet":"Moon light is mild .","time1": "1366154481" } 

查看 Sample-Json-simple.json 文件:

[root@sandbox ~]# cat Sample-Json-simple.json 
{"username":"abc","tweet":"Sun shine is bright.","timestamp": 1366150681 } 
{"username":"xyz","tweet":"Moon light is mild .","timestamp": 1366154481 } 
[root@sandbox ~]#  

Sample-Json-simple.json 加载到 HDFS:

[root@sandbox ~]# hadoop fs -mkdir  /user/hive-simple-data/ 
[root@sandbox ~]# hadoop fs -put Sample-Json-simple.json /user/hive-simple-data/ 

创建一个外部 Hive 表,simple_json_table

hive> use orders; 
OK 
Time taken: 1.147 seconds 
hive>  
CREATE EXTERNAL TABLE simple_json_table ( 
username string, 
tweet string, 
time1 string) 
ROW FORMAT SERDE 'org.apache.hive.hcatalog.data.JsonSerDe' 
LOCATION '/user/hive-simple-data/'; 
OK 
Time taken: 0.433 seconds 
hive>  

现在验证记录:

hive> select * from simple_json_table ; 
OK 
abc      Sun shine is bright.    1366150681 
xyz      Moon light is mild .    1366154481 
Time taken: 0.146 seconds, Fetched: 2 row(s) 
hive>  

示例 2 – 使用 Hive 访问嵌套 JSON 文档(Hive 0.14 及更高版本)

我们将看到如何使用 HiveQL 查询嵌套 JSON 文档。假设我们想访问以下 Sample-Json-complex.json 文件在 HiveSample-Json-complex.json

{"DocId":"Doc1","User1":{"Id":9192,"Username":"u2452","ShippingAddress":{"Address1":"6373 Sun Street","Address2":"apt 12","City":"Foster City","State":"CA"},"Orders":[{"ItemId":5343,"OrderDate":"12/23/2017"},{"ItemId":7362,"OrderDate":"12/24/2017"}]}} 

Sample-Json-simple.json 加载到 HDFS:

[root@sandbox ~]# hadoop fs -mkdir  /user/hive-complex-data/ 
[root@sandbox ~]# hadoop fs -put Sample-Json-complex.json /user/hive-complex-data/ 

创建一个外部 Hive 表,json_nested_table

hive>  
CREATE EXTERNAL TABLE json_nested_table( 
DocId string, 
user1 struct<Id: int, username: string, shippingaddress:struct<address1:string,address2:string,city:string,state:string>, orders:array<struct<ItemId:int,orderdate:string>>> 
) 
ROW FORMAT SERDE 
'org.apache.hive.hcatalog.data.JsonSerDe' 
LOCATION 
'/user/hive-complex-data/'; 
OK 
Time taken: 0.535 seconds 
hive>  

验证记录:

hive> select DocId,user1.username,user1.orders FROM json_nested_table; 
OK 
Doc1     u2452   [{"itemid":5343,"orderdate":"12/23/2017"},{"itemid":7362,"orderdate":"12/24/2017"}] 
Time taken: 0.598 seconds, Fetched: 1 row(s) 
hive>  

示例 3 – 使用 Hive 和 Avro 进行模式演变(Hive 0.14 及更高版本)

在生产环境中,我们必须更改表结构以应对新的业务需求。表模式必须更改以添加/删除/重命名表列。这些更改中的任何一项都会对下游 ETL 作业产生不利影响。为了避免这些,我们必须对 ETL 作业和目标表进行相应的更改。

架构演变允许你在保持与旧数据架构向后兼容的同时更新用于写入新数据的架构。然后你可以将所有数据一起读取,就像所有数据具有一个架构一样。请阅读以下 URL 上的 Avro 序列化更多信息:avro.apache.org/。在以下示例中,我将演示 Avro 和 Hive 表如何在不失败 ETL 作业的情况下吸收源表架构更改。我们将创建一个 MySQL 数据库中的客户表,并使用 Avro 文件将其加载到目标 Hive 外部表中。然后我们将向源表添加一个额外的列,以查看 Hive 表如何无错误地吸收该更改。连接到 MySQL 创建源表(customer):

mysql -u root -p 

GRANT ALL PRIVILEGES ON *.* TO 'sales'@'localhost' IDENTIFIED BY 'xxx';  

mysql -u sales  -p 

mysql> create database orders; 

mysql> use orders; 

CREATE TABLE customer( 
cust_id INT , 
cust_name  VARCHAR(20) NOT NULL, 
cust_city VARCHAR(20) NOT NULL, 
PRIMARY KEY ( cust_id ) 
); 

customer 表中插入记录:

INSERT into customer (cust_id,cust_name,cust_city) values (1,'Sam James','Austin'); 
INSERT into customer (cust_id,cust_name,cust_city) values (2,'Peter Carter','Denver'); 
INSERT into customer (cust_id,cust_name,cust_city) values (3,'Doug Smith','Sunnyvale'); 
INSERT into customer (cust_id,cust_name,cust_city) values (4,'Harry Warner','Palo Alto'); 

在 Hadoop 上,运行以下 sqoop 命令以导入 customer 表并将数据存储在 Avro 文件中到 HDFS:


hadoop fs -rmr /user/sqoop_data/avro 
sqoop import -Dmapreduce.job.user.classpath.first=true  
--connect jdbc:mysql://localhost:3306/orders   
--driver com.mysql.jdbc.Driver  
--username sales --password xxx  
--target-dir /user/sqoop_data/avro  
--table customer  
--as-avrodatafile  

验证目标 HDFS 文件夹:

[root@sandbox ~]# hadoop fs -ls /user/sqoop_data/avro 
Found 7 items 
-rw-r--r--   1 root hdfs          0 2017-09-09 08:57 /user/sqoop_data/avro/_SUCCESS 
-rw-r--r--   1 root hdfs        472 2017-09-09 08:57 /user/sqoop_data/avro/part-m-00000.avro 
-rw-r--r--   1 root hdfs        475 2017-09-09 08:57 /user/sqoop_data/avro/part-m-00001.avro 
-rw-r--r--   1 root hdfs        476 2017-09-09 08:57 /user/sqoop_data/avro/part-m-00002.avro 
-rw-r--r--   1 root hdfs        478 2017-09-09 08:57 /user/sqoop_data/avro/part-m-00003.avro 

创建一个 Hive 外部表以访问 Avro 文件:

use orders; 
drop table customer ; 
CREATE EXTERNAL TABLE customer  
( 
cust_id INT , 
cust_name  STRING , 
cust_city STRING   
) 
STORED AS AVRO 
location '/user/sqoop_data/avro/'; 

验证 Hive customer 表:

hive> select * from customer; 
OK 
1  Sam James   Austin 
2  Peter Carter      Denver 
3  Doug Smith  Sunnyvale 
4  Harry Warner      Palo Alto 
Time taken: 0.143 seconds, Fetched: 4 row(s) 
hive>  

完美!我们没有错误。我们成功地将源 customer 表导入目标 Hive 表,使用了 Avro 序列化。现在,我们在源表中添加一个列,并再次导入以验证我们可以在没有任何架构更改的情况下访问目标 Hive 表。连接到 MySQL 并添加一个额外的列:

mysql -u sales  -p 

mysql>  
ALTER TABLE customer 
ADD COLUMN cust_state VARCHAR(15) NOT NULL; 

mysql> desc customer; 
+------------+-------------+------+-----+---------+-------+ 
| Field      | Type        | Null | Key | Default | Extra | 
+------------+-------------+------+-----+---------+-------+ 
| cust_id    | int(11)     | NO   | PRI | 0       |       | 
| cust_name  | varchar(20) | NO   |     | NULL    |       | 
| cust_city  | varchar(20) | NO   |     | NULL    |       | 
| CUST_STATE | varchar(15) | YES  |     | NULL    |       | 
+------------+-------------+------+-----+---------+-------+ 
4 rows in set (0.01 sec) 

mysql>  

现在插入行:

INSERT into customer (cust_id,cust_name,cust_city,cust_state) values (5,'Mark Slogan','Huston','TX'); 
INSERT into customer (cust_id,cust_name,cust_city,cust_state) values (6,'Jane Miller','Foster City','CA'); 

在 Hadoop 上,运行以下 sqoop 命令以导入 customer 表,以便追加新的地址列和数据。我使用了 appendwhere "cust_id > 4" 参数来仅导入新行:

sqoop import -Dmapreduce.job.user.classpath.first=true  
--connect jdbc:mysql://localhost:3306/orders   
--driver com.mysql.jdbc.Driver  
--username sales --password xxx  
--table customer  
--append  
--target-dir /user/sqoop_data/avro  
--as-avrodatafile  
--where "cust_id > 4"  

验证 HDFS 文件夹:

[root@sandbox ~]# hadoop fs -ls /user/sqoop_data/avro 
Found 7 items 
-rw-r--r--   1 root hdfs          0 2017-09-09 08:57 /user/sqoop_data/avro/_SUCCESS 
-rw-r--r--   1 root hdfs        472 2017-09-09 08:57 /user/sqoop_data/avro/part-m-00000.avro 
-rw-r--r--   1 root hdfs        475 2017-09-09 08:57 /user/sqoop_data/avro/part-m-00001.avro 
-rw-r--r--   1 root hdfs        476 2017-09-09 08:57 /user/sqoop_data/avro/part-m-00002.avro 
-rw-r--r--   1 root hdfs        478 2017-09-09 08:57 /user/sqoop_data/avro/part-m-00003.avro 
-rw-r--r--   1 root hdfs        581 2017-09-09 09:00 /user/sqoop_data/avro/part-m-00004.avro 
-rw-r--r--   1 root hdfs        586 2017-09-09 09:00 /user/sqoop_data/avro/part-m-00005.avro 

现在,让我们验证我们的目标 Hive 表是否仍然能够访问旧的和新的 Avro 文件:

hive> select * from customer; 
OK 
1  Sam James   Austin 
2  Peter Carter      Denver 
3  Doug Smith  Sunnyvale 
4  Harry Warner      Palo Alto 
Time taken: 0.143 seconds, Fetched: 4 row(s 

太好了!没有错误。尽管如此,一切照旧;现在我们将向 Hive 表添加一个新列,以查看新添加的 Avro 文件:


hive> use orders; 
hive> ALTER TABLE customer ADD COLUMNS (cust_state STRING); 
hive> desc customer; 
OK 
cust_id              int                                          
cust_name            string                                       
cust_city            string                                       
cust_state           string                                       
Time taken: 0.488 seconds, Fetched: 4 row(s 

验证 Hive 表中的新数据:

hive> select * from customer; 
OK 
1  Sam James   Austin      NULL 
2  Peter Carter      Denver      NULL 
3  Doug Smith  Sunnyvale   NULL 
4  Harry Warner      Palo Alto   NULL 
5  Mark Slogan Huston      TX 
6  Jane Miller Foster City CA 
Time taken: 0.144 seconds, Fetched: 6 row(s) 
hive>  

太棒了!看看客户 ID 56。我们可以看到新添加的列(cust_state)及其值。您可以使用相同的技术进行删除列和替换列的实验。现在我们相当清楚地了解了如何使用 Apache Hive 访问数据。在下一节中,我们将学习如何使用 HBase 访问数据,HBase 是一个 NoSQL 数据存储。

Apache HBase

我们刚刚学习了 Hive,这是一个用户可以使用 SQL 命令访问数据的数据库。但是,也有一些数据库用户不能使用 SQL 命令。这些数据库被称为NoSQL 数据存储。HBase 就是一个 NoSQL 数据库。那么,NoSQL 实际上意味着什么呢?NoSQL 不仅仅意味着 SQL。在 HBase 这样的 NoSQL 数据存储中,关系型数据库管理系统(RDBMS)的主要特性,如验证和一致性,被放宽了。另外,RDBMS 或 SQL 数据库与 NoSQL 数据库之间的重要区别在于写入时模式与读取时模式。在写入时模式中,数据在写入表时进行验证,而读取时模式支持在读取数据时进行验证。通过放宽在写入数据时的基本数据验证,NoSQL 数据存储能够支持存储大量数据速度。目前市场上大约有 150 种 NoSQL 数据存储。每种 NoSQL 数据存储都提供一些独特的特性。一些流行的 NoSQL 数据存储包括 HBase、Cassandra、MongoDB、Druid、Apache Kudu 和 Accumulo 等。

您可以在nosql-database.org/上获取所有类型 NoSQL 数据库的详细列表。

HBase 是一个被许多大型公司如 Facebook、Google 等广泛使用的流行 NoSQL 数据库。

HDFS 与 HBase 之间的差异

以下解释了 HDFS 和 HBase 之间的关键差异。Hadoop 建立在 HDFS 之上,支持存储大量(PB 级)数据集。这些数据集通过批处理作业,使用 MapReduce 算法进行访问。为了在如此庞大的数据集中找到数据元素,需要扫描整个数据集。另一方面,HBase 建立在 HDFS 之上,并为大型表提供快速的记录查找(和更新)。HBase 内部将数据存储在 HDFS 上存在的索引 StoreFiles 中,以实现高速查找。

Hive 与 HBase 之间的差异

HBase 是一个数据库管理系统;它支持事务处理和分析处理。Hive 是一个数据仓库系统,只能用于分析处理。HBase 支持低延迟和随机数据访问操作。Hive 仅支持批处理,这导致高延迟。HBase 不支持任何 SQL 接口与表数据进行交互。您可能需要编写 Java 代码来读取和写入 HBase 表中的数据。有时,处理涉及多个数据集连接的数据集的 Java 代码可能非常复杂。但 Hive 支持使用 SQL 进行非常容易的访问,这使得读取和写入其表中的数据变得非常简单。在 HBase 中,数据建模涉及灵活的数据模型和列式数据存储,必须支持数据反规范化。HBase 表的列在将数据写入表时确定。在 Hive 中,数据模型涉及具有固定模式(如 RDBMS 数据模型)的表。

HBase 的关键特性

以下是一些 HBase 的关键特性:

  • 排序行键:在 HBase 中,数据处理通过三个基本操作/API 进行:get、put 和 scan。这三个 API 都使用行键访问数据,以确保数据访问的流畅。由于扫描是在行键的范围内进行的,因此 HBase 根据行键的字典顺序对行进行排序。使用这些排序行键,可以从其起始和终止行键简单地定义扫描。这在单个数据库调用中获取所有相关数据方面非常强大。应用程序开发者可以设计一个系统,通过根据其时间戳查询最近的行来访问最近的数据集,因为所有行都是根据最新的时间戳在表中按顺序存储的。

  • 控制数据分片:HBase 表的行键强烈影响数据分片。表数据按行键、列族和列键的升序排序。良好的行键设计对于确保数据在 Hadoop 集群中均匀分布非常重要。由于行键决定了表行的排序顺序,因此表中的每个区域最终负责行键空间的一部分的物理存储。

  • 强一致性:HBase 更倾向于一致性而不是可用性。它还支持基于每行的 ACID 级语义。当然,这会影响写性能,可能会变慢。总体而言,权衡有利于应用程序开发者,他们将保证数据存储始终具有数据的正确值。

  • 低延迟处理:HBase 支持对存储的所有数据进行快速、随机的读取和写入。

  • 灵活性:HBase 支持任何类型——结构化、半结构化、非结构化。

  • 可靠性:HBase 表数据块被复制多次,以确保防止数据丢失。HBase 还支持容错。即使在任何区域服务器故障的情况下,表数据始终可用于处理。

HBase 数据模型

这些是 HBase 数据模型的关键组件:

  • :在 HBase 中,数据存储在逻辑对象中,称为,该表具有多个行。

  • :HBase 中的行由一个行键和一个或多个列组成。行键用于排序行。目标是按这种方式存储数据,使得相关的行彼此靠近。行键可以是多个列的组合。行键类似于表的主键,必须是唯一的。HBase 使用行键在列中查找数据。例如,customer_id可以是customer表的行键。

  • :HBase 中的列由一个列族和一个列限定符组成。

  • 列限定符:它是表的列名。

  • 单元格:这是行、列族和列限定符的组合,包含一个值和一个时间戳,该时间戳表示值的版本。

  • 列族:它是一组位于同一位置并存储在一起的列的集合,通常出于性能考虑。每个列族都有一组存储属性,例如缓存、压缩和数据编码。

RDBMS 表与列式数据存储之间的差异

我们都知道数据在任何关系型数据库管理系统(RDBMS)表中是如何存储的。它看起来像这样:

ID Column_1 Column_2 Column_3 Column_4
1 A 11 P XX
2 B 12 Q YY
3 C 13 R ZZ
4 D 14 S XX1

列 ID 用作表的唯一/主键,以便从表的其它列访问数据。但在像 HBase 这样的列式数据存储中,相同的表被分为键和值,并按如下方式存储:

Key Value
1 Column_1
1 Column_2
1 Column_3
1 Column_4
2 Column_1
2 Column_2
2 Column_3
2 Column_4
3 Column_1
3 Column_2
3 Column_3
3 Column_4

在 HBase 中,每个表都是一个排序映射格式,其中每个键按升序排序。内部,每个键和值都被序列化并以字节数组格式存储在磁盘上。每个列值通过其对应键进行访问。因此,在前面的表中,我们定义了一个键,它是两个列的组合,行 + 列。例如,为了访问第 1 行的Column_1数据元素,我们必须使用一个键,即行 1 + column_1。这就是为什么行键设计在 HBase 中非常关键。在创建 HBase 表之前,我们必须为每个列决定一个列族。列族是一组列的集合,这些列位于同一位置并一起存储,通常出于性能原因。每个列族都有一组存储属性,例如缓存、压缩和数据编码。例如,在一个典型的CUSTOMER表中,我们可以定义两个列族,即cust_profilecust_address。所有与客户地址相关的列都分配给列族cust_address;所有其他列,即cust_idcust_namecust_age,都分配给列族cust_profile。分配列族后,我们的示例表将如下所示:

Key Value
1 Column_1
1 Column_2
1 Column_3
1 Column_4
2 Column_1
2 Column_2
2 Column_3
2 Column_4
3 Column_1
3 Column_2
3 Column_3
3 Column_4

当向表中插入数据时,HBase 将为每个单元格的每个版本自动添加时间戳。

HBase 架构

如果我们要从 HBase 表中读取数据,我们必须提供一个合适的行 ID,HBase 将根据提供的行 ID 进行查找。HBase 使用以下排序嵌套映射来返回行 ID 的列值:行 ID、列族、列在时间戳和值。HBase 始终部署在 Hadoop 上。以下是一个典型的安装:

它是 HBase 集群的主服务器,负责 RegionServer 的管理、监控和管理,例如将 region 分配给 RegionServer、region 拆分等。在分布式集群中,HMaster 通常运行在 Hadoop NameNode 上。

ZooKeeper 是 HBase 集群的协调器。HBase 使用 ZooKeeper 作为分布式协调服务来维护集群中的服务器状态。ZooKeeper 维护哪些服务器是活跃的和可用的,并提供服务器故障通知。RegionServer 负责 region 的管理。RegionServer 部署在 DataNode 上。它提供读写服务。RegionServer 由以下附加组件组成:

  • Regions:HBase 表通过行键范围水平分割成 region。一个 region 包含表中从 region 的起始键到结束键之间的所有行。预写日志WAL)用于存储尚未存储在磁盘上的新数据。

  • MemStore 是一个写缓存。它存储尚未写入磁盘的新数据。在写入磁盘之前进行排序。每个 region 每个列族有一个 MemStore。Hfile 在磁盘/HDFS 上以排序的键/值形式存储行:

HBase 架构概述

  • HBase 集群由一个活动主服务器和一个或多个备份主服务器组成

  • 该集群包含多个 RegionServer

  • HBase 表始终很大,行被分割成称为region的分区/碎片

  • 每个 RegionServer 托管一个或多个 region

  • HBase 目录被称为 META 表,它存储表 region 的位置

  • ZooKeeper 存储 META 表的位置

  • 在写入过程中,客户端将 put 请求发送到 HRegionServer

  • 数据写入 WAL

  • 然后将数据推入 MemStore 并向客户端发送确认

  • 一旦 MemStore 中积累足够的数据,它就会将数据刷新到 HDFS 上的 Hfile

  • HBase 的压缩过程定期激活,将多个 HFile 合并成一个 Hfile(称为压缩

HBase 行键设计

行键设计是 HBase 表设计的一个非常关键的部分。在键设计期间,必须采取适当的措施以避免热点。在设计不良的键的情况下,所有数据都将被摄入到少数几个节点中,导致集群不平衡。然后,所有读取都必须指向这些少数节点,导致数据读取速度变慢。我们必须设计一个键,以帮助将数据均匀地加载到集群的所有节点上。可以通过以下技术避免热点:

  • 密钥加盐:这意味着在密钥的开头添加一个任意值,以确保行在所有表区域中均匀分布。例如,aa-customer_idbb-customer_id等。

  • 密钥哈希:密钥可以被哈希,哈希值可以用作行键,例如,HASH(customer_id)

  • 反向时间戳密钥:在这种技术中,您必须定义一个常规密钥,然后将其与一个反向时间戳相关联。时间戳必须通过从任意最大值中减去它来反转,然后附加到密钥上。例如,如果customer_id是您的行 ID,则新的密钥将是customer_id + 反向时间戳。

设计 HBase 表时的以下是一些指南:

  • 每个表定义不超过两个列族

  • 尽可能保持列族名称尽可能小

  • 尽可能保持列名尽可能小

  • 尽可能保持行键长度尽可能小

  • 不要将行版本设置在较高水平

  • 表不应超过 100 个区域

示例 4 – 从 MySQL 表加载数据到 HBase 表

我们将使用我们之前创建的相同的customer表:

mysql -u sales -p
mysql> use orders;
mysql> select * from customer;
+---------+--------------+--------------+------------+
| cust_id | cust_name | cust_city | InsUpd_on |
+---------+--------------+--------------+------------+
| 1 | Sam James | Austin | 1505095030 |
| 2 | Peter Carter | Denver | 1505095030 |
| 3 | Doug Smith | Sunnyvale | 1505095030 |
| 4 | Harry Warner | Palo Alto | 1505095032 |
| 5 | Jen Turner | Cupertino | 1505095036 |
| 6 | Emily Stone | Walnut Creek | 1505095038 |
| 7 | Bill Carter | Greenville | 1505095040 |
| 8 | Jeff Holder | Dallas | 1505095042 |
| 10 | Mark Fisher | Mil Valley | 1505095044 |
| 11 | Mark Fisher | Mil Valley | 1505095044 |
+---------+--------------+--------------+------------+
10 rows in set (0.00 sec)

启动 HBase 并在 HBase 中创建一个customer表:

hbase shell
create 'customer','cf1'

使用 Sqoop 在 HBase 中加载数据库customer表数据:

hbase
sqoop import --connect jdbc:mysql://localhost:3306/orders --driver com.mysql.jdbc.Driver --username sales --password sales1 --table customer --hbase-table customer --column-family cf1 --hbase-row-key cust_id

验证 HBase 表:

hbase shell
scan 'customer'

您必须看到 HBase 表中的所有 11 行。

示例 5 – 从 MySQL 表增量加载数据到 HBase 表

mysql -u sales -p
mysql> use orders;

插入一个新客户并更新现有客户:

mysql> Update customer set cust_city = 'Dublin', InsUpd_on = '1505095065' where cust_id = 4;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql> INSERT into customer (cust_id,cust_name,cust_city,InsUpd_on) values (12,'Jane Turner','Glen Park',1505095075);
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from customer;
+---------+--------------+--------------+------------+
| cust_id | cust_name | cust_city | InsUpd_on |
+---------+--------------+--------------+------------+
| 1 | Sam James | Austin | 1505095030 |
| 2 | Peter Carter | Denver | 1505095030 |
| 3 | Doug Smith | Sunnyvale | 1505095030 |
| 4 | Harry Warner | Dublin | 1505095065 |
| 5 | Jen Turner | Cupertino | 1505095036 |
| 6 | Emily Stone | Walnut Creek | 1505095038 |
| 7 | Bill Carter | Greenville | 1505095040 |
| 8 | Jeff Holder | Dallas | 1505095042 |
| 10 | Mark Fisher | Mil Valley | 1505095044 |
| 11 | Mark Fisher | Mil Valley | 1505095044 |
| 12 | Jane Turner | Glen Park | 1505095075 | +---------+--------------+--------------+------------+
11 rows in set (0.00 sec)
mysql>

示例 6 – 将 MySQL 客户更改数据加载到 HBase 表中

在这里,我们使用了InsUpd_on列作为我们的 ETL 日期:

sqoop import --connect jdbc:mysql://localhost:3306/orders --driver com.mysql.jdbc.Driver --username sales --password sales1 --table customer --hbase-table customer --column-family cf1 --hbase-row-key cust_id --append -- -m 1 --where "InsUpd_on > 1505095060"

hbase shell
hbase(main):010:0> get 'customer', '4'
COLUMN          CELL
cf1:InsUpd_on   timestamp=1511509774123, value=1505095065
cf1:cust_city   timestamp=1511509774123, value=Dublin cf1:cust_name   timestamp=1511509774123, value=Harry Warner
3 row(s) in 0.0200 seconds

hbase(main):011:0> get 'customer', '12'
COLUMN           CELL
cf1:InsUpd_on    timestamp=1511509776158, value=1505095075
cf1:cust_city    timestamp=1511509776158, value=Glen Park
cf1:cust_name    timestamp=1511509776158, value=Jane Turner
3 row(s) in 0.0050 seconds

hbase(main):012:0>

示例 7 – Hive 与 HBase 集成

现在,我们将使用 Hive 外部表访问 HBase customer表:

create external table customer_hbase(cust_id string, cust_name string, cust_city string, InsUpd_on string)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'with serdeproperties ("hbase.columns.mapping"=":key,cf1:cust_name,cf1:cust_city,cf1:InsUpd_on")tblproperties("hbase.table.name"="customer");

hive> select * from customer_hbase;
OK
1 Sam James Austin 1505095030
10 Mark Fisher Mil Valley 1505095044
11 Mark Fisher Mil Valley 1505095044
12 Jane Turner Glen Park 1505095075
2 Peter Carter Denver 1505095030
3 Doug Smith Sunnyvale 1505095030
4 Harry Warner Dublin 1505095065
5 Jen Turner Cupertino 1505095036
6 Emily Stone Walnut Creek 1505095038
7 Bill Carter Greenville 1505095040
8 Jeff Holder Dallas 1505095042
Time taken: 0.159 seconds, Fetched: 11 row(s)
hive>

摘要

在本章中,我们看到了如何使用称为 Hadoop SQL 接口的 Hive 存储和访问数据。我们研究了 Hive 中的各种分区和索引策略。工作示例帮助我们理解了在 Hive 中使用 Avro 访问 JSON 数据和模式演变。在第二部分中,我们研究了名为 HBase 的 NoSQL 数据存储以及它与 RDBMS 的区别。HBase 表的行设计对于平衡读写以避免区域热点至关重要。必须记住本章中讨论的 HBase 表设计最佳实践。工作示例展示了数据导入 HBase 表和其与 Hive 集成的简单路径。

在下一章中,我们将探讨设计实时数据分析的工具和技术。

第六章:设计实时流数据处理管道

本书的前三章都涉及批数据。在了解了 Hadoop 的安装、数据摄取工具和技术以及数据存储后,让我们转向数据流。我们不仅将探讨如何处理实时数据流,还将探讨围绕它们设计管道的方法。

在本章中,我们将涵盖以下主题:

  • 实时流概念

  • 实时流组件

  • Apache Flink 与 Spark

  • Apache Spark 与 Storm

实时流概念

在以下几节中,我们将了解与实时流应用程序相关的一些关键概念。

数据流

数据流是从一端到另一端、从发送者到接收者、从生产者到消费者的连续数据流动。数据的速度和量可能不同;它可能每秒 1GB 的数据,也可能每秒或每分钟 1KB 的数据。

批处理与实时数据处理

在批处理中,数据以批次的形式收集,每个批次都会被发送进行处理。批次的间隔可以是任何时间,从一天到一分钟不等。在当今的数据分析和商业智能领域,数据不会超过一天进行批处理。否则,业务团队将无法对日常业务情况有任何洞察。例如,企业数据仓库团队可能会收集过去 24 小时内所有订单,并将所有收集到的订单发送到分析引擎进行报告。

批次也可以是一分钟。在 Spark 框架中(我们将在第七章学习 Spark,大规模数据处理框架),数据以微批次的形式进行处理。

在实时处理中,数据(事件)在源端产生事件后立即从生产者(发送者)传输到消费者(接收者)。例如,在电子商务网站上,当客户在该网站上下单时,订单会立即在分析引擎中处理。优势是,该公司的业务团队可以实时(在几毫秒或亚毫秒内)全面了解其业务。这将帮助他们调整促销活动以增加收入,这一切都在实时进行。

下面的图像解释了流处理架构:

复杂事件处理

复杂事件处理CEP)是结合来自多个来源的数据以发现复杂关系或模式的事件处理。CEP 的目标是尽快识别有意义的事件(如机会或威胁)并对它们做出响应。从根本上说,CEP 是关于将业务规则应用于流事件数据。例如,CEP 用于诸如股票交易、欺诈检测、医疗索赔处理等用例。

下面的图像解释了流处理架构:

流处理组件

持续可用性

任何实时应用都应始终可用,没有任何中断。事件收集、处理和存储组件应配置为具有高可用性的假设。任何组件的故障都可能导致业务运行的重大中断。例如,在信用卡欺诈检测应用中,所有欺诈交易都需要被拒绝。如果应用在途中停止并且无法拒绝欺诈交易,那么这将会导致巨大的损失。

低延迟

在任何实时应用中,事件应在几毫秒内从源头流向目标。源头收集事件,处理框架将事件移动到其目标数据存储,在那里可以进一步分析以发现趋势和模式。所有这些都应该实时发生,否则可能会影响业务决策。例如,在信用卡欺诈检测应用中,预期所有传入的交易都应该被分析以查找可能的欺诈交易,如果有。如果流处理所需的时间超过预期,那么这些交易可能通过系统,给业务造成重大损失。

可扩展的处理框架

硬件故障可能会干扰流处理应用。为了避免这种常见的场景,我们始终需要一个提供内置 API 以支持连续计算、容错事件状态管理、故障时的检查点功能、在途聚合、窗口等功能的处理框架。幸运的是,所有最近的 Apache 项目,如 Storm、Spark、Flink 和 Kafka,都支持所有这些功能以及更多。开发者可以使用 Java、Python 和 Scala 使用这些 API。

水平可扩展性

流处理平台应支持水平可扩展性。这意味着在数据负载更高时,向集群添加更多物理服务器以保持吞吐量 SLA。这样,通过添加更多节点而不是向现有服务器添加更多 CPU 和内存来提高处理性能;这被称为垂直可扩展性

存储

流的优选格式是键值对。这种格式由 JSON 和 Avro 格式很好地表示。首选的持久化键值类型数据是 NoSQL 数据存储,如 HBase 和 Cassandra。目前市场上总共有 100 个 NoSQL 开源数据库。选择正确的数据库,一个支持实时事件存储的数据库,是非常具有挑战性的,因为所有这些数据库都为数据持久性提供了一些独特的功能。一些例子包括模式无关性、高度可分布式、支持商品硬件、数据复制等。

下面的图像解释了所有流处理组件:

在本章中,我们将详细讨论消息队列和流处理框架。在下一章中,我们将重点关注数据索引技术。

实时流组件

在以下章节中,我们将介绍一些重要的实时流组件。

消息队列

消息队列允许您发布和订阅事件/记录的流。在我们的实时流架构中,我们可以使用各种替代方案作为消息队列。例如,有 RabbitMQ、ActiveMQ 和 Kafka。在这些中,Kafka 由于其各种独特的特性而获得了巨大的流行。因此,我们将详细讨论 Kafka 的架构。关于 RabbitMQ 和 ActiveMQ 的讨论超出了本书的范围。

那么 Kafka 是什么呢?

Kafka 是一个快速、可扩展、持久和容错的发布-订阅消息系统。Apache Kafka 是一个开源的流处理项目。它提供了一个统一、高吞吐量且低延迟的平台,用于处理实时数据流。它提供了一个分布式存储层,支持大规模可扩展的 pub/sub 消息队列。Kafka Connect 通过连接到外部系统支持数据导入和导出。Kafka Streams 提供了 Java API 进行流处理。Kafka 与 Apache Spark、Apache Cassandra、Apache HBase、Apache Spark 等结合使用,以进行实时流处理。

Apache Kafka 最初由 LinkedIn 开发,并在 2011 年初开源。2014 年 11 月,LinkedIn 上几位从事 Kafka 工作的工程师创建了一家名为 Confluent 的新公司,专注于 Kafka。请使用此 URL www.confluent.io/ 了解更多关于 Confluent 平台的信息。

Kafka 特性

Kafka 有以下特性:

  • Kafka 是可扩展的:Kafka 集群由多个物理服务器组成,这有助于分散数据负载。在需要额外吞吐量的情况下,可以轻松扩展,因为可以添加更多服务器以保持 SLA。

  • Kafka 是持久的:在流处理过程中,Kafka 将消息持久化到持久存储中。这种存储可以是服务器本地磁盘或 Hadoop 集群。在消息处理失败的情况下,可以从磁盘访问消息并重新播放以再次处理消息。默认情况下,消息存储七天;这可以进一步配置。

  • Kafka 是可靠的:Kafka 通过一个名为数据复制的功能提供消息可靠性。每条消息至少复制三次(这是可配置的),以便在数据丢失的情况下,可以使用消息的副本进行处理。

  • Kafka 支持高性能吞吐量:由于其独特的架构、分区、消息存储和水平可扩展性,Kafka 有助于每秒处理数以 TB 计的数据。

Kafka 架构

以下图像显示了 Kafka 的架构:

Kafka 架构组件

让我们详细看看每个组件:

  • 生产者:生产者将消息发布到特定的 Kafka 主题。生产者可以为每条消息记录附加一个键。默认情况下,生产者以轮询方式将消息发布到主题分区。有时,可以根据消息键的哈希值配置生产者将消息写入特定的主题分区。

  • 主题:所有消息都存储在一个主题中。主题是一个类别或数据源名称,记录被发布到其中。主题可以比作关系数据库中的一个表。多个消费者可以订阅单个主题以消费消息记录。

  • 分区:主题被划分为多个分区。Kafka 通过将主题划分为分区并将每个分区放置在 Kafka 集群中单独的代理(服务器)上,提供了主题并行性。每个分区在磁盘上有一个独立的分区日志,其中存储消息。每个分区包含一个有序的、不可变的消息序列。每个消息被分配一个唯一的序列号,称为 偏移量。消费者可以从分区的任何位置读取消息——从开始处或从任何偏移量。

  • 消费者:消费者订阅一个主题并消费消息。为了提高可伸缩性,同一应用中的消费者可以被分组到一个消费者组中,其中每个消费者可以读取来自唯一分区的消息。

  • 代理:Kafka 被划分为多个称为 代理 的服务器。所有代理的总和被称为 Kafka 集群。Kafka 代理处理来自生产者的消息写入和来自消费者的消息读取。Kafka 代理存储所有来自生产者的消息。默认周期为七天。这个周期(保留周期)可以根据需求进行配置。保留周期直接影响到 Kafka 代理的本地存储。如果配置了较长的保留周期,则需要更多的存储空间。在保留周期结束后,消息将被自动丢弃。

  • Kafka Connect:根据 Kafka 文档,Kafka Connect 允许构建和运行可重用的生产者或消费者,它们将 Kafka 主题连接到现有的应用或数据系统中。例如,连接到关系数据库的连接器可能会捕获表中每个更改。

  • Kafka Streams:流 API 允许一个应用作为流处理器,从一个或多个主题中消费输入流,并将输出流发送到一个或多个输出主题,有效地将输入流转换为输出流。

Kafka Connect 深入了解

Kafka Connect 是 Confluent 平台的一部分。它与 Kafka 集成。使用 Kafka Connect,从多个来源到多个目标构建数据管道变得非常容易。源连接器从另一个系统(例如,从关系型数据库到 Kafka)导入数据,而目标连接器导出数据(例如,Kafka 主题的内容到 HDFS 文件)。

Kafka Connect 架构

以下图像展示了 Kafka Connect 的架构:

数据流可以解释如下:

  • 各种来源连接到Kafka Connect 集群Kafka Connect 集群从这些来源拉取数据。

  • Kafka Connect 集群由一组执行连接器和任务的容器化工作进程组成,这些任务自动协调彼此以分配工作并提供可扩展性和容错性。

  • Kafka Connect 集群将数据推送到Kafka 集群

  • Kafka 集群将数据持久化到代理本地磁盘或 Hadoop。

  • 流处理应用如 Storm、Spark Streaming 和 Flink 从Kafka 集群中拉取数据用于流转换、聚合、连接等操作。这些应用可以将数据回传到 Kafka 或持久化到外部数据存储,如 HBase、Cassandra、MongoDB、HDFS 等。

  • Kafka Connect 集群Kafka 集群中拉取数据并将其推送到 Sinks。

  • 用户可以扩展现有的 Kafka 连接器或开发全新的连接器。

Kafka Connect 工作进程的独立模式与分布式模式

用户可以通过两种方式运行 Kafka Connect:独立模式或分布式模式。

在独立模式下,单个进程运行所有连接器。它不具有容错性。由于它只使用单个进程,因此不具有可扩展性。通常,它对用户在开发和测试目的上很有用。

在分布式模式下,多个工作进程运行 Kafka Connect。在这种模式下,Kafka Connect 可扩展且容错,因此在生产部署中使用。

让我们更深入地了解 Kafka 和 Kafka Connect(独立模式)。在这个例子中,我们将执行以下操作:

  1. 安装 Kafka

  2. 创建一个主题

  3. 发送几条消息以验证生产者和消费者

  4. Kafka Connect-File-source 和 file-sink

  5. Kafka Connect-JDBC -Source

以下图像展示了使用Kafka Connect的用例:

让我们通过运行一些示例来了解 Kafka 和 Kafka Connect 是如何工作的。有关更多详细信息,请使用以下链接访问 Kafka Confluent 的文档:docs.confluent.io/current/

安装 Kafka

让我们执行以下步骤来安装 Kafka:

  1. www.confluent.io/download/下载 Confluent

  2. 点击 Confluent 开源版

  3. tar.gz下载文件confluent-oss-4.0.0-2.11.tar.gz并执行以下操作:

tar xvf confluent-oss-4.0.0-2.11.tar.gz
cd /opt/confluent-4.0.0/etc/kafka
vi server.properties
  1. 取消注释listeners=PLAINTEXT://:9092

  2. 启动 Confluent:

$ ./bin/confluent start schema-registry
  1. 启动zookeeper
zookeeper is [UP]
  1. 启动kafka
kafka is [UP]
  1. 启动 schema-registry
schema-registry is [UP]
A4774045:confluent-4.0.0 m046277$

创建主题

执行以下步骤以创建主题:

  1. 列出现有主题

  2. 打开另一个终端并输入以下命令:

/opt/confluent-4.0.0
bin/kafka-topics --list --zookeeper localhost:2181
_schemas
  1. 创建一个主题:
bin/kafka-topics --create --zookeeper localhost:2181 --replication-factor 1 --partitions 3 --topic my-first-topic

Created topic "my-first-topic"
  1. 仔细检查新创建的主题:
bin/kafka-topics --list --zookeeper localhost:2181
_schemas
my-first-topic

生成消息以验证生产者和消费者

执行以下步骤以生成消息以验证生产者和消费者:

  1. 向 Kafka my-first-topic 发送消息
bin/kafka-console-producer --broker-list localhost:9092 --topic my-first-topic
test1
test2
test3
  1. 启动消费者以消费消息

  2. 打开另一个终端并输入以下命令:

$ bin/kafka-console-consumer --bootstrap-server localhost:9092 --topic my-first-topic --from-beginning
test3
test2
test1
  1. 前往生产者终端并输入另一条消息:
test4
  1. 验证消费者终端以检查是否可以看到消息 test4

使用文件源和目标连接器的 Kafka Connect

让我们看看如何使用文件源和目标创建主题,以下是一些帮助:

cd /opt/confluent-4.0.0/etc/kafka
vi connect-file-test-source.properties
name=local-file-source
connector.class=FileStreamSource
tasks.max=1
file=/opt/kafka_2.10-0.10.2.1/source-file.txt
topic=my-first-topic
vi connect-file-test-sink.properties
name=local-file-sink
connector.class=FileStreamSink
tasks.max=1
file=/opt/kafka_2.10-0.10.2.1/target-file.txt
topics=my-first-topic

执行以下步骤:

  1. 启动源连接器和目标连接器:
cd /opt/confluent-4.0.0
$ ./bin/connect-standalone config/connect-standalone.properties config/connect-file-test-source.properties config/connect-file-test-sink.properties

echo 'test-kafka-connect-1' >> source-file.txt
echo 'test-kafka-connect-2' >> source-file.txt
echo 'test-kafka-connect-3' >> source-file.txt
echo 'test-kafka-connect-4' >> source-file.txt
  1. 仔细检查 Kafka 主题是否已收到消息:
$ ./bin/kafka-console-consumer.sh --zookeeper localhost:2181 --from-beginning --topic my-first-topic

test3
test1
test4

{"schema":{"type":"string","optional":false},"payload":"test-kafka-connect-1"}
{"schema":{"type":"string","optional":false},"payload":"test-kafka-connect-2"}
{"schema":{"type":"string","optional":false},"payload":"test-kafka-connect-3"}
{"schema":{"type":"string","optional":false},"payload":"test-kafka-connect-4"}

test2
  1. 验证 target-file.txt
$ cat target-file.txt

{"schema":{"type":"string","optional":false},"payload":"test-kafka-connect-1"}
{"schema":{"type":"string","optional":false},"payload":"test-kafka-connect-2"}
{"schema":{"type":"string","optional":false},"payload":"test-kafka-connect-3"}
{"schema":{"type":"string","optional":false},"payload":"test-kafka-connect-4"}

使用 JDBC 和文件目标连接器的 Kafka Connect

以下图像显示了如何将数据库表中的所有记录推送到文本文件:

让我们使用 Kafka Connect 实现前面的示例:

  1. 安装 SQLite:
$ sqlite3 firstdb.db

SQLite version 3.16.0 2016-11-04 19:09:39
Enter ".help" for usage hints.

sqlite>
sqlite> CREATE TABLE customer(cust_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, cust_name VARCHAR(255));
sqlite> INSERT INTO customer(cust_id,cust_name) VALUES(1,'Jon');
sqlite> INSERT INTO customer(cust_id,cust_name) VALUES(2,'Harry');
sqlite> INSERT INTO customer(cust_id,cust_name) VALUES(3,'James');
sqlite> select * from customer;

1|Jon
2|Harry
3|James
  1. 配置 JDBC 源连接器:
cd /opt/confluent-4.0.0
vi ./etc/kafka-connect-jdbc/source-quickstart-sqlite.properties
name=test-sqlite-jdbc-autoincrement
connector.class=io.confluent.connect.jdbc.JdbcSourceConnector
tasks.max=1
connection.url=jdbc:sqlite:firstdb.db
mode=incrementing
incrementing.column.name=cust_id
topic.prefix=test-sqlite-jdbc-
  1. 配置文件目标连接器:
cd /opt/confluent-4.0.0
vi etc/kafka/connect-file-sink.properties
name=local-file-sink
connector.class=FileStreamSink
tasks.max=1
file=/opt/confluent-4.0.0/test.sink.txt
topics=test-sqlite-jdbc-customer
  1. 启动 Kafka Connect(.jdbs 源和文件目标连接器):
./bin/connect-standalone ./etc/schema-registry/connect-avro-standalone.properties ./etc/kafka-connect-jdbc/source-quickstart-sqlite.properties ./etc/kafka/connect-file-sink.properties
  1. 验证消费者:
$ ./bin/kafka-avro-console-consumer --new-consumer --bootstrap-server localhost:9092 --topic test-sqlite-jdbc-customer --from-beginning

--new-consumer 选项已弃用,将在未来的主要版本中删除。如果提供了 --bootstrap-server 选项,则默认使用新消费者:

{"cust_id":1,"cust_name":{"string":"Jon"}}
{"cust_id":2,"cust_name":{"string":"Harry"}}
{"cust_id":3,"cust_name":{"string":"James"}}
  1. 验证目标文件:
tail -f /opt/confluent-4.0.0/test.sink.txt

Struct{cust_id=1,cust_name=Jon}
Struct{cust_id=2,cust_name=Harry}
Struct{cust_id=3,cust_name=James}
  1. 在客户表中插入更多记录:
sqlite> INSERT INTO customer(cust_id,cust_name) VALUES(4,'Susan');
sqlite> INSERT INTO customer(cust_id,cust_name) VALUES(5,'Lisa');
  1. 验证目标文件:
tail -f /opt/confluent-4.0.0/test.sink.txt

你将在目标文件中看到所有客户记录(cust_id)。使用前面的示例,你可以自定义并实验任何其他目标连接器。

以下表格展示了 Confluent 平台上可用的 Kafka 连接器(由 Confluent 开发并完全支持):

连接器名称 源/目标
JDBC 源和目标
HDFS 目标
Elasticsearch 目标
Amazon S3 目标

关于 Confluent 其他认证连接器的更多信息,请使用此 URL:www.confluent.io/product/connectors/

你必须已经观察到 Kafka Connect 是一个基于配置的流处理框架。这意味着我们只需要配置源和目标连接器文件。我们不需要使用 Java 或 Scala 等低级语言编写任何代码。但现在,让我们转向另一个流行的实时流处理框架,称为 Apache Storm。让我们了解一些 Apache Storm 的酷特性。

Apache Storm

Apache Storm 是一个免费且开源的分布式实时流处理框架。在撰写本书时,Apache Storm 的稳定发布版本为 1.0.5。Storm 框架主要使用 Clojure 编程语言编写。最初,它是由 Nathan Marz 和 Backtype 团队创建和开发的。该项目后来被 Twitter 收购。

在他关于 Storm 框架的一次演讲中,Nathan Marz 谈到了使用任何框架(如 Storm)的流处理应用程序。这些应用程序涉及队列和工作线程。一些数据源线程将消息写入队列,而其他线程则从队列中取出这些消息并写入目标数据存储。这里的主要缺点是源线程和目标线程不匹配各自的数据负载,这导致数据堆积。这也导致了数据丢失和额外的线程维护。

为了避免上述挑战,Nathan Marz 提出了一种优秀的架构,该架构将源线程和工作线程抽象为 Spouts 和 Bolts。这些 Spouts 和 Bolts 被提交到拓扑框架,该框架负责整个流处理。

Apache Storm 的功能

Apache Storm 是分布式的。在流的工作负载增加的情况下,可以向 Storm 集群添加多个节点以添加更多的工作者和更多的处理能力来处理。

它是一个真正的实时流处理系统,支持低延迟。事件可以从源到目标在毫秒、秒或分钟内到达,具体取决于用例。

Storm 框架支持多种编程语言,但 Java 是首选。Storm 是容错的。即使集群中的任何节点失败,它也会继续运行。Storm 是可靠的。它支持至少一次或恰好一次的处理。

使用 Storm 框架没有复杂性。对于更详细的信息,请参阅 Storm 文档:storm.apache.org/releases/1.0.4/index.html

Storm 拓扑

以下图像显示了典型的Storm 拓扑

图片

Storm 拓扑组件

以下章节解释了 Storm 拓扑的所有组件:

  • 拓扑:拓扑是由流分组连接的 spouts 和 bolts 组成的DAG(有向无环图)。拓扑会持续运行,直到被终止。

  • :流是一个无界的元组序列。元组可以是任何数据类型。它支持所有 Java 数据类型。

  • 流分组:流分组决定了哪个 bolt 从 spout 接收元组。基本上,这些都是关于流如何在不同的 bolt 之间流动的策略。以下是 Storm 中内置的流分组。

  • 随机分组:这是一个默认的分组策略。元组被随机分布,每个 bolt 都会得到相同数量的流进行处理。

  • 字段分组: 在这种策略中,流字段的相同值将被发送到同一个 Bolt。例如,如果所有元组都是按customer_id分组的,那么相同customer_id的所有元组将被发送到同一个 Bolt 任务,而另一个customer_id的所有元组将被发送到另一个 Bolt 任务。

  • 全部分组: 在全部分组中,每个元组被发送到每个 Bolt 任务。当需要在同一组数据上执行两个不同的函数时,可以使用它。在这种情况下,流可以被复制,每个函数可以在数据的每个副本上计算。

  • 直接分组: 这是一个特殊的分组类型。在这里,开发者可以在元组本身发射的组件中定义分组逻辑。元组的生产者决定哪个消费者的任务将接收这个元组。

  • 自定义分组: 开发者可以通过实现CustomGrouping方法来决定实现自己的分组策略。

  • Spout: Spout 连接到数据源并将流数据摄入到 Storm 拓扑中。

  • Bolt: 一个喷口向 Bolt 发送一个元组。Bolt 负责事件转换、将事件与其他事件连接、过滤、聚合和窗口化。它将元组发送到另一个 Bolt 或持久化到目标。在拓扑中所有处理都在 Bolt 中完成。Bolt 可以执行从过滤到函数、聚合、连接、与数据库通信等任何操作。

  • Storm 集群: 下图显示了Storm 集群的所有组件:

图片

  • Storm 集群节点: Storm 集群的三个主要节点是 Nimbus、Supervisor 和 ZooKeeper。以下部分将详细解释所有组件。

  • Nimbus 节点: 在 Storm 中,这是 Storm 集群的主节点。它将代码分发到集群并启动工作任务。基本上,它将任务分配给集群中的每个节点。它还监控每个提交作业的状态。在作业失败的情况下,Nimbus 将作业重新分配到集群中的不同管理节点。如果 Nimbus 不可用,工作进程仍然会继续运行。然而,没有 Nimbus,当需要时,工作进程不会被重新分配到其他机器。在节点不可用的情况下,分配给该节点的任务将超时,Nimbus 将把这些任务重新分配到其他机器。在 Nimbus 和 Supervisor 都不可用的情况下,它们需要像什么都没发生一样重新启动,并且不会影响任何工作进程。

  • 管理节点: 在 Storm 中,这是一个从节点。它通过 ZooKeeper 与 Nimbus 通信。它在其自身的管理节点中启动和停止工作进程。例如,如果管理节点发现某个特定的工作进程已死亡,则它将立即重启该工作进程。如果管理节点在尝试几次后无法重启工作进程,则它将向 Nimbus 报告此情况,Nimbus 将在不同的管理节点上重启该工作进程。

  • Zookeeper 节点:它在 Storm 集群中的主节点(Nimbus)和从节点(supervisors)之间充当协调器。在生产环境中,通常设置一个包含三个 Zookeeper 实例(节点)的 Zookeeper 集群。

在单节点集群上安装 Storm

以下是在单机上安装 Storm 集群的步骤:

  1. 安装 jdk。确保您已安装 1.8:
$ java -version

您应该看到以下输出:

openjdk version "1.8.0_141"
OpenJDK Runtime Environment (build 1.8.0_141-b16)
OpenJDK 64-Bit Server VM (build 25.141-b16, mixed mod
  1. 创建一个文件夹以下载 Storm 的 .tar 文件:
$ mkdir /opt/storm
$ cd storm
  1. 创建一个文件夹以持久化 Zookeeper 和 Storm 数据:
$ mkdir /usr/local/zookeeper/data
$ mkdir /usr/local/storm/data
  1. 下载 Zookeeper 和 Storm:
$ wget http://apache.osuosl.org/zookeeper/stable/zookeeper-3.4.10.tar.gz
$ gunzip zookeeper-3.4.10.tar.gz
$ tar -xvf zookeeper-3.4.10.tar
$ wget http://mirrors.ibiblio.org/apache/storm/apache-storm-1.0.5/apache-storm-1.0.5.tar.gz
$ gunzip apache-storm-1.0.5.tar.gz
$ tar -xvf apache-storm-1.0.5.tar
  1. 配置 Zookeeper 并将以下内容设置到 Zookeeper (zoo.cfg):
$ cd zookeeper-3.4.10
$ vi con/zoo.cfg
tickTime = 2000
dataDir = /usr/local/zookeeper/data
clientPort = 2181
  1. 按照以下方式配置 Storm:
$ cd /opt/ apache-storm-1.0.5
$ vi conf/storm.yaml
  1. 添加以下内容:
storm.zookeeper.servers:
 - "127.0.0.1"
 nimbus.host: "127.0.0.1"
 storm.local.dir: "/usr/local/storm/data"
 supervisor.slots.ports:
 - 6700
 - 6701
 - 6702
 - 6703

(对于额外的工作者,添加更多端口,例如 6704 等)

  1. 启动 Zookeeper:
$ cd /opt/zookeeper-3.4.10
$ bin/zkServer.sh start &amp;amp;
  1. 启动 Nimbus:
$ cd /opt/ apache-storm-1.0.5
$ bin/storm nimbus &amp;amp;
  1. 启动 Supervisor:
$ bin/storm supervisor &amp;amp;
  1. 在 Storm UI 中验证安装:
http://127.0.0.1:8080

开发 Storm 的实时流式管道

在本节中,我们将创建以下三个管道:

  • Kafka - Storm - MySQL 的流式管道

  • Kafka - Storm - HDFS - Hive 的流式管道

在本节中,我们将了解数据流如何从 Kafka 流向 Storm 到 MySQL 表。

整个管道将按以下方式工作:

  1. 我们将使用 Kafka 控制台生产者 API 在 Kafka 中摄取客户记录(customer_firstnamecustomer_lastname)。

  2. 之后,Storm 将从 Kafka 拉取消息。

  3. 将建立到 MySQL 的连接。

  4. Storm 将使用 MySQL-Bolt 将记录摄取到 MySQL 表中。MySQL 将自动生成 customer_id

  5. 将使用 SQL 访问 MySQL 表数据(customer_idcustomer_firstnamecustomer_lastname)。

我们将开发以下 Java 类:

  • MysqlConnection.java:此类将与本地 MySQL 数据库建立连接。

  • MysqlPrepare.java:此类将准备要插入数据库的 SQL 语句。

  • MysqlBolt:这是一个 storm bolt 框架,用于从 Kafka 发射元组到 MySQL。

  • MySQLKafkaTopology:这是一个 Storm Topology 框架,用于构建将 spouts(Kafka)绑定到 Bolts(MySQL)的工作流程。在这里,我们使用本地 Storm 集群。

从 Kafka 到 Storm 到 MySQL 的管道流式传输

以下图像显示了管道的组件。在这个管道中,我们将学习消息如何实时从 Kafka 流向 Storm 到 MySQL:

图片

以下是为 MysqlConnection.java 编写的完整 Java 代码:

package com.StormMysql;
import java.sql.Connection;
import java.sql.DriverManager;
public class MysqlConnection {
private String server_name;
 private String database_name;
 private String user_name;
 private String password;
 private Connection connection;

public MysqlConnection(String server_name, String database_name, String user_name, String password)
 {
 this.server_name=server_name;
 this.database_name=database_name;
 this.user_name=user_name;
 this.password=password;
 }

public Connection getConnection()
 {
 return connection;
 }

public boolean open()
 {
 boolean successful=true;
 try{
 Class.*forName*("com.mysql.jdbc.Driver");
 connection = DriverManager.*getConnection*("jdbc:mysql://"+server_name+"/"+database_name+"?"+"user="+user_name+"&amp;amp;password="+password);
 }catch(Exception ex)
 {
 successful=false;
 ex.printStackTrace();
 }
 return successful;
 }

public boolean close()
 {
 if(connection==null)
 {
 return false;
 }

boolean successful=true;
 try{
 connection.close();
 }catch(Exception ex)
 {
 successful=false;
 ex.printStackTrace();
 }

return successful;
 }
 }

以下是为 MySqlPrepare.java 编写的完整代码:

package com.StormMysql;
import org.apache.storm.tuple.Tuple;
import java.sql.PreparedStatement;
public class MySqlPrepare {
 private MysqlConnection conn;

public MySqlPrepare(String server_name, String database_name, String user_name, String password)
 {
 conn = new MysqlConnection(server_name, database_name, user_name, password);
 conn.open();
 }

public void persist(Tuple tuple)
 {
 PreparedStatement statement=null;
 try{
 statement = conn.getConnection().prepareStatement("insert into customer (cust_id,cust_firstname, cust_lastname) values (default, ?,?)");
 statement.setString(1, tuple.getString(0));

statement.executeUpdate();
 }catch(Exception ex)
 {
 ex.printStackTrace();
 }finally {
 if(statement != null)
 {
 try{
 statement.close();
 }catch(Exception ex)
 {
 ex.printStackTrace();
 }
 }
 }
 }

public void close()
 {
 conn.close();
 }
 }

以下是为 MySqlBolt.java 编写的完整代码:

package com.StormMysql;

import java.util.Map;

import org.apache.storm.topology.BasicOutputCollector;
 import org.apache.storm.topology.OutputFieldsDeclarer;
 import org.apache.storm.topology.base.BaseBasicBolt;
 import org.apache.storm.tuple.Fields;
 import org.apache.storm.tuple.Tuple;
 import org.apache.storm.tuple.Values;
 import org.apache.storm.task.TopologyContext;
 import java.util.Map;

public class MySqlBolt extends BaseBasicBolt {

private static final long *serialVersionUID* = 1L;
 private MySqlPrepare mySqlPrepare;

@Override
 public void prepare(Map stormConf, TopologyContext context)
 {
 mySqlPrepare=new MySqlPrepare("localhost", "sales","root","");
 }

public void execute(Tuple input, BasicOutputCollector collector) {
 *//* *TODO Auto-generated method stub* mySqlPrepare.persist(input);
 *//System.out.println(input);* }
@Override
 public void cleanup() {
 mySqlPrepare.close();
 }
}

以下是为 KafkaMySQLTopology.java 编写的完整代码:

package com.StormMysql;
import org.apache.storm.Config;
 import org.apache.storm.spout.SchemeAsMultiScheme;
 import org.apache.storm.topology.TopologyBuilder;
 import org.apache.storm.kafka.*;
 import org.apache.storm.LocalCluster;
 import org.apache.storm.generated.AlreadyAliveException;
 import org.apache.storm.generated.InvalidTopologyException;
public class KafkaMySQLTopology
 {
 public static void main( String[] args ) throws AlreadyAliveException, InvalidTopologyException
 {
 ZkHosts zkHosts=new ZkHosts("localhost:2181");
String topic="mysql-topic";
 String consumer_group_id="id7";
SpoutConfig kafkaConfig=new SpoutConfig(zkHosts, topic, "", consumer_group_id);
kafkaConfig.scheme=new SchemeAsMultiScheme(new StringScheme());
KafkaSpout kafkaSpout=new KafkaSpout(kafkaConfig);
TopologyBuilder builder=new TopologyBuilder();
 builder.setSpout("KafkaSpout", kafkaSpout);
 builder.setBolt("MySqlBolt", new MySqlBolt()).globalGrouping("KafkaSpout");
LocalCluster cluster=new LocalCluster();
Config config=new Config();
cluster.submitTopology("KafkaMySQLTopology", config, builder.createTopology());
try{
 Thread.*sleep*(10000);
 }catch(InterruptedException ex)
 {
 ex.printStackTrace();
 }
// cluster.killTopology("KafkaMySQLTopology");
 // cluster.shutdown();
}
 }

使用 pom.xml 文件在 IDE 中构建您的项目。

使用 Kafka 到 Storm 到 HDFS 的管道流式传输

在本节中,我们将了解数据流如何从 Kafka 流向 Storm 到 HDFS,并使用 Hive 外部表访问它们。

以下图像显示了管道的组件。在这个管道中,我们将学习消息如何实时从 Kafka 流向 Storm 再流向 HDFS:

图片

整个管道将按以下方式工作:

  1. 我们将使用 Kafka 控制台生产者 API 在 Kafka 中导入客户记录(customer_idcustomer_firstnamecustomer_lastname

  2. 之后,Storm 将从 Kafka 拉取消息

  3. 将与 HDFS 建立连接

  4. Storm 将使用 HDFS-Bolt 将记录导入 HDFS

  5. 将创建 Hive 外部表以存储(customer_idcustomer_firstnamecustomer_lastname

  6. 将使用 SQL 访问 Hive 表数据(customer_idcustomer_firstnamecustomer_lastname

我们将开发以下 Java 类:

KafkaTopology.java: 这是一个 Storm Topology 框架,用于构建工作流程以将 spouts(Kafka)绑定到 Bolts(HDFS)。在这里,我们使用的是本地 Storm 集群。

在之前的示例管道中,可以开发多个独立的数据流解析和转换类来处理 Kafka 生产者和消费者。

以下是 KafkaToplogy.java 的完整 Java 代码:

package com.stormhdfs;
import org.apache.storm.Config;
 import org.apache.storm.LocalCluster;
 import org.apache.storm.generated.AlreadyAliveException;
 import org.apache.storm.generated.InvalidTopologyException;
 import org.apache.storm.hdfs.bolt.HdfsBolt;
 import org.apache.storm.hdfs.bolt.format.DefaultFileNameFormat;
 import org.apache.storm.hdfs.bolt.format.DelimitedRecordFormat;
 import org.apache.storm.hdfs.bolt.format.RecordFormat;
 import org.apache.storm.hdfs.bolt.rotation.FileRotationPolicy;
 import org.apache.storm.hdfs.bolt.rotation.FileSizeRotationPolicy;
 import org.apache.storm.hdfs.bolt.sync.CountSyncPolicy;
 import org.apache.storm.hdfs.bolt.sync.SyncPolicy;
 import org.apache.storm.kafka.KafkaSpout;
 import org.apache.storm.kafka.SpoutConfig;
 import org.apache.storm.kafka.StringScheme;
 import org.apache.storm.kafka.ZkHosts;
 import org.apache.storm.spout.SchemeAsMultiScheme;
 import org.apache.storm.topology.TopologyBuilder;
public class KafkaTopology {
 public static void main(String[] args) throws AlreadyAliveException, InvalidTopologyException {
// zookeeper hosts for the Kafka clusterZkHosts zkHosts = new ZkHosts("localhost:2181");
// Create the KafkaSpout configuartion
 // Second argument is the topic name
 // Third argument is the zookeeper root for Kafka
 // Fourth argument is consumer group id
SpoutConfig kafkaConfig = new SpoutConfig(zkHosts,
 "data-pipleline-topic", "", "id7");
// Specify that the kafka messages are String
kafkaConfig.scheme = new SchemeAsMultiScheme(new StringScheme());
// We want to consume all the first messages in the topic everytime
 // we run the topology to help in debugging. In production, this
 // property should be false
kafkaConfig.startOffsetTime = kafka.api.OffsetRequest.*EarliestTime*();
RecordFormat format = new DelimitedRecordFormat().withFieldDelimiter("|");
 SyncPolicy syncPolicy = new CountSyncPolicy(1000);
FileRotationPolicy rotationPolicy = new FileSizeRotationPolicy(1.0f,FileSizeRotationPolicy.Units.*MB*);
DefaultFileNameFormat fileNameFormat = new DefaultFileNameFormat();

fileNameFormat.withPath("/user/storm-data");

fileNameFormat.withPrefix("records-");

fileNameFormat.withExtension(".txt");

HdfsBolt bolt =
 new HdfsBolt().withFsUrl("hdfs://127.0.0.1:8020")
 .withFileNameFormat(fileNameFormat)
 .withRecordFormat(format)
 .withRotationPolicy(rotationPolicy)
 .withSyncPolicy(syncPolicy);

// Now we create the topology
TopologyBuilder builder = new TopologyBuilder();

// set the kafka spout class
builder.setSpout("KafkaSpout", new KafkaSpout(kafkaConfig), 1);

// configure the bolts
 // builder.setBolt("SentenceBolt", new SentenceBolt(), 1).globalGrouping("KafkaSpout");
 // builder.setBolt("PrinterBolt", new PrinterBolt(), 1).globalGrouping("SentenceBolt");
builder.setBolt("HDFS-Bolt", bolt ).globalGrouping("KafkaSpout");

// create an instance of LocalCluster class for executing topology in local mode.
LocalCluster cluster = new LocalCluster();
 Config conf = new Config();

// Submit topology for execution
cluster.submitTopology("KafkaTopology", conf, builder.createTopology());

try {
 // Wait for some time before exiting
System.out.println("Waiting to consume from kafka");
 Thread.sleep(10000);
 } catch (Exception exception) {
 System.out.println("Thread interrupted exception : " + exception);
 }

// kill the KafkaTopology
 //cluster.killTopology("KafkaTopology");

// shut down the storm test cluster
 // cluster.shutdown();
}
 }

相应的 Hive 表如下:

CREATE EXTERNAL TABLE IF NOT EXISTS customer (
customer_id INT,
customer_firstname String,
customer_lastname String))
COMMENT 'customer table'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '|'
STORED AS TEXTFILE
location '/user/storm-data';
$ hive > select * from customer;

其他流行的实时数据流框架

除了 Apache Storm 之外,还有相当多的其他开源实时数据流框架。在本节中,我将简要讨论仅限于开源非商业框架。但,在本节末尾,我将提供一些商业供应商产品的链接,这些产品提供了一些非常有趣的功能。

Kafka Streams API

Kafka Streams 是用于构建流式应用程序的库。Kafka Streams 是用于构建应用程序和微服务的客户端库,其中输入和输出数据存储在 Kafka 集群中。Kafka Streams API 转换并丰富了数据。

以下 Kafka Streams API 的重要特性:

  • 它是开源 Apache Kafka 项目的一部分。

  • 它支持每条记录的低延迟流处理(毫秒级)。在 Kafka Streams API 中没有微批处理的概念。流中的每条记录都会单独处理。

  • 它支持无状态处理(过滤和映射)、有状态处理(连接和聚合),以及窗口操作(例如,计算最后 1 分钟、最后 5 分钟、最后 30 分钟或最后一天的数据等)。

  • 要运行 Kafka Streams API,无需构建一个拥有多台机器的单独集群。开发者可以在他们的 Java 应用程序或微服务中使用 Kafka Streams API 来处理实时数据。

  • Kafka Streams API 具有高度的扩展性和容错性。

  • Kafka Streams API 完全与部署无关。它可以在裸机、虚拟机、Kubernetes 容器和云上部署。没有任何限制。流 API 从不部署在 Kafka 代理上。它就像任何其他 Java 应用程序一样,是一个独立的应用程序,部署在 Kafka 代理之外。

  • 它使用 Kafka 安全模型。

  • 自 0.11.0 版本以来,它支持恰好一次语义。

让我们再次回顾一下早期的图像,以找出 Kafka Streams API 在整体 Kafka 架构中的确切位置。

这里有一些有用的网址,可以帮助您详细了解 Kafka Streams:

Spark Streaming

请注意,我们将在第七章大型数据处理框架中讨论 Spark,该章完全致力于 Spark。然而,在本节中,我将讨论 Spark Streaming 的一些重要特性。为了更好地理解,建议读者首先学习第七章[大型数据处理框架],然后再回来阅读本节以了解更多关于 Spark Streaming 的内容。

使用 Hadoop MapReduce 进行批处理,使用 Apache Storm 进行实时流处理是一种通用做法。

使用这两种不同的编程模型会导致代码量增加、需要修复的错误数量以及开发工作量的增加;它还引入了学习曲线并导致其他问题。Spark Streaming 有助于解决这些问题,并提供一个可扩展的、高效的、健壮的、且与批处理集成的系统。

Spark Streaming 的优势在于其能够与批处理相结合。可以使用常规 Spark 编程创建 RDD 并将其与 Spark 流连接。此外,代码库相似,如果需要,可以轻松迁移——并且从 Spark 来看,学习曲线几乎为零。

Spark Streaming 是核心 Spark API 的扩展。它扩展 Spark 以进行实时流处理。Spark Streaming 具有以下特点:

  • 它是可扩展的——可以在数百个节点上进行扩展

  • 它提供高吞吐量并实现第二级延迟

  • 它具有容错性,并且能够有效地从失败中恢复

  • 它与批处理和交互式数据处理集成

Spark Streaming 将数据流应用程序处理为一系列非常小、确定性的批处理作业。

Spark Streaming 提供了 Scala、Java 和 Python 的 API。Spark Streaming 根据时间将实时数据流划分为多个批次。时间可以从一秒到几分钟/小时不等。通常,批次被划分为几秒。Spark 将每个批次视为 RDD,并根据 RDD 操作(map、filter、join flatmap、distinct、reduceByKey 等)进行处理。最后,RDD 的处理结果以批次的格式返回。

以下图像展示了 Spark Streaming 的数据流:

图片

这里有一些有用的 URL,用于详细了解 Spark Streaming:

Apache Flink

Apache Flink 的文档这样描述 Flink:Flink 是一个开源的分布式流处理框架。

Flink 提供准确的结果,并支持无序或迟到数据集。它是状态化的和容错的,可以在保持恰好一次应用程序状态的同时无缝恢复失败。它在大型规模上运行,在数千个节点上运行,具有非常好的吞吐量和延迟特性。

以下列出的是 Apache Flink 的特性:

  • Flink 为状态化计算保证了恰好一次语义

  • Flink 支持具有事件时间语义的流处理和窗口

  • Flink 支持基于时间、计数或会话的灵活窗口,除了数据驱动的窗口

  • Flink 具有高吞吐量和低延迟的能力

  • Flink 的 savepoints 提供了一个状态版本化机制,使得更新应用程序或重新处理历史数据成为可能,而不会丢失状态和最小化停机时间

  • Flink 设计用于在具有数千个节点的超大规模集群上运行,除了独立集群模式外,Flink 还提供了对 YARN 和 Mesos 的支持

Flink 的核心是一个分布式流数据流引擎。它支持一次处理一个流,而不是一次处理整个流的批次。

Flink 支持以下库:

  • CEP

  • 机器学习

  • 图形处理

  • Apache Storm 兼容性

Flink 支持以下 API:

  • DataStream API:此 API 帮助所有流,转换,即过滤、聚合、计数和窗口

  • DataSet API:此 API 帮助所有批数据转换,即连接、分组、映射和过滤

  • 表 API:支持在关系数据流上使用 SQL

  • 流式 SQL:支持在批处理和流式表上使用 SQL

以下图像描述了 Flink 编程模型:

图片

以下图像描述了 Flink 架构:

图片

以下列出的是 Flink 编程模型的组件:

  • :收集数据并发送到 Flink 引擎的数据源

  • 转换:在这个组件中,整个转换过程发生

  • 汇入点:处理后的流被发送到的目标

这里有一些有用的网址,可以详细了解 Spark Streaming:

在接下来的几节中,我们将探讨各种流框架的比较。

Apache Flink 与 Spark 的比较

Spark Streaming 的主要重点是流批处理操作,称为 微批处理。这种编程模型适用于许多用例,但并非所有用例都需要亚秒级延迟的实时流处理。例如,像信用卡欺诈预防这样的用例需要毫秒级延迟。因此,微批处理编程模型不适用于这种情况。(但,Spark 的最新版本 2.4 支持毫秒级数据延迟。)

Apache Flink 支持毫秒级延迟,适用于诸如欺诈检测等用例。

Apache Spark 与 Storm 的比较

Spark 使用微批处理来处理事件,而 Storm 则逐个处理事件。这意味着 Spark 的延迟为秒级,而 Storm 提供毫秒级延迟。Spark Streaming 提供了一个高级抽象,称为 离散流DStream,它表示 RDD 的连续序列。(但,Spark 的最新版本 2.4 支持毫秒级数据延迟。)最新的 Spark 版本支持 DataFrame。

几乎可以使用相同的代码(API)进行 Spark Streaming 和 Spark 批处理作业。这有助于重用这两种编程模型的大部分代码库。此外,Spark 支持机器学习和图 API。因此,同样,相同的代码库也可以用于这些用例。

概述

在本章中,我们首先详细了解了实时流处理概念,包括数据流、批处理与实时处理、CEP、低延迟、连续可用性、水平可扩展性、存储等。后来,我们学习了 Apache Kafka,它是现代实时流数据管道的重要组成部分。Kafka 的主要特性是可扩展性、持久性、可靠性和高吞吐量。

我们还学习了 Kafka Connect;其架构、数据流、源和连接器。我们研究了案例研究,使用 Kafka Connect 的文件源、文件汇入点、JDBC 源和文件汇入点连接器来设计数据管道。

在后面的章节中,我们学习了各种开源实时流处理框架,例如 Apache Storm 框架。我们也看到了一些实际的应用示例。Apache Storm 是一个分布式框架,支持低延迟和多种编程语言。Storm 具有容错性和可靠性,支持至少一次或恰好一次的处理。

Spark Streaming 有助于解决这些问题,并提供了一个可扩展、高效、弹性且与批量处理集成的系统。Spark Streaming 的优势在于其与批量处理的结合能力。Spark Streaming 可扩展,提供高吞吐量。它支持微批处理以实现二级延迟,具有容错性,并与批量及交互式数据处理集成。

Apache Flink 保证恰好一次的语义,支持事件时间语义,高吞吐量和低延迟。它被设计用于在大型集群上运行。

第七章:大规模数据处理框架

随着数据源的数量和复杂性的增加,从数据中提取价值也变得越来越困难。自从 Hadoop 出现以来,它已经构建了一个可大规模扩展的文件系统 HDFS。它采用了函数编程中的 MapReduce 概念来应对大规模数据处理挑战。随着技术不断进化以克服数据挖掘带来的挑战,企业也在寻找方法来拥抱这些变化,以保持领先。

在本章中,我们将关注以下数据处理解决方案:

  • MapReduce

  • Apache Spark

  • Spark SQL

  • Spark Streaming

MapReduce

MapReduce 是从函数编程中借用的一个概念。数据处理被分解为映射阶段,其中进行数据准备,以及归约阶段,其中计算实际结果。MapReduce 之所以扮演了重要角色,是因为我们可以通过将数据分片到多个分布式服务器来实现巨大的并行性。没有这个优势,MapReduce 实际上无法很好地执行。

让我们用一个简单的例子来了解 MapReduce 在函数编程中的工作原理:

  • 输入数据使用我们选择的映射函数进行处理

  • 映射函数的输出应该处于归约函数可消费的状态

  • 映射函数的输出被送入归约函数以生成必要的结果

让我们通过一个简单的程序来理解这些步骤。该程序使用以下文本(随机创建的)作为输入:

Bangalore,Onion,60
Bangalore,Chilli,10
Bangalore,Pizza,120
Bangalore,Burger,80
NewDelhi,Onion,80
NewDelhi,Chilli,30
NewDelhi,Pizza,150
NewDelhi,Burger,180
Kolkata,Onion,90
Kolkata,Chilli,20
Kolkata,Pizza,120
Kolkata,Burger,160

输入由以下字段组成:城市名称产品名称和当天的项目价格

我们想编写一个程序来显示给定城市所有产品的总成本。这可以通过多种方式完成。但让我们尝试使用 MapReduce 来解决这个问题,看看它是如何工作的。

映射程序如下:

#!/usr/bin/env perl -wl

use strict;
use warnings;

while(<STDIN>) {
    chomp;
    my ($city, $product, $cost) = split(',');
    print "$city $cost";
}

归约程序如下:

#!/usr/bin/perl

use strict;
use warnings;

my %reduce;

while(<STDIN>) {
    chomp;
    my ($city, $cost) = split(/\s+/);
    $reduce{$city} = 0 if not defined $reduce{$city};
    $reduce{$city} += $cost;
}

print "-" x 24;
printf("%-10s : %s\n", "City", "Total Cost");
print "-" x 24;

foreach my $city (sort keys %reduce) {
    printf("%-10s : %d\n", $city, $reduce{$city});
}

我们使用 UNIX 终端创建一个数据管道,如下所示:

[user@node-1 ~]$ cat input.txt | perl map.pl | perl reduce.pl 
------------------------
City : Total Cost
------------------------
Bangalore : 270
Kolkata : 390
NewDelhi : 440

如我们所见,结果是预期的。这是一个非常简单的 MapReduce 案例。让我们尝试看看发生了什么:

  • 每个输入行都由map.pl程序处理并打印城市和价格

  • map.pl程序的输出被送入reduce.pl,它对所有记录执行SUM()操作并将它们按城市分类

让我们洗牌input.txt并看看我们是否得到期望的结果。

这里是修改后的input.txt

Bangalore,Onion,60
NewDelhi,Onion,80
Bangalore,Pizza,120
Bangalore,Burger,80
Kolkata,Onion,90
Kolkata,Pizza,120
Kolkata,Chilli,20
NewDelhi,Chilli,30
NewDelhi,Burger,180
Kolkata,Burger,160
NewDelhi,Pizza,150
Bangalore,Chilli,10

MapReduce 操作的输出如下:

[user@node-1 ~]$ cat input-shuffled.txt | perl map.pl | perl reduce.pl 
------------------------
City : Total Cost
------------------------
Bangalore : 270
Kolkata : 390
NewDelhi : 440

没有区别,因为映射和归约操作都是一次性独立执行的。这里没有数据并行性。整个过程可以在以下图中可视化:

图片

如我们所见,在映射阶段之后有一个输入数据的副本,在归约阶段之后的最终输出是我们感兴趣的。

运行单线程进程是有用的,并且在我们不需要处理大量数据时是必需的。当输入大小无界且无法适应单个服务器时,我们需要开始考虑分布式/并行算法来处理当前的问题。

Hadoop MapReduce

Apache MapReduce 是一个框架,它使我们能够更容易地在非常大的分布式数据集上运行 MapReduce 操作。Hadoop 的一个优点是具有一个具有机架感知性和可扩展性的分布式文件系统。Hadoop 作业调度器足够智能,可以确保计算发生在数据所在的节点上。这也是一个非常重要的方面,因为它减少了网络 I/O 的数量。

让我们看看框架如何通过这个图帮助我们在大量并行计算中更容易地运行:

图片

这个图看起来比之前的图复杂一些,但大部分工作都是由 Hadoop MapReduce 框架为我们自己完成的。我们仍然编写代码来映射和归约我们的输入数据。

让我们详细看看当我们使用前面的图中的 Hadoop MapReduce 框架处理我们的数据时会发生什么:

  • 我们将输入数据分解成块。

  • 每个数据块都被喂给一个 mapper 程序。

  • 所有 mapper 程序的输出被收集、洗牌和排序。

  • 每个排序好的数据块被喂给 reducer 程序。

  • 所有 reducer 的输出被组合起来生成输出数据。

流式 MapReduce

Streaming MapReduce 是 Hadoop MapReduce 框架中的一项功能,在这里我们可以使用任何外部程序作为 Mapper 和 Reducer。只要这些程序可以被目标操作系统执行,它们就可以被接受来运行 Map 和 Reduce 任务。

在编写这些程序时,以下是一些需要注意的事项:

  • 这些程序应该从 STDIN 读取输入。

  • 它们应该能够处理无限量的数据(流)否则它们会崩溃。

  • 在使用流式 MapReduce 之前,应该提前很好地了解这些程序的内存需求,否则我们可能会看到不可预测的行为。

在前面的部分,我们编写了简单的 Perl 脚本来执行映射和归约。在当前场景中,我们也将使用相同的程序来了解它们如何执行我们的任务。

如果你仔细观察,map.pl 可以处理无限量的数据,并且不会有任何内存开销。但是,reduce.pl 程序使用 Perl Hash 数据结构来执行归约操作。在这里,我们可能会遇到一些内存压力,尤其是在处理真实世界的数据时。

在这个练习中,我们使用随机输入数据,如下所示:

[user@node-3 ~]$ cat ./input.txt
 Bangalore,Onion,60
 NewDelhi,Onion,80
 Bangalore,Pizza,120
 Bangalore,Burger,80
 Kolkata,Onion,90
 Kolkata,Pizza,120
 Kolkata,Chilli,20
 NewDelhi,Chilli,30
 NewDelhi,Burger,180
 Kolkata,Burger,160
 NewDelhi,Pizza,150
 Bangalore,Chilli,10

之后,我们需要将 mapper 和 reducer 脚本复制到所有 Hadoop 节点上:

我们使用的是作为第十章,生产 Hadoop 集群部署一部分构建的同一个 Hadoop 集群。如果您还记得,节点是 master,node-1node-2node-3

[user@master ~]$ scp *.pl node-1:~
[user@master ~]$ scp *.pl node-2:~
[user@master ~]$ scp *.pl node-3:~

在这一步,我们正在将输入复制到hadoop /tmp/目录。

请根据您的企业标准在您的生产环境中使用一个合理的目录。在这里,/tmp目录仅用于说明目的。

[user@node-3 ~]$ hadoop fs -put ./input.txt /tmp/

在这一步,我们使用 Hadoop 流式 MapReduce 框架来使用我们的脚本来执行计算:

map.plreduce.pl的内容与我们之前使用的例子完全相同。

[user@node-3 ~]$ hadoop jar \
    /usr/hdp/current/hadoop-mapreduce-client/hadoop-streaming.jar \
    -input hdfs:///tmp/input.txt \
    -output hdfs:///tmp/output-7 \
    -mapper $(pwd)/map.pl \
    -reducer $(pwd)/reduce.pl

输出存储在 HDFS 中,我们可以这样查看:

[user@node-3 ~]$ hadoop fs -cat /tmp/output-7/part*
 NewDelhi, 440
 Kolkata, 390
 Bangalore, 270
[user@node-3 ~]$

如果我们仔细观察,结果与我们的传统程序完全一致。

Java MapReduce

在上一节中,我们看到了如何使用任何任意编程语言在 Hadoop 上运行 MapReduce 操作。但在大多数实际场景中,如果我们利用 Hadoop MapReduce 基础设施提供的库,那会更好,因为它们功能强大,并为我们处理许多需求。

让我们尝试编写一个简单的 Java 程序,使用 MapReduce 库,看看我们是否能生成与之前练习相同的输出。在这个例子中,我们将使用官方文档中的官方 MapReduce 实现。

文档位于:hadoop.apache.org/docs/r2.8.0/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html

由于我们的输入与示例非常不同,而且我们还想找到给定城市中所有产品的总价,我们必须根据我们的 CSV input.txt文件更改 mapper 程序。reduce 函数与官方文档中相同,其中我们的 mapper 函数生成一个<City, Price>对。这可以很容易地被现有实现消费。

我们将我们的程序命名为TotalPrice.java。让我们看看我们的源代码是什么样的:

[user@node-3 ~]$ cat TotalPrice.java 
import java.io.IOException;
import java.util.StringTokenizer;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class TotalPrice {
  public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable>{
    public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
      StringTokenizer itr = new StringTokenizer(value.toString(), ",");
      Text city = new Text(itr.nextToken());
      itr.nextToken();
      IntWritable price = new IntWritable(Integer.parseInt(itr.nextToken()));
      context.write(city, price);
    }
  }

  public static class IntSumReducer extends Reducer<Text,IntWritable,Text,IntWritable> {
  private IntWritable result = new IntWritable();

    public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
      int sum = 0;
      for (IntWritable val : values) {
        sum += val.get();
      }
      result.set(sum);
      context.write(key, result);
    }
  }

  public static void main(String[] args) throws Exception {
    Configuration conf = new Configuration();
    Job job = Job.getInstance(conf, "TotalPriceCalculator");
    job.setJarByClass(TotalPrice.class);
    job.setMapperClass(TokenizerMapper.class);
    job.setCombinerClass(IntSumReducer.class);
    job.setReducerClass(IntSumReducer.class);
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    FileInputFormat.addInputPath(job, new Path(args[0]));
    FileOutputFormat.setOutputPath(job, new Path(args[1]));
    System.exit(job.waitForCompletion(true) ? 0 : 1);
  }
}

一旦我们有了源代码,我们需要编译它来创建一个Java 归档JAR)文件。这可以通过以下方式完成:

 [user@node-3 ~]$ javac -cp `hadoop classpath` TotalPrice.java 
 [user@node-3 ~]$ jar cf tp.jar TotalPrice*.class

一旦我们创建了 JAR 文件,我们可以使用 Hadoop 命令提交作业来处理input.txt,并在/tmp/output-12目录中生成输出:

就像流式 MapReduce 的情况一样,我们不需要将源代码复制到所有的 Hadoop 服务器上。

 [user@node-3 ~]$ hadoop jar tp.jar TotalPrice /tmp/input.txt /tmp/output-12

这次运行应该会顺利,并将在/tmp/output-12目录中生成输出文件。我们可以使用以下命令查看输出内容:

[user@node-3 ~]$ hadoop fs -cat /tmp/output-12/part*
Bangalore       270
Kolkata 390
NewDelhi        440

这与之前的运行完全一致。

如我们所见,Hadoop Mapreduce 框架已经采取了所有必要的步骤来确保整个管道进度保持在它的控制之下,从而给我们带来期望的结果。

尽管我们使用了非常简单的数据集进行计算,但 Hadoop Mapreduce 确保,无论我们处理的数据大小如何,我们之前编写的相同程序都能得到我们想要的结果。这使得它成为一个非常适合批量作业的强大架构。

摘要

到目前为止,我们已经看到 Hadoop Mapreduce 是一个强大的框架,它提供了流式和批量操作模式,可以用非常简单的指令处理大量数据。尽管 Mapreduce 最初是 Hadoop 的计算框架选择,但它未能满足市场不断变化的需求,因此开发了新的架构来解决这些问题。在下一节中,我们将学习一个名为Apache Spark的框架。

Apache Spark 2

Apache Spark 是一个通用的集群计算系统。它非常适合大规模数据处理。当完全在内存中运行时,它的性能比 Hadoop 高 100 倍,当完全从磁盘运行时,性能高 10 倍。它拥有复杂的定向无环图执行引擎,支持无环数据流模型。

Apache Spark 为 Java、Scala、Python 和 R 编程语言提供了第一类支持,以适应更广泛的受众。它提供了超过 80 种不同的算子来构建并行应用程序,而无需担心底层基础设施。

Apache Spark 拥有针对结构化查询语言(Structured Query Language,简称 Spark SQL)的库,支持在程序中使用 ANSI SQL 编写查询。它还支持计算流数据,这在当今实时数据处理需求中非常必要,例如为交互式用户体验系统提供仪表板。Apache Spark 还拥有机器学习库,如Mlib,用于运行科学程序。然后它还支持编写遵循图数据结构的程序,称为GraphX。这使得它成为一个真正强大的框架,支持大多数先进的计算方式。

Apache Spark 不仅运行在 Hadoop 平台上,还运行在各种系统上,如 Apache Mesos、Kubernetes、Standalone 或云。这使得它成为当今企业选择利用该系统力量的完美选择。

在接下来的章节中,我们将学习更多关于 Spark 及其生态系统的内容。我们在这个练习中使用 Spark 2.2.0 版本。

使用 Ambari 安装 Spark

在上一章中,我们已经有一个正在运行的 Ambari 安装。我们将利用相同的安装来添加 Spark 支持。让我们看看我们如何实现这一点。

Ambari Admin 中的服务选择

一旦我们登录到 Ambari Admin 界面,我们会看到创建的主集群。在这个页面上,我们点击左侧菜单中的操作按钮。屏幕显示如下。从该菜单中,我们点击添加服务选项:

图片

添加服务向导

一旦我们点击“添加服务”菜单项,我们将看到一个向导,我们必须从 Ambari 支持的所有服务列表中选择 Spark 2。屏幕看起来像这样:

图片

在服务选择完成后,点击“下一步”按钮。

服务器放置

一旦选择了 Spark 2 服务,其他依赖服务也会自动为我们选择,并允许我们选择主服务器的放置位置。我已将默认选择保持不变:

图片

当更改看起来不错时,点击“下一步”按钮。

客户端和从节点选择

在此步骤中,我们可以选择作为之前步骤中选定的主服务器客户端的节点列表。我们还可以选择可以安装客户端工具的服务器列表。根据您的选择进行选择:

图片

在更改完成后,点击“下一步”按钮。

服务定制

由于 Hive 也是作为 Spark 2 选择的一部分进行安装的,因此我们有机会自定义 Hive 数据源的具体细节。我已经在主节点上创建了数据库,用户名为hive,密码为hive,数据库也命名为hive。在更改生产环境中的设置时,请选择一个强大的密码。

定制屏幕看起来像这样:

图片

当更改正确完成后,点击“下一步”。

软件部署

在此屏幕上,我们展示了我们迄今为止所做的选择摘要。点击“部署”以开始在所选服务器上部署 Spark 2 软件。如果我们觉得我们错过了任何定制,我们可以在这一步取消向导并重新开始:

图片

Spark 安装进度

在此步骤中,我们展示了 Spark 软件安装及其其他依赖项的进度。一旦一切部署完成,我们将显示任何警告和错误的摘要。正如我们从以下屏幕中可以看到的,在安装过程中遇到了一些警告,这表明在向导完成后我们需要重新启动一些服务。不要担心,看到这些错误是很正常的。我们将在接下来的步骤中纠正这些错误,以确保 Spark 系统成功运行:

图片

点击“完成”以完成向导。

服务重启和清理

由于在安装过程中出现了警告,我们必须重新启动所有受影响的组件。重启过程显示在此屏幕上:

图片

一旦我们确认,所有相关的服务都将重新启动,我们将拥有一个成功运行的系统。

这完成了在由 Ambari 管理的现有 Hadoop 集群上安装 Spark 2 的过程。在接下来的章节中,我们将学习更多关于 Spark 中的各种数据结构和库。

Apache Spark 数据结构

尽管 Mapreduce 提供了一种处理大量数据的有力方式,但它由于几个缺点而受到限制:

  • 缺乏对各种运算符的支持

  • 实时数据处理

  • 缓存数据结果以加快迭代速度

这只是列举了一小部分。由于 Apache Spark 是从底层构建的,它以非常通用的方式处理大数据计算问题,并为开发者提供了数据结构,使得表示任何类型的数据和使用这些数据结构进行更好的计算变得更加容易。

RDDs, DataFrames and datasets

Apache Spark 的核心是称为 RDD 的分布式数据集,也称为弹性分布式数据集。这些是不可变的、存在于集群中的数据集,具有高度可用性和容错性。RDD 中的元素可以并行操作,为 Spark 集群提供了很大的能力。

由于数据已经存在于存储系统中,如 HDFS、RDBMS、S3 等,因此可以轻松地从这些外部数据源创建 RDD。API 还为我们提供了从现有的内存数据元素创建 RDD 的能力。

这些 RDD 没有预定义的结构。因此,它们可以采取任何形式,通过利用 Spark 库中的不同操作符,我们可以编写强大的程序,提供必要的结果,而不必过多担心数据复杂性。

为了满足 RDBMS 的需求,DataFrame 应运而生,其中 DataFrame 可以与关系数据库系统中的表进行比较。正如我们所知,表有行和列,数据结构在事先是已知的。通过了解数据结构,可以在数据处理过程中执行多种优化。

Spark 数据集与 DataFrame 有些相似。但它们通过支持使用本地语言对象(Java 和 Scala)的半结构化数据对象来扩展 DataFrame 的功能。DataFrame 是一个不可变的对象集合,具有关系模式的语义。由于我们处理的是半结构化数据和本地语言对象,因此存在一个编码/解码系统,负责在类型之间自动转换。

下面是一个快速比较表:

特性 RDD DataFrame Dataset
数据类型 非结构化数据 结构化数据 半结构化数据
模式要求 完全自由形式 严格数据类型 松散耦合
Spark 提供的优化 对于非结构化数据不需要 利用已知的数据类型进行优化 推断的数据类型提供一定程度的优化
高级表达式/过滤器 数据形式复杂,难以处理 我们知道我们处理的数据,因此可以利用这些

Apache Spark 编程

Apache Spark 提供了非常好的编程语言支持。它为 Java、Scala、Python 和 R 编程语言提供了第一级支持。尽管编程语言中可用的数据结构和运算符在本质上相似,但我们必须使用特定于编程语言的构造来达到所需的逻辑。在本章中,我们将使用 Python 作为首选的编程语言。然而,Spark 本身对这些编程语言是中立的,并且使用任何编程语言都会产生相同的结果。

使用 Python 的 Apache Spark 可以有两种不同的方式。第一种方式是启动pyspark交互式外壳,它帮助我们运行 Python 指令。体验类似于 Python 外壳实用程序。另一种方式是编写可以由 spark-submit 命令调用的独立程序。为了使用独立的 Spark 程序,我们必须了解 Spark 程序的基本结构:

图片

Spark 程序的一般结构包括一个主函数,该函数在 RDD 上执行不同的运算符以生成所需的结果。Spark 库支持超过 80 种不同类型的运算符。从高层次上讲,我们可以将这些运算符分为两种类型:转换和动作。转换运算符将数据从一种形式转换为另一种形式。动作运算符从数据生成结果。为了优化集群中的资源以实现性能,Apache Spark 实际上在检查点中执行程序。只有当有动作运算符时,才会到达检查点。这是需要记住的一个重要事项,尤其是如果你是 Spark 编程的新手。即使是经验最丰富的程序员有时也会对为什么他们没有看到预期的结果而感到困惑,因为他们没有在数据上使用任何动作运算符。

回到前面的图表,我们有一个驱动程序,它有一个主程序,该程序对存储在类似 HDFS 的文件系统中的数据进行几个操作/转换,并给出我们期望的结果。我们知道 RDD 是 Spark 编程语言中的基本并行数据存储。Spark 足够智能,可以从种子存储(如 HDFS)创建这些 RDD,一旦创建,它可以在内存中缓存 RDD,并通过使它们容错来提高这些 RDD 的可用性。即使由于节点崩溃,RDD 的副本离线,对相同 RDD 的后续访问也将快速从原始生成的计算中生成。

分析的样本数据

为了理解 Spark 的编程 API,我们应该有一个样本数据集,我们可以对其进行一些操作以获得信心。为了生成这个数据集,我们将从上一章的员工数据库中导入样本表。

这些是我们遵循的指令来生成这个数据集:

登录到服务器并切换到 Hive 用户:

ssh user@node-3
[user@node-3 ~]$ sudo su - hive

这将使我们进入一个远程 shell,在那里我们可以从 MySQL 数据库中导出表:

[hive@node-3 ~]$ mysql -usuperset -A -psuperset -h master employees -e "select * from vw_employee_salaries" > vw_employee_salaries.tsv
[hive@node-3 ~]$ wc -l vw_employee_salaries.tsv 
2844048 vw_employee_salaries.tsv
[hive@node-3 ~]$ 

接下来,我们应该使用以下命令将文件复制到 Hadoop:

[hive@node-3 ~]$ hadoop fs -put ./vw_employee_salaries.tsv /user/hive/employees.csv

现在,数据准备已经完成,因为我们已经成功将其复制到 HDFS。我们可以开始使用 Spark 来使用这些数据。

Interactive data analysis with pyspark

Apache Spark 发行版附带一个名为pyspark的交互式 shell。由于我们处理的是像 Python 这样的解释型编程语言,我们可以在学习的同时编写交互式程序。

如果你还记得,我们使用 Apache Ambari 安装了 Spark。因此,我们必须遵循 Apache Ambari 的标准目录位置来访问与 Spark 相关的二进制文件:

[hive@node-3 ~]$ cd /usr/hdp/current/spark2-client/
[hive@node-3 spark2-client]$ ./bin/pyspark 
Python 2.7.5 (default, Aug  4 2017, 00:39:18) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
Setting default log level to "WARN".
To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).
Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  '_/
   /__ / .__/\_,_/_/ /_/\_\   version 2.2.0.2.6.4.0-91
      /_/

Using Python version 2.7.5 (default, Aug  4 2017 00:39:18)
SparkSession available as 'spark'.
>>> 

前面的步骤启动了交互式 Spark shell。

在理解 Spark 数据结构的第一个步骤中,我们将从 HDFS 加载employees.csv文件,并使用以下说明来计算文件中的总行数:

>>> ds = spark.read.text("employees.csv")
>>> ds.count()
2844048                                                                         
>>> 

如我们所见,计数与之前在 Unix shell 上的加载操作相匹配。

现在,让我们尝试从文件中加载前五条记录并尝试查看数据结构对象的模式:

>>> ds.first()
Row(value=u'emp_no\tbirth_date\tfirst_name\tlast_name\tgender\thire_date\tsalary\tfrom_date\tto_date')
>>> ds.head(5)
[Row(value=u'emp_no\tbirth_date\tfirst_name\tlast_name\tgender\thire_date\tsalary\tfrom_date\tto_date'), Row(value=u'10001\t1953-09-02\tGeorgi\tFacello\tM\t1986-06-26\t60117\t1986-06-26\t1987-06-26'), Row(value=u'10001\t1953-09-02\tGeorgi\tFacello\tM\t1986-06-26\t62102\t1987-06-26\t1988-06-25'), Row(value=u'10001\t1953-09-02\tGeorgi\tFacello\tM\t1986-06-26\t66074\t1988-06-25\t1989-06-25'), Row(value=u'10001\t1953-09-02\tGeorgi\tFacello\tM\t1986-06-26\t66596\t1989-06-25\t1990-06-25')]
>>> ds.printSchema()
root
 |-- value: string (nullable = true)

>>> 

如我们所见,即使我们有 CSV(制表符分隔的文件),Spark 也将文件读取为以换行符分隔的普通文本文件,其模式中只包含一个值,即字符串数据类型。

在这种操作模式下,我们将每条记录视为一行,我们只能执行几种类型的操作,例如计算给定名称的所有出现次数:

>>> ds.filter(ds.value.contains("Georgi")).count()
2323                                                                            
>>> 

这种操作模式与日志处理有些相似。但 Spark 的真正力量来自于将数据视为具有行和列的表格,也称为DataFrames

>>> ds = spark.read.format("csv").option("header", "true").option("delimiter", "\t").load("employees.csv")
>>> ds.count()
2844047   
>>> ds.show(5)
+------+----------+----------+---------+------+----------+------+----------+----------+
|emp_no|birth_date|first_name|last_name|gender| hire_date|salary| from_date|   to_date|
+------+----------+----------+---------+------+----------+------+----------+----------+
| 10001|1953-09-02|    Georgi|  Facello|     M|1986-06-26| 60117|1986-06-26|1987-06-26|
| 10001|1953-09-02|    Georgi|  Facello|     M|1986-06-26| 62102|1987-06-26|1988-06-25|
| 10001|1953-09-02|    Georgi|  Facello|     M|1986-06-26| 66074|1988-06-25|1989-06-25|
| 10001|1953-09-02|    Georgi|  Facello|     M|1986-06-26| 66596|1989-06-25|1990-06-25|
| 10001|1953-09-02|    Georgi|  Facello|     M|1986-06-26| 66961|1990-06-25|1991-06-25|
+------+----------+----------+---------+------+----------+------+----------+----------+
only showing top 5 rows

>>> 
>>> ds.printSchema()
root
 |-- emp_no: string (nullable = true)
 |-- birth_date: string (nullable = true)
 |-- first_name: string (nullable = true)
 |-- last_name: string (nullable = true)
 |-- gender: string (nullable = true)
 |-- hire_date: string (nullable = true)
 |-- salary: string (nullable = true)
 |-- from_date: string (nullable = true)
 |-- to_date: string (nullable = true)

>>> 

现在,我们可以看到 Spark 已经自动将输入 CSV 文本转换为 DataFrame。但所有字段都被视为字符串。

让我们尝试使用 Spark 的架构推断功能自动找到字段的类型:

>>> ds = spark.read.format("csv").option("header", "true").option("delimiter", "\t").option("inferSchema", "true").load("employees.csv")
18/03/25 19:21:15 WARN FileStreamSink: Error while looking for metadata directory.
18/03/25 19:21:15 WARN FileStreamSink: Error while looking for metadata directory.
>>> ds.count()                                                                  
2844047                                                                         
>>> ds.show(2)
+------+-------------------+----------+---------+------+-------------------+------+-------------------+-------------------+
|emp_no|         birth_date|first_name|last_name|gender|          hire_date|salary|          from_date|            to_date|
+------+-------------------+----------+---------+------+-------------------+------+-------------------+-------------------+
| 10001|1953-09-02 00:00:00|    Georgi|  Facello|     M|1986-06-26 00:00:00| 60117|1986-06-26 00:00:00|1987-06-26 00:00:00|
| 10001|1953-09-02 00:00:00|    Georgi|  Facello|     M|1986-06-26 00:00:00| 62102|1987-06-26 00:00:00|1988-06-25 00:00:00|
+------+-------------------+----------+---------+------+-------------------+------+-------------------+-------------------+
only showing top 2 rows

>>> ds.printSchema()
root
 |-- emp_no: integer (nullable = true)
 |-- birth_date: timestamp (nullable = true)
 |-- first_name: string (nullable = true)
 |-- last_name: string (nullable = true)
 |-- gender: string (nullable = true)
 |-- hire_date: timestamp (nullable = true)
 |-- salary: integer (nullable = true)
 |-- from_date: timestamp (nullable = true)
 |-- to_date: timestamp (nullable = true)

>>> 

现在,我们可以看到所有字段都有一个合适的数据类型,这与 MySQL 表定义最接近。

我们可以在数据上应用简单的操作来查看结果。让我们尝试找到总男性记录数:

>>> ds.filter(ds.gender == "M").count()
1706321 

此外,尝试找到工资超过$100K 的男性记录:

>>> ds.filter(ds.gender == "M").filter(ds.salary > 100000).count()
57317   

难道很简单吗?官方 Spark 文档中还有许多其他可用于探索的操作符。

Standalone application with Spark

在上一节中,我们看到了如何使用交互式 shell pyspark来学习 Spark Python API。在本节中,我们将编写一个简单的 Python 程序,我们将在 Spark 集群上运行它。在现实场景中,这就是我们在 Spark 集群上运行应用程序的方式。

为了做到这一点,我们将编写一个名为MyFirstApp.py的程序,其内容如下:

[hive@node-3 ~]$ cat MyFirstApp.py 
from pyspark.sql import SparkSession

# Path to the file in HDFS
csvFile = "employees.csv"

# Create a session for this application
spark = SparkSession.builder.appName("MyFirstApp").getOrCreate()

# Read the CSV File
csvTable = spark.read.format("csv").option("header", "true").option("delimiter", "\t").load(csvFile)

# Print the total number of records in this file
print "Total records in the input : {}".format(csvTable.count())

# Stop the application
spark.stop()
[hive@node-3 ~]$ 

为了在 Spark 集群上运行此程序,我们必须使用 spark-submit 命令,该命令在调度和协调整个应用程序生命周期方面执行必要的操作:

[hive@node-3 ~]$ /usr/hdp/current/spark2-client/bin/spark-submit ./MyFirstApp.py 2>&1 | grep -v -e INFO -e WARN
Total records in the input : 2844047

如预期的那样,这些是我们输入文件中的总记录数(不包括标题行)。

Spark 流应用程序

Spark 的一个强大功能是构建处理实时流数据并产生实时结果的应用程序。为了更好地理解这一点,我们将编写一个简单的应用程序,尝试在输入流中查找重复消息并打印所有唯一消息。

当我们处理不可靠的数据流并只想提交唯一数据时,此类应用程序非常有用。

此应用程序的源代码如下:

[hive@node-3 ~]$ cat StreamingDedup.py 
from pyspark import SparkContext
from pyspark.streaming import StreamingContext

context = SparkContext(appName="StreamingDedup")
stream = StreamingContext(context, 5)

records = stream.socketTextStream("localhost", 5000)
records
    .map(lambda record: (record, 1))
    .reduceByKey(lambda x,y: x + y)
    .pprint()

ssc.start()
ssc.awaitTermination()

在此应用程序中,我们连接到端口5000上的远程服务,该服务在其自己的页面上发出消息。程序每 5 秒总结一次操作结果,如StreamingContext参数中定义的那样。

现在,让我们使用 UNIX netcat 命令(nc)和简单循环启动一个简单的 TCP 服务器:

for i in $(seq 1 10)
do
  for j in $(seq 1 5)
  do
   sleep 1
   tail -n+$(($i * 3)) /usr/share/dict/words | head -3
  done
done | nc -l 5000

然后,将我们的程序提交到 spark 集群:

[hive@node-3 ~]$ /usr/hdp/current/spark2-client/bin/spark-submit ./StreamingDedup.py 2>&1 | grep -v -e INFO -e WARN

程序启动后,我们看到以下输出:

-------------------------------------------
Time: 2018-03-26 04:33:45
-------------------------------------------
(u'16-point', 5)
(u'18-point', 5)
(u'1st', 5)

-------------------------------------------
Time: 2018-03-26 04:33:50
-------------------------------------------
(u'2', 5)
(u'20-point', 5)
(u'2,4,5-t', 5)

我们看到每个单词的计数正好是 5,这是预期的,因为我们已经在 Unix 命令循环中打印了五次。

我们可以通过这张图来理解这一点:

图片

输入流产生一个连续的数据流,该数据流被Spark 程序实时消费。之后,通过消除重复项来打印结果。

如果按时间顺序查看,从时间零到五秒(T0 - T5)的数据被处理,并在T5时间提供结果。对于所有其他时间槽也是如此。

在这个简单的示例中,我们刚刚学习了如何使用 Spark Streaming 构建实时应用程序的基础知识。

Spark SQL 应用程序

当使用 Spark 编写应用程序时,开发人员可以选择在结构化数据上使用 SQL 来获取所需的结果。以下示例使我们更容易理解如何做到这一点:

[hive@node-3 ~]$ cat SQLApp.py 
from pyspark.sql import SparkSession

# Path to the file in HDFS
csvFile = "employees.csv"

# Create a session for this application
spark = SparkSession.builder.appName("SQLApp").getOrCreate()

# Read the CSV File
csvTable = spark.read.format("csv").option("header", "true").option("delimiter", "\t").load(csvFile)
csvTable.show(3)

# Create a temporary view
csvView = csvTable.createOrReplaceTempView("employees")

# Find the total salary of employees and print the highest salary makers
highPay = spark.sql("SELECT first_name, last_name, emp_no, SUM(salary) AS total FROM employees GROUP BY emp_no, first_name, last_name ORDER BY SUM(salary)")

# Generate list of records
results = highPay.rdd.map(lambda rec: "Total: {}, Emp No: {}, Full Name: {} {}".format(rec.total, rec.emp_no, rec.first_name, rec.last_name)).collect()

# Show the top 5 of them
for r in results[:5]:
    print(r)

# Stop the application
spark.stop()
[hive@node-3 ~]$ 

在此示例中,我们从employees.csv构建一个 DataFrame,然后在内存中创建一个名为employees的视图。稍后,我们可以使用 ANSI SQL 编写和执行查询以生成必要的结果。

由于我们感兴趣的是查找最高薪酬的员工,结果正如预期的那样:

[hive@node-3 ~]$ /usr/hdp/current/spark2-client/bin/spark-submit ./SQLApp.py 2>&1 | grep -v -e INFO -e WARN
[rdd_10_0]
+------+----------+----------+---------+------+----------+------+----------+----------+
|emp_no|birth_date|first_name|last_name|gender| hire_date|salary| from_date|   to_date|
+------+----------+----------+---------+------+----------+------+----------+----------+
| 10001|1953-09-02|    Georgi|  Facello|     M|1986-06-26| 60117|1986-06-26|1987-06-26|
| 10001|1953-09-02|    Georgi|  Facello|     M|1986-06-26| 62102|1987-06-26|1988-06-25|
| 10001|1953-09-02|    Georgi|  Facello|     M|1986-06-26| 66074|1988-06-25|1989-06-25|
+------+----------+----------+---------+------+----------+------+----------+----------+
only showing top 3 rows

Total: 40000.0, Emp No: 15084, Full Name: Aloke Birke
Total: 40000.0, Emp No: 24529, Full Name: Mario Antonakopoulos
Total: 40000.0, Emp No: 30311, Full Name: Tomofumi Coombs
Total: 40000.0, Emp No: 55527, Full Name: Kellyn Ouhyoung
Total: 40000.0, Emp No: 284677, Full Name: Richara Eastman

如我们所见,Apache Spark 提供的简化 API 使得在 CSV 数据上编写 SQL 查询(无需 RDBMS)变得更加容易,以获取我们想要的结果。

摘要

在本章中,你了解了大规模数据处理框架的基本概念,并且还了解到 Spark 的一个强大功能是构建处理实时流数据并产生实时结果的应用程序。

在接下来的几章中,我们将讨论如何使用 Elasticsearch 堆栈构建实时数据搜索管道。

第八章:构建企业搜索平台

在学习了数据摄取和数据持久化方法之后,让我们来学习如何搜索数据。在本章中,我们将学习以下重要内容:

  • 数据搜索技术

  • 构建实时搜索引擎

  • 搜索实时全文数据

  • 数据索引技术

  • 构建实时数据搜索管道

数据搜索的概念

在我们的日常生活中,我们总是在不断寻找某些东西。早上,我们寻找牙刷、报纸、搜索股价、公交时刻表、公文包等等。这个清单可以一直列下去。当我们在一天结束时上床睡觉时,这种搜索活动就停止了。我们使用很多工具和技术来搜索这些事物,以最大限度地减少实际搜索时间。我们使用谷歌搜索大多数事情,比如新闻、股价、公交时刻表以及我们需要的任何东西。为了搜索一本书的特定页面,我们使用书的索引。所以,重点是搜索是我们生活中非常重要的活动。从这个过程中可以提炼出两个重要的概念,那就是搜索工具和搜索时间。想想看,如果你想知道一家公司的特定股价,而加载这个页面需要几分钟,你肯定会非常烦恼。这是因为在这种情况下,搜索时间对你来说是不可接受的。那么问题来了,如何减少搜索时间? 我们将在本章中学习这一点。

企业搜索引擎的需求

就像我们都需要一个工具来搜索自己的东西一样,每个公司也需要一个搜索引擎来构建,以便内部和外部实体可以找到他们想要的东西。

例如,员工需要搜索他的/她的年假余额、特定月份的工资条等等。人力资源部门可能需要搜索在财务组工作的员工等等。在一家电子商务公司中,产品目录是最可搜索的对象。这是一个非常敏感的对象,因为它直接影响到公司的收入。如果客户想要买一双鞋,他/她首先可以做的就是搜索公司的产品目录。如果搜索时间超过几秒钟,客户可能会对产品失去兴趣。也可能发生这样的情况,同一个客户会去另一个网站买鞋,从而导致收入损失。

看起来,即使拥有世界上所有的技术和数据,如果没有两个关键组件,我们也做不了太多:

  • 数据搜索

  • 数据索引

例如,像谷歌、亚马逊和苹果这样的公司已经改变了世界对搜索的期望。我们都期望他们能够随时随地进行搜索,使用任何工具,如网站、移动设备和像谷歌 Echo、Alexa 和 HomePad 这样的语音激活工具。我们期望这些工具能够回答我们所有的问题,从“今天天气怎么样?”到给我列出附近的所有加油站。

随着这些期望的增长,索引更多数据的需要也在增长。

构建企业搜索引擎的工具

以下是一些流行的工具/产品/技术:

  • Apache Lucene

  • Elasticsearch

  • Apache Solr

  • 自定义(内部)搜索引擎

在本章中,我将详细讨论 Elasticsearch。我将在概念层面上讨论 Apache Solr。

Elasticsearch

Elasticsearch 是一个开源的搜索引擎。它基于 Apache Lucene。它是分布式的,并支持多租户能力。它使用无模式的 JSON 文档,并具有基于 HTTP 的内置网络接口。它还支持分析 RESTful 查询工作负载。它是一个基于 Java 的数据库服务器。其主要协议是 HTTP/JSON。

为什么选择 Elasticsearch?

到目前为止,Elasticsearch 是最流行的数据索引工具。这是因为以下特性:

  • 它是快速的。数据以实时速度索引。

  • 它是可扩展的。它水平扩展。

  • 它是灵活的。它支持任何数据格式,结构化、半结构化或非结构化。

  • 它是分布式的。如果一个节点失败,集群仍然可用于业务。

  • 它支持任何语言的数据搜索查询:Java、Python、Ruby、C#等等。

  • 它有一个Hadoop 连接器,这促进了 Elasticsearch 和 Hadoop 之间顺畅的通信。

  • 它支持在大型数据集上进行强大的数据聚合,以找到趋势和模式。

  • Elastic stack(Beats、Logstash、Elasticsearch 和 Kibana)和 X-Pack 为数据摄取、数据索引、数据可视化、数据安全和监控提供开箱即用的支持。

Elasticsearch 组件

在我们深入探讨之前,让我们了解 Elasticsearch 的一些重要组件。

索引

Elasticsearch 索引是一组 JSON 文档。Elasticsearch 是一个可能包含多个索引的数据存储。每个索引可以分成一个或多个类型。类型是相似文档的集合。一个类型可以包含多个文档。从数据库的类比来说,索引是一个数据库,而它的每个类型都是一个表。每个 JSON 文档是表中的一行。

在 Elasticsearch 6.0.0 或更高版本中创建的索引可能只能包含一个映射类型。

映射类型将在 Elasticsearch 7.0.0 中完全移除。

文档

在 Elasticsearch 中的文档意味着一个 JSON 文档。它是存储在索引中的基本数据单元。一个索引由多个文档组成。在关系型数据库管理系统(RDBMS)的世界里,文档不过是表中的一行。例如,一个客户文档可能看起来像以下这样:

{
"name": "Sam Taylor",
"birthdate": "1995-08-11",
"address":
{
"street": "155 rabbit Street",
"city": "San Francisco",
"state": "ca",
"postalCode": "94107"
},
"contactPhone":
[
{
"type": "home",
"number": "510-415-8929"
},
{
"type": "cell",
"number": "408-171-8187"
}
]
}

映射

映射是索引的架构定义。就像数据库一样,我们必须定义一个表的数据库结构。我们必须创建一个表,它的列和列数据类型。在 Elasticsearch 中,我们必须在其创建期间定义索引的结构。我们可能需要定义哪些字段可以被索引、可搜索和可存储。

好消息是,Elasticsearch 支持 动态映射。这意味着在索引创建时映射不是必需的。可以创建没有映射的索引。当文档被发送到 Elasticsearch 进行索引时,Elasticsearch 会自动定义每个字段的 数据结构,并将每个字段设置为可搜索字段。

集群

Elasticsearch 是由节点(服务器)组成的集合。每个节点可能存储索引中的部分数据,并为所有节点提供联邦索引和搜索功能。每个集群默认都有一个独特的名称,即 elasticsearch。集群被划分为多种类型的节点,即主节点和数据节点。但可以使用仅安装了主节点和数据节点的单个节点来创建 Elasticsearch 集群:

  • 主节点:它控制整个集群。集群中可以有多个主节点(建议三个)。其主要功能是索引创建或删除以及将分片(分区)分配给数据节点。

  • 数据节点:它存储实际的索引数据在分片中。它们支持所有数据相关操作,如聚合、索引搜索等。

类型

文档被划分为各种逻辑类型,例如订单文档、产品文档、客户文档等。而不是创建三个单独的订单、产品、客户索引,单个索引可以逻辑上划分为订单、产品、客户类型。在 RDBMS 类比中,类型就是数据库中的一个表。因此,类型是索引的逻辑分区。

类型在 Elasticsearch 6.0 版本中已弃用。

如何在 Elasticsearch 中索引文档?

让我们通过索引这三个示例文档来学习 Elasticsearch 实际是如何工作的。在学习这个过程中,我们将涉及到 Elasticsearch 的几个重要功能/概念。

这些是要索引的三个示例 JSON 文档:

{
"name": "Angela Martin",
"birthdate": "1997-11-02",
"street": "63542 Times Square",
"city": "New York",
"state": "NY",
"zip": "10036",
"homePhone": "212-415-8929",
"cellPhone": "212-171-8187"
} ,
{
"name": "Sam Taylor",
"birthdate": "1995-08-11",
"street": "155 rabbit Street",
"city": "San Francisco",
"state": "ca",
"zip": "94107",
"homePhone": "510-415-8929",
"cellPhone": "408-171-8187"
} ,
{
"name": "Dan Lee",
"birthdate": "1970-01-25",
"street": "76336 1st Street",
"city": "Los Angeles",
"state": "ca",
"zip": "90010",
"homePhone": "323-892-5363",
"cellPhone": "213-978-1320"
}

Elasticsearch 安装

首先要做的。让我们安装 Elasticsearch。

请按照以下步骤在您的服务器上安装 Elasticsearch。假设您正在使用 CentOS 7 在服务器上安装 Elasticsearch。

需要的最小硬件配置是什么?

  • RAM:4 GB

  • CPU:2 核

需要安装哪个 JDK?我们需要 JDK 8。如果您服务器上没有安装 JDK 8,请按照以下步骤安装 JDK 8:

  1. 切换到主目录:
 $ cd ~
  1. 下载 JDK RPM:
$ wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u73-b02/jdk-8u73-linux-x64.rpm
  1. 使用 YUM 安装 RPM(假设您有 sudo 权限):
$ sudo yum -y localinstall jdk-8u73-linux-x64.rpm

由于我们已经成功在服务器上安装了 JDK 8,让我们开始安装 Elasticsearch。

Elasticsearch 安装

对于详细的安装步骤,请参阅以下 URL:

https://www.elastic.co/guide/en/elasticsearch/reference/current/rpm.html

  1. Elasticsearch v6.2.3 的 RPM 可以从网站上下载,并按照以下步骤安装:
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.1.2.rpm
$ sudo rpm --install elasticsearch-6.1.2.rpm
  1. 要配置 Elasticsearch 在系统启动时自动启动,请运行以下命令。
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable elasticsearch.service
  1. Elasticsearch 可以如下启动和停止:
sudo systemctl start elasticsearch.service
sudo systemctl stop elasticsearch.service

主要配置文件位于名为elasticsearch.ymlconfig文件夹中。

elasticsearch.yml中进行以下初始配置更改。找到并替换以下参数:

cluster.name: my-elaticsearch
path.data: /opt/data
path.logs: /opt/logs
network.host: 0.0.0.0
http.port: 9200

现在启动 Elasticsearch:

sudo systemctl start elasticsearch.service

使用以下 URL 检查 Elasticsearch 是否正在运行:

http://localhost:9200

我们将得到以下响应:

// 20180320161034
// http://localhost:9200/
{
 "name": "o7NVnfX",
"cluster_name": "my-elasticsearch",
"cluster_uuid": "jmB-_FEuTb6N_OFokwxF1A",
"version": {
"number": "6.1.2",
"build_hash": "5b1fea5",
"build_date": "2017-01-10T02:35:59.208Z",
"build_snapshot": false,
"lucene_version": "7.1.0",
"minimum_wire_compatibility_version": "5.6.0",
"minimum_index_compatibility_version": "5.0.0"
},
"tagline": "You Know, for Search"
}

现在,我们的 Elasticsearch 运行良好。让我们创建一个索引来存储我们的文档。

创建索引

我们将使用以下curl命令创建我们的第一个名为my_index的索引:

curl -XPUT 'localhost:9200/my_index?pretty' -H 'Content-Type: application/json' -d'
{
"settings" : {
"index" : {
"number_of_shards" : 2,
"number_of_replicas" : 1
}
}
}
'

我们将得到以下响应:

{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "my_index"
}

在索引创建 URL 中,我们使用了设置、分片和副本。让我们了解分片和副本的含义。

主分片

我们已创建包含三个分片的索引。这意味着 Elasticsearch 会将索引划分为三个分区。每个分区称为分片。每个分片都是一个完整的、独立的 Lucene 索引。基本思想是 Elasticsearch 会将每个分片存储在不同的数据节点上,以增加可伸缩性。在创建索引时,我们必须指定我们想要的分片数量。然后,Elasticsearch 将自动处理。在文档搜索过程中,Elasticsearch 将聚合来自所有可用分片的所有文档,以合并结果以满足用户的搜索请求。这对用户来说是完全透明的。因此,概念是索引可以被划分为多个分片,并且每个分片可以托管在每个数据节点上。分片的放置将由 Elasticsearch 本身负责。如果我们没有在创建索引的 URL 中指定分片数量,Elasticsearch 将默认为每个索引创建五个分片。

副本分片

我们已创建包含一个副本的分片索引。这意味着 Elasticsearch 将为每个分片创建一个副本(副本),并将每个副本放置在除原始分片外的单独数据节点上。因此,现在有两个分片:主分片(原始分片)和副本分片(主分片的副本)。在高量搜索活动期间,Elasticsearch 可以从放置在不同数据节点上的主分片或副本分片提供查询结果。这就是 Elasticsearch 如何通过每个搜索查询可能访问不同的数据节点来增加查询吞吐量的方式。

总结来说,主分片和副本分片都提供了横向可伸缩性和吞吐量。它通过在所有副本上并行执行搜索来扩展您的搜索量/吞吐量。

Elasticsearch 是一个分布式数据存储。这意味着数据可以被分成多个数据节点。例如,假设我们只有一个数据节点,并且我们一直在同一个数据节点上摄取和索引文档,那么在达到该节点的硬件容量后,我们可能无法再摄取文档。因此,为了容纳更多文档,我们必须向现有的 Elasticsearch 集群添加另一个数据节点。如果我们添加另一个数据节点,Elasticsearch 将重新平衡分片到新创建的数据节点。因此,现在用户搜索查询可以同时适应两个数据节点。如果我们创建了一个副本分片,那么每个分片将创建两个副本并放置在这两个数据节点上。现在,如果一个数据节点出现故障,用户搜索查询仍然可以使用一个数据节点执行。

这张图片展示了用户搜索查询是如何从数据节点执行的:

以下图片显示,即使数据节点 A 崩溃,用户查询仍然可以从数据节点 B 执行:

让我们验证新创建的索引:

curl -XGET 'localhost:9200/_cat/indices?v&amp;amp;amp;pretty'

我们将获得以下响应:

health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open my_index 2MXqDHedSUqoV8Zyo0l-Lw 5 1 1 0 6.9kb 6.9kb

让我们了解响应:

  • Health: 这表示集群的整体健康状态为黄色。有三个状态:绿色、黄色和红色。状态 Green 表示集群完全功能正常,一切看起来都很好。状态 "Yellow" 表示集群完全可用,但一些副本尚未分配。在我们的例子中,因为我们只使用一个节点和 5 个分片以及每个分片 1 个副本,Elasticsearch 不会将所有分片的副本都分配到单个数据节点上。集群状态 "Red" 表示集群部分可用,某些数据集不可用。原因可能是数据节点已关闭或其他原因。

  • Status: Open。这意味着集群对业务开放。

  • Index : 索引名称。在我们的例子中,索引名称是 my_index

  • Uuid : 这是唯一的索引 ID。

  • Pri : 主分片的数量。

  • Rep : 副本分片的数量。

  • docs.count : 索引中的文档总数。

  • docs.deleted : 从索引中至今已删除的文档总数。

  • store.size : 主分片和副本分片占用的存储大小。

  • pri.store.size : 仅由主分片占用的存储大小。

将文档导入索引

以下 curl 命令可以用来将单个文档摄取到 my_index 索引中:

curl -X PUT 'localhost:9200/my_index/customer/1' -H 'Content-Type: application/json' -d '
{
"name": "Angela Martin",
"birthdate": "1997-11-02",
"street": "63542 Times Square",
"city": "New York",
"state": "NY",
"zip": "10036",
"homePhone": "212-415-8929",
"cellPhone": "212-171-8187"
}'

在之前的命令中,我们使用了一个名为 customer 的类型,它是索引的逻辑分区。在关系型数据库管理系统(RDBMS)的类比中,类型就像 Elasticsearch 中的表。

此外,我们在类型 customer 后面使用了数字 1。它是客户的 ID。如果我们省略它,那么 Elasticsearch 将为文档生成一个任意的 ID。

我们有多个文档需要插入到 my_index 索引中。在命令行中逐个插入文档非常繁琐且耗时。因此,我们可以将所有文档包含在一个文件中,然后批量插入到 my_index

创建一个 sample.json 文件并包含所有三个文档:

{"index":{"_id":"1"}}

{"name": "Sam Taylor","birthdate": "1995-08-11","address":{"street": "155 rabbit Street","city": "San Francisco","state": "CA","zip": "94107"},"contactPhone":[{"type": "home","number": "510-415-8929"},{"type": "cell","number": "408-171-8187"}]}

{"index":{"_id":"2"}}
{"name": "Dan Lee","birthdate": "1970-01-25","address":{"street": "76336 1st Street","city": "Los Angeles","state": "CA","zip": "90010"},"contactPhone":[{"type": "home","number": "323-892-5363"},{"type": "cell","number": "213-978-1320"}]}

{"index":{"_id":"3"}}

{"name": "Angela Martin","birthdate": "1997-11-02","address":{"street": "63542 Times Square","city": "New York","state": "NY","zip": "10036"},"contactPhone":[{"type": "home","number": "212-415-8929"},{"type": "cell","number": "212-171-8187"}]}

批量插入

使用以下命令一次性将文件 sample.json 中的所有文档导入:

curl -H 'Content-Type: application/json' -XPUT 'localhost:9200/my_index/customer/_bulk?pretty&amp;amp;amp;refresh' --data-binary "@sample.json"

让我们使用我们喜欢的浏览器验证所有记录。它将显示所有三个记录:

http://localhost:9200/my_index/_search

文档搜索

由于我们在 my_index 索引中已有文档,我们可以搜索这些文档:

查找一个 city = "Los Angeles" 的文档,查询如下:

curl -XGET 'http://localhost:9200/my_index2/_search?pretty' -H 'Content-Type: application/json' -d' {
"query": {
"match": {
"city": "Los Angeles" }
}
}'

响应:

{
"took" : 3,
"timed_out" : false,
"_shards" : {
"total" : 3,"successful" : 3,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 1.3862944,
"hits" : [
{
"_index" : "my_index",
"_type" : "customer",
"_id" : "3",
"_score" : 1.3862944,
"_source" : {
"name" : "Dan Lee",
"birthdate" : "1970-01-25",
"street" : "76336 1st Street",
"city" : "Los Angeles", "state" : "ca",
"postalCode" : "90010",
"homePhone" : "323-892-5363",
"cellPhone" : "213-978-1320"
}
}
]
}
}

如果我们分析响应,我们可以看到源部分返回了我们正在寻找的文档。该文档位于索引 my_index 中,"_type" : "customer""_id" : "3"。Elasticsearch 成功搜索了所有 三个分片

hits 部分,有一个名为 _score 的字段。Elasticsearch 计算文档中每个字段的关联频率并将其存储在索引中。这被称为文档的权重。这个权重是基于四个重要因素计算的:词频、逆频率、文档频率和字段长度频率。这又引出了另一个问题,Elasticsearch 是如何索引文档的?

例如,我们有以下四个文档需要索引到 Elasticsearch 中:

  • 我喜欢 Elasticsearch

  • Elasticsearch 是一个文档存储

  • HBase 是键值数据存储

  • 我喜欢 HBase

Term Frequency Document No.
a 2 2
index 1 2
Elasticsearch 2 1,2
HBase 2 1
I 2 1,4
is 2 2,3
Key 1 3
love 2 1,4
store 2 2,3
value 1 3

当我们在 Elasticsearch 中导入三个文档时,会创建一个倒排索引,如下所示。

现在,如果我们想查询术语 Elasticsearch,那么只需要搜索两个文档:1 和 2。如果我们运行另一个查询来查找 love Elasticsearch,那么在发送仅来自第一个文档的结果之前,需要搜索三个文档(文档 1、2 和 4)。

此外,还有一个重要的概念我们需要理解。

元字段

当我们将文档导入索引时,Elasticsearch 会为每个索引文档添加一些元字段。以下是我们示例 my_index 的元字段列表:

  • _index:索引的名称。my_index

  • _type:映射类型。"customer"(在 6.0 版本中已弃用)。

  • _uid_type + _id(在 6.0 版本中已弃用)。

  • _iddocument_id(1)。

  • _all:将索引中所有字段的值连接成一个可搜索的字符串(在 6.0 版本中已弃用)。

  • _ttl:在自动删除之前,文档的生命周期。

  • _timestamp:为文档提供时间戳。

  • _source:这是一个实际的文档,默认情况下会自动索引。

映射

在关系型数据库的类比中,映射意味着定义表模式。我们总是定义一个表结构,即列数据类型。在 Elasticsearch 中,我们还需要为每个字段定义数据类型。但接下来又出现了一个问题。为什么我们在将三个文档导入my_index索引之前没有定义它?答案是简单的。Elasticsearch 并不关心。据说Elasticsearch 是一个无模式的数据库模型

如果我们没有定义映射,Elasticsearch 会动态地为我们创建一个映射,将所有字段定义为文本类型。Elasticsearch 足够智能,能够识别日期字段并将date数据类型分配给它们。

让我们查找索引my_index的现有动态映射:

curl -XGET 'localhost:9200/my_index2/_mapping/?pretty'

响应:

{
"my_index" : {
"mappings" : {
customer" : {
"properties" : {
"birthdate" : {
"type" : "date"
},
"cellPhone" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"city" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"homePhone" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
type" : "keyword",
"ignore_above" : 256
}
}
},
"postalCode" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"state" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"street" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
}

Elasticsearch 支持以下两种映射类型:

  • 静态映射

  • 动态映射

静态映射

在静态映射中,我们始终知道我们的数据,并为每个字段定义适当的数据类型。静态映射必须在索引创建时定义。

动态映射

我们已经在我们的示例中使用了动态映射。基本上,我们没有为任何字段定义任何数据类型。但当我们使用_Bulk加载文档时,Elasticsearch 透明地定义了每个字段的textdate数据类型。Elasticsearch 智能地识别出我们的Birthdate字段是一个日期字段,并将其分配了date数据类型。

Elasticsearch 支持的数据类型

以下表格总结了 Elasticsearch 中可用的数据类型:

常见 复杂 地理 专用
字符串 数组 地理点 ip
关键词 对象(单个 JSON) 地理形状 completion
日期 嵌套(JSON 数组) token_count
长整型 join
短整型 percolator
字节 murmur3
双精度浮点数
浮点数
布尔型
二进制
整数范围
浮点范围
长整型范围
双精度浮点数范围
日期范围

大多数数据类型无需解释。但以下是对一些特定数据类型的解释:

  • Geo-Point:您可以在此处定义纬度和经度点

  • Geo-Shape:用于定义形状

  • Completion:此数据类型用于定义单词自动补全。

  • Join:定义父子关系

  • Percolator:这是用于查询-dsl。

  • Murmur3:在索引时,用于计算哈希值并将其存储到索引中。

映射示例

让我们重新创建另一个索引,second_index,它与我们的first_index类似,具有静态映射,我们将分别定义每个字段的类型:

curl -XPUT localhost:9200/second_index -d '{
"mappings": {
"customer": {
"_source": {
"enabled": false
},
"properties": {
"name": {"type": "string", "store": true},
"birthdate": {"type": "string"},
"street": {"type": "string"},
"city": {"type": "date"},
"state": {"type": "string", "index": "no", "store": true}
"zip": {"type": "string", "index": "no", "store": true}}
}
}
}

让我们了解前面的映射。我们禁用了客户类型的_source字段。这意味着,我们摆脱了 Elasticsearch 默认存储和索引文档的行为。现在,由于我们已禁用它,我们将单独处理每个字段,以决定该字段是否应该被索引、存储或两者兼有。

因此,在前面的示例中,我们只想存储三个字段,namestatezip。我们也不希望索引statezip字段。这意味着statezip字段是不可搜索的。

分析器

我们已经学习了倒排索引。我们知道 Elasticsearch 将文档存储到倒排索引中。这种转换称为分析。这是成功响应索引搜索查询所必需的。

此外,很多时候,在将文档发送到 Elasticsearch 索引之前,我们需要进行某种类型的转换。我们可能需要将文档转换为小写,如果有的话,移除文档中的 HTML 标签,删除两个单词之间的空白,根据分隔符对字段进行分词等。

Elasticsearch 提供了以下内置分析器:

  • 标准分析器:这是一个默认分析器。它使用标准分词器来分割文本。它规范化标记,将标记转换为小写,并删除不需要的标记。

  • 简单分析器:这个分析器由小写分词器组成。

  • 空白分析器:它使用空白分词器在空格处分割文本。

  • 语言分析器:Elasticsearch 提供了许多特定于语言的分词器,例如英语等。

  • 指纹分析器:指纹分析器是一个专业分析器。它创建一个指纹,可以用于重复检测。

  • 模式分析器:模式分析器使用正则表达式将文本分割成术语。

  • 停用分析器:它使用字母分词器来分割文本。它从标记流中删除停用词。例如,所有停用词如 a、an、the、is 等。

  • 关键词分析器:这个分析器将整个流作为一个单独的标记进行分词。它可以用于邮政编码。

  • 字符过滤器:在标记之前准备字符串。例如:移除 HTML 标签。

  • 分词器:必须有一个分词器。它用于将字符串分解成单个术语或标记。

  • 标记过滤器:更改、添加或删除标记。词干提取器是一个标记过滤器,用于获取单词的基本形式,例如:learned, learning => learn

标准分析器的示例:

curl -XPOST 'localhost:9200/_analyze?pretty' -H 'Content-Type: application/json' -d'
{
"analyzer": "standard",
"text": " 1\. Today it's a Sunny-day, very Bright."
}'

响应:

[today, it's , a, sunny, day, very, bright ]

简单分析器的示例:

curl -XPOST 'localhost:9200/_analyze?pretty' -H 'Content-Type: application/json' -d'
{
"analyzer": "simple",
"text": " 1\. Today it's a Sunny-day, very Bright."
}'

响应:

[today, it's , a, sunny, day, very, bright ]

Elasticsearch 堆栈组件

Elasticsearch 堆栈由以下组成

  • Beats

  • Logstash

  • Elasticsearch

  • Kibana

让我们简要地研究它们。

Beats

请参考以下 URL 了解更多关于节拍的信息:www.elastic.co/products/beats

Beats 是轻量级的数据传输工具。Beats 作为代理安装在服务器上。它们的主要功能是收集数据并将其发送到 Logstash 或 Elasticsearch。我们还可以配置 Beats 将数据发送到 Kafka 主题。

有多个节拍。每个节拍都旨在收集特定的数据集和指标。以下是一些节拍类型:

  • Filebeat:用于收集日志文件。它们将收集、解析和可视化常见日志格式简化为单个命令。Filebeat 内置了内部模块(auditd、Apache、nginx、系统、MySQL)。

  • Metricbeat:用于收集指标。它们从任何系统和服务中收集指标,例如内存、CPU 和磁盘。Metricbeat 是一种轻量级的方式,用于发送系统和服务的统计信息。

  • Packetbeat:用于收集网络数据。Packetbeat 是一个轻量级的网络数据包分析器,它将数据发送到 Logstash 或 Elasticsearch。

  • Winlogbeat:用于收集 Windows 事件数据。Winlogbeat 将 Windows 事件日志实时流式传输到 Elasticsearch 和 Logstash。

  • Auditbeat:用于收集审计数据。Auditbeat 收集审计框架数据。

  • 心跳(Heartbeat):用于收集在线时间监控数据。心跳将此信息和响应时间发送到 Elasticsearch。

安装 Filebeat:

$wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-6.1.2-x86_64.rpm
$ sudo rpm --install filebeat-6.1.2-x86_64.rpm
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable filebeat.service

Logstash

Logstash 是一个轻量级、开源的数据处理管道。它允许从各种来源收集数据,实时转换,并将其发送到任何期望的目的地。

它通常用作 Elasticsearch 的数据管道,Elasticsearch 是一个流行的分析和搜索引擎。Logstash 由于其紧密集成、强大的日志处理能力以及超过 200 个预构建的开源插件,这些插件可以帮助您以您想要的方式索引数据,因此成为加载数据到 Elasticsearch 的热门选择。

以下为 Logstash.conf 的结构:

input {
...
}
filter {
...
}
output {
..
}

安装 Logstash:

$ wget https://artifacts.elastic.co/downloads/logstash/logstash-6.1.2.rpm
$ sudo rpm --install logstash-6.1.2.rpm
$ sudo /bin/systemctl daemon-reload
$ sudo systemctl start logstash.service

Kibana

Kibana 是一款开源的数据可视化和探索工具,用于日志和时间序列分析、应用监控和运营智能用例。Kibana 与流行的分析和搜索引擎 Elasticsearch 紧密集成,这使得 Kibana 成为可视化存储在 Elasticsearch 中的数据的默认选择。Kibana 还因其强大的易用功能而受到欢迎,例如直方图、折线图、饼图、热图和内置的地理空间支持

安装 Kibana:

$wget https://artifacts.elastic.co/downloads/kibana/kibana-6.1.2-x86_64.rpm
$ sudo rpm --install kibana-6.1.2-x86_64.rpm
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable kibana.service

用例

假设我们有一个部署在应用服务器上的应用程序。该应用程序正在记录访问日志。那么我们如何使用仪表板分析这个访问日志呢?我们希望创建以下信息的实时可视化:

  • 各种响应代码的数量

  • 响应总数

  • IP 列表

提议的技术栈:

  • Filebeat:读取访问日志并写入 Kafka 主题

  • Kafka:消息队列和缓冲消息

  • Logstash:从 Kafka 拉取消息并写入 Elasticsearch 索引

  • Elasticsearch:用于索引消息

  • Kibana:仪表板可视化

为了解决这个问题,我们在应用服务器上安装了 filebeat。Filebeat 将实时读取访问日志的每一行并将其写入 Kafka 主题。消息将在 Kafka 中缓冲。Logstash 将从 Kafka 主题中拉取消息并写入 Elasticsearch。

Kibana 将通过读取 Elasticsearch 索引中的消息来创建实时流式仪表板。以下是我们用例的架构:

以下是一步步的代码示例,Acccss.log

127.0.0.1 - - [21/Mar/2017:13:52:29 -0400] "GET /web-portal/performance/js/common-functions.js HTTP/1.1" 200 3558
127.0.0.1 - - [21/Mar/2017:13:52:30 -0400] "GET /web-portal/performance/js/sitespeed-functions.js HTTP/1.1" 200 13068
127.0.0.1 - - [21/Mar/2017:13:52:34 -0400] "GET /web-portal/img/app2-icon-dark.png HTTP/1.1" 200 4939
127.0.0.1 - - [21/Mar/2017:13:52:43 -0400] "GET /web-search-service/service/performanceTest/release/list HTTP/1.1" 200 186
127.0.0.1 - - [21/Mar/2017:13:52:44 -0400] "GET /web-portal/performance/fonts/opan-sans/OpenSans-Light-webfont.woff HTTP/1.1" 200 22248
127.0.0.1 - - [21/Mar/2017:13:52:44 -0400] "GET /web-portal/performance/img/icon/tile-actions.png HTTP/1.1" 200 100
127.0.0.1 - - [21/Mar/2017:13:52:44 -0400] "GET /web-portal/performance/fonts/fontawesome/fontawesome-webfont.woff?v=4.0.3 HTTP/1.1" 200 44432

以下为完整的 Filebeat.ymal

在 Kafka 输出部分,我们提到了 Kafka 代理的详细信息。output.kafka

# initial brokers for reading cluster metadata
hosts: ["localhost:6667"]

以下为完整的 Filebeat.ymal

###################### Filebeat Configuration Example #########################
# This file is an example configuration file highlighting only the most common
# options. The filebeat.reference.yml file from the same directory contains all the
# supported options with more comments. You can use it as a reference.
#
# You can find the full configuration reference here:
# https://www.elastic.co/guide/en/beats/filebeat/index.html
# For more available modules and options, please see the filebeat.reference.yml sample
# configuration file.
#======================== Filebeat prospectors========================
filebeat.prospectors:
# Each - is a prospector. Most options can be set at the prospector level, so
# you can use different prospectors for various configurations.
# Below are the prospector specific configurations.
- type: log
# Change to true to enable this prospector configuration.
enabled: true
# Paths that should be crawled and fetched. Glob based paths.
paths:
- /var/log/myapp/*.log
#- c:programdataelasticsearchlogs*
#json.keys_under_root: true
#json.add_error_key: true
# Exclude lines. A list of regular expressions to match. It drops the lines that are
# matching any regular expression from the list.
#exclude_lines: ['^DBG']
# Include lines. A list of regular expressions to match. It exports the lines that are
# matching any regular expression from the list.
#include_lines: ['^ERR', '^WARN']
# Exclude files. A list of regular expressions to match. Filebeat drops the files that
# are matching any regular expression from the list. By default, no files are dropped.
#exclude_files: ['.gz$']
# Optional additional fields. These fields can be freely picked
# to add additional information to the crawled log files for filtering
#fields:
# level: debug
# review: 1
fields:
app: myapp
env: dev
dc: gce
### Multiline options
# Mutiline can be used for log messages spanning multiple lines. This is common
# for Java Stack Traces or C-Line Continuation
# The regexp Pattern that has to be matched. The example pattern matches all lines starting with [#multiline.pattern: ^[
# Defines if the pattern set under pattern should be negated or not. Default is false.
#multiline.negate: false
# Match can be set to "after" or "before". It is used to define if lines should be append to a pattern
# that was (not) matched before or after or as long as a pattern is not matched based on negate.
# Note: After is the equivalent to previous and before is the equivalent to to next in Logstash
#multiline.match: after
#============================= Filebeat modules ===============================
filebeat.config.modules:
# Glob pattern for configuration loading
path: ${path.config}/modules.d/*.yml
# Set to true to enable config reloading
reload.enabled: false
# Period on which files under path should be checked for changes
#reload.period: 10s
#==================== Elasticsearch template setting ==========================
setup.template.settings:
index.number_of_shards: 3
#index.codec: best_compression
#_source.enabled: false
#================================ General =====================================
# The name of the shipper that publishes the network data. It can be used to group
# all the transactions sent by a single shipper in the web interface.
#name:
# The tags of the shipper are included in their own field with each
# transaction published.
#tags: ["service-X", "web-tier"]
# Optional fields that you can specify to add additional information to the
# output.
#fields:
# env: staging
#============================== Dashboards =====================================
# These settings control loading the sample dashboards to the Kibana index. Loading
# the dashboards is disabled by default and can be enabled either by setting the
# options here, or by using the `-setup` CLI flag or the `setup` command.
#setup.dashboards.enabled: false
# The URL from where to download the dashboards archive. By default this URL
# has a value which is computed based on the Beat name and version. For released
# versions, this URL points to the dashboard archive on the artifacts.elastic.co
# website.
#setup.dashboards.url:
#============================== Kibana =====================================
# Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API.
# This requires a Kibana endpoint configuration.
setup.kibana:
# Kibana Host
# Scheme and port can be left out and will be set to the default (http and 5601)
# In case you specify and additional path, the scheme is required: http://localhost:5601/path
# IPv6 addresses should always be defined as: https://[2001:db8::1]:5601
#host: "localhost:5601"
#============================= Elastic Cloud ==================================
# These settings simplify using filebeat with the Elastic Cloud (https://cloud.elastic.co/).
# The cloud.id setting overwrites the `output.elasticsearch.hosts` and
# `setup.kibana.host` options.
# You can find the `cloud.id` in the Elastic Cloud web UI.
#cloud.id:
# The cloud.auth setting overwrites the `output.elasticsearch.username` and
# `output.elasticsearch.password` settings. The format is `<user>:<pass>`.
#cloud.auth:
#================================ Outputs =====================================
# Configure what output to use when sending the data collected by the beat.
#-----------------------------------Kafka Output-------------------------------
output.kafka: # initial brokers for reading cluster metadata hosts: ["localhost:6667"] # message topic selection + partitioning
topic: logs-topic
partition.round_robin:
reachable_only: false
required_acks: 1
compression: gzip
max_message_bytes: 1000000
#-------------------------- Elasticsearch output ------------------------------
#output.elasticsearch:
# Array of hosts to connect to.
#hosts: ["localhost:9200"]
# Optional protocol and basic auth credentials.
#protocol: "https"
#username: "elastic"
#password: "changeme"
#----------------------------- Logstash output --------------------------------#output.logstash:
# The Logstash hosts
#hosts: ["localhost:5044"]
# Optional SSL. By default is off.
# List of root certificates for HTTPS server verifications
#ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]
# Certificate for SSL client authentication
#ssl.certificate: "/etc/pki/client/cert.pem"
# Client Certificate Key
#ssl.key: "/etc/pki/client/cert.key"
#================================ Logging =====================================
# Sets log level. The default log level is info.
# Available log levels are: error, warning, info, debug
logging.level: debug
# At debug level, you can selectively enable logging only for some components.
# To enable all selectors use ["*"]. Examples of other selectors are "beat",
# "publish", "service".
#logging.selectors: ["*"]
#============================== Xpack Monitoring ===============================
# filebeat can export internal metrics to a central Elasticsearch monitoring
# cluster. This requires xpack monitoring to be enabled in Elasticsearch. The
# reporting is disabled by default.
# Set to true to enable the monitoring reporter.
#xpack.monitoring.enabled: false
# Uncomment to send the metrics to Elasticsearch. Most settings from the
# Elasticsearch output are accepted here as well. Any setting that is not set is
# automatically inherited from the Elasticsearch output configuration, so if you
# have the Elasticsearch output configured, you can simply uncomment the
# the following line.
#xpack.monitoring.elasticsearch:

在开始将消息摄入 Kafka 之前,我们必须在 Kafka 中创建一个 logs-topic 主题。假设我们已经在服务器上安装了 Kafka。请参阅 第二章,Hadoop 生命周期管理 了解有关 Kafka 的更多信息。

创建 logs-topic:

bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic logs-topic

以下为 Logstash.conf(从 Kafka 读取消息并将其推送到 Elasticsearch):

input
{
kafka
{
bootstrap_servers => "127.0.0.1:6667"
group_id => "logstash_logs"
topics => ["logs-topic"]
consumer_threads => 1
type => "kafka_logs"
}
}
filter {
if [type] == "kafka_logs"
{
json {
source => "message"
}
grok {
match => { "message" => "%{IP:ip} - - [%{GREEDYDATA:log_timestamp}] %{GREEDYDATA:middle} %{NUMBER:status} %{NUMBER:bytes}" }
}
mutate {
add_field => {
"App" => "%{[fields][app]}"
}
}
}
}
output {
if [App] == "myapp"
{
elasticsearch
{
action => "index"
codec => "plain"
hosts => ["http://127.0.0.1:9200"]
index => "log_index-%{+YYYY-MM-dd}"
}
}
}

在 Kafka 部分,我们提到了以下内容:

Kafka bootstrap_servers => "127.0.0.1:6667"
Kafka topics => ["logs-topic"]

在过滤器部分我们将每条消息转换为 JSON 格式。之后,我们将每条消息解析并分成多个字段,例如 iptimestampstatus。此外,我们还在每条消息中添加了应用程序名称 myapp 字段。

在输出部分,我们将每条消息写入 Elasticsearch。索引名称为 log_index-YYYY-MM-dd

摘要

在本章中,你了解了 Elasticsearch 集群的基本概念和组件。

之后,我们讨论了 Elasticsearch 如何使用倒排索引索引文档。我们还讨论了映射和分析技术。我们学习了如何在将事件摄入 Elasticsearch 之前对其进行去规范化。我们讨论了 Elasticsearch 如何使用横向扩展和吞吐量。在学习了 Elasticstack 组件,如 Beats、Logstash 和 Kibana 之后,我们处理了一个实际用例,展示了如何使用 Filebeat 将访问日志事件摄入 Kafka。我们编写了代码从 Kafka 拉取消息并使用 Logstash 将其摄入 Elasticsearch。最后,我们学习了使用 Kibana 进行数据可视化。

在下一章中,我们将看到如何构建分析来设计数据可视化解决方案,以推动业务决策。

第九章:设计数据可视化解决方案

一旦数据生活在 Hadoop 生态系统中,并且已经经过处理,下一步的逻辑步骤就是构建驱动业务决策的分析。

在本章中,我们将学习以下主题:

  • 数据可视化

  • Apache Druid

  • Apache Superset

数据可视化

数据可视化是通过图形手段理解原始数据中各种实体之间关系的过程。这是一个非常强大的技术,因为它使最终用户能够以非常简单的方式获取信息,即使他们根本不了解底层数据。

数据可视化在从大数据中洞察的视觉沟通中扮演着非常重要的角色。它既是艺术也是科学,需要投入一些努力来理解数据;同时,我们还需要对目标受众有一定的了解。

到目前为止,我们已经看到任何类型的数据都可以存储在Hadoop 文件系统HDFS)中。为了将复杂的数据结构转换为视觉形式,我们需要了解用于表示数据的标准技术。

在数据可视化中,信息以图形的形式传达给最终用户,这些图形可以是 1D、2D、3D,甚至更高维度。这完全取决于我们试图传达的意义。

让我们看看用于向用户传达视觉信息的标准图形:

  • 柱状图/条形图

  • 线形/面积图

  • 饼图

  • 雷达图

  • 散点/气泡图

  • 标签云

  • 气泡图

柱状图/条形图

这是一个二维数据图形表示,其中数据点以垂直/水平条的形式显示。每个条代表一个数据点。当没有时间维度与数据点相关时,这些点显示的顺序可能没有差别。当我们处理用于表示条形图的时间序列数据时,我们通常遵循 X(水平)轴上的显示时间顺序。

让我们看看一个由四个数据点生成的示例图表。这些数据代表每个用户的金额:

图片

解释:图表既有行和列中的文本数据,也有视觉元素。如果你仔细观察,文本数据的大小较小,只有四条记录。但视觉图形直接传达信息,而不需要了解任何关于数据的信息。

图表传达的信息是:

  • 西塔的钱比所有人都要多

  • 吉塔的钱最少

其他解释也是可能的。它们留给读者去思考。

线形/面积图

这通常也是一个二维图表,其中每个数据点都表示为画布上的一个点,所有属于同一数据集的点都通过线条连接。当水平/垂直轴的区域完全覆盖到线条时,此图表就变成了面积图。

同一个图表中可以有不止一条线,这表示同一实体的多个数据系列。

让我们看看基于之前相同数据的这个面积图的样本:

数据图

这些是图表的特性:

  • x 轴上有所有人的名单

  • y 轴表示从0100的金额

  • 图表上有四个点,对应于表格形式的值

  • 点用直线连接

  • 线条下方填充的区域使其成为一个面积图

饼图

这也是一个以圆形中的多个扇区形式绘制的二维图表。当我们要强调所有数据点的相对重要性时,这种图表很有用。

让我们看看之前相同数据集绘制的示例图表,以更好地理解它:

图表

如您所见,使用这个图表很容易理解每个人拥有的金额的相对重要性。

得出的结论与之前的图表相似。但图表是一个简单的圆圈,这里没有多个维度来增加用户的负担。

雷达图

这也是一个二维图形,其中数据轴是等距扇形的边缘(就像饼图的边缘)。当有多个维度,我们想要理解每个数据点的相对重要性时,这种图表很有用。

为了更好地理解这个图表,让我们看看这个样本数据和图形:

图表

数据由八列组成:

  • 第一列:所有用户的名单

  • 第二至第八列:一周中的天数和每个人那天拥有的美元数

我们想要绘制一个图表,显示以下内容:

  • 每天的总美元数

  • 每个人每天拥有的美元数

我们已经将所有这些信息绘制在雷达图中,其中轴是扇区(天数),最大值为400。每个用户的值都是叠加绘制的,这样我们就可以知道总价值而不是相对价值(这与面积堆叠类似)。

散点/气泡图

散点图可以是一个多维图形。这是我们理解起来比较简单的一种图形,因为我们将在画布上渲染每个数据点,对应于轴上的数值。这种图表有助于理解轴上每个点的相对重要性。

气泡图是散点图的一种变体,画布上的点以大泡泡的形式显示值(以表示其重要性)。

让我们用这个例子来看看这两种图形:

扇形图

左侧的图形是气泡图,右侧的是散点图。

让我们看看数据和生成的图表。

输入数据:

  • 由五行组成,而我们在列中有销售产品数量

使用气泡图:

  • y 轴显示产品的数量

  • x 轴仅表示位置,并不反映输入数据的值

  • 画布上的每个点表示对应产品数量的销售

使用散点图:

  • y 轴显示完成的销售

  • x 轴显示销售的产品

  • 画布上的每个点表示输入中的每一行

其他图表

在本节中未涵盖但值得在 d3js.org 网站上探索的其他许多图形类型。这将帮助您了解数据如何表示,以便向用户传达非常好的信息。

Hadoop 中的实用数据可视化

Hadoop 拥有丰富的数据源和应用生态系统,帮助我们构建丰富的可视化。在接下来的章节中,我们将了解两个这样的应用:

  • Apache Druid

  • Apache Superset

我们还将学习如何使用 Apache Superset 与 RDBMS(如 MySQL)中的数据一起使用。

Apache Druid

Apache Druid 是一个分布式、高性能的列式存储。其官方网站是 druid.io

Druid 允许我们存储实时和历史数据,这些数据本质上是时间序列的。它还提供了快速的数据聚合和灵活的数据探索。该架构支持在千兆字节大小上存储万亿数据点。

为了更了解 Druid 架构,请参阅此白皮书 static.druid.io/docs/druid.pdf

德鲁伊组件

让我们快速了解一下 Druid 集群的不同组件:

组件 描述
德鲁伊代理 这些节点知道数据在集群中的位置。这些节点被应用程序/客户端联系以获取 Druid 中的数据。
德鲁伊协调器 这些节点管理历史节点上的数据(它们加载、删除和负载均衡数据)。
德鲁伊统治者 此组件负责接受任务并返回任务的状态。
德鲁伊路由器 当数据量达到或超过太字节级别时,需要这些节点。这些节点将请求路由到代理。
德鲁伊历史 这些节点存储不可变段,是 Druid 集群的骨干。它们提供加载段、删除段,并在段请求上提供查询服务。

其他所需组件

下表展示了其他一些所需的组件:

组件 描述
Zookeeper Apache Zookeeper 是一个高度可靠的分布式协调服务
组件 描述

Apache Druid 安装

Apache Druid 可以以独立模式或作为 Hadoop 集群的一部分进行安装。在本节中,我们将了解如何通过 Apache Ambari 安装 Druid。

添加服务

首先,我们在 Hadoop 集群中服务列表下方调用操作下拉菜单。

屏幕看起来像这样:

选择 Druid 和 Superset

在这个设置中,我们将同时安装 Druid 和 Superset。Superset 是我们将在下一步学习的数据可视化应用程序。

选择屏幕看起来像这样:

在两个服务都选择完毕后,点击下一步。

服务器上的服务放置

在这一步,我们将有一个选择,可以选择应用程序需要安装的服务器。我已经选择了节点 3 来完成这个任务。你可以选择任何你希望的服务器。

屏幕看起来像这样:

当更改完成后,点击下一步。

选择从属节点和客户端

在这里,我们有一个选择,可以选择安装组件所需的从属节点和客户端节点。我已经保留了为我预先选定的选项:

服务配置

在这一步,我们需要选择 Druid 和 Superset 应用程序使用的元数据存储的数据库、用户名和密码。请随意选择默认值。我已经为它们两个选择了 MySQL 作为后端存储。

屏幕看起来像这样:

一旦更改看起来不错,点击屏幕底部的下一步按钮。

服务安装

在这一步,应用程序将自动安装,并在计划的末尾显示状态。

安装完成后,点击下一步。当前屏幕的更改看起来像这样:

安装摘要

一切成功完成后,我们会看到所做工作的摘要。完成时点击完成:

将样本数据导入 Druid

一旦我们所有的 Druid 相关应用程序都在我们的 Hadoop 集群中运行,我们需要一个样本数据集,以便运行一些分析任务。

让我们看看如何加载数据。从互联网下载 Druid 存档:

[druid@node-3 ~$ curl -O http://static.druid.io/artifacts/releases/druid-0.12.0-bin.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
                               Dload Upload Total Spent Left Speed
100 222M 100 222M 0 0 1500k 0 0:02:32 0:02:32 --:--:-- 594k

解压存档:

[druid@node-3 ~$ tar -xzf druid-0.12.0-bin.tar.gz

将样本维基百科数据复制到 Hadoop:

[druid@node-3 ~]$ cd druid-0.12.0
[druid@node-3 ~/druid-0.12.0]$ hadoop fs -mkdir /user/druid/quickstart
[druid@node-3 ~/druid-0.12.0]$ hadoop fs -put quickstart/wikiticker-2015-09-12-sampled.json.gz /user/druid/quickstart/

提交导入请求:

[druid@node-3 druid-0.12.0]$ curl -X 'POST' -H 'Content-Type:application/json' -d @quickstart/wikiticker-index.json localhost:8090/druid/indexer/v1/task;echo
{"task":"index_hadoop_wikiticker_2018-03-16T04:54:38.979Z"}

在这一步之后,Druid 将自动将数据导入 Druid 集群,进度可以在 Overlord 控制台中查看。

该界面可通过http://<overlord-ip>:8090/console.html访问。屏幕看起来像这样:

一旦导入完成,我们将看到作业状态为成功。

如果发生FAILED导入,请确保配置为存储 Druid 集群元数据的后端正在运行。

尽管 Druid 与 OpenJDK 安装配合良好,但我遇到了一些类在运行时不可用的问题。为了克服这个问题,我不得不使用 Oracle Java 版本 1.8 来运行所有 Druid 应用程序。

现在,我们已经准备好开始使用 Druid 来进行我们的可视化任务。

MySQL 数据库

Apache Superset 还允许我们读取 RDBMS 系统中如 MySQL 存在的数据。我们将在本节中创建一个示例数据库,稍后我们可以使用 Superset 来创建可视化。

示例数据库

员工数据库是一个标准数据集,包含一个示例组织和他们的员工、薪资和部门数据。我们将看到如何为我们的任务设置它。

本节假设 MySQL 数据库已经配置并正在运行。

下载示例数据集

在任何可以访问 MySQL 数据库的服务器上,使用以下命令从 GitHub 下载示例数据集:

[user@master ~]$ sudo yum install git -y
[user@master ~]$ git clone https://github.com/datacharmer/test_db
Cloning into 'test_db'...
remote: Counting objects: 98, done.
remote: Total 98 (delta 0), reused 0 (delta 0), pack-reused 98
Unpacking objects: 100% (98/98), done.

将数据复制到 MySQL

在这一步,我们将导入文件中的数据内容到 MySQL 数据库中:

[user@master test_db]$ mysql -u root < employees.sql
INFO
CREATING DATABASE STRUCTURE
INFO
storage engine: InnoDB
INFO
LOADING departments
INFO
LOADING employees
INFO
LOADING dept_emp
INFO
LOADING dept_manager
INFO
LOADING titles
INFO
LOADING salaries
data_load_time_diff
NULL

验证表的一致性

这是一个重要的步骤,只是为了确保我们导入的所有数据都正确存储在数据库中。一致性检查的摘要如下所示,验证过程中会显示:

[user@master test_db]$ mysql -u root -t < test_employees_sha.sql
+----------------------+
| INFO                 |
+----------------------+
| TESTING INSTALLATION |
+----------------------+
+--------------+------------------+------------------------------------------+
| table_name   | expected_records | expected_crc                             |
+--------------+------------------+------------------------------------------+
| employees    | 300024 | 4d4aa689914d8fd41db7e45c2168e7dcb9697359 |
| departments  |  9 | 4b315afa0e35ca6649df897b958345bcb3d2b764 |
| dept_manager |               24 | 9687a7d6f93ca8847388a42a6d8d93982a841c6c |
| dept_emp     | 331603 | d95ab9fe07df0865f592574b3b33b9c741d9fd1b |
| titles       | 443308 | d12d5f746b88f07e69b9e36675b6067abb01b60e |
| salaries     | 2844047 | b5a1785c27d75e33a4173aaa22ccf41ebd7d4a9f |
+--------------+------------------+------------------------------------------+
+--------------+------------------+------------------------------------------+
| table_name   | found_records    | found_crc                        |
+--------------+------------------+------------------------------------------+
| employees    | 300024 | 4d4aa689914d8fd41db7e45c2168e7dcb9697359 |
| departments  |  9 | 4b315afa0e35ca6649df897b958345bcb3d2b764 |
| dept_manager |               24 | 9687a7d6f93ca8847388a42a6d8d93982a841c6c |
| dept_emp     | 331603 | d95ab9fe07df0865f592574b3b33b9c741d9fd1b |
| titles       | 443308 | d12d5f746b88f07e69b9e36675b6067abb01b60e |
| salaries     | 2844047 | b5a1785c27d75e33a4173aaa22ccf41ebd7d4a9f |
+--------------+------------------+------------------------------------------+
+--------------+---------------+-----------+
| table_name   | records_match | crc_match |
+--------------+---------------+-----------+
| employees    | OK | ok        |
| departments  | OK | ok        |
| dept_manager | OK            | ok |
| dept_emp     | OK | ok        |
| titles       | OK | ok        |
| salaries     | OK | ok        |
+--------------+---------------+-----------+
+------------------+
| computation_time |
+------------------+
| 00:00:11         |
+------------------+
+---------+--------+
| summary | result |
+---------+--------+
| CRC     | OK |
| count   | OK |
+---------+--------+

现在数据已经正确加载到名为 employees 的 MySQL 数据库中。

单个标准化表

在数据仓库中,与许多小相关表相比,拥有标准化表是一种标准做法。让我们创建一个包含员工、薪资和部门详情的单个标准化表。

MariaDB [employees]> create table employee_norm as select e.emp_no, e.birth_date, CONCAT_WS(' ', e.first_name, e.last_name) full_name , e.gender, e.hire_date, s.salary, s.from_date, s.to_date, d.dept_name, t.title from employees e, salaries s, departments d, dept_emp de, titles t where e.emp_no = t.emp_no and e.emp_no = s.emp_no and d.dept_no = de.dept_no and e.emp_no = de.emp_no and s.to_date < de.to_date and s.to_date < t.to_date order by emp_no, s.from_date;
Query OK, 3721923 rows affected (1 min 7.14 sec)
Records: 3721923  Duplicates: 0  Warnings: 0

MariaDB [employees]> select * from employee_norm limit 1\G
*************************** 1\. row ***************************
    emp_no: 10001
birth_date: 1953-09-02
 full_name: Georgi Facello
    gender: M
 hire_date: 1986-06-26
    salary: 60117
 from_date: 1986-06-26
   to_date: 1987-06-26
 dept_name: Development
     title: Senior Engineer
1 row in set (0.00 sec)

MariaDB [employees]> 

一旦我们标准化了数据,我们将展示如何使用此表中的数据来生成丰富的可视化。

Apache Superset

Superset 是一个现代的企业级商业智能应用程序。这个应用程序的重要特性是我们可以直接从浏览器运行所有分析。为此不需要安装任何特殊软件。

如果你还记得,我们在前面的章节中已经安装了 Superset 和 Druid。现在我们需要学习如何使用 Superset 来构建丰富的可视化。

访问 Superset 应用程序

在你的网络浏览器中打开 http://<SERVER-IP>:9088/。如果一切运行正常,我们将看到一个登录界面,如下所示:

将用户名输入为 admin,密码为安装过程中选择的密码。

Superset 仪表板

仪表板是 Superset 应用程序中的重要组成部分。它们让我们以图形形式展示分析计算的成果。仪表板是由切片创建的,而切片又是从 Superset 应用程序中配置的各种数据源构建的。

登录成功后,系统不会自动为我们创建任何仪表板。我们将看到一个空白的仪表板列表,如下所示:

为了构建仪表板,我们首先需要配置数据源。因此,让我们点击顶部导航中的“源”菜单,然后点击“刷新 Druid 元数据”:

在此步骤之后,我们将被带到数据源页面,并自动在此处出现一个新的数据源。记得我们之前已经将此数据集上传到 Druid 了吗?

现在,我们可以点击数据源名称(绿色),这将带我们到数据源探索页面:

如我们所见,此页面被分为多个部分。

  • 左侧 UI

    • 数据源和图表类型:在此列中,我们可以选择我们需要使用的数据源,以及我们希望在右侧看到的图形类型。

    • 时间:这是我们可以将数据源的数据限制在给定时间范围内的列。初学者往往会在这个列上犯错误,因为他们不会在右侧看到任何数据。所以,选择一个起始时间值(建议使用 100 年前这样的相对值以获得更好的结果)。

    • 按组分组:此列用于根据输入数据的维度对数据进行分组。

    • 其他选项:在“按组分组”下方还有其他选项,我们将在接下来的步骤中探讨。

  • 右侧 UI

    • 此 UI 包含我们在左侧选择的选项的结果。

理解维基百科编辑数据

在我们开始构建可视化之前。让我们更仔细地看看我们已导入 Druid 的数据以及我们可以从这些数据中渲染哪些类型的图形:

度量/维度 数据类型 描述
delta LONG 以数值形式表示的更改
deleted LONG 文章中删除的数据,以数值形式表示
added LONG 添加的数据,以数值形式表示
isMinor STRING 布尔值,表示这是否是一个小编辑
page STRING 发生更改的维基百科页面
isRobot STRING 更改是否由机器人(不是人类但某种形式的程序)进行的
channel STRING 发生更改的维基百科频道
regionName STRING 进行更改的地理区域名称
cityName STRING 进行更改的城市名称
countryIsoCode STRING 进行更改的国家的 ISO 代码
user STRING 进行更改的维基百科用户或 IP 地址
countryName STRING 进行更改的国家名称
isAnonymous STRING 更改是否由匿名用户(未登录状态)进行的
regionIsoCode STRING 进行更改的地理区域的 ISO 代码
metroCode STRING 这与美国邮政编码类似(见www.nlsinfo.org/usersvc/NLSY97/NLSY97Rnd9geocodeCodebookSupplement/gatt101.html
namespace STRING 维基百科文章/页面命名空间
comment STRING 为这次修改添加的注释
isNew STRING 如果这是一个新页面则为true(见en.wikipedia.org/wiki/Wikipedia:Glossary#N
isUnpatrolled STRING 如果修改不是受监控的,则为true(见en.wikipedia.org/wiki/Wikipedia:New_pages_patrol

因此,我们已经列出了数据的所有属性。让我们看看样本之一,以便更好地理解我们在谈论什么:

{
  "time": "2015-09-12T00:47:21.578Z",
  "channel": "#en.wikipedia",
  "cityName": null,
  "comment": "Copying assessment table to wiki",
  "countryIsoCode": null,
  "countryName": null,
  "isAnonymous": false,
  "isMinor": false,
  "isNew": false,
  "isRobot": true,
  "isUnpatrolled": false,
  "metroCode": null,
  "namespace": "User",
  "page": "User:WP 1.0 bot/Tables/Project/Pubs",
  "regionIsoCode": null,
  "regionName": null,
  "user": "WP 1.0 bot",
  "delta": 121,
  "added": 121,
  "deleted": 0
}

一旦我们对数据维度有了了解,我们需要看看我们可以从这些数据中回答哪些类型的问题。这些问题是我们可以轻松获得的见解。稍后,我们可以用最适合我们的图形形式来表示这些见解。

让我们看看我们可以从这些数据中回答的一些问题。

一维见解

  • 哪些城市进行了修改?

  • 哪些页面被修改了?

  • 哪些国家进行了修改?

  • 创建了多少个新页面?

维度上的计数

  • 从每个城市进行了多少次修改?

  • 哪些是进行修改的前十大城市?

  • 哪些是贡献修改的前十大用户?

  • 哪些命名空间被频繁修改?

多维见解

  • 在所有国家,上午 9:00 到 10:00 之间有多少次修改?

  • 机器人在何时进行编辑?

  • 哪个国家有最多的机器人目标修改来源,以及何时?

看起来很有趣,对吧?我们为什么不尝试使用 Apache Superset 创建一个包含这些见解的仪表板呢?

为了做到这一点,我们需要在 Superset 应用程序中遵循这个简单的流程:

  1. 数据源:

    • 从支持的数据库中定义新的数据源

    • 刷新 Apache Druid 数据源

  2. 创建切片

  3. 使用切片制作仪表板

如果我们回想一下,我们已经在之前的章节中完成了步骤 1。因此,我们可以直接进行第二步和第三步。

使用维基百科数据创建 Superset 切片

让我们看看我们可以使用 Superset 应用程序中的切片功能生成哪些类型的图形。

唯一用户计数

在这个切片中,我们看到如何生成图形以找到对数据集中的编辑做出贡献的唯一用户。

首先,我们需要从顶部导航转到切片页面。之后,屏幕看起来像这样:

从这个页面,点击加号图标(+)添加一个新的切片:

之后,我们看到系统配置的数据源列表。我们必须点击数据源名称:

图片

点击 wikiticker 后,我们将被带到可视化页面,在那里我们定义想要渲染为图形的维度。

对于当前的使用场景,让我们从 UI 中选择以下选项:

UI 位置 图形 说明
侧边栏 图片 数据源选择为[druid-ambari].[wikiticker],并将图形类型选择为“Big Number*”。在时间部分,选择“since”为 5 年前,并将其余值保留为默认值。在度量部分,从自动完成中选择 COUNT(DISTINCT user_unique)。在子标题部分,添加“Unique User Count”,这是在屏幕上显示的。之后,点击顶部的查询按钮。
图形输出 图片 我们在这个图形中看到查询的结果。
保存切片 图片 点击顶部的“另存为”按钮将显示一个弹出窗口,如上图所示,我们需要添加相应的值。将切片保存为Unique Users并将其添加到名为“我的仪表板 1”的新仪表板中。

听起来很简单,对吧?让我们不要急于查看仪表板。让我们在接下来的几节中创建更多来自数据的分析。

美国主要地区的词云

在本节中,我们将学习如何为在 Druid 数据源中为 Wikipedia 编辑做出贡献的美国主要地区构建词云。我们可以继续编辑上一节中的相同切片,或者像上一节中提到的,进入一个空白切片。

让我们集中选择生成词云所需的价值:

UI 位置 图形 说明
侧边栏 图片 将数据源选择为[druid-ambari].[wikiticker],并将图形类型选择为“Word Cloud”。在时间部分,选择“Since”为 5 年前,并将其余值保留为默认值。
图片 在系列部分,从下拉菜单中选择 regionName。在度量中,选择 COUNT(*),这是总编辑数。
图片 在过滤器部分,选择 countryIsoCode;它应该是 US。添加另一个过滤器以仅选择有效区域(跳过 null 代码)。将值添加到图形中所示的位置。
图形输出 图片 点击查询后,我们看到这个漂亮的词云。
保存切片 点击顶部的“另存为”按钮将显示一个类似这样的弹出窗口,其中我们需要添加相应的值。将切片保存为Word Cloud - Top US Regions并将其添加到名为“我的仪表板 1”的新仪表板中

词云的重要性在于我们可以根据它们的相对大小看到顶级单词。这种类型的可视化在想要看到相对重要性的单词较少时非常有帮助。

让我们尝试从数据中生成另一个图形。

太阳花图 – 前 10 个城市

在本节中,我们将了解本章中尚未见过的另一种类型的图表。但首先,让我们提出用例。

我们想要找到每个渠道、城市名称和 namespaces 的所有三个级别的唯一用户;也就是说,图形应该能够显示给我们:

  • 每个渠道的唯一用户

  • 每个渠道/城市名称的唯一用户

  • 每个渠道/城市名称/namespace 的唯一用户

为了显示这种层次结构数据,我们可以使用太阳花图。

让我们检查一下我们需要选择什么类型的值来渲染这种图表:

UI 位置 图形 说明
侧边栏 将数据源选择为[druid-ambari].[wikiticker],图形类型选择为 Sunburst。在时间部分,选择“自 5 年前”的值,并将其余值保留为默认值。
在层次结构部分,从下拉菜单中选择channelcityNamenamespace。在主要指标和次要指标中,选择 COUNT(DISTINCT user_unique),这是总用户数。
过滤器部分,选择cityName并使用正则表达式添加非空条件
点击顶部的“另存为”按钮将显示一个类似这样的弹出窗口。我们需要在这里添加相应的值。将切片保存为Sunburst - Top 10 Cities并将其添加到名为“我的仪表板 1”的新仪表板中
图形输出 点击查询后,我们看到这个漂亮的图形。

如我们所见,图形中有三个同心环:

  • 最内层环是channel维度

  • 中间环显示cityName维度

  • 最外层环是namespace维度

当我们悬停在最内层环上时,我们可以看到它如何向外扩展到最外层圆圈。其他环也会发生同样的事情。

这种类型的图形在我们想要对我们的数据进行漏斗分析时非常有帮助。让我们在下一节中看看另一种分析类型。

通过有向力布局显示前 50 个渠道和 namespaces

定向力布局DFL)是一种点与点相互连接的网络布局。由于它是一种力布局,我们可以看到点在屏幕上移动,因为d3.js应用了物理引擎。

在这个网络图中,我们想了解在唯一用户度量指标下,命名空间和频道之间的连接性。由于这是一个网络图,我们将看到节点在不同的路径中重复。

让我们看看我们如何到达这个图:

UI 位置 图形 说明
侧边栏 选择数据源为[druid-ambari].[wikiticker],图形类型为定向力布局*. *在时间部分,选择 since 的值为 5 年前,并将其余值保留为默认值。
在源/目标部分,从下拉菜单中选择channelnamespace。在度量部分,选择 COUNT(DISTINCT user_unique),这是总用户数。我们将行限制设置为 50,这样我们只会看到前 50 项。.
点击顶部的“另存为”按钮将显示一个类似这样的弹出窗口,其中我们需要添加相应的值。将切片保存为DFL - Top 50 Channels & Namespaces。将其添加到名为My Dashboard 1的新仪表板中。
图形输出 点击查询后,我们看到这个漂亮的图形。

随意拖动图形中的节点,以了解更多它们之间是如何相互连接的。节点的大小表示唯一用户数及其分解(类似于太阳风图)。

让我们在下一节花些时间学习另一个可视化和业务用例。

前 25 个国家/频道分布

现在我们将学习桑基图,这是一种类似于瀑布的方式,用于表示数据之间的分解和互联性。在这种情况下,我们想找出当涉及到唯一用户度量时,channelName 和 countryName 维度是如何相关的:

UI 位置 图形 说明
侧边栏 选择数据源为[druid-ambari].[wikiticker],图形类型为桑基图。在时间部分,选择 Since 的值为 5 年前,并将其余值保留为默认值。
源/目标部分,从下拉菜单中选择channelcountryName。在度量中,选择 COUNT(),这是总编辑数。将行限制保持在 25;这样我们只会看到前 25 项。.*
过滤器部分,选择 countryName 并启用正则表达式过滤器,以便只选择具有有效国家名称的记录。
图片 点击顶部的“另存为”按钮将显示一个弹出窗口。我们需要在这里添加相应的值。将切片保存为“前 25 个国家/频道分布”并将其添加到名为“我的仪表板 1”的新仪表板中。
图形输出 图片 点击查询后,我们看到这个漂亮的图形。

这就完成了到目前为止我们可以生成的所有分析列表。现在在下一节中,我们将看到如何在仪表板中使用这些信息(这本来就是我们的目标)。

从切片创建维基百科编辑仪表板

到目前为止,我们已经看到了如何在 Apache Superset 应用程序中创建存储在 Apache Druid 数据库中的维基百科编辑数据的切片。现在是时候看看如何创建一个仪表板,这样我们就可以与业务团队或任何其他我们希望分享洞察力的团队共享它。

在这个过程中,第一步是点击顶部导航栏上的仪表板菜单。这将带我们到“添加新仪表板”页面,在那里我们需要填写以下详细信息。

元素 描述
标题 我们想要创建的仪表板名称 我的仪表板 1
别名 仪表板的简短别名 dash1
切片 我们想要添加到仪表板中的切片列表。
  1. 太阳花图 - 前 10 大城市

  2. DFL - 前 50 个频道与命名空间

  3. 前 25 个国家/频道贡献

  4. 词云 - 前 25 个美国地区

  5. 独立用户

|

其他字段 我们可以留其他字段为空,因为它们不是创建仪表板的必需项

这是本页的图形:

图片

当屏幕底部的更改看起来不错时,点击保存按钮。

这将带我们到下一步,我们可以看到仪表板已成功创建:

图片

我们可以在仪表板列表中看到“我的仪表板 1”。为了访问此仪表板,点击它,我们将被带到仪表板屏幕:

图片

如我们所见,我们有一种非常强大的方式来表示所有原始数据。这肯定会对最终用户产生影响,确保信息得到传达。

到目前为止,我们已经学习了如何从存储在 Apache Druid 列式数据库中的数据创建切片和仪表板。在下一节中,我们将看到如何连接到 RDBMS 并从该数据生成切片和仪表板。

Apache Superset 与 RDBMS

Apache Superset 使用 Python 编程语言构建,并支持许多关系型数据库,因为它使用 SQLAlchemy 作为数据库驱动程序。这些驱动程序的安装超出了本节的范围。但是,安装它们应该非常简单。大多数情况下,操作系统供应商为我们打包它们。因此,我们不需要担心这些驱动程序的手动安装。

支持的数据库

这里列出了 Apache Superset 支持的一些数据库:

数据库名称 Python 包名称 驱动程序 URI 前缀 详细信息
MySQL mysqlclient mysql:// Oracle MySQL 数据库
PostgreSQL psycopg2 postgresql+psycopg2:// 世界上最先进的开源数据库
Presto pyhive presto:// 开源分布式查询引擎
Oracle cx_Oracle oracle:// Oracle 公司创建的多模型数据库管理系统
Sqlite sqlite:// 快速、可扩展的嵌入式数据库库
Redshift sqlalchemy-redshift postgresql+psycopg2:// 基于 PostgreSQL 构建的 Amazon Redshift 列式数据库
MSSQL pymssql mssql:// Microsoft SQL Server
Impala impyla impala:// 在 Hadoop 上运行的 Apache Impala 大规模并行处理 SQL 引擎
SparkSQL pyhive jdbc+hive:// Apache Spark 中的 SQL 编写模块
Greenplum psycopg2 postgresql+psycopg2:// 高级、功能齐全的开源数据平台 Greenplum
Athena PyAthenaJDBC awsathena+jdbc:// Amazon Athena 是无服务器交互式查询服务
Vertica sqlalchemy-vertica-python vertica+vertica_python:// Vertica 是大数据分析软件
ClickHouse sqlalchemy-clickhouse clickhouse:// 开源分布式、列式数据存储

上表的部分内容摘自 Apache Superset 的官方文档(superset.incubator.apache.org/installation.html#database-dependencies

理解员工数据库

如果你还记得,在前面的章节中,我们已经导入了一个名为 Employees 的示例数据库,并将其加载到 MySQL 数据库中。我们将进一步深入研究这个示例数据存储,以便了解我们可以从这个数据存储中生成哪些类型的分析。

员工表

employees 表包含员工的详细信息(随机生成数据),以下属性如下:

数据类型 描述
emp_no INTEGER 员工编号
birth_date DATE 员工出生日期
first_name STRING 员工名字
last_name STRING 员工姓氏
gender STRING 员工性别,男性为 M,女性为 F
hire_date STRING 员工最新加入日期

部门表

departments 表包含组织中每个部门的基本详细信息。以下表格进一步解释了这一点:

表列 数据类型 描述
dept_no STRING 部门编号
dept_name STRING 部门名称

部门经理表

dept_manager 表记录了担任特定部门经理的员工记录。更多详细信息请参阅此表:

表列 数据类型 描述
emp_no INT 担任此部门经理的员工 ID
dept_no STRING 部门 ID
from_date DATE 员工担任该部门经理的起始日期
to_date DATE 员工担任该部门经理的结束日期

部门员工表

dept_emp 表包含所有记录,显示每位员工属于一个部门的时间长度。

表列 数据类型 描述
emp_no INT 员工 ID
dept_no STRING 部门 ID
from_date DATE 员工属于该部门的起始日期
to_date DATE 员工在该部门的最后日期

职位表

titles 表包含从给定日期到结束日期的所有员工角色。更多详细信息如下所示:

表列 数据类型 描述
emp_no INT 员工 ID
title STRING 员工的职位
from_date DATE 员工担任此角色的起始日期
to_date DATE 员工扮演此角色的最后日期

薪资表

salaries 表包含特定员工的薪资历史。更多详细信息如下表所示:

表列 数据类型 描述
emp_no INT 员工 ID
salary INT 员工薪资
from_date DATE 计算薪资的起始日
to_date DATE 计算薪资的最后日期

标准化员工表

employee_norm 表包含来自员工、薪资、部门、dept_emp 和职位表的资料。让我们详细查看此表:

表列 数据类型 描述
emp_no INT 员工 ID
birth_date DATE 员工出生日期
full_name STRING 员工全名
gender STRING 员工性别
hire_date DATE 员工加入日期
salary INT 员工在该期间的薪资
from_date DATE 薪资周期开始
to_date DATE 薪资周期结束日期
dept_name STRING 员工在薪资周期内工作的部门
title STRING 此时间段内员工的职位

在了解了员工数据库中各种表的知识后,我们现在对已有的数据有了些了解。接下来的任务是找出我们可以从这些数据中生成哪些类型的分析。我们将在下一节中学习这一点。

员工数据库的 Superset 切片

一旦我们对存储在 MySQL 数据库中的数据类型有了基本的了解,我们现在将看到我们可以从这些数据中回答哪些类型的问题。

一维洞察:

  • 组织中有多少名员工?

  • 组织中为所有员工支付的总薪资是多少?

  • 有多少个部门?

多维洞察

  • 每年支付的总薪酬是多少?

  • 每个部门的总薪酬是多少?

  • 每年谁是最高薪酬的员工?

如果我们沿着这些思路思考,我们应该能够回答关于数据的一些非常重要的问题,并生成漂亮的图形。

让我们来看看在接下来的章节中我们可以生成哪些类型的可视化示例。

注册 MySQL 数据库/表格

在我们开始为员工表生成切片之前,我们首先应该注册它。注册过程包括以下步骤。

通过点击顶部导航栏中的“来源”菜单下的“数据库”下拉菜单来打开数据库,如下所示:

图片

在此之后,我们需要点击页面上的加号(+)图标:

图片

这将带我们到一个可以注册新数据库的页面。屏幕看起来如下所示:

图片

我们将填写以下详细信息,如下所示。

字段名 描述
数据库 employees 我们想要注册的数据库名称。(输入与 MySQL 数据库中相同的名称)
SQLAlchemy URI mysql+pymysql://superset:superset@master:3306/employees 以编程方式访问此数据库的 URI。这将包括协议/驱动程序、用户名、密码、主机名和数据库名
其他字段 保持默认设置

在此之后,点击“保存”按钮,这将使用 Apache Superset 保存数据库详细信息。我们将被带到表格列表页面,如下所示:

图片

如我们所见,我们已将 MySQL 后端注册的员工数据库。

在下一步中,我们需要从顶部菜单中选择表格:

图片

由于我们没有注册任何表格,我们将看到一个空页,如下所示:

图片

为了注册一个新的表格,我们必须点击 UI 中的加号(图标),这将带我们到以下页面:

图片

按照以下所示输入字段的值,完成后点击保存:

字段名 描述
表名 employee_norm 我们想要注册的表格名称。
数据库 employees 选择已与 Superset 注册的数据库。

现在,我们可以看到表格已成功注册,如下面的屏幕截图所示:

图片

Superset 的重要特性之一是它会根据数据类型自动选择我们可以在表格列上执行的不同类型操作。这决定了我们在 UI 的其余部分显示哪些维度和度量。

为了选择这些选项,我们需要通过点击编辑图标来编辑表格,然后我们就会看到这个页面:

图片

如我们所见,Apache Superset 自动识别了每个字段的数据类型,并且还为我们提供了选择这些维度用于各种活动的选项。这些活动在以下表格中列出:

活动 描述
可分组 如果复选框被选中,则该字段可以用作分组操作 (GROUP BY 在 SQL 中) 的一部分。
可筛选 如果复选框被选中,则该字段可以用作条件操作 (WHERE 子句) 的一部分。
计数唯一 如果复选框被选中,则该字段可以用作对字段进行计数 (DISTINCT) 操作的一部分。
总计 如果复选框被选中,则该字段可以用作 SUM() 函数的一部分。
最小/最大 表示该字段可以用作查找最小和最大值的操作的一部分。
是否时间维度 表示该字段是时间维度。

按照上述方式修改,然后点击保存按钮。

现在我们已经准备好在接下来的步骤中开始创建切片和仪表板。

切片和仪表板创建

正如我们在前面的章节中看到的,为了创建仪表板,我们首先需要创建切片。在本节中,我们将学习如何创建一些切片。

部门薪酬细分

在这个切片中,我们将学习如何创建一个可视化,它将显示每个部门的薪酬细分百分比:

UI 位置 图形 描述
侧边栏 图片 数据源与图表类型:选择 [employees].[employee_norm] 作为 数据源,分布 - NVD3 - 饼图作为图表类型。在 时间 部分,选择 birth_date 作为时间列,并选择 100 年前作为 起始 列。在 度量 部分,从下拉菜单中选择 sum_salary 作为值,并选择 dept_name 作为 分组依据
图形输出 图片 点击查询按钮将渲染这个令人满意的图表。将其保存为“部门薪酬细分”。

就像在前面的章节中一样,看看创建一个看起来好的图形是多么容易,而且不需要任何编程知识。

在下一节中,我们将学习关于来自同一员工数据库的另一种类型的图形。

薪酬多样性

这是一个重要的图形,其中我们确定了组织历史中性别之间的薪酬多样性。在这里,我们以平均薪酬为基础进行分析。

UI 位置 图形 描述
侧边栏 图片 数据源与图表类型:选择 [employees].[employee_norm] 作为数据源,时间序列 - 折线图作为图表类型。在时间部分,选择 birth_date 作为时间列,并选择 100 年前作为起始列。在度量部分,选择 avg_salary 作为度量,并选择 gender 作为 分组依据
输出 展示每年按性别平均薪资的图形。以薪资多样性为标题保存此图形。

如我们从图形中看到的,薪资在性别之间分配均匀,并且非常接近。在此期间,平均薪资也有类似的增长。

在下一节中,我们将学习生成另一种类型的图形,它将为我们提供对数据的不同见解。

每年每个角色的薪资变化

这是一个重要的统计数据,我们想要找出在组织内部不同职位随年份变化的薪资变化情况。

UI 位置 图形 描述
侧边栏 数据源与图表类型:选择[employees].[employee_norm]作为数据源,时间序列 - 百分比变化作为图表类型。在时间部分,选择 from_date 作为时间列,Year 作为时间粒度,100 years ago 作为列。在度量部分,选择 sum_salary 作为度量,title 作为分组
输出 点击查询,得到以下图形。以每年每个角色的薪资变化为名保存此图形。

从这个图形中,我们可以发现组织内部少数角色在总薪资方面存在很大的差异。

到目前为止,我们已经创建了三个切片,我们将使用迄今为止创建的切片创建一个新的仪表板。

仪表板创建

在这一步,我们将通过转到仪表板页面并点击添加仪表板图标(如前几节所示)来创建一个新的仪表板。

我们将看到以下屏幕,其中我们选择我们迄今为止创建的三个切片并点击保存:

一旦仪表板成功保存,我们可以看到如下所示:

如我们所见,仪表板是一种非常强大的方式,可以以简单的方式表达大量数据。

摘要

在本章中,我们学习了数据可视化的概念以及它是如何帮助用户在没有任何底层数据知识的情况下接收所需信息的。然后,我们看到了不同的数据图形化展示方式。

我们了解了 Hadoop 应用,如 Apache Druid 和 Apache Superset,它们用于数据可视化,并学习了如何使用它们与如 MySQL 这样的关系数据库管理系统(RDBMS)。我们还看到了一个示例数据库,以帮助我们更好地理解应用。

在下一章中,我们将学习如何在云上构建我们的 Hadoop 集群。

第十章:使用云开发应用程序

在计算机的早期阶段,CPU 功率和存储非常稀缺,因此购买相关设备的成本非常高。随着 80 年代初苹果和微软个人计算机发展的进步,越来越多的个人和组织获得了这些计算设备。随着芯片制造技术的发展,现在单芯片上可以放置数十亿甚至数万亿个晶体管,这些计算设备的大小急剧减小,从占据整个房间到仅由数据中心中的一个机架单元组成。当计算速度和存储设备容量开始增加时,个人和企业开始意识到,有效地管理他们的计算资源正成为一个挑战。

互联网的广泛应用也对个人获取资源的方式做出了重大贡献。

在本章中,我们将涵盖以下主题:

  • 什么是云?

  • 云中的可用技术

  • 规划云基础设施

    • 云中的高可用性

    • 云中的业务连续性规划

    • 云中的安全性

  • 在云中构建 Hadoop 集群

  • 云与内部应用

  • 云中的数据访问

什么是云?

云计算,或简称云,是一种简单的方式来租用和使用互联网上的资源,如电子存储空间、计算能力、网络带宽、IP 地址、数据库、Web 服务器等。云推动了“按使用付费”的范式,客户只需为使用这些资源付费,就像电网为客户计费其电力消耗一样。

云计算改变了个人和组织访问和管理互联网上服务器和应用程序的方式。在云计算之前,每个人都习惯于在自己的场所或专用数据中心管理自己的服务器和应用程序。计算(CPU 和 GPU)多核单芯片原始计算能力的增加以及存储空间(HDD 和 SSD)的增加,给有效利用现有计算资源带来了挑战。

云中的可用技术

随着云计算的日益普及,企业开始构建各种技术并将其提供给消费者。我们将通过列表介绍那些开创云服务的企业,以及他们提供的不同类型的技术。

这里是一份提供云服务的公司列表:

  • 微软 Azure (Azure)

  • 亚马逊网络服务

  • 谷歌云平台

  • IBM

  • Salesforce

  • SAP

  • Oracle

  • VMware

各种类型的资源正以以下形式提供给消费者:

  • 平台即服务

  • 基础设施即服务

  • 软件即服务

  • 后端即服务

  • 网络即服务

随着这些服务提供的增加,许多组织无需关注基础设施,如房地产、服务器、防火墙、负载均衡器、交换机、电源供应等。相反,他们可以直接从云服务提供商那里购买这些服务,然后只需关注他们正在构建的应用程序。

现在,让我们看看顶级提供商,微软、亚马逊和谷歌,提供哪些技术:

技术 Azure Amazon Web Services Google Cloud 描述
服务器 Azure 计算 Amazon EC2 Google Compute Engine (GCE) 这种技术涉及提供按需服务器,这些服务器可以是虚拟化的或本质上是专用/裸金属的。
存储 Azure 存储 Amazon EBS Google 存储 这是一种按需存储,可以根据需要附加到计算节点。一些供应商提供按需调整这些存储设备大小的能力。
网络 Azure 网络 Google 网络服务 提供商根据应用程序的网络需求提供从 100 Mbps 到 10 Gbps 的网络带宽。
数据库 Azure 数据库 Amazon RDS Google Cloud SQL 使用托管数据库,我们无需担心数据库服务器的维护,因为供应商会自动处理这些支持。请注意,在某些情况下,我们需要自己规划高可用性。
内容分发 Azure CDN Amazon CloudFront Google Cloud CDN 如果我们想通过利用分发网络将静态资源推送给用户,从而显著降低延迟,这将非常有帮助。我们还可以将其用作私有存储,存储所有文件,如备份、会议录音等。
域名系统 (DNS) Azure DNS Amazon Route S3 Google Cloud DNS DNS 对于在互联网上运行我们的应用程序至关重要。这项服务通过确保我们的服务器对其他基础设施的可用性,而无需运行自己的 DNS 服务器,使我们的生活变得更加容易。
商务邮件 Microsoft o365 Amazon WorkMail Google Mail 对于需要以安全且可扩展的方式访问电子邮件和日历的组织来说,这是必不可少的。
机器学习 Azure AI + 机器学习 Amazon 机器学习 Google ML Engine 机器学习技术如今已成为热门词汇。供应商们提供了与机器学习相关的多种技术,我们只需关注我们需要做什么,而无需担心运行这些算法所需的基础设施。
分布式拒绝服务 (DDoS) 保护 Azure DDoS Protection AWS Shield 对于无法承受服务中断的组织来说,这是一件非常重要的事情,当发生大规模拒绝服务攻击影响其网站的常规访客时。
监控 Azure Monitor Amazon CloudWatch Google monitoring 如果不监控我们的应用程序和基础设施,我们可能无法看到我们的表现。这些服务帮助我们保持业务正常进行,并响应触发应用程序和云上运行的基础设施停机的事件。
容器 Azure Container ServiceAKS Amazon Elastic Container Service For KubernetesAmazon EKS Google Kubernetes Engine 这是一种基础设施,允许您以容器形式运行应用程序,而不是拥有完整的计算环境来运行它们。

规划云基础设施

传统组织拥有自己的 IT/基础设施团队来管理他们的专用服务器和网络。在规划迁移到云的过程中,为了更好地操作基础设施,我们必须牢记以下几点。

规划云基础设施涉及:

  • 专用或共享服务器

  • 高可用性

  • 业务连续性规划

  • 安全性

  • 网络架构

专用服务器与共享服务器的比较

云服务提供商为我们提供了租用完全拥有物理硬件或与其他云用户(如我们)共享物理硬件的服务器选项。为了做出决定,我们需要了解这些模型各自的优缺点。

专用服务器

这些是类型的服务器,其所有权属于单个用户或组织,并且不会与其他用户共享。这种设置有几个优点,如下所示:

  • 我们完全拥有物理服务器,并且我们分配的任何进一步的服务器都将部署在同一硬件上

  • 我们可能需要为这种设置支付更多费用

  • 由于 Spectre 和 Meltdown,我们得到了更好的保护,因为硬件没有与任何人共享

  • 由于我们完全拥有硬件,我们不会受到邻居的影响

共享服务器

对于简单的实验来说,拥有一个完整的服务器是昂贵的。在这种情况下,我们可以选择共享设置,在给定的物理硬件上租用一些资源。以下是一些共享服务器的优点:

  • 我们只对我们按需租用的虚拟服务器付费。

  • 尽管云服务提供商提供了绝对隔离,但由于 Spectre 和 Meltdown,我们需要稍微小心一些。

  • 比专用服务器更容易部署。

高可用性

根据我们计划运行的应用程序类型,我们必须了解供应商为这些应用程序提供的服务级别协议SLA)在正常运行时间方面的条款,并据此规划我们的应用程序。

让我们看看使用 DNS 实现应用高可用性的简单方法:

在这种设计中,以下事情会发生:

  • 当用户尝试使用像 Google Chrome 或 Firefox 这样的网络浏览器连接到我们的网站时,它首先尝试联系 DNS 服务器

  • DNS服务器了解我们的前端服务器,并返回所有服务器的列表

  • 浏览器将直接连接到前端服务器

  • 前端服务器连接到数据库并返回请求的资源

在这个设计中,我们需要注意以下事项:

  • 前端服务器直接暴露在互联网上,因此我们应该采取适当的措施,如防火墙或 DDos 保护,以保护我们的服务器

  • 这些前端服务器也应安装最新的操作系统软件,以防止任何攻击

  • 数据库服务器不应对外界可见,因此应设置适当的防火墙以允许前端服务器的请求

云服务提供商提供私有 IP 地址。为了最小化数据库服务器意外暴露给互联网的风险,我们应该阻止这些服务器的公共互联网访问。

让我们看看另一个设计,它也能保护我们的 Web 服务器免受互联网攻击:

图片

与之前的设计相比,我们在以下方面进行了以下更改:

  • 浏览器联系DNS服务器以连接到我们的网站时,DNS服务器提供负载均衡器LB)/代理服务器的 IP 地址

  • 浏览器连接到这个LB

  • LB跟踪哪些后端服务器可用,然后将请求转发到服务器:

    • 服务器与数据库DB)通信并完成响应的构建

    • 响应被发送回LB(负载均衡器)

  • LB将响应发送到浏览器

如果我们仔细观察这个设计,我们会看到它相对于之前设计的优势:

  • LB隐藏了我们的基础设施,因此外部人员无法轻易知道我们的基础设施中有多少服务器

  • LB保护我们的 Web 服务器免受多种攻击

  • LB可以进行 SSL 卸载,其中所有加密/解密操作都在LB级别进行,我们的服务器可以免受 SSL 开销的影响

根据组织的安全策略,您可能需要在 Web 服务器上启用 SSL。

业务连续性规划

业务连续性规划BCP)在组织处于成长阶段时是一个非常重要的考虑因素。网络、服务器、数据库或其他云基础设施组件的任何停机都可能使整个业务陷入瘫痪。

在规划 BCP 时,有几个关键事项需要牢记:

  • 基础设施不可用

  • 自然灾害

  • 商业数据

基础设施不可用

如果云提供商提供的服务出现计划外中断,它将使我们的所有服务都中断。为了最大限度地提高我们业务的可用性,我们需要在另一个地理区域建立备份设置。这对某些组织来说可能很昂贵,因为整个设置将需要复制,但出于业务连续性的考虑,这是在规划云基础设施时需要考虑的重要功能。

自然灾害

如地震、洪水、火灾事故等事件很难预测。因此,我们需要根据我们的服务器在云中的位置以及供应商遵循的数据中心建设技术标准,制定必要的计划以保持我们的业务运营。

商业数据

商业数据以多种形式存在,并以文件、数据库服务器和大数据系统的形式存储。对于 BCP,我们需要仔细分析在其他远程位置我们可以计划保留我们数据的副本,并执行测试运行以查看我们的应用程序是否可以从任一地理位置通过单击按钮无缝运行。

由于我们在这里处理多个地理区域,我们需要了解,当数据量巨大时,从数据中心到另一个数据中心进行复制需要时间。如果我们在原始设计中没有考虑 BCP,我们的应用程序也必须重新设计。

BCP 设计示例

这张图试图解释我们如何通过在多个数据中心设置相同的应用程序来实现业务连续性计划(BCP):

图片

该系统可以是:

  • 热热

  • 热冷

热热系统

在热热系统中,两个数据中心同时处于活动状态,并为用户的流量提供服务。在这里,我们采用多个 CDN 和地理位置技术将用户路由到指定的数据中心。

在这样做时所面临的挑战是,如果一个区域完全变为空白,另一个区域应该有足够的余量来确保其他区域的流量被吸收

采用这种系统的优势在于用户体验很好,因为在这个设计中,用户会被路由到最近的系统

热冷系统

在这个系统/设计中,任何时候只有一个区域处于活动状态,只有在业务连续性计划(BCP)的情况下,我们才会回退到另一个区域。

我们在使用此系统时面临的挑战如下:

  • 很容易忘记另一个区域,直到问题出现;持续保持两个区域在数据与软件方面的同步非常重要。

  • 由于只有一个区域是活跃的,因此必须仔细考虑将用户正确地故障转移到另一个数据中心。

采用这种系统的优势在于所有写入操作都在一个区域进行,这使数据库设计变得简单。

安全性

考虑到迁移到云,安全性非常重要。以下是需要注意的事项:

  • 服务器安全

  • 应用安全

  • 网络安全

  • 单点登录

  • AAA 要求

服务器安全

既然我们在谈论云,我们就永远无法物理访问服务器(除非我们从云服务提供商那里获得许可)。在这种情况下,我们必须了解云提供商遵循的政策和实践水平,以确保我们应用程序将要运行的服务器物理安全。

例如,当政府考虑迁移到云时,可能需要一套不同的物理安全限制。同样,还有几个标准,如 PCI 和 HIPAA,对这个模型实施更严格的规则。

如果我们的业务需要遵守这些标准,我们需要选择支持所有这些的云变体。

应用安全

在云上,我们可以选择自己托管应用程序,或者使用作为服务提供的应用程序软件即服务(SaaS)(SaaS)。如果我们自己托管在自行配置的服务器上(无论是专用还是共享),我们需要在服务器级别实施正确的防火墙规则,以及正确的用户访问规则,以确保我们的软件只允许授权和正确认证的用户。

如果应用是内部的,我们应该确保我们的员工能够使用 2FA 或 3FA 方法登录这些服务。

网络安全

为了保护我们在云上的服务器,我们需要实施适当的防火墙规则、DNS 区域,甚至拥有我们自己的虚拟专用网络,以确保我们的所有资产不受损害,不会暴露在互联网上。

云与互联网同义,我们的数据和基础设施面临着持续的威胁。除非我们实施适当的安全措施,否则一切对每个人来说都是开放的,他们可以随意从我们的系统中获取他们喜欢的东西。

单点登录

单点登录SSO)在那些在云上使用多个应用程序的组织中变得流行。最近,组织已经停止构建自己的应用程序来运营业务,而是开始采用其他服务的使用。当这些应用程序的数量增加时,这些应用程序的用户不断面临在所有这些网站上输入用户名和密码的挑战。

为了提供无缝的浏览体验,同时遵守企业安全标准,许多提供商实施 OAuth 和 SAML,因为它们是行业认可的。

这些 SSO/身份提供者与公司员工数据库集成,以进一步将云应用程序融入企业,如图所示:

图片

这个设计试图解释组织如何利用身份提供者实现 SSO:

  • 组织与身份提供者共享员工和组织详情:

    • 密码可能会共享,也可能不会共享,因为这可能会危及整个组织,如果发生泄露

    • SSO 系统可以强制对员工实施自己的密码

  • 当用户尝试打开组织中的任何应用程序时,它会将用户重定向到 SSO 提供商

  • SSO 提供商完成认证,并将必要的凭据与应用程序共享

  • 应用程序根据 SSO 的反馈授权用户

  • 应用程序打开用户特定详情,然后用户可以与应用程序交互

现在,这些 SSO 的最大优势是,一旦用户与系统建立了会话,他们就可以无需进一步登录即可登录其他企业批准的应用程序。

与 SSO 提供商交互时,机密性是最大的挑战,因此组织应仔细评估并选择符合其安全需求的正确解决方案。

AAA 需求

当涉及到安全时,重要的是要理解遵循 AAA 标准的应用程序将处理企业面临的大多数挑战。

AAA 标准处理:

  • 认证

  • 授权

  • 审计

认证确保用户的身份得到适当的验证。

授权进一步控制是否允许特定用户根据公司政策访问某些资源。

审计确保跟踪所有尝试访问和使用资源的行为——这也可以用于任何调查,并提供适当的会计和计费(如果需要)。

通过遵循这些最佳实践,我们可以确保大规模上事情运行顺利。

在云中构建 Hadoop 集群

我们之前看到,云提供了一种灵活且简单的方式来租赁资源,如服务器、存储、网络等。云通过按需付费模式使消费者非常容易,但云的大部分复杂性都被提供商隐藏起来。

为了更好地理解 Hadoop 是否适合在云上运行,让我们进一步挖掘并看看云是如何内部组织的。

云的核心机制如下:

  • 非常多的服务器,具有各种硬件配置

  • 通过 IP 网络连接并可供使用的服务器

  • 大型数据中心来托管这些设备

  • 涵盖地理区域的数据中心,具有演进的网络和数据中心设计

如果我们仔细观察,我们正在讨论以下内容:

  • 非常多的不同 CPU 架构

  • 大量具有各种速度和性能的存储设备

  • 速度和互连性各不相同的网络

让我们看看云上这样一个数据中心的一个简单设计:

在前面的图中,我们有以下设备:

  • S1S2:机架交换机

  • U1-U6:机架服务器

  • R1:路由器

  • 存储区域网络

  • 网络附加存储

如我们所见,云服务提供商拥有大量这样的架构,以使它们可扩展和灵活。

您可以正确地猜到,当这种服务器的数量增加,并且当我们请求新的服务器时,提供商可以在该区域内的任何地方分配服务器。

这使得计算和存储在一起变得有点具有挑战性,但也提供了弹性。

为了解决这种同地问题,一些云服务提供商提供了创建虚拟网络并获取专用服务器的选项,然后在这些服务器上分配所有虚拟节点。这有点类似于数据中心设计,但足够灵活,在不需要时可以回收资源。

让我们回到 Hadoop,并提醒自己,为了从 Hadoop 系统中获得最佳性能,我们应该让 CPU 功率更接近存储。这意味着 CPU 和存储之间的物理距离应该小得多,因为总线速度与处理需求相匹配。

CPU 和存储之间的 I/O 速度越慢(例如,iSCSI、存储区域网络、网络附加存储等),我们从 Hadoop 系统获得的表现就越差,因为数据是通过网络获取的,保存在内存中,然后被送到 CPU 进行进一步处理。

这是在设计云上的 Hadoop 系统时需要记住的重要事项之一。

除了性能原因外,还有其他需要考虑的事项:

  • 扩展 Hadoop

  • 管理 Hadoop

  • 保护 Hadoop

现在,让我们尝试理解如何在云环境中处理这些问题。

在前面的章节中,我们看到了 Hadoop 可以通过以下方式安装:

  • 独立部署

  • 半分布式

  • 全分布式

当我们想在云上部署 Hadoop 时,我们可以使用以下方式:

  • 自定义 shell 脚本

  • 云自动化工具(Chef、Ansible 等)

  • Apache Ambari

  • 云服务提供商提供的方法

    • Google Cloud Dataproc

    • Amazon EMR

    • Microsoft HDInsight

  • 第三方管理的 Hadoop

    • Cloudera
  • 云无关部署

    • Apache Whirr

Google Cloud Dataproc

在本节中,我们将学习如何使用 Google Cloud Dataproc 设置单个节点 Hadoop 集群。

这些步骤可以分解为以下内容:

  1. 获取 Google Cloud 账户。

  2. 激活 Google Cloud Dataproc 服务。

  3. 创建新的 Hadoop 集群。

  4. 登录到 Hadoop 集群。

  5. 删除 Hadoop 集群。

获取 Google Cloud 账户

本节假设您已经拥有 Google Cloud 账户。

激活 Google Cloud Dataproc 服务

一旦您登录到 Google Cloud 控制台,您需要访问 Cloud Dataproc 服务。激活界面看起来大致如下:

图片

创建新的 Hadoop 集群

一旦在项目中启用 Dataproc,我们就可以点击创建来创建一个新的 Hadoop 集群。

之后,我们看到另一个屏幕,我们需要配置集群参数:

图片

我已经将大部分内容留给了它们的默认值。稍后,我们可以点击创建按钮,它将为我们创建一个新的集群。

登录到集群

集群成功创建后,我们将自动转到集群列表页面。从那里,我们可以启动一个 SSH 窗口来登录到我们创建的单节点集群。

SSH 窗口看起来大致如下:

图片

如您所见,Hadoop 命令已经为我们准备好了,我们可以运行任何标准 Hadoop 命令来与系统交互。

删除集群

为了删除集群,点击删除按钮,它将显示一个确认窗口,如下截图所示。之后,集群将被删除:

图片

看起来很简单,对吧?是的。云服务提供商已经让用户使用云变得非常简单,并且只需为使用付费。

云中的数据访问

云已经成为存储个人数据和商业数据的重要目的地。根据数据的重要性和保密性要求,组织开始使用云来存储它们的关键数据集。

下面的图试图总结典型企业的各种访问模式以及它们如何利用云来存储他们的数据:

图片

云服务提供商提供不同类型的存储。让我们看看这些类型是什么:

  • 块存储

  • 基于文件的存储

  • 加密存储

  • 离线存储

块存储

这种类型的存储主要在我们要将其与计算服务器一起使用,并通过主机操作系统管理存储时非常有用。

为了更好地理解这一点,这种类型的存储相当于我们购买笔记本电脑/MacBook 时附带的硬盘/SSD。在笔记本电脑存储的情况下,如果我们决定增加容量,我们需要用另一个硬盘替换现有的硬盘。

当涉及到云时,如果我们想增加更多容量,我们只需购买另一个更大容量的存储并将其连接到我们的服务器。这就是云变得流行的一个原因,因为它使得添加或缩小我们需要的存储变得非常容易。

好记的是,由于我们的应用程序有许多不同的访问模式,云供应商还提供了具有不同存储/速度要求的块存储,这些要求用它们自己的容量/IOPS 等来衡量。

让我们以这个容量升级需求为例,看看我们如何利用云上的这块存储。

为了理解这一点,让我们看看这个图例中的例子:

图片

想象一下管理员创建的服务器,名为 DB1,原始容量为 100 GB。后来,由于客户需求意外增加,一个应用程序开始消耗所有 100 GB 的存储空间,因此管理员决定将容量增加到 1 TB(1,024 GB)。

这是此场景中工作流程的样子:

  1. 在云上创建一个新的 1 TB 磁盘

  2. 将磁盘连接到服务器并挂载

  3. 对数据库进行备份

  4. 将数据从现有磁盘复制到新磁盘

  5. 启动数据库

  6. 验证数据库

  7. 在旧磁盘上销毁数据并归还磁盘

此过程简化了,但在生产中,这可能会花费一些时间,具体取决于管理员执行的类型维护。但从云的角度来看,获取新的块存储非常快。

文件存储

文件是计算的基础。如果您熟悉 UNIX/Linux 环境,您已经知道在 Unix 世界中,一切都是文件。但不要因此混淆,因为每个操作系统都有自己处理硬件资源的方式。在这种情况下,我们不关心操作系统如何处理硬件资源,而是谈论用户作为其日常业务的一部分存储的重要文档。

这些文件可以是:

  • 电影/会议录音

  • 图片

  • 电子表格

  • 文档

即使它们在我们电脑中看起来很简单,它们也可能具有重大的商业重要性,并且在我们考虑将它们存储在云上时应该谨慎处理。

大多数云服务提供商都提供了一种简单的方法来在云上存储这些简单的文件,并且在安全性方面也提供了灵活性。

获取此类存储的典型工作流程如下:

  1. 创建一个唯一标识的新存储桶

  2. 为此存储桶添加私有/公共可见性

  3. 将多地域复制要求添加到存储在此存储桶中的数据

一些云服务提供商根据客户在其存储桶创建过程中选择的特性数量来计费。

请为包含机密数据的存储桶选择一个难以发现的名称,并使其保持私有。

加密存储

这对于关键业务数据来说是一个非常重要的要求,因为我们不希望信息泄露到组织范围之外。云服务提供商为我们提供了静态加密功能。一些供应商选择自动执行此操作,而一些供应商还提供了灵活性,让我们可以选择加密密钥和加密/解密我们拥有的数据的方法。根据组织政策,我们应该在云上处理此类事务时遵循最佳实践。

随着存储设备性能的提高,加密在解密/加密文件时不会增加显著的开销。这在下图中表示:

图片

继续之前的例子,当我们选择加密1 TB的底层块存储时,我们可以利用云提供的加密服务,它们会自动为我们加密和解密数据。因此,我们不需要在主机操作系统上使用特殊软件来进行加密和解密。

记住,加密可以是供应商提供的块存储和基于文件的存储方案中的功能之一。

冷存储

这种存储对于在云上存储很少访问的重要备份非常有用。由于我们在这里处理的是一种特殊类型的数据,我们还应该意识到云服务提供商可能会对从这种存储中访问数据收取显著较高的费用,因为它旨在一次性写入并遗忘(直到需要时)。这种机制的优点是我们甚至可以以较低的费用存储 PB 级的数据。

摘要

在本章中,我们探讨了云计算的含义,并看到了云计算如何彻底改变了我们在互联网上访问和管理服务器和应用程序的方式。然后,我们走过了不同提供商在云上提供的一系列不同技术的列表。

我们还学习了如何规划我们的云基础设施,并了解了在云上构建自己的 Hadoop 集群所涉及的不同步骤。最后,我们看到了在云上存储和访问我们数据的不同方式。

在下一章中,我们将探讨一些策略和最佳实践来部署您的 Hadoop 集群。

第十一章:生产级 Hadoop 集群部署

Hadoop 本身始于一个强大的核心和文件系统,旨在处理大数据挑战。后来,在之上开发了众多应用程序,形成了一个相互兼容的应用程序大生态系统。随着应用程序数量的增加,创建和管理 Hadoop 环境的挑战也随之增加。

在本章中,我们将探讨以下内容:

  • Apache Ambari

  • 带有 Ambari 的 Hadoop 集群

Apache Ambari 架构

Apache Ambari 采用主/从架构,其中主节点指导从节点执行特定操作并报告每个操作的状态。主节点负责跟踪基础设施的状态。为了做到这一点,主节点使用数据库服务器,该服务器可以在设置时进行配置。

为了更好地理解 Ambari 的工作原理,让我们看一下 Ambari 的高级架构,如下所示:

图片

在核心,我们有以下应用程序:

  • Ambari 服务器

  • Ambari 代理

  • Ambari 网页界面

  • 数据库

Ambari 服务器

Ambari 服务器(ambari-server)是一个 shell 脚本,它是主服务器上所有管理活动的入口点。此脚本内部使用 Python 代码 ambari-server.py,并将所有请求路由到它。

Ambari 服务器有以下入口点,当向 ambari-server 程序传递不同参数时可用:

  • 守护进程管理

  • 软件升级

  • 软件设置

  • LDAP/PAM/Kerberos 管理

  • Ambari 备份和恢复

  • 其他选项

守护进程管理

当脚本通过命令行使用 startstopresetrestart 参数调用时,守护进程管理模式被激活。

例如,如果我们想启动 Ambari 背景服务器,我们可以运行以下命令:

Example: ambari-server start

软件升级

安装 Ambari 后,我们可以使用此模式升级 Ambari 服务器本身。当我们使用 upgrade 标志调用 ambari-server 程序时,会触发此操作。如果我们想升级整个 Ambari 堆栈,我们可以传递 upgradestack 标志:

Example: ambari-server upgrade

软件设置

一旦从互联网下载 Ambari(或通过 YUM 和 APT 安装),我们需要对软件进行初步设置。当我们向程序传递 setup 标志时,可以触发此模式。此模式将询问我们几个需要回答的问题。除非我们完成此步骤,否则 Ambari 无法用于管理我们的服务器:

Example: ambari-server setup

LDAP/PAM/Kerberos 管理

轻量级目录访问协议LDAP)在企业中用于身份管理。为了使用基于 LDAP 的身份验证,我们需要使用以下标志:setup-ldap(用于使用 ambari 设置 ldap 属性)和 sync-ldap(用于从 ldap 服务器同步数据):

Example: ambari-server setup-ldap
Example: ambari-server sync-ldap

可插拔身份验证模块PAM)是任何 UNIX 或 Linux 操作系统身份验证和授权的核心。如果我们想利用基于 PAM 的访问权限为 Ambari,我们需要使用 setup-pam 选项运行它。如果我们想从 LDAP 迁移到基于 PAM 的身份验证,我们需要使用 migrate-ldap-pam 运行它:

Example: ambari-server setup-pam
Example: ambari-server migrate-ldap-pam

Kerberos 是另一种在网络环境中非常有用的高级身份验证和授权机制。这简化了在大型服务器上的真实性、授权和审计AAA)。如果我们想为 Ambari 使用 Kerberos,我们可以使用 setup-kerberos 标志:

Example: ambari-server setup-kerberos

Ambari 备份和还原

如果我们想要对当前安装的 Ambari(不包括数据库)进行快照,我们可以进入此模式。这支持通过 backuprestore 标志调用的备份和还原方法:

Example: ambari-server backup
Example: ambari-server restore

其他选项

除了这些选项之外,还有其他选项可以通过 Ambari 服务器程序调用,您可以使用 -h(帮助)标志调用这些选项。

Ambari 代理

Ambari 代理是一个程序,它运行在我们想要用 Ambari 管理的所有节点上。这个程序定期向主节点发送心跳。使用此代理,ambari-server 在服务器上执行许多任务。

Ambari 网络界面

这是 Ambari 应用程序的一个强大功能。这个网络应用程序由运行在主主机上的 Ambari 服务器程序暴露;我们可以在端口 8080 上访问此应用程序,并且它受到身份验证的保护。

一旦我们登录到这个网络门户,我们就可以控制并查看我们 Hadoop 集群的各个方面。

数据库

Ambari 支持多个关系数据库管理系统(RDBMS),以跟踪整个 Hadoop 基础设施的状态。在第一次设置 Ambari 服务器时,我们可以选择我们想要使用的数据库。

在撰写本文时,Ambari 支持以下数据库:

  • PostgreSQL

  • Oracle

  • MySQL 或 MariaDB

  • 嵌入式 PostgreSQL

  • Microsoft SQL Server

  • SQL Anywhere

  • Berkeley DB

使用 Ambari 设置 Hadoop 集群

在本节中,我们将学习如何使用 Ambari 从头开始设置全新的 Hadoop 集群。为了做到这一点,我们需要四台服务器——一台用于运行 Ambari 服务器,另外三台节点用于运行 Hadoop 组件。

服务器配置

以下表格显示了我们在本次练习中使用的服务器的配置:

服务器类型 名称 CPU RAM 磁盘
Ambari 服务器节点 master 1 3.7 GB 100 GB
Hadoop 节点 1 node-1 2 13 GB 250 GB
Hadoop 节点 2 node-2 2 13 GB 250 GB
Hadoop 节点 3 node-3 2 13 GB 250 GB

由于这是一个示例设置,我们对此配置感到满意。对于现实世界的场景,请根据您的需求选择配置。

准备服务器

本节以及所有后续章节都假设您在所有服务器上都有正常工作的互联网连接,并且已经安全地设置了防火墙以防止任何入侵。

所有服务器都运行 CentOS 7 操作系统,因为它是一个使用 RPM/YUM 进行软件包管理的系统。在以下部分看到 yum 时,不要感到困惑。

在我们开始使用服务器之前,我们需要运行一些基本的实用程序,这些程序可以帮助我们解决服务器上的各种问题。它们是作为下一个命令的一部分安装的。如果你不确定它们是什么,不要担心。除了 mysql-connector-javawget 之外,所有其他实用程序都是非强制性的:

sudo yum install mysql-connector-java wget iftop iotop smartctl -y

安装 Ambari 服务器

创建 Hadoop 集群的第一个步骤是启动我们的 Ambari 服务器应用程序。因此,使用 SSH 登录到主节点,并按以下顺序执行以下步骤:

  1. 使用以下命令下载适用于 CentOS 7 的 Ambari YUM 仓库:
[user@master ~]$ wget http://public-repo-1.hortonworks.com/ambari/centos7/2.x/updates/2.6.1.5/ambari.repo

  1. 在此步骤之后,我们需要使用以下命令将 ambari.repo 文件移动到 /etc/yum.repos.d 目录:
[user@master ~]$ sudo mv ambari.repo /etc/yum.repos.d
  1. 下一个步骤是使用以下命令安装 ambari-server 软件包:
[user@master ~]$ sudo yum install ambari-server -y
  1. 我们将使用 MySQL 服务器作为我们的 Ambari 服务器。因此,让我们安装所需的软件包:
[user@master ~]$ sudo yum install mariadb-server -y
  1. 在我们接触 Ambari 设置过程之前,让我们配置 MySQL 服务器(或 MariaDB)。这是通过以下命令完成的:
[user@master ~]$ sudo service mariadb start
Redirecting to /bin/systemctl start mariadb.service
  1. 然后,创建一个名为 ambari 的数据库和一个名为 ambari 的用户,密码为 ambari,这样在以下步骤中设置 Ambari 服务器配置就会变得容易。可以使用以下 SQL 查询完成此操作:
CREATE DATABASE ambari;
GRANT ALL PRIVILEGES ON ambari.* to ambari@localhost identified by 'ambari';
GRANT ALL PRIVILEGES ON ambari.* to ambari@'%' identified by 'ambari';
FLUSH PRIVILEGES;
  1. 将这四行存储到一个名为 ambari.sql 的文本文件中,并使用以下命令执行:
[user@master ~] mysql -uroot < ambari.sql
  1. 这将创建一个数据库、用户并赋予必要的权限。

请在生产设置中使用强密码,否则您的系统将容易受到任何攻击。

现在我们已经完成了准备工作,让我们运行 Ambari 服务器设置。请注意,我们需要回答一些突出显示的问题,如下所示:

[user@master ~]$ sudo ambari-server setup
Using python /usr/bin/python
Setup ambari-server
Checking SELinux...
SELinux status is 'enabled'
SELinux mode is 'enforcing'
Temporarily disabling SELinux
WARNING: SELinux is set to 'permissive' mode and temporarily disabled.
OK to continue [y/n] (y)? <ENTER>
Customize user account for ambari-server daemon [y/n] (n)? <ENTER>
Adjusting ambari-server permissions and ownership...
Checking firewall status...
WARNING: iptables is running. Confirm the necessary Ambari ports are accessible. Refer to the Ambari documentation for more details on ports.
OK to continue [y/n] (y)? <ENTER>
Checking JDK...
[1] Oracle JDK 1.8 + Java Cryptography Extension (JCE) Policy Files 8
[2] Oracle JDK 1.7 + Java Cryptography Extension (JCE) Policy Files 7
[3] Custom JDK
==============================================================================
Enter choice (1): <ENTER>
To download the Oracle JDK and the Java Cryptography Extension (JCE) Policy Files you must accept the license terms found at http://www.oracle.com/technetwork/java/javase/terms/license/index.html and not accepting will cancel the Ambari Server setup and you must install the JDK and JCE files manually.
Do you accept the Oracle Binary Code License Agreement [y/n] (y)? <ENTER>
Downloading JDK from http://public-repo-1.hortonworks.com/ARTIFACTS/jdk-8u112-linux-x64.tar.gz to /var/lib/ambari-server/resources/jdk-8u112-linux-x64.tar.gz
jdk-8u112-linux-x64.tar.gz... 100% (174.7 MB of 174.7 MB)
Successfully downloaded JDK distribution to /var/lib/ambari-server/resources/jdk-8u112-linux-x64.tar.gz
Installing JDK to /usr/jdk64/
Successfully installed JDK to /usr/jdk64/
Downloading JCE Policy archive from http://public-repo-1.hortonworks.com/ARTIFACTS/jce_policy-8.zip to /var/lib/ambari-server/resources/jce_policy-8.zip

Successfully downloaded JCE Policy archive to /var/lib/ambari-server/resources/jce_policy-8.zip
Installing JCE policy...
Checking GPL software agreement...
GPL License for LZO: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html
Enable Ambari Server to download and install GPL Licensed LZO packages [y/n] (n)? y <ENTER>
Completing setup...
Configuring database...
Enter advanced database configuration [y/n] (n)? y <ENTER>
Configuring database...
==============================================================================
Choose one of the following options:
[1] - PostgreSQL (Embedded)
[2] - Oracle
[3] - MySQL / MariaDB
[4] - PostgreSQL
[5] - Microsoft SQL Server (Tech Preview)
[6] - SQL Anywhere
[7] - BDB
==============================================================================
Enter choice (1): 3 <ENTER>
Hostname (localhost): 
Port (3306): 
Database name (ambari): 
Username (ambari): 
Enter Database Password (bigdata): ambari <ENTER>
Re-enter password: ambari <ENTER>
Configuring ambari database...
Configuring remote database connection properties...
WARNING: Before starting Ambari Server, you must run the following DDL against the database to create the schema: /var/lib/ambari-server/resources/Ambari-DDL-MySQL-CREATE.sql
Proceed with configuring remote database connection properties [y/n] (y)? <ENTER>
Extracting system views...
ambari-admin-2.6.1.5.3.jar
...........
Adjusting ambari-server permissions and ownership...
Ambari Server 'setup' completed successfully.
  1. 一旦设置完成,我们需要使用设置过程中生成的上一个文件在 Ambari 数据库中创建表。可以使用以下命令完成此操作:
[user@master ~] mysql -u ambari -pambari ambari < /var/lib/ambari-server/resources/Ambari-DDL-MySQL-CREATE.sql
  1. 下一个步骤是启动 ambari-server 守护进程。这将启动我们将用于以下步骤创建 Hadoop 集群的 Web 界面:
[user@master ~]$ sudo ambari-server start
Using python /usr/bin/python
Starting ambari-server
Ambari Server running with administrator privileges.
Organizing resource files at /var/lib/ambari-server/resources...
Ambari database consistency check started...
Server PID at: /var/run/ambari-server/ambari-server.pid
Server out at: /var/log/ambari-server/ambari-server.out
Server log at: /var/log/ambari-server/ambari-server.log
Waiting for server start...............................
Server started listening on 8080
DB configs consistency check: no errors and warnings were found.
Ambari Server 'start' completed successfully.
  1. 服务器设置完成后,配置 JDBC 驱动程序(这对其他所有节点也有帮助):
[user@master ~] sudo ambari-server setup --jdbc-db=mysql --jdbc-driver=/usr/share/java/mysql-connector-java.jar

准备 Hadoop 集群

在我们继续创建 Hadoop 集群之前,我们还需要执行几个步骤。

由于我们已经启动并运行了 Ambari 服务器,让我们生成一个 RSA 密钥对,我们可以用它来在 Ambari 服务器和 Ambari 代理节点之间进行通信。

这个密钥对允许 Ambari 服务器节点登录到所有 Hadoop 节点,并以自动化的方式执行安装。

如果你已经作为获取服务器和基础设施的一部分完成了这一步,则此步骤是可选的:

[user@master ~]$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/user/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): <ENTER>
Enter same passphrase again: <ENTER>
Your identification has been saved in /home/user/.ssh/id_rsa.
Your public key has been saved in /home/user/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:JWBbGdAnRHM0JFj35iSAcQk+rC0MhyHlrFawr+d2cZ0 user@master
The key's randomart image is:
+---[RSA 2048]----+
|.oo   *@@**    |
| +oo +o==*.o     |
| .=.. = .oo +    |
| .o+ o . o =     |
|.. .+ . S . .    |
|. .  o . E      |
| . .  o    |
|  o. .           |
|  ...            |
+----[SHA256]-----+

这将在/home/user/.ssh目录内生成两个文件:

  • ~/.ssh/id_rsa:这是一个私钥文件,必须将其保存在一个秘密的地方

  • ~/.ssh/id_rsa.pub:这是一个公钥文件,它允许使用私钥文件进行任何 SSH 登录

应将此id_rsa.pub文件的内容放入所有 Hadoop 节点上的~/.ssh/authorized_keys中。在这种情况下,它们是节点服务器(1–3)。

在服务器配置过程中就可以完成传播所有公钥的步骤,因此每次获取新服务器时都可以避免手动步骤。

现在,我们将只使用 Ambari Web 界面来完成所有工作。

创建 Hadoop 集群

在本节中,我们将使用 Ambari Web 界面构建一个 Hadoop 集群。本节假设以下事项:

  • 节点(1–3)可以通过主服务器使用 SSH 进行访问

  • 管理员可以使用主服务器上的id-rsa私钥登录到节点(1–3)

  • UNIX 用户可以通过运行sudo在节点(1–3)服务器上执行所有管理操作。

  • Ambari 服务器设置已完成

  • Ambari Web 界面可以通过浏览器无任何防火墙限制进行访问

Ambari Web 界面

让我们打开一个网页浏览器,并使用http://<server-ip>:8080连接到 Ambari 服务器 Web 界面。我们将看到一个登录屏幕,如下所示。请输入admin作为用户名,并输入admin作为密码以继续:

图片

登录成功后,我们将被带到首页。

Ambari 首页

这是主页面,其中 UI 上有多个选项。由于这是一个全新的安装,目前还没有集群数据。

让我们看看这个截图的首页:

图片

从这个地方,我们可以执行以下活动:

创建一个集群

如你所猜,这个部分用于启动一个向导,它将帮助我们通过浏览器创建一个 Hadoop 集群。

管理用户和组

本节有助于管理可以使用和管理的 Ambari Web 应用程序的用户和组。

部署视图

此界面有助于为不同类型的用户创建视图,以及他们可以通过 Ambari Web 界面执行的操作。

由于我们的目标是创建一个新的 Hadoop 集群,我们将点击“启动安装向导”按钮,开始创建 Hadoop 集群的过程。

集群安装向导

Hadoop 集群创建被分解为多个步骤。我们将在接下来的章节中逐一介绍这些步骤。首先,我们面对一个屏幕,我们需要为我们的 Hadoop 集群命名。

命名你的集群

我已经将packt选为 Hadoop 集群的名称。当屏幕上输入 Hadoop 名称时,请点击“下一步”。屏幕看起来像这样:

图片

选择 Hadoop 版本

一旦我们为 Hadoop 集群命名,系统就会显示一个界面,让我们选择要运行的 Hadoop 版本。

在撰写本文时,Ambari 支持以下 Hadoop 版本:

  • Hadoop 2.3

  • Hadoop 2.4

  • Hadoop 2.5

  • Hadoop 2.6(至 2.6.3.0)

您可以选择任何版本进行安装。我选择了默认选项,即版本 2.6.3.0,这在以下截图中可以看到:

点击屏幕底部的“下一步”继续到下一步。

选择服务器

下一个逻辑步骤是选择要安装 Hadoop-2.6.3.0 版本的服务器列表。如果您还记得原始表格,我们命名了我们的节点服务器(1–3)。我们将在 UI 中输入这些名称。

由于安装将完全自动化,我们还需要在 UI 中提供在前一节中生成的 RSA 私钥。这将确保主节点可以通过 SSH 无密码登录到服务器。

此外,我们还需要提供一个 UNIX 用户名,该用户名已在所有节点(1–3)服务器上创建,并且也可以接受 RSA 密钥进行身份验证。

id_rsa.pub 添加到节点(1–3)服务器上的 ~/.ssh/authorized_keys

请记住,这些主机名应该在 DNS域名系统)服务器中有适当的条目,否则安装将无法从这一步继续进行。

我给出的名称可以在以下截图看到:

输入数据后,点击“注册”和“确认”。

设置节点

在这一步,如果详细信息准确,Ambari 代理将自动安装到指定的节点上。成功确认看起来像这样:

如果我们要删除任何节点,这就是我们可以做到的屏幕。准备好进入下一步时,点击“下一步”。

选择服务

现在,我们需要选择要在我们选择的三个服务器上安装的应用程序/服务列表。

在撰写本文时,Ambari 支持以下服务:

应用程序/服务 应用程序描述
HDFS Hadoop 分布式文件系统
YARN + MapReduce2 下一代 MapReduce 框架
Tez 基于 YARN 的 Hadoop 查询处理框架
Hive 用于 ad hoc 查询的数据仓库系统
HBase 非关系型分布式数据库
Pig 用于分析 HDFS 中数据集的脚本平台
Sqoop 在 Hadoop 和 RDBMS 之间传输数据的工具
Oozie 带有 Web UI 的 Hadoop 作业工作流协调
ZooKeeper 提供服务的分布式系统协调服务
Falcon 数据处理和管理平台
Storm 流处理框架
Flume 用于收集、聚合并将流数据移动到 HDFS 的分布式系统
Accumulo 分布式键/值存储
Ambari Infra Ambari 组件使用的共享服务
Ambari Metrics 基于 Grafana 的指标收集和存储系统
Atlas 元数据和治理平台
Kafka 分布式流平台
Knox Hadoop 所有组件的单点认证提供者
Log Search Ambari 管理的服务日志聚合器和查看器
Ranger Hadoop 数据安全应用
Ranger KMS 密钥管理服务器
SmartSense Hortonworks Smart Sense 工具,用于诊断应用程序
Spark 大规模数据处理框架
Zeppelin Notebook 基于 Web 的数据分析笔记本
Druid 列式数据存储
Mahout 机器学习算法
Slider 用于监控 YARN 上应用程序的框架
Superset 基于浏览器的 RDBMS 和 Druid 数据探索平台

作为当前步骤的一部分,我们只选择了 HDFS 及其依赖项。屏幕如下所示:

图片

一旦你做出了选择,点击 UI 底部的“下一步”按钮。

节点上的服务放置

在此步骤中,我们展示了在安装所选的三个节点上自动选择服务的情况。如果我们想自定义节点上服务的放置,我们可以这样做。放置情况如下所示:

图片

当更改看起来不错时,点击“下一步”。

选择从属节点和客户端节点

一些应用程序支持从属和客户端实用工具。在此屏幕上,我们需要选择我们希望在哪些节点上安装这些应用程序的节点。如果你不确定,请点击“下一步”。屏幕如下所示:

图片

自定义服务

尽管 Ambari 自动选择了大多数属性和应用程序之间的链接,但它为我们提供了一些灵活性,可以选择一些功能的值,例如:

  • 数据库

  • 用户名

  • 密码

以及其他有助于应用程序平稳运行的属性。这些在当前屏幕中以红色突出显示。

为了自定义这些,我们需要转到带有突出显示属性的标签页,并根据我们的需求选择值。屏幕如下所示:

图片

在正确配置所有服务属性后,我们将在 UI 中看不到任何红色内容,并且可以点击页面底部的“下一步”按钮。

审查服务

在此步骤中,我们展示了我们迄今为止所做的更改摘要。我们有一个选项可以打印更改,这样我们就不会忘记它们(别担心,所有这些都可以在 UI 稍后找到)。现在我们可以点击“部署”。这是实际更改将应用于节点的时候。

如果我们取消此过程,服务器将不会进行任何更改。当前向导的状态如下所示:

图片

在节点上安装服务

在上一步点击“部署”之后,Ambari 服务器将生成一个部署计划,并使用所有节点上运行的 Ambari 代理并行地在所有节点上部署应用程序。

在这一步,我们可以实时看到正在部署的进度。

一旦所有组件都安装完毕,它们将自动启动,我们可以在屏幕上看到成功的完成状态:

当一切操作成功完成后,请点击“下一步”。如果出现任何故障,我们会看到哪些失败了,并会提供一个选项来重试安装。如果出现任何故障,我们需要深入错误并修复根本问题。

如果你已经遵循了本节开头给出的说明,你应该一切运行顺利。

安装摘要

在这一步,我们会看到已安装内容的摘要。屏幕看起来像这样:

点击“完成”按钮,这标志着 Hadoop 集群设置的结束。接下来,我们将被带到集群仪表板。

集群仪表板

这是刚刚创建的 Hadoop 集群的主页,在这里我们可以看到已安装的所有服务的列表和健康传感器。

我们可以通过这个界面管理 Hadoop 集群的所有方面。请随意探索这个界面,并尝试操作以了解更多信息:

这标志着使用 Ambari 创建 Hadoop 集群的结束。

Hadoop 集群

到目前为止,我们已经看到了如何使用 Ambari 创建单个 Hadoop 集群。但是,是否真的需要多个 Hadoop 集群?

答案取决于业务需求。在单集群与多集群之间,都有一些权衡。

在我们深入探讨这两种方案的优缺点之前,让我们看看在什么场景下我们可能会使用其中之一。

整个业务的单一集群

这是最直接的方法,每个企业至少从一个集群开始。随着业务多样性的增加,组织倾向于为每个部门或业务单元选择一个集群。

以下是一些优点:

  • 易于操作:由于只有一个 Hadoop 集群,因此管理它非常容易,在管理它时,团队规模也将是最优的。

  • 一站式服务:由于所有公司数据都在一个地方,因此很容易想出创新的方法来使用数据并生成数据上的分析。

  • 集成成本:企业内部团队和部门可以非常容易地与这个单一系统集成。在管理他们的应用程序时,他们需要处理的配置更简单。

  • 服务成本:企业可以更好地了解其整个大数据使用情况,并且可以以不那么严格的方式计划扩展其系统。

采用这种方法的一些缺点如下:

  • 规模成为挑战:尽管 Hadoop 可以在数百甚至数千台服务器上运行,但管理如此大的集群,尤其是在升级和其他变更期间,成为一个挑战。

  • 单点故障:Hadoop 在 HDFS 文件系统中内置了复制功能。当更多节点失败时,数据丢失的可能性增加,且难以从中恢复。

  • 治理是一个挑战:随着数据、应用程序和用户的规模增加,如果没有适当的规划和实施,跟踪数据是一个挑战。

    • 安全和机密数据管理:企业处理各种数据,从高度敏感的数据到临时数据不等。当所有类型的数据都放入大数据解决方案中时,我们必须采用非常强大的身份验证和授权规则,以确保数据只对正确的受众可见。

带着这些想法,让我们来看看企业中拥有 Hadoop 集群的另一种可能性。

多个 Hadoop 集群

虽然在组织内部维护单个 Hadoop 集群更容易,但有时为了使业务顺利运行并减少对单点故障系统的依赖,有时需要拥有多个 Hadoop 集群。

这些多个 Hadoop 集群可以用于以下几个原因:

  • 冗余

  • 冷备份

  • 高可用性

  • 业务连续性

  • 应用环境

冗余

当我们考虑冗余的 Hadoop 集群时,我们应该考虑我们可以保持多少冗余。正如我们所知,Hadoop 分布式文件系统HDFS)内部已经内置了数据冗余。

考虑到 Hadoop 集群周围构建了大量的生态系统(如 YARN、Kafka 等服务),我们应该仔细思考和计划是否要使整个生态系统冗余,或者只通过将数据保存在不同的集群中来使数据冗余。

由于有工具可以从一个 HDFS 复制数据到另一个 HDFS,因此使 Hadoop 的 HDFS 部分冗余更容易。

让我们通过这张图来看看实现这一目标的可能方法:

正如我们所见,主 Hadoop 集群运行了所有其应用程序的全栈,数据通过多个来源供应给它。

我们已经定义了两种类型的冗余集群:

一个完全冗余的 Hadoop 集群

这个集群运行与主集群完全相同的应用程序集合,并且数据定期从主 Hadoop 集群复制。由于这是从主集群到第二个集群的单向复制,因此当我们对这个完全冗余的集群进行任何更改时,我们可以 100%确信主集群不会受到影响。

需要理解的一个重要问题是,我们在这个集群中运行所有其他应用程序实例。由于每个应用程序都维护其预定义位置的状态,应用程序状态不会从主 Hadoop 集群复制到这个集群,这意味着在主 Hadoop 集群中创建的作业在这个集群中是不可见的。同样适用于 Kafka 主题、zookeeper 节点等。

这种类型的集群有助于运行不同的环境,例如 QA、预发布等。

数据冗余的 Hadoop 集群

在这种类型的集群设置中,我们创建一个新的 Hadoop 集群,并从主集群复制数据,就像之前的例子一样;但在这里,我们并不担心在这个集群中运行的其他应用程序。

这种设置适用于:

  • 在不同的地理区域为 Hadoop 进行数据备份

  • 与其他企业/组织共享大数据

冷备份

对于企业来说,冷备份很重要,因为数据会随着时间的推移而老化。尽管 Hadoop 被设计用来存储无限量的数据,但并非总是需要保留所有数据以供处理。

有时为了审计目的和历史原因,有必要保留数据。在这种情况下,我们可以创建一个仅包含 HDFS(文件系统)组件的专用 Hadoop 集群,并定期将所有数据同步到这个集群中。

该系统的设计类似于数据冗余的 Hadoop 集群。

高可用性

尽管 Hadoop 架构中包含多个组件,但由于内部设计,并非所有组件都高度可用。

Hadoop 的核心组件是其分布式、容错的文件系统 HDFS。HDFS 有多个组件,其中之一是 NameNode,它是 HDFS 中文件位置的注册表。在 HDFS 的早期版本中,NameNode 是单点故障,而在最近版本中,Secondary NameNode 已被添加以协助满足 Hadoop 集群的高可用性需求。

为了使 Hadoop 生态系统的每个组件都成为一个高可用系统,我们需要添加多个冗余节点(它们有自己的成本),这些节点协同工作形成一个集群。

另一点需要注意的是,在单个地理区域内,使用 Hadoop 实现高可用性是可能的,因为数据的本地性与应用程序是 Hadoop 的关键因素之一。当我们有多个数据中心参与时,我们需要考虑其他方法来实现数据中心间的高可用性。

业务连续性

这部分是业务连续性计划BCP)的一部分,如果计划不当,自然灾害可能会结束 Hadoop 系统。

在这里,策略将是使用多个地理区域作为提供者来运行大数据系统。当我们谈论多个数据中心时,明显的挑战是网络以及管理两个系统的成本。最大的挑战之一是如何保持多个区域同步。

一种可能的解决方案是在其他地理区域构建一个完全冗余的 Hadoop 集群,并定期保持数据同步。在任何一个区域发生灾难/故障的情况下,我们的业务不会停止,因为我们可以平稳地运行我们的操作。

应用环境

许多企业内部遵循不同的方式将软件发布到生产环境中。作为其中的一部分,他们遵循几种持续集成方法,以便更好地控制 Hadoop 环境的稳定性。构建多个较小的 Hadoop 集群,其中包含主生产环境的 X%的数据,并在这些集群中运行所有应用程序是很好的。

应用程序可以在这些专用环境中(如 QA、Staging 等)构建它们的集成测试,并在一切正常后将其软件发布到生产环境。

我遇到的一种做法是,组织倾向于直接将代码发送到生产环境,最终因为未经测试的工作流程或错误而面临应用程序的中断。拥有专门的 Hadoop 应用程序环境来彻底测试软件,实现更高的正常运行时间和更满意的客户是一个好的做法。

Hadoop 数据复制

我们在前面几节中看到,拥有高度可用的数据对于企业成功并跟上其竞争至关重要。

在本节中,我们将探讨实现高度可用数据设置的可能方法。

HDFS 数据复制

Hadoop 使用 HDFS 作为其核心来存储文件。HDFS 具有机架感知性,并且足够智能,能够在数据节点上运行应用程序时减少网络数据传输。

在 HDFS 环境中进行数据复制的一种首选方式是使用 DistCp。官方文档可在以下 URL 找到:hadoop.apache.org/docs/r1.2.1/distcp.html

我们将看到一些从一个 Hadoop 集群复制数据到另一个 Hadoop 集群的示例。但在那之前,让我们看看数据是如何布局的:

为了将生产 Hadoop 集群中的数据复制到备份 Hadoop 集群,我们可以使用distcp。让我们看看如何操作:

hadoop distcp hdfs://NameNode1:8020/projects hdfs://NameNode2:8020/projects
hadoop distcp hdfs://NameNode1:8020/users hdfs://NameNode2:8020/users
hadoop distcp hdfs://NameNode1:8020/streams hdfs://NameNode2:8020/streams
hadoop distcp hdfs://NameNode1:8020/marketing hdfs://NameNode2:8020/marketing
hadoop distcp hdfs://NameNode1:8020/sales hdfs://NameNode2:8020/sales

当我们运行distcp命令时,会创建一个 MapReduce 作业来自动找出文件列表,然后将它们复制到目标位置。

完整的命令语法如下:

Distcp [OPTIONS] <source path …> <destination path>
  • OPTIONS:这些是命令接受的多个选项,它们控制执行的行为。

  • source path:源路径可以是 Hadoop 支持的任何有效的文件系统 URI。DistCp 支持一次性处理多个源路径。

  • 目标路径:这是一个单独的路径,所有源路径都需要复制到这个路径。

让我们更详细地看看一些重要的选项:

标志/选项 描述
append 如果目标文件已存在,则增量地将数据写入目标文件(仅执行append,不执行块级检查以进行增量复制)。
async 以非阻塞方式执行复制。
atomic 即使其中一个失败,也会执行所有文件复制或中止。
Tmp <路径> 用于原子提交的路径。
delete 如果在源树中不存在,则从目标位置删除文件。
Bandwidth <参数> 限制复制过程中使用的网络带宽。
f <文件路径> 包含需要复制的所有路径的文件名列表。
i 忽略文件复制过程中的任何错误。
Log <文件路径> 执行日志保存的位置。
M <数字> 用于复制的最大并发映射数。
overwrite 即使在目标位置存在,也会覆盖文件。
update 仅复制缺少的文件和目录。
skipcrccheck 如果通过,则在传输过程中将跳过 CRC 检查。

Summary

在本章中,我们学习了 Apache Ambari,并详细研究了其架构。然后我们了解了如何使用 Ambari 准备和创建自己的 Hadoop 集群。为了做到这一点,我们在准备集群之前还研究了根据需求配置 Ambari 服务器。我们还了解了单集群和多集群,以及根据业务需求如何使用它们。

posted @ 2025-10-07 17:59  绝不原创的飞龙  阅读(3)  评论(0)    收藏  举报