下一代大数据教程-全-
下一代大数据教程(全)
一、下一代大数据
尽管大数据令人兴奋,但大多数任务关键型数据仍存储在关系数据库管理系统中。这一事实得到了最*在线研究的支持,并得到了我自己在众多大数据和商业智能项目中工作的专业经验的证实。尽管人们对非结构化和半结构化数据有着广泛的兴趣,但对于大多数组织来说,从最大的公司和政府机构到小型企业和技术初创企业,结构化数据仍然占管理数据的很大比例。处理非结构化和半结构化数据的用例虽然有价值且有趣,但却少之又少。除非你在一家处理大量非结构化数据的公司工作,比如谷歌、脸书或苹果,否则你最有可能处理结构化数据。
自 10 多年前 Hadoop 问世以来,大数据已经发展成熟。除去所有的宣传,结构化数据处理和分析显然已经成为大数据的下一代杀手级用例。大多数大数据、商业智能和高级分析用例都处理结构化数据。事实上,Apache Impala、Apache Phoenix 和 Apache Kudu 等大数据领域的一些最受欢迎的进步,以及 Apache Spark 最*对 Spark SQL 和 DataFrames API 的强调,都是为了提供结构化数据处理和分析的能力。这在很大程度上是因为大数据最终被接受为企业的一部分。随着大数据*台的改进和获得新的功能,它们已经成为昂贵的数据仓库*台和关系数据库管理系统的合适替代品,用于存储、处理和分析任务关键型结构化数据。
关于这本书
本书面向商业智能和数据仓库专业人员,他们有兴趣使用 Apache Kudu、Apache Impala 和 Apache Spark 获得关于下一代大数据处理和分析的实用和真实的见解。有经验的大数据专业人士,如果希望了解更多关于 Kudu 和其他高级企业主题的信息,如实时数据摄取和复杂事件处理、物联网(IoT)、分布式内存计算、云中的大数据、大数据治理和管理、实时数据可视化、数据整理、数据仓库优化和大数据仓储,也将从本书中受益。
我假设读者对 Hadoop 的各种组件有基本的了解。一些关系数据库管理系统、商业智能和数据仓库方面的知识也很有帮助。如果您想运行提供的示例代码,需要一些编程经验。我主要关注三个主要的 Hadoop 组件:Apache Spark、Apache Impala 和 Apache Kudu。
阿帕奇 Spark
Apache Spark 是下一代数据处理框架,具有高级内存功能和有向无环图(DAG)引擎。它可以通过内置的机器学习、图形处理、流和 SQL 支持来处理交互式、实时和批处理工作负载。Spark 是为了解决 MapReduce 的局限性而开发的。在大多数数据处理任务中,Spark 比 MapReduce 快 10-100 倍。Spark 是最受欢迎的 Apache 项目之一,目前被世界上一些最大的创新公司使用。我在第五章和第六章讨论 Apache Spark 和 Spark 与 Kudu 的集成。
阿帕奇黑斑羚
Apache Impala 是一个大规模并行处理(MPP) SQL 引擎,旨在运行在 Hadoop *台上。该项目由 Cloudera 启动,最终捐赠给了 Apache 软件基金会。在性能和可扩展性方面,Impala 可以与传统的数据仓库*台相媲美,它是为商业智能和 OLAP 工作负载而设计的。Impala 兼容一些最流行的 BI 和数据可视化工具,例如 Tableau、Qlik、Zoomdata、Power BI 和 MicroStrategy 等等。我在第三章和第四章介绍了 Apache Impala 和 Impala 与 Kudu 的集成。
Apache Kudu
Apache Kudu 是一个新的可变列存储引擎,旨在处理快速数据插入和更新以及高效的表扫描,实现实时数据处理和分析工作负载。当与 Impala 一起使用时,Kudu 是大数据仓库、EDW 现代化、物联网(IoT)、实时可视化、复杂事件处理和机器学习的特征存储的理想选择。作为一个存储引擎,Kudu 的性能和可扩展性可以与其他列存储格式(如 Parquet 和 ORC)相媲美。它的执行速度也明显快于使用 HBase 的 Apache Phoenix。我在第二章讨论 Kudu。
浏览本书
这本书由易于理解的章节组成,每次只关注一两个关键概念。第 1 到 9 章被设计成按顺序阅读,每一章都建立在前一章的基础上。第 10 到 13 章可以根据你的兴趣以任何顺序阅读。这些章节充满了实际的例子和一步一步的指示。在此过程中,您将发现大量关于最佳实践和建议的实用信息,这些信息将引导您在大数据之旅中朝着正确的方向前进。
第一章——新一代大数据简要介绍了本书的内容。
第二章-Kudu 简介提供了对 Apache Kudu 的介绍,从讨论 Kudu 的架构开始。我谈论各种话题,比如如何使用客户端 API 从 Impala、Spark、Python、C++和 Java 访问 Kudu。我提供了关于如何管理、配置和监控 Kudu 的细节,包括 Kudu 的备份和恢复以及高可用性选项。我还讨论了 Kudu 的优势和局限性,包括实用的变通方法和建议。
第三章——黑斑羚简介提供阿帕奇黑斑羚的简介。我用简单易懂的例子讨论了 Impala 的技术架构和功能。我将详细介绍如何执行系统管理、监控和性能调优。
第四章–使用 Impala 和 Kudu 进行高性能数据分析涵盖了 Impala 和 Kudu 的集成,并提供了关于如何利用这两种组件来提供高性能数据分析环境的实际示例和现实建议。我讨论了 Impala 和 Kudu 的优势和局限性,包括实用的解决方法和建议。
第五章——Spark 简介提供了 Apache Spark 的简介。我介绍了 Spark 的架构和功能,用实用的解释和易于理解的例子来帮助您立即开始 Spark 开发。
第六章–使用 Spark 和 Kudu 进行高性能数据处理涵盖了 Spark 和 Kudu 集成,并提供了关于如何使用这两种组件进行大规模数据处理和分析的实际示例和现实建议。
第七章–批处理和实时数据接收和处理包括使用本地和第三方商业工具(如 Flume、Kafka、Spark Streaming、StreamSets、Talend、Pentaho 和 such)进行批处理和实时数据接收和处理。我提供了如何实现复杂事件处理和物联网(IoT)的分步示例。
第章第 8——大数据仓库包括使用 Impala 和 Kudu 设计和实现星形和雪花形维度模型。我将讨论如何利用 Impala 和 Kudu 进行数据仓库存储,包括它的优点和局限性。我还讨论了 EDW 现代化用例,如数据整合、数据归档、分析和 ETL 卸载。
第九章——大数据可视化和数据整理讨论了为超大数据集设计的实时数据可视化和争论工具,并提供了易于理解的示例和建议。
第十章——分布式内存大数据计算涵盖了 Alluxio,以前称为 Tachyon。我讨论了它的架构和功能。我还讨论了 Apache Ignite 和 Geode。
第十一章—大数据治理和管理涵盖了大数据治理和管理。我使用 Cloudera Navigator 讨论了数据沿袭、元数据管理、审计和策略实现。我还研究了其他流行的数据治理和元数据管理应用程序。
第十二章——云中的大数据包含了在云中部署和使用 Apache Kudu、Spark 和 Impala 的分步说明和示例。
第十三章—大数据案例研究提供了六个创新的大数据案例研究,包括挑战、实现细节、解决方案和成果的详细信息。案例研究是在 Cloudera 的许可下提供的。
摘要
如果您想遵循本书中的示例,我建议您建立自己的 Cloudera 集群作为开发环境。您还可以使用最新版本的 Cloudera Quickstart VM,可以从 Cloudera 的网站上免费下载。我不建议使用不同的数据*台,如 Hortonworks、MapR、EMR 或 Databricks,因为它们与本书中讨论的其他组件不兼容,如 Impala 和 Kudu。
二、Kudu 简介
Kudu 是一个 Apache 许可的开源列存储引擎,为 Apache Hadoop *台构建。它支持快速顺序和随机读写,支持实时流处理和分析工作负载。 i 它与 Impala 集成,允许您使用 SQL 插入、删除、更新、向上插入和检索数据。Kudu 还与 Spark(和 MapReduce)集成,用于快速和可扩展的数据处理和分析。像 Apache Hadoop 生态系统中的其他项目一样,Kudu 运行在商用硬件上,并被设计为高度可伸缩和高度可用。
Apache Kudu 项目由 Cloudera 的软件工程师 Todd Lipcon 于 2012 年创立,他是 Hadoop、HBase 和 Thrift 项目的 PMC 成员和委员。 ii Kudu 的开发是为了解决 HDFS 和 HBase 的局限性,同时结合两者的优势。虽然 HDFS 支持快速分析和大型表扫描,但存储在 HDFS 的文件是不可变的,只能在创建后追加。 iii HBase 使更新和随机访问数据成为可能,但对于分析工作负载来说速度较慢。Kudu 可以处理高速数据和实时分析,允许您同时更新 Kudu 表和运行分析工作负载。在某些情况下,HDFS 上的批处理和分析仍然略快于 Kudu,HBase 在随机读写性能方面优于 Kudu。Kudu 在中间的某个地方。如图 2-1 所示,在随机读取和写入方面,Kudu 的性能与 HDFS 的 Parquet (Kudu 在某些情况下更快)和 HBase 非常接*,因此大多数时候性能差异可以忽略不计。
图 2-1
High-level performance comparison of HDFS, Kudu, and HBase
在 Kudu 之前,一些数据工程师使用一种称为 Lambda 架构的数据处理架构来解决 HDFS 和 HBase 的局限性。Lambda 架构有一个速度和批处理层(从技术上讲,还有一个服务层)。交易数据转到速度层(通常是 HBase ),用户在这里可以立即访问最新数据。速度层的数据以拼花格式定期(每小时或每天)复制到批处理层(通常为 HDFS ),用于报告和分析。正如您在图 2-2 中看到的,数据被复制了两次,数据流水线比 Lambda 架构所需的更复杂。这有点类似于典型的企业数据仓库环境,其中 OLTP 数据库代表“速度层”,数据仓库充当“批处理层”
图 2-2
Lambda Architecture
Kudu 使 Lambda 架构过时,因为它能够同时处理随机读写和分析工作负载。如图 2-3 所示,有了 Kudu,就不会有数据重复,数据管道也简单多了。
图 2-3
Modern data ingest pipeline using Kudu
Kudu 代表结构化数据
Kudu 被设计用来存储类似于关系数据库的结构化数据。事实上,Kudu(与 Impala 一起使用时)通常用于关系数据管理和分析。Kudu 在功能、性能和可伸缩性方面与商业数据仓库*台不相上下。我们将在本章后面讨论 Impala 和 Kudu 的集成,并在第四章中更详细地讨论。
用例
在开始之前,先说一下 Kudu 不是什么。库都并不是要取代 HBase 或者 HDFS。HBase 是一个无模式的 NoSQL 风格的数据存储,这使得它适用于稀疏数据或需要可变模式的应用程序。HBase 专为需要随机读写的 OLTP 类型工作负载而设计。有关 HBase 的更多信息,请参见 HBase 在线文档。
HDFS 旨在存储所有类型的数据:结构化、半结构化和非结构化。如果您需要将数据存储在高度可伸缩的文件系统中,HDFS 是一个很好的选择。如前所述,在运行分析工作负载时,HDFS(使用 Parquet)在某些情况下仍然比 Kudu 快。有关 HDFS 的更多信息,请参阅 HDFS 在线文档。
如前所述,Kudu 擅长存储结构化数据。它没有 SQL 接口,因此您需要将 Kudu 与 Impala 配对。通常认为应该存储在关系数据库或时间序列数据库中的数据也很可能存储在 Kudu 中。下面是一些可以利用 Kudu 的用例。 iv
关系数据管理和分析
Kudu(与 Impala 一起使用时)展示了关系数据库的大部分特征。它在行和列中存储数据,并在数据库和表中组织它们。Impala 提供了一个高度可伸缩的 MPP SQL 引擎,允许您使用 ANSI SQL 命令与 Kudu 表进行交互,就像使用关系数据库一样。关系数据库用例可以分为两大类,联机事务处理(OLTP)和决策支持系统(DSS ),或者用现代术语来说就是数据仓库。Kudu 不是为 OLTP 设计的,但是它可以用于数据仓库和其他企业数据仓库(EDW)现代化用例。
数据库
Kudu 可用于维度建模——现代数据仓库和在线分析处理(OLAP)的基础。Kudu 缺少外键约束、自动递增列和其他一些您通常会在传统数据仓库*台中发现的特性;但是,这些限制并不妨碍您在事实和维度表中组织数据。通过 ODBC/JDBC,可以使用您最喜欢的 BI 和 OLAP 工具访问 Impala。我在第八章中讨论了使用 Impala 和 Kudu 的数据仓库。
ETL 卸载
ETL 卸载是您可以使用 Kudu 的许多 EDW 优化用例之一。由于 ETL 流程的运行时间远远超出了其处理窗口,并且进入了工作时间,因此关键报告对整个组织都不可用。通过将耗时的 ETL 处理卸载到廉价的 Kudu 集群,ETL 作业可以在工作时间之前完成,从而在业务用户需要时为他们提供关键的报告和分析。我在第八章中讨论了使用 Impala 和 Kudu 的 ETL 卸载。
分析卸载和活动归档
Impala 是一个非常快速和可伸缩的 MPP SQL 引擎。通过将一些特别的查询和报告重定向到 Impala 和 Kudu,可以减轻企业数据仓库的负载。与花费数百万美元升级您的数据仓库相比,分析卸载和活动归档是优化您的 EDW 环境的更智能、更具成本效益的方式。我在第八章中讨论了使用 Impala 和 Kudu 的分析卸载和主动归档。
数据整合
对于大型组织来说,将数百或数千个遗留数据库分散在整个企业中并不罕见,这需要支付数百万美元的许可、管理和基础架构成本。通过将这些数据库整合到一个 Kudu 集群中,并使用 Impala 提供 SQL 访问,您可以显著降低成本,同时提高性能和可伸缩性。我在第八章中讨论了使用 Impala 和 Kudu 的数据整合。
物联网与时间序列
Kudu 非常适合物联网和时间序列应用,在这些应用中,传感器数据的实时数据摄取、可视化和复杂事件处理至关重要。小米、 v 和澳洲国防部 vi 等几家大公司和政府机构都在成功使用 Kudu 进行物联网用例。我在第七章使用 Impala、Kudu 和 StreamSets 讨论物联网、实时数据摄取和复杂事件处理。我在第九章中讨论了使用 Zoomdata 的实时数据可视化。
机器学习*台的特征存储
数据科学团队通常会创建一个集中的功能库,他们可以在其中发布并与其他团队共享精选的权威功能集,以创建机器学习模型。使用不可变的数据格式(如 ORC 和 Parquet)创建和维护特征存储非常耗时、麻烦,并且需要太多不必要的艰苦工作,尤其是对于大型数据集。使用 Kudu 作为快速和高度可扩展的可变特征库,数据科学家和工程师可以使用熟悉的 SQL 语句轻松更新和添加特征。在数据科学家不断迭代构建、测试和提高预测模型准确性的敏捷环境中,在数秒或数分钟内更新要素库的能力至关重要。在第六章中,我们使用 Kudu 作为使用 Spark MLlib 构建预测机器学习模型的特征库。
Note
Kudu 允许每个表最多 300 列。如果需要存储 300 个以上的特性,HBase 是比较合适的存储引擎。HBase 表可以包含数千或数百万列。使用 HBase 的缺点是,与 Kudu 相比,它在处理全表扫描时效率不高。Apache Kudu 社区正在讨论在 Kudu 的未来版本中解决 300 列的限制。
严格来说,可以通过设置不安全标志来绕过 Kudu 的 300 列限制。例如,如果您需要创建一个包含 1000 列的 Kudu 表,您可以使用以下标志启动 Kudu master:-unlock-unsafe-flags-max-num-columns = 1000。这还没有经过 Kudu 开发团队的彻底测试,因此不建议用于生产。
关键概念
Kudu 引入了几个概念来描述其架构的不同部分。
表 A 表是 Kudu 中存储数据的地方。每个 Kudu 表都有一个主键,并被分成称为 tablets 的段。
*板电脑*板电脑或隔板是桌子的一部分。
*板电脑服务器*板电脑服务器存储*板电脑并向客户端提供*板电脑。
主设备主设备跟踪所有集群元数据并协调元数据操作。
所有集群元数据的目录表中央存储。目录表存储有关桌子和*板电脑的位置、它们的当前状态以及副本数量的信息。目录表存储在主数据库中。
体系结构
类似于其他 Hadoop 组件的设计,如 HDFS 和 HBase(以及他们的谷歌同行 BigTable 和 GFS),Kudu 有一个主从架构。如图 2-4 所示,Kudu 由一个或多个主服务器组成,负责集群协调和元数据管理。Kudu 还拥有一台或多台*板电脑服务器,用于存储数据并提供给客户端应用程序。 vii 对于一个*板来说,任何时候都只能有一个代理大师,领袖。如果领导变得不可用,则另一个主设备被选举成为新的领导。类似于主,一个*板服务器充当领导者,其余的都是跟随者。所有写请求都发送给领导者,而读请求则发送给领导者或副本。存储在 Kudu 中的数据使用 Raft Consensus 算法进行复制,只要副本总数中的大部分仍然可用,就可以保证数据的可用性在一些副本丢失后仍然存在。只要有可能,Kudu 就会复制逻辑操作而不是实际的物理数据,从而限制跨集群的数据移动量。
Note
Raft 共识算法在 Diego Ongaro 和 John Ousterhout 所著的《Raft 论文:寻找可理解的共识算法(扩展版)》中有详细描述。 viii 迭戈·翁加罗(Diego Ongaro)的博士论文《共识:桥接理论与实践》(Consensus:Bridging Theory and Practice),由斯坦福大学于 2014 年发表,对论文内容进行了更详细的阐述。IX
图 2-4
Kudu Architecture
多版本并发控制(MVCC)
大多数现代数据库使用某种形式的并发控制来确保读取一致性,而不是传统的锁定机制。Oracle 从 6.0 版开始就有了多版本一致性模型。 x Oracle 使用回滚段中维护的数据来提供读取一致性。回滚段包含已被未提交或最*提交的事务修改的先前数据。 xi MemSQL 和 SAP HANA 也使用 MVCC 管理并发。最初,SQL Server 只支持悲观并发模型,使用锁定来强制并发。结果,读者屏蔽作家,作家屏蔽读者。随着并发用户和操作数量的增加,阻塞问题和锁争用的可能性也会增加,从而导致性能和可伸缩性问题。SQL Server 领域的情况变得如此糟糕,以至于开发人员和 DBA 不得不在查询中使用 NOLOCK 提示,或者设置 READ UNCOMITTED 隔离级别,容忍脏读以换取较小的性能提升。从 SQL Server 2005 开始,微软引入了自己的多版本并发控制版本,称为行级版本控制。 xii SQL Server 没有相当于回滚段的功能,所以它使用 tempdb 来存储以前提交的数据。Teradata 没有多版本一致性模型,依赖事务和锁来实现并发控制。 十三
与 Oracle、MemSQL 和 SAP HANA 类似,Kudu 使用多版本并发控制来确保读取一致性。 xiv 读者不屏蔽作家,作家不屏蔽读者。Kudu 的乐观并发模型意味着在大型全表扫描期间不需要操作来获取锁,从而大大提高了查询性能和可伸缩性。
黑斑羚和库杜
Impala 是 Kudu 默认的 MPP SQL 引擎。Impala 允许您使用 SQL 与 Kudu 进行交互。如果您有使用 SQL 和存储引擎紧密集成的传统关系数据库的经验,您可能会发现 Kudu 和 Impala 相互解耦并不常见。Impala 旨在与其他存储引擎(如 HDFS、HBase 和 S3)一起工作,而不仅仅是 Kudu。将其他 SQL 引擎如 Apache Drill (DRILL-4241)和 Hive (HIVE-12971)与 Kudu 集成的工作也在进行中。解耦存储、SQL 和处理引擎是开源社区的常见做法。
Impala-Kudu 的整合非常成功,但仍有工作要做。虽然它在性能和可伸缩性方面与传统的数据仓库*台相当或者超过了传统的数据仓库*台,但是 Impala-Kudu 缺乏大多数传统数据仓库*台中的一些企业特性。我们将在本章后面讨论其中的一些限制。
主关键字
每个 Kudu 表都需要有一个主键。Kudu 的主键是作为聚集索引实现的。对于聚集索引,行以与索引相同的顺序物理存储在 tablet 中。还要注意,Kudu 没有自动递增特性,所以在向 Kudu 表中插入行时,必须包含唯一的主键值。如果没有主键值,可以使用 Impala 内置的 uuid()函数或另一种方法生成唯一值。
数据类型
和其他关系数据库一样,Kudu 支持各种数据类型(表 2-1 )。
表 2-1
List of Data Types, with Available and Default Encoding
| 数据类型 | 编码 | 默认 | | :-- | :-- | :-- | | 布尔 | 普通,游程长度 | 运行长度 | | 8 位有符号整数 | 普通、位洗牌、游程长度 | 比特洗牌 | | 16 位有符号整数 | 普通、位洗牌、游程长度 | 比特洗牌 | | 32 位有符号整数 | 普通、位洗牌、游程长度 | 比特洗牌 | | 64 位有符号整数 | 普通、位洗牌、游程长度 | 比特洗牌 | | unixtime_micros(自 Unix 纪元以来的 64 位微秒) | 普通、位洗牌、游程长度 | 比特洗牌 | | 单精度(32 位)IEEE-754 浮点数 | 普通,位图 | 比特洗牌 | | 双精度(64 位)IEEE-754 浮点数 | 普通,位图 | 比特洗牌 | | UTF-8 编码字符串(未压缩时最大 64KB) | 普通,前缀,字典 | 词典 | | 二进制(最多 64KB 未压缩) | 普通,前缀,字典 | 词典 |您可能注意到 Kudu 目前不支持 decimal 数据类型。这是 Kudu 的一个关键限制。float 和 double 数据类型仅存储非常接*的值,而不是 IEEE 754 规范中定义的精确值。 xv 由于这种行为,float 和 double 都不适合存储财务数据。在撰写本文时,对十进制数据类型的支持仍在开发中(Apache Kudu 1.5 / CDH 5.13)。十进制支持在 Kudu 1.7 中到来。更多详情请查看 KUDU-721。有各种解决方法。您可以将财务数据存储为字符串,然后在每次需要读取数据时使用 Impala 将值转换为十进制。因为 Parquet 支持小数,所以另一个解决方法是对事实表使用 Parquet,对维度表使用 Kudu。
如表 2-1 所示,根据列的类型,Kudu 列可以使用不同的编码类型。支持的编码类型包括普通、位混洗、游程、字典和前缀。默认情况下,Kudu 列是未压缩的。Kudu 支持使用 Snappy、zlib 或 LZ4 压缩编解码器进行列压缩。有关 Kudu 编码和压缩支持的更多细节,请参考 Kudu 的文档。
Note
在 Kudu 的早期版本中,日期和时间被表示为 BIGINT。从 Impala 2.9/CDH 5.12 开始,可以在 Kudu 表中使用时间戳数据类型。然而,有几件事要记住。Kudu 使用 64 位值表示日期和时间列,而 Impala 使用 96 位值表示日期和时间。存储在 Kudu 中时,Impala 生成的纳秒值四舍五入。在读写时间戳列时,Kudu 的 64 位表示和 Impala 的 96 位表示之间存在转换开销。有两种解决方法:使用 Kudu 客户端 API 或 Spark 来插入数据,或者继续使用 BIGINT 来表示日期和时间。XVI
分割
表分区是增强 Kudu 表的性能、可用性和可管理性的常用方法。分区允许将表细分成更小的部分,即片。分区使 Kudu 能够以更精细的粒度访问表,从而利用分区修剪。所有 Kudu 表都需要进行表分区,表分区对应用程序是完全透明的。Kudu 支持散列、范围、复合散列-范围和散列-散列分区。下面是 Kudu 中分区的几个例子。
哈希分区
有时,为了避免 IO 瓶颈,需要在分区之间随机均匀地分布数据。使用哈希分区,数据根据应用于分区键的哈希函数放入分区。并不是说不允许在散列分区表上添加分区。如果希望添加更多的分区,就必须重建整个散列分区表。
CREATE TABLE myTable (
id BIGINT NOT NULL,
name STRING,
PRIMARY KEY(id)
)
PARTITION BY HASH PARTITIONS 4
STORED AS KUDU;
范围划分
范围分区根据每个分区的分区键值的预定义范围将数据存储在分区中。范围分区允许向表中添加新的分区,从而增强了分区的可管理性。它还通过分区修剪提高了读取操作的性能。一个缺点是:如果按分区键顺序插入数据,范围分区会导致热点。
CREATE TABLE myTable (
year INT,
deviceid INT,
totalamt INT,
PRIMARY KEY (deviceid, year)
)
PARTITION BY RANGE (year) (
PARTITION VALUE = 2016,
PARTITION VALUE = 2017,
PARTITION VALUE = 2018
)
STORED AS KUDU;
哈希范围分区
散列范围分区结合了散列和范围分区的优点,同时最大限度地减少了它们的限制。使用哈希分区可确保写入 IO 均匀分布在*板电脑服务器上,而使用范围分区可确保可以添加新的*板电脑来适应未来的增长。
CREATE TABLE myTable (
id BIGINT NOT NULL,
sensortimestamp BIGINT NOT NULL,
sensorid INTEGER,
temperature INTEGER,
pressure INTEGER,
PRIMARY KEY(rowid,sensortimestamp)
)
PARTITION BY HASH (id) PARTITIONS 16,
RANGE (sensortimestamp)
(
PARTITION unix_timestamp('2017-01-01') <= VALUES < unix_timestamp('2018-01-01'),
PARTITION unix_timestamp('2018-01-01') <= VALUES < unix_timestamp('2019-01-01'),
PARTITION unix_timestamp('2019-01-01') <= VALUES < unix_timestamp('2020-01-01')
)
STORED AS KUDU;
我将在第四章中更详细地讨论表分区。
Spark 和酷都
Spark 是 Kudu 理想的数据处理和摄取工具。Spark SQL 和 DataFrame API 使得与 Kudu 的交互变得很容易。我将在第六章更详细地讨论 Spark 和 Kudu 集成。
您可以通过 DataFrame API 将 Spark 与 Kudu 结合使用。您可以使用 spark-shell 或 spark-submit 中的- packages 选项来包含 kudu-spark 依赖项。您还可以从 central.maven.org 手动下载 jar 文件,并将其包含在您的- jars 选项中。如果您在 Scala 2.11 中使用 spark2,请使用 kudu-spark2_2.11 工件。例如:
spark-shell –-packages org.apache.kudu:kudu-spark2_2.11:1.1.0
spark-shell --jars kudu-spark2_2.11-1.1.0.jar
酷都语境
您使用 Kudu 上下文来对 Kudu 表执行 DML 语句。 xvii 例如,如果我们需要将数据插入一个 Kudu 表中:
import org.apache.kudu.spark.kudu._
val kuduContext = new KuduContext("kudumaster01:7051")
case class CustomerData(id: Long, name: String, age: Short)
val data = Array(CustomerData(101,"Lisa Kim",60), CustomerData(102,"Casey Fernandez",45))
val insertRDD = sc.parallelize(data)
val insertDF = sqlContext.createDataFrame(insertRDD)
insertDF.show
+----------+---------------+---+
|customerid| name|age|
+----------+---------------+---+
| 101| Lisa Kim| 60|
| 102|Casey Fernandez| 45|
+----------+---------------+---+
将数据帧插入 Kudu 表。我假设该表已经存在。
kuduContext.insertRows(insertDF, "impala::default.customers")
确认数据已成功插入。
val df = sqlContext.read.options(Map("kudu.master" -> "kuducluster:7051","kudu.table" -> "impala::default.customers")).kudu
df.select("id","name","age").show()
+---+---------------+---+
| id| name|age|
+---+---------------+---+
|102|Casey Fernandez| 45|
|101| Lisa Kim| 60|
+---+---------------+---+
我将在第六章更详细地讨论 Spark 和 Kudu 集成。
Note
从 Kudu 1.6 开始,Spark 通过利用扫描局部性来提高性能。Spark 将扫描最*的*板电脑副本,而不是扫描领导者,领导者可能在不同的*板电脑服务器中。
Spark 流和 Kudu
在清单 2-1 所示的示例中,我们将使用 Flafka (Flume 和 Kafka)和 Spark Streaming 从 Flume spooldir 源读取数据,将其存储在 Kafka 中,并使用 Spark Streaming 处理数据并将其写入 Kudu。
Spark 2.0 中包含了一个基于 Spark SQL 的新的流处理引擎,称为结构化流。从 Spark 2.2.0 开始,结构化流的实验标签已经被移除。然而,在撰写本文时,Cloudera 仍然不支持结构化流(CDH 5.13)。第七章更详细地描述了 Flafka 和 Spark 流。
import org.apache.kudu.client.CreateTableOptions;
import org.apache.kudu.spark.kudu._
import org.apache.spark._
import org.apache.spark.rdd.NewHadoopRDD
import org.apache.spark.SparkConf
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.flume._
import org.apache.spark.streaming.Seconds
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.util.IntParam
import org.apache.spark.sql.SQLContext
object FlumeStreaming {
case class MySensorData(tableid: String, deviceid: String, thedate: String, thetime: String, temp: Short, status: String)
def readSensorData(str: String): MySensorData = {
val col = str.split(",")
val thetableid = col(0)
val thedeviceid = col(1)
val thedate = col(2)
val thetime = col(3)
val thetemp = col(4)
val thestatus = col(5)
MySensorData(col(0), col(1), col(2), col(3), col(4).toShort, col(5))
}
def main(args: Array[String]) {
val sparkConf = new SparkConf().setMaster("local[2]").setAppName("FlumeStreaming")
val sc = new SparkContext(sparkConf)
val ssc = new StreamingContext(sc, Seconds(1))
// the arguments are for host name and port number
val flumeStream = FlumeUtils.createPollingStream(ssc,args(0),args(1).toInt)
val sensorDStream = flumeStream.map (x => new String(x.event.getBody.array)).map(readSensorData)
sensorDStream.foreachRDD {rdd =>
val sqlContext = new SQLContext(sc)
import sqlContext.implicits._
val kuduContext = new KuduContext("kudumaster01:7051")
// convert the RDD into a DataFrame and insert it into the Kudu table
val DataDF = rdd.toDF
kuduContext.insertRows(DataDF, "impala::default.sensortable")
DataDF.registerTempTable("currentDF")
// Update the table based on the thresholds
val WarningFilteredDF = sqlContext.sql("select * from currentDF where temp > 50 and temp <= 60")
WarningFilteredDF.registerTempTable("warningtable")
val UpdatedWarningDF = sqlContext.sql("select tableid,deviceid,thedate,thetime,temp,'WARNING' as status from warningtable")
kuduContext.updateRows(UpdatedWarningDF, "impala::default.sensortable")
val CriticalFilteredDF = sqlContext.sql("select * from currentDF where temp > 61")
CriticalFilteredDF.registerTempTable("criticaltable")
val UpdatedCriticalDF = sqlContext.sql("select tableid,deviceid,thedate,thetime,temp,'CRITICAL' as status from criticaltable")
kuduContext.updateRows(UpdatedCriticalDF, "impala::default.sensortable")
}
ssc.start()
ssc.awaitTermination()
}
}
Listing 2-1Spark Streaming and Kudu
清单 2-2 显示了 flume 配置文件,其中 Kafka 被用作 flume 通道。
agent1.sources = source1
agent1.channels = channel1
agent1.sinks = spark
agent1.sources.source1.type = spooldir
agent1.sources.source1.spoolDir = /tmp/streaming
agent1.sources.source1.channels = channel1
agent1.channels.channel1.type = org.apache.flume.channel.kafka.KafkaChannel
agent1.channels.channel1.brokerList = kafkabroker01:9092, kafkabroker02:9092, kafkabroker03:9092
agent1.channels.channel1.zookeeperConnect = server03:2181
agent1.channels.channel1.topic = mytopic
agent1.sinks.spark.type = org.apache.spark.streaming.flume.sink.SparkSink
agent1.sinks.spark.hostname = 127.0.0.1
agent1.sinks.spark.port = 9999
agent1.sinks.spark.channel = channel1
agent1.sinks.spark.batchSize=5
Listing 2-2Flume configuration file
编译完包后,将应用程序提交给集群来执行它。
spark-submit \
--class FlumeStreaming \
--jars kudu-spark_2.10-0.10.0.jar \
--master yarn-client \
--driver-memory=512m \
--executor-memory=512m \
--executor-cores 4 \
/mydir/spark/flume_streaming_kudu/target/scala-2.10/test-app_2.10-1.0.jar \ localhost 9999
带 Spark 流和 Kudu 的 Flafka 管道应如图 2-5 所示。
图 2-5
A Flafka pipeline with Spark Streaming and Kudu
Kudu C++、Java 和 Python 客户端 API
Kudu 提供了 NoSQL 风格的 Java、C++和 Python 客户端 API。需要 Kudu 提供最佳性能的应用程序应该使用客户端 API。事实上,第七章中讨论的一些数据摄取工具,比如 StreamSets、CDAP 和 Talend,利用客户端 API 将数据摄取到 Kudu 中。通过 API 对 DML 的修改可以立即在 Impala 中进行查询,而不需要执行无效元数据。
Kudu Java 客户端 API
清单 2-3 提供了一个使用 Java 客户端 API 的例子。
import org.apache.kudu.ColumnSchema;
import org.apache.kudu.Schema;
import org.apache.kudu.Type;
import org.apache.kudu.client.*;
import java.util.ArrayList;
import java.util.List;
public class JavaKuduClient {
public static void main(String[] args) {
// Create Kudu client object
KuduClient myKuduClient = new KuduClient.KuduClientBuilder("kudumaster01").build();
// Create the schema
List<ColumnSchema> myColumns = new ArrayList(3);
myColumns.add(new ColumnSchema.ColumnSchemaBuilder("rowid", Type.INT32)
.key(true)
.build());
myColumns.add(new ColumnSchema.ColumnSchemaBuilder("customername", Type.STRING)
.build());
myColumns.add(new ColumnSchema.ColumnSchemaBuilder("customerage", Type.INT8)
.build());
List<String> partKeys = new ArrayList<>();
partKeys.add("key");
// Create the table based on the schema
Schema mySchema = new Schema(myColumns);
client.createTable("CustomersTbl", mySchema, new CreateTableOptions().setRangePartitionColumns(partKeys));
// Open the Kudu table
KuduTable myTable = myKuduClient.openTable("CustomersTbl");
KuduSession mySession = myKuduClient.newSession();
// Insert new rows
Insert myInsert = myTable.newInsert();
myInsert.getRow().addInt("rowid", 1);
myInsert.getRow().addString("customername", "Jerry Walsh");
myInsert.getRow().addInt("customerage", 64)
mySession.apply(myInsert);
// Update existing rows
Update myUpdate = myTable.newUpdate();
myUpdate.getRow().addInt("rowid", 1);
myUpdate.getRow().addString("customername", "Jerome Walsh");
myUpdate.getRow().addInt("customerage", 65)
mySession.apply(myUpdate);
// Upsert rows
Upsert myUpsert = myTable.newUpsert();
myUpsert.getRow().addInt("rowid", 2);
myUpsert.getRow().addString("customername", "Tim Stein");
myUpsert.getRow().addInt("customerage", 49)
myUpsert.apply(myUpdate);
// Delete row
Delete myDelete = myTable.newDelete()
myDelete.getrow().addString("rowid", 1);
mySession.apply(myDelete)
// Display rows
List<String> myColumns = new ArrayList<String>();
myColumns.add("rowid");
myColumns.add("customername");
myColumns.add("customerage");
KuduScanner myScanner = myClient.newScannerBuilder(myTable)
.setProjectedColumnNames(myColumns)
.build();
while (myScanner.hasMoreRows()) {
RowResultIterator myResultIterator = myScanner.nextRows();
while (myResultIterator.hasNext()) {
RowResult myRow = myResultIterator.next();
System.out.println(myRow.getInt("rowid"));
System.out.println(myRow.getString("customername"));
System.out.println(myRow.getString("customerage"));
}
}
// Delete table
myKuduClient.deleteTable(myTable);
// Close the connection
myKuduClient.shutdown();
}
}
Listing 2-3Sample Java code using the Kudu client API
Maven 工件
pom.xml 文件中需要以下内容。
<dependency>
<groupId>org.apache.kudu</groupId>
<artifactId>kudu-client</artifactId>
<version>1.1.0</version>
</dependency>
Kudu Python 客户端 API
Python 客户端 API 提供了一种与 Kudu 交互的简单方法。Python API 仍处于试验阶段,可能会随时更改。参见清单 2-4 中的示例。
import kudu
from kudu.client import Partitioning
from datetime import datetime
# Connect to Kudu
myclient = kudu.connect(host='kudumaster01', port=7051)
# Define the columns
mybuilder = kudu.schema_builder()
mybuilder.add_column('rowid').type(kudu.int64).nullable(False).primary_key()
mybuilder.add_column('customername', type_=kudu.string, nullable=False)
mybuilder.add_column('customerage', type_=kudu.int8, nullable=False)
myschema = mybuilder.build()
# Define partitioning method
mypartitioning = Partitioning().add_hash_partitions(column_names=['rowid'], num_buckets=24)
# Create new table
myclient.create_table('customers', myschema, mypartitioning)
# Open a table
mytable = myclient.table('customers')
# Create a new session
mysession = client.new_session()
# Insert a row
myinsert = mytable.new_insert({'rowid': 1, 'customername': "Jason Weinstein", 'customerage': 62})
mysession.apply(myinsert)
# Upsert a row
myupsert = mytable.new_upsert({'rowid': 2, 'customername': "Frank Nunez", 'customerage': 47})
session.apply(myupsert)
# Updating a row
myupdate = table.new_update({'rowid': 1, 'customername': "Jason Dean Weinstein"})
session.apply(myupdate)
# Delete a row
mydelete = table.new_delete({'rowid': 1})
session.apply(mydelete`)
# Flush the session
mysession.flush()
# Create a scanner with a predicate
myscanner = mytable.scanner()
myscanner.add_predicate(table['customerage'] < 50)
# Read the data. Note that this method doesn't scale well for large table scans
myresult = myscanner.open().read_all_tuples()
Listing 2-4Sample Python code using the Kudu client API
Kudu C++客户端 API
Kudu 还提供了一个 C++客户端 API。参见清单 2-5 中的示例。
#include <ctime>
#include <iostream>
#include <sstream>
#include "kudu/client/callbacks.h"
#include "kudu/client/client.h"
#include "kudu/client/row_result.h"
#include "kudu/client/stubs.h"
#include "kudu/client/value.h"
#include "kudu/client/write_op.h"
#include "kudu/common/partial_row.h"
#include "kudu/util/monotime.h"
using kudu::client::KuduClient;
using kudu::client::KuduClientBuilder;
using kudu::client::KuduColumnSchema;
using kudu::client::KuduError;
using kudu::client::KuduInsert;
using kudu::client::KuduPredicate;
using kudu::client::KuduRowResult;
using kudu::client::KuduScanner;
using kudu::client::KuduSchema;
using kudu::client::KuduSchemaBuilder;
using kudu::client::KuduSession;
using kudu::client::KuduStatusFunctionCallback;
using kudu::client::KuduTable;
using kudu::client::KuduTableAlterer;
using kudu::client::KuduTableCreator;
using kudu::client::KuduValue;
using kudu::client::sp::shared_ptr;
using kudu::KuduPartialRow;
using kudu::MonoDelta;
using kudu::Status;
using std::string;
using std::vector;
int main(int argc, char* argv[]) {
// Enable verbose debugging for the client library.
// Set parameter to 0 to disable
kudu::client::SetVerboseLogLevel(2);
// Create and connect a client.
shared_ptr<KuduClient> client;
KUDU_CHECK_OK(KuduClientBuilder().add_master_server_addr("kudumaster01:7051").Build(&client));
KUDU_LOG(INFO) << "Client connection created.";
// Create a schema.
// Available data types: INT8 = 0, INT16 = 1, INT32 = 2,
// INT64 = 3, STRING = 4, BOOL = 5, FLOAT = 6, DOUBLE = 7,
// BINARY = 8, UNIXTIME_MICROS = 9, TIMESTAMP = UNIXTIME_MICROS
KuduSchema mytable_schema;
KuduSchemaBuilder mytable_builder;
categories_builder.AddColumn("rowid")->Type(KuduColumnSchema::INT32)->NotNull();
categories_builder.AddColumn("name")->Type(KuduColumnSchema::STRING)->NotNull();
categories_builder.AddColumn("age")->Type(KuduColumnSchema::INT8)->NotNull();
categories_builder.AddColumn("salary")->Type(KuduColumnSchema::DOUBLE)->NotNull();
categories_builder.SetPrimaryKey({"rowid"});
KUDU_CHECK_OK(categories_builder.Build(&mytable_schema));
KUDU_LOG(INFO) << "Created a schema for mytable";
// Delete table if it exists
bool exists;
KUDU_CHECK_OK(client->TableExists("mytable", &exists));
if (exists) {
KUDU_CHECK_OK(client->DeleteTable("mytable"));
KUDU_LOG(INFO) << "Deleting table if it exists.";
}
// Generate the split keys for the table.
vector<const KuduPartialRow*> splits;
int32_t num_tablets = 20
int32_t increment = 1000 / num_tablets;
for (int32_t i = 1; i < num_tablets; i++) {
KuduPartialRow* row = mytable_schema.NewRow();
KUDU_CHECK_OK(row->SetInt32(0, i * increment));
splits.push_back(row);
}
vector<string> column_names;
column_names.push_back("rowid");
// Create the table.
KuduTableCreator* table_creator = client->NewTableCreator();
KUDU_CHECK_OK(table_creator->table_name("mytable")
.schema(&mytable_schema)
.set_range_partition_columns(column_names)
.split_rows(splits)
.Create());
// Confirm if the table was successfully created
bool created;
KUDU_CHECK_OK(client->TableExists("mytable", &created));
created ? KUDU_LOG(INFO) << "Created table mytable." :
KUDU_LOG(INFO) << "Failed to create table mytable.";
// Insert two rows into the table.
shared_ptr<KuduTable> table;
client->OpenTable("mytable", &table);
KuduInsert* my_insert = table->NewInsert();
KuduPartialRow* row = categories_insert->mutable_row();
KUDU_CHECK_OK(row->SetInt32("rowid", 100));
KUDU_CHECK_OK(row->SetStringCopy("name", "Fred Smith"));
KUDU_CHECK_OK(row->SetInt8("age", 56));
KUDU_CHECK_OK(row->SetDouble("salary", 110000));
KUDU_CHECK_OK(session->Apply(my_insert));
KuduInsert* my_insert = table->NewInsert();
KuduPartialRow* row = categories_insert->mutable_row();
KUDU_CHECK_OK(row->SetInt32("rowid", 101));
KUDU_CHECK_OK(row->SetStringCopy("name", "Linda Stern"));
KUDU_CHECK_OK(row->SetInt8("age", 29));
KUDU_CHECK_OK(row->SetDouble("salary", 75000));
KUDU_CHECK_OK(session->Apply(my_insert));
KUDU_CHECK_OK(session->Flush());
KUDU_LOG(INFO) << "Inserted two rows into mytable";
// Scan one row based on a predicate
KuduScanner scanner(table.get());
// Add a predicate: WHERE name = "Linda Stern"
KuduPredicate* pred = table->NewComparisonPredicate(
"name", KuduPredicate::EQUAL, KuduValue::FromString("Linda Stern"));
KUDU_RETURN_NOT_OK(scanner.AddConjunctPredicate(pred));
KUDU_RETURN_NOT_OK(scanner.Open());
vector<KuduRowResult> results;
while (scanner.HasMoreRows()) {
KUDU_RETURN_NOT_OK(scanner.NextBatch(&results));
for (vector<KuduRowResult>::iterator iter = results.begin();
iter != results.end();
iter++) {
const KuduRowResult& result = *iter;
string_t myname;
KUDU_RETURN_NOT_OK(result.GetString("name", &myname));
KUDU_LOG(INFO) << "Scanned some rows out of a table" << myname;
}
results.clear();
}
// Delete the table.
KUDU_CHECK_OK(client->DeleteTable("mytable"));
KUDU_LOG(INFO) << "Deleted mytable.";
}
Listing 2-5Sample C++ code using the Kudu client API
更多例子 xviii 可以在 Kudu 官网 xix 和 github 资源库找到。 xx 在线提供的示例代码由 Kudu 开发团队提供,作为本章的参考。
备份和恢复
Kudu 没有备份和恢复工具。但是,有几种方法可以使用 Impala、Spark 和第三方工具(如 StreamSets 和 Talend)来备份(和恢复)Kudu 表。
Note
HDFS 快照不能用于备份 Kudu 表,因为 Kudu 数据不在 HDFS。XXI
经由 CTAS 的备份
备份 Kudu 表的最简单方法是使用 CREATE TABLE AS (CTAS)。您基本上只是在 HDFS 创建 Kudu 表的另一个副本,最好是 Parquet 格式(或其他压缩格式),这样您就可以将文件复制到远程位置,比如另一个集群或 S3。
CREATE TABLE AS DimCustomer_copy AS SELECT * FROM DimCustomer;
+-----------------------+
| summary |
+-----------------------+
| Inserted 18484 row(s) |
+-----------------------+
您可以先创建表格,以便在需要时自定义表格选项,然后使用“插入”从 Kudu 表格中插入数据。
CREATE TABLE DimCustomer_Parquet (
ID STRING,
CustomerKey BIGINT,
FirstName STRING,
LastName STRING,
BirthDate STRING,
YearlyIncome FLOAT,
TotalChildren INT,
EnglishEducation STRING,
EnglishOccupation STRING,
HouseOwnerFlag INT,
NumberCarsOwned INT
)
STORED AS PARQUET;
set COMPRESSION_CODEC=gzip;
insert into DimCustomer_Parquet Select * from DimCustomer;
Modified 18484 row(s) in 4.52s
Note
Kudu 表不支持 CREATE TABLE LIKE 语法。如果您尝试使用该语法创建一个表,您将会收到一条类似如下的错误消息:“错误:分析异常:不支持使用 CREATE TABLE LIKE 克隆 Kudu 表。”
用 HDFS 命令检查文件。
hadoop fs -du -h /user/hive/warehouse/dimcustomer_parquet
636.8 K 1.9 M /user/hive/warehouse/dimcustomer_parquet/f948582ab9f8dfbb-5e57d0ca00000000_1052047868_data.0.parq
将拼花文件复制到另一个集群或 S3
现在,您可以使用 distcp 将 Parquet 文件复制到另一个集群中。
hadoop distctp -pb hftp://kuducluster:50070/user/hive/warehouse/dimcustomer_parquet hdfs://kuducluster2/backup_files
你也可以把文件复制到 S3。
hadoop distcp –pb -Dfs.s3a.access.key=s3-access-key -Dfs.s3a.secret.key=s3-secret-key hdfs://user/hive/warehouse/dimcustomer_parquet s3a://myWarehouseBucket/backup_files
为了保持一致性,请注意,我使用了-pb 选项来保证保留 Parquet 数据文件的特殊块大小。XXII
Note
Cloudera 有一个名为 Cloudera 企业备份和灾难恢复(BDR)的集群复制功能。BDR 提供了一个易于使用的图形用户界面,允许您安排从一个集群到另一个集群的复制。BDR 不支持 Kudu,但是您可以复制位于 HDFS 的目标拼花文件。XXIII
通过 impala-shell 将结果导出到本地目录、NFS 或 SAN 卷
Impala-shell 可以生成带分隔符的文件,然后您可以将其压缩并复制到远程服务器或 NFS/SAN 卷。请注意,这种方法不适用于大型表。
impala-shell -q "SELECT * FROM DimCustomer" --delimited --output_delimiter=, --output_file /backup_nfs/dimcustomer_bak.csv
使用 Kudu 客户端 API 导出结果
Kudu 客户端 API 也可以用来导出数据。参见清单 2-6 中的示例。
import org.apache.kudu.ColumnSchema;
import org.apache.kudu.Schema;
import org.apache.kudu.Type;
import org.apache.kudu.client.*;
import java.util.ArrayList;
import java.util.List;
public class TableBackup {
public static void main(String[] args) {
// Create Kudu client object
KuduClient myKuduClient = new KuduClient.KuduClientBuilder("kudumaster").build();
KuduTable myTable = myKuduClient.openTable("CustomersTbl");
KuduSession mySession = myKuduClient.newSession();
// Display rows
List<String> myColumns = new ArrayList<String>();
myColumns.add("rowid");
myColumns.add("customername");
myColumns.add("customerage");
KuduScanner myScanner = myKuduClient.newScannerBuilder(myTable)
.setProjectedColumnNames(myColumns)
.build();
while (myScanner.hasMoreRows()) {
RowResultIterator myResultIterator = myScanner.nextRows();
while (myResultIterator.hasNext()) {
RowResult myRow = myResultIterator.next();
System.out.println(myRow.getInt("rowid"));
System.out.println(myRow.getString("customername"));
System.out.println(myRow.getString("customerage"));
}
}
}
}
Listing 2-6Sample Java code using the Kudu client API to export data
编译 java 代码并从命令行运行它。将结果重定向到文件。这种方法适用于小型数据集。
java TableBackup >> /backup_nfs/mybackup.txt
使用 Spark 导出结果
您也可以使用 Spark 备份数据。这更适合大型表,因为您可以控制并行性、执行器数量、执行器核心和执行器内存。
首先创建一个数据帧。
val df = sqlContext.read.options(Map("kudu.master" -> "localhost:7051","kudu.table" -> "impala::default.DimCustomer")).kudu
以 CSV 格式保存数据。
df.coalesce(1).write.format("com.databricks.spark.csv").option("header", "true").save("/backup/dimcustomer_bak")
或者你可以把它保存为拼花地板。
df.coalesce(1).write.mode("append").parquet("/backup/dimcustomer_p_bak”)
使用合并来限制写入 HDFS 时生成的文件数量可能会导致性能问题。我将在第五章详细讨论联合。
使用 Spark 和 Kudu 数据源 API 进行复制
我们可以使用 Spark 将数据从一个 Kudu 集群复制到另一个集群。
启动 Spark 壳。
spark-shell --packages org.apache.kudu:kudu-spark_2.10:1.1.0 --driver-class-path mysql-connector-java-5.1.40-bin.jar --jars mysql-connector-java-5.1.40-bin.jar
连接到 Kudu master 并检查 users 表中的数据。我们将把这个 Kudu 表与另一个集群中的另一个 Kudu 表同步。
import org.apache.kudu.spark.kudu._
val kuduDF = sqlContext.read.options(Map("kudu.master" -> "kuducluster:7051","kudu.table" -> "impala::default.users")).kudu
kuduDF.select("userid","name","city","state","zip","age").sort($"userid".asc).show()
+------+---------------+---------------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+---------------+---------------+-----+-----+---+
| 100| Wendell Ryan| San Diego| CA|92102| 24|
| 101|Alicia Thompson| Berkeley| CA|94705| 52|
| 102|Felipe Drummond| Palo Alto| CA|94301| 33|
| 103| Teresa Levine| Walnut Creek| CA|94507| 47|
| 200| Jonathan West| Frisco| TX|75034| 35|
| 201| Andrea Foreman| Dallas| TX|75001| 28|
| 202| Kirsten Jung| Plano| TX|75025| 69|
| 203| Jessica Nguyen| Allen| TX|75002| 52|
| 300| Fred Stevens| Torrance| CA|90503| 23|
| 301| Nancy Gibbs| Valencia| CA|91354| 49|
| 302| Randy Park|Manhattan Beach| CA|90267| 21|
| 303| Victoria Loma| Rolling Hills| CA|90274| 75|
+------+---------------+---------------+-----+-----+---+
让我们继续将数据插入到另一个 Kudu 集群的表中。目标表需要存在于另一个 Kudu 簇中。
val kuduContext = new KuduContext("kuducluster2:7051")
kuduContext.insertRows(kuduDF, "impala::default.users2")
验证目标表中的数据。
impala-shell
select * from users2 order by userid;
+------+---------------+---------------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+---------------+---------------+-----+-----+---+
| 100| Wendell Ryan| San Diego| CA|92102| 24|
| 101|Alicia Thompson| Berkeley| CA|94705| 52|
| 102|Felipe Drummond| Palo Alto| CA|94301| 33|
| 103| Teresa Levine| Walnut Creek| CA|94507| 47|
| 200| Jonathan West| Frisco| TX|75034| 35|
| 201| Andrea Foreman| Dallas| TX|75001| 28|
| 202| Kirsten Jung| Plano| TX|75025| 69|
| 203| Jessica Nguyen| Allen| TX|75002| 52|
| 300| Fred Stevens| Torrance| CA|90503| 23|
| 301| Nancy Gibbs| Valencia| CA|91354| 49|
| 302| Randy Park|Manhattan Beach| CA|90267| 21|
| 303| Victoria Loma| Rolling Hills| CA|90274| 75|
+------+---------------+---------------+-----+-----+---+
行已成功复制。
使用流集进行实时复制
StreamSets 是一个强大的实时和批量接收工具,主要用于实时流和物联网(IoT)用例。您可以使用流集实时或接*实时地将数据从 Oracle、MySQL、SQL Server 或 Kudu 等 JDBC 源复制到另一个目的地。StreamSets 提供了两个源来促进通过 JDBC 连接的复制:JDBC 查询消费者和 JDBC 多表消费者。JDBC 查询使用者源使用用户定义的 SQL 查询从表中读取数据。参见图 2-6 。
图 2-6
StreamSets and Kudu
JDBC 多表消费者源读取同一个数据库中的多个表。JDBC 多表消费者源适用于数据库复制。StreamSets 包括一个 Kudu 目的地;或者,JDBC 生成器是一个(较慢的)选项,可用于将数据复制到其他关系数据库。第七章更详细地介绍了流集。
使用 Talend、Pentaho 和 CDAP 等 ETL 工具复制数据
Talend(图 2-9 )和木桶数据*台(图 2-7 )为 Kudu 提供原生支持。两者都提供 Kudu 源和汇,并可用于将数据从一个 Kudu 集群复制到一个或多个 Kudu 集群;另一个目的地,如 S3;或 RDBMS,如 SQL Server、MySQL 或 Oracle。其他工具如 Pentaho PDI(图 2-8 )没有本地 Kudu 支持。然而,它可以通过 Impala 将数据传输到 Kudu,尽管速度较慢。第七章详细介绍了 StreamSets、Talend、Pentaho 和 CDAP 中的批处理和实时摄取工具。
图 2-9
ETL with Talend and Kudu
图 2-8
ETL with Pentaho and Kudu
图 2-7
ETL with CDAP and Kudu Note
Talend Kudu 组件由第三方公司 One point Ltd .提供。这些组件可从 Talend Exchange 免费下载,网址为—https://exchange.talend.com/
。在将 Talend 与 Kudu 配合使用之前,需要安装 Kudu 输出和输入组件。
蟒蛇和黑斑羚
使用 Python 不是备份大型 Kudu 表的最快或最具可伸缩性的方法,但对于中小型数据集来说应该足够了。下面是从 Python 中访问 Kudu 表的最常用方法列表。
黑斑羚
Cloudera 构建了一个名为 Impyla 的 Python 包。 xxiv Impyla 只是使用标准 ODBC/JDBC 与 Impala 通信。Impyla 的一个很好的特性是它能够轻松地将查询结果转换成 pandas 数据帧(不要与 Spark 数据帧混淆)。这里有一个例子。
>>> from impala.util import as_pandas
>>> cur.execute('SELECT id, name, salary FROM employees')
>>> df = as_pandas(cur)
>>> type(df)
<class 'pandas.core.frame.DataFrame'>
>>> df
id name salary
0 001 James Chan 100000
1 002 Roger Lim 75000
2 003 Dan Tanner v 65000
3 004 Lilian Russo 90000
4 005 Edith Sarkisian 110000
pyodbc
pyodbc 是一个流行的开源 Python 包,可以用来通过 ODBC/JDBC 访问数据库。下面是一个如何使用 pyodbc 的例子。要了解更多关于 pyodbc 的信息,请访问它在 github.com/mkleehammer/pyodbc.的 github 页面
import pyodbc
myconnection_str = '''Driver=/mypath/libclouderaimpalaodbc.dylib;HOST=localhost;PORT=21050'''
myconnection = pyodbc.connect("myconnection_str")
cursor = myconnection.cursor()
cursor.execute("select id, name, salary from employees")
sqllcemy(SQL 语法)
SQLAlchemy 是一个用于 Python 的 SQL 工具包,具有对象关系映射器(ORM)的特性。要了解更多关于 SQLAlchemy 的信息,请访问它在 sqlalchemy.org 的网站。这里有一个关于如何使用 SQLAlchemy 连接到 Impala 的例子。
import sqlalchemy
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
myconnection_str = '''Driver=/mypath/libclouderaimpalaodbc.dylib;HOST=localhost;PORT=21050'''
myconnection = create_engine("myconnection_str")
session = sessionmaker(bind=db)
user = session.query(Employees).filter_by(salary > '65000').first()
高可用性选项
除了 Kudu 的默认*板电脑复制因子为 3(可以增加到 5 或 7),Kudu 没有内置的高可用性工具或功能。幸运的是,您可以使用 Cloudera Enterprise 中的内置组件和 StreamSets 等第三方工具来为 Kudu 提供高可用性功能。通过拥有两个或更多 Kudu 集群,高可用性可以保护您免受整个站点故障的影响。集群可以位于地理上分散的数据中心或云提供商。 xxvi 请注意,复制的数据量可能会影响性能和成本。拥有主动-主动环境的另一个好处是能够将两个群集用于不同的使用情形。例如,第二个集群可用于即席查询、构建机器学习模型和其他数据科学工作负载,而第一个集群用于实时分析或具有明确定义的 SLA 的用例。让我们探索 Kudu 的几个高可用性选项。
使用 Kafka 和 Spark 流的主动-主动双重摄取
在此选项中,所有数据都发布到 Kafka 集群。Spark 流用于从 Kafka 主题中读取数据。使用 Spark Streaming,您可以选择在将数据写入 Kudu 之前执行数据转换和清理。图 2-10 显示了两个 Kudu 目的地,但是根据您的 HA 要求,您可以有更多目的地。
图 2-10
Active-Active Dual Ingest with Kafka and Spark Streaming
使用 MirrorMaker 进行主动-主动 Kafka 复制
另一个高可用性选项是使用 MirrorMaker 复制 Kafka。如图 2-11 所示,使用 MirrorMaker 将摄取到源 Kafka 集群的数据复制到目标 Kafka 集群。从那里,数据被 Spark 流读取并写入 Kudu 目的地,类似于图 2-10 。如果您的目标是双重摄取,那么使用 MirrorMaker 来复制您的 Kudu 集群可能是多余的。但是,它提供了更好的数据保护,因为数据是在两个或更多 Kafka 和 Kudu 集群上复制的。
图 2-11
Active-Active Kafka Replication with MirrorMaker
使用 Kafka 和 StreamSets 的主动-主动双重摄取
该选项与图 2-10 非常相似,但使用流集代替 Spark 流。StreamSets 比 Spark Streaming 更易于使用和管理,并提供内置的监控、警报和异常处理。它还提供了一个事件框架,使得基于事件开始任务变得容易。在大多数项目中,我通常推荐 StreamSets 而不是 Spark Streaming(图 2-12 )。
图 2-12
Active-Active Dual Ingest with Kafka and StreamSets
带流集的主动-主动双重接收
从技术上来说,你不需要卡夫卡来进行主动-主动双重摄取。StreamSets 允许您配置多个目的地。使用流选择器处理器,它甚至可以根据某些条件将数据路由到不同的目的地(图 2-13 )。使用 Kafka 提供了额外的高可用性和可伸缩性,在大多数情况下仍然推荐使用。我会在第七章中更详细地介绍流集。
图 2-13
Active-Active Dual Ingest with Kafka and StreamSets
管理和监控
就像其他数据管理*台一样,Kudu 提供了帮助系统管理和监控的工具。
Cloudera 经理 Kudu 服务
Cloudera Manager 是 Cloudera 的集群管理工具,为管理 Cloudera 企业集群提供了单一*台。使用 Cloudera Manager,您可以执行常见的管理任务,例如启动和停止 Kudu 服务、更新配置、监控性能和检查日志。
Kudu Master Web UI
Kudu Masters 提供了一个 web 界面(在端口 8051 上可用),该界面提供了有关集群的信息。它显示有关 tablet 服务器、心跳、主机名、表和模式的信息。您还可以查看可用日志、内存使用和资源消耗的详细信息。
Kudu *板电脑服务器 Web 用户界面
每个 table server 还提供了一个 web 界面(在端口 8050 上可用),该界面提供了关于 table t server 集群的信息。它显示有关*板电脑服务器上托管的每台*板电脑的更多详细信息、调试和状态信息、资源消耗以及可用日志。
Kudu 度量
Kudu 提供了几个指标,您可以使用它们来监控集群并对集群进行故障排除。您可以通过执行$ Kudu-tserver-dump _ metrics _ JSON 或 kudu-master - dump_metrics_json 来获得可用 Kudu 指标的列表。一旦知道了想要检查的指标,就可以通过 HTTP 访问/metrics end-point 来收集实际值。这些指标也由 Cloudera Manager 收集和汇总。例如:
curl -s 'http://tabletserver01:8050/metrics?include_schema=1&metrics=read_bytes_rate'
Kudu 命令行工具
除了主服务器和*板服务器提供的 Cloudera Manager 和可访问的 web 用户界面,Kudu 还包括用于常见系统管理任务的命令行工具。
验证集群运行状况
ksck:检查集群元数据是否一致,主服务器和*板服务器是否正在运行。默认情况下,Ksck 检查所有表和 tables,但是您可以使用 tables 标志指定要检查的表的列表,或者使用 tables 标志指定 tables 服务器的列表。使用校验和扫描和校验和快照检查数据中的不一致。
用法:
kudu cluster ksck --checksum_scan [--tables <tables>] <master_address>
文件系统
检查:检查 Kudu 文件系统的不一致性
用法:
kudu fs check [-fs_wal_dir=<dir>] [-fs_data_dirs=<dirs>] [-repair]
list:显示本地文件系统中的 tablet 副本列表
用法:
kudu local_replica list [-fs_wal_dir=<dir>] [-fs_data_dirs=<dirs>] [-list_detail]
data_size:总结给定本地副本的数据大小/空间使用情况。
用法:
kudu local_replica data_size <tablet_id_pattern> [-fs_wal_dir=<dir>] [-fs_data_dirs=<dirs>] [-format=<format>]
掌握
状态:获得一个 Kudu 大师的状态
用法:
kudu master status <master_address>
时间戳:获取 Kudu 主机的当前时间戳
用法:
kudu master timestamp <master_address>
列表:列出 Kudu 集群中的主节点
用法:
kudu master list <master_addresses> [-columns=<columns>] [-format=<format>] [-timeout_ms=<ms>]
衡量 Kudu 集群的性能
loadgen:运行负载生成,随后进行可选扫描
loadgen 以集群可以执行的速度将自动生成的随机数据插入到现有的或自动创建的表中。Loadgen 还可以检查实际插入的行数是否与原始行数匹配。
用法:
kudu perf loadgen <master_addresses> [-buffer_flush_watermark_pct=<pct>] [-buffer_size_bytes=<bytes>] [-buffers_num=<num>] [-error_buffer_size_bytes=<bytes>] [-flush_per_n_rows=<rows>] [-keep_auto_table] [-num_rows_per_thread=<thread>] [-num_threads=<threads>] [-run_scan] [-seq_start=<start>] [-show_first_n_errors=<errors>] [-string_fixed=<fixed>] [-string_len=<len>] [-table_name=<name>] [-table_num_buckets=<buckets>] [-table_num_replicas=<replicas>] [-use_random]
桌子
删除:删除表格
用法:
kudu table delete <master_addresses> <table_name>
列表:列出所有表格
用法:
kudu table list <master_addresses> [-list_tablets]
药片
leader_step_down:强制*板电脑的 leader 副本下台
用法:
kudu tablet leader_step_down <master_addresses> <tablet_id>
add_replica:向*板电脑的 Raft 配置添加新副本
用法:
kudu tablet change_config add_replica <master_addresses> <tablet_id> <ts_uuid> <replica_type>
move_replica:将一个 tablet 副本从一个 tablet 服务器移动到另一个
副本移动工具通过向新服务器添加副本,然后从旧服务器删除副本,有效地将副本从一台*板服务器移动到另一台。
用法:
kudu tablet change_config move_replica <master_addresses> <tablet_id> <from_ts_uuid> <to_ts_uuid>
*板电脑服务器
状态:获取 Kudu *板电脑服务器的状态
用法:
kudu tserver status <tserver_address>
时间戳:获取 Kudu Tablet 服务器的当前时间戳
用法:
kudu tserver timestamp <tserver_address>
列表:列出 Kudu 集群中的*板电脑服务器
用法:
kudu tserver list <master_addresses> [-columns=<columns>] [-format=<format>] [-timeout_ms=<ms>]
查阅 Kudu 的在线命令行参考指南 xxvii 以获得 Kudu 命令行工具的完整列表和描述。
已知问题和限制
Kudu 有几个问题和局限性。根据您的使用案例,它们可以被认为是小问题或大问题。他们中的大多数都有解决方法,但有些没有。在构建新的应用程序或将工作负载迁移到 Kudu 时,您必须意识到这些限制。我在下面列出了一些主要的。Kudu 提交者和贡献者正在努力修正这些限制。
- Kudu 不支持 DECIMAL、CHAR、VARCHAR、DATE 和复杂类型,如 ARRAY、MAP 和 STRUCT。
- Kudu 表最多可以有 300 列。
- Kudu 没有二级索引。
- Kudu 没有外键。
- 不支持多行多表交易。
- Kudu 没有内置的备份和恢复以及高可用性功能。
- Kudu 不支持行、列和表级别的基于角色的访问控制。
- Kudu 建议*板服务器的最大数量为 100 台。
- Kudu 建议最多 3 个大师。
- Kudu 建议每台*板电脑服务器在复制后和压缩后的最大存储数据量为 8TB。
- Kudu 建议复制后每台*板电脑服务器的最大*板电脑数量为 2000 台。
- Kudu 建议在复制后创建表时,每个 tablet 服务器的最大 table t 数量为 60。
- Kudu 不支持机架感知、多数据中心和滚动重启。
有关 Kudu 限制的更完整和最新列表,请参考 Cloudera 的在线文档。XXVIII
安全
Kudu 支持 Kerberos 进行强认证。Kudu 客户端和服务器之间的通信使用 TLS 加密。Kudu 不支持表、行或列级访问控制。相反,它使用白名单样式的访问控制列表来实现粗粒度授权。两种访问级别包括超级用户和用户。未经验证的用户将无法访问 Kudu 集群。XXIX
从安全角度来看,还有很多工作要做。同时,加强安全性的其他建议包括限制对 Kudu 表的直接访问,以及通过商业智能工具的语义层实现基于角色的访问控制。实现数据库视图来模拟行和列级别的基于角色的访问控制是另一种选择。还可以研究如何配置 IP 访问列表,以限制某些 IP 地址对 RPC 的主服务器使用的端口(默认端口是 7051)的访问。
有关 Kudu security 的最新开发,请参考 Cloudera 的在线文档。
摘要
尽管 Hadoop 以其处理结构化、非结构化和半结构化数据的能力而闻名,但结构化关系数据仍然是大多数公司数据管理和分析策略的重点,并且在可预见的未来仍将如此。 xxx 事实上,大部分大数据用例都涉及从关系数据库复制工作负载。Kudu 是结构化数据的完美存储引擎。在整本书中,我们将重点关注 Kudu 以及它如何与 Hadoop 生态系统中的其他项目和第三方应用程序集成,以实现有用的业务用例。
参考
- 环球新闻网;“随着 Cloudera Enterprise 5.10 的发布,Cloudera 宣布 Apache Kudu 正式上市,”Cloudera,2017,
https://globenewswire.com/news-release/2017/01/31/912363/0/en/Cloudera-Announces-General-Availability-of-Apache-Kudu-with-Release-of-Cloudera-Enterprise-5-10.html
- 托德·利普孔;“可变大数据中的勇敢新世界:关系存储”,奥赖利,2017,
https://conferences.oreilly.com/strata/strata-ny/public/schedule/speaker/75982
- 吉米·香;《Apache HBase 写入路径》,Cloudera,2012,
https://blog.cloudera.com/blog/2012/06/hbase-write-path/
- 阿帕奇软件基金会;《阿帕奇库杜简介》,ASF,2017,
https://kudu.apache.org/docs/#kudu_use_cases
- 阿帕奇软件基金会;“Apache 软件基金会宣布推出 Apache Kudu v 1.0,”ASF,2017,
https://blogs.apache.org/foundation/entry/the_apache_software_foundation_announces100
- 帕特·帕特森;“与@ApacheKafka、#StreamSets、@ApacheKudu & @Cloudera 在澳大利亚@DeptDefence 的创新-在 Kudu Slack 频道发现,”Twitter,2017,
https://twitter.com/metadaddy/status/843842328242634754
- 托德·利普孔;“Kudu:针对快速数据的快速分析存储”,Cloudera,2015 年,
https://kudu.apache.org/kudu.pdf
- 迭戈·翁加罗和约翰·奥斯特胡特;《寻找一种可理解的共识算法(扩展版)》,斯坦福大学,2014 年,
https://raft.github.io/raft.pdf
- 迭戈·翁加罗;《共识:理论与实践的桥梁》,斯坦福大学,2014 年,
https://github.com/ongardie/dissertation#readme
- 尼尔·钱德勒;“Oracle 的锁定模型——多版本并发控制”,Neil Chandler,2013,
https://chandlerdba.com/2013/12/01/oracles-locking-model-multi-version-concurrency-control/
- 甲骨文;《多版本并发控制》,甲骨文,2018,
https://docs.oracle.com/cd/B19306_01/server.102/b14220/consist.htm#i17881
- 微软;《SQL Server 2005 中的数据库并发和行级版本控制》,微软,2018,
https://technet.microsoft.com/en-us/library/cc917674.aspx
- Teradata《关于并发控制》,Teradata,2018,
https://info.teradata.com/HTMLPubs/DB_TTU_16_00/index.html#page/General_Reference%2FB035-1091-160K%2Fvju1472241438286.html%23
- 大卫·阿尔维斯和詹姆斯·金利;《阿帕奇库杜读写路径》,Cloudera,2017,
https://blog.cloudera.com/blog/2017/04/apache-kudu-read-write-paths/
- 微软;“使用十进制、浮点和实数数据”,微软,2018,
https://technet.microsoft.com/en-us/library/ms187912(v=sql.105).aspx
- 阿帕奇黑斑羚;“时间戳数据类型”,Apache Impala,2017,
https://impala.apache.org/docs/build/html/topics/impala_timestamp.html
- Cloudera《黑斑羚命令与库杜的例子》,Cloudera,2017,
https://kudu.apache.org/docs/developing.html#_kudu_integration_with_spark
- 威廉·柏克莱,《scantoken_noncoveringrange.cc》,Cloudera,2017,
https://gist.github.com/wdberkeley/50e2e47548a0daa3d3bff68e388da37a
- 阿帕奇库杜,《用阿帕奇库杜开发应用》,阿帕奇库杜,2017,
http://kudu.apache.org/docs/developing.html
- apache kudu“Kudu C++客户端样本”,Apache Kudu,2018 年,
- 阿巴契人:“阿帕契族常见问题”阿帕契族 2018 年“??””
- Cloudera《用 Impala 表使用拼花文件格式》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/latest/topics/impala_parquet.html
- Cloudera《如何使用 Cloudera Enterprise BDR 备份和恢复 HDFS 数据》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/latest/topics/cm_bdr_howto_hdfs.html
- Cloudera《面向 Impala 的全新 Python 客户端》,Cloudera,2018,
http://blog.cloudera.com/blog/2014/04/a-new-python-client-for-impala/
- Cloudera《将数据导入 Cloudera 数据科学工作台》,Cloudera,2018,
https://www.cloudera.com/documentation/data-science-workbench/latest/topics/cdsw_import_data.html#impala_impyla
- Cloudera“利用 Cloudera Enterprise 实现主动/主动多集群部署”,Cloudera,2018,
https://www.cloudera.com/content/dam/www/marketing/resources/whitepapers/implementing-active-deployments-with-cloudera-enterprise-whitepaper.pdf.landing.html
- Cloudera《Apache Kudu 命令行工具参考》,Cloudera,2018,
https://kudu.apache.org/docs/command_line_tools_reference.html
- Cloudera《黑斑羚的整合局限》,Cloudera,2018,
https://www.cloudera.com/documentation/kudu/latest/topics/kudu_known_issues.html#impala_kudu_limitations
- Cloudera《阿帕奇库杜安全》,Cloudera,2018,
https://www.cloudera.com/documentation/kudu/latest/topics/kudu_security.html
- Cloudera“戴尔调查:尽管信息管理环境快速变化,结构化数据仍是焦点”,Cloudera,2018,
http://www.dell.com/learn/us/en/uscorp1/press-releases/2015-04-15-dell-survey
三、Impala 简介
Impala 是一个大规模并行处理(MPP) SQL 引擎,它是为在 Hadoop *台上运行而全新设计和构建的。 i Impala 提供快速、低延迟的响应时间,适用于商业智能应用和特定数据分析。Impala 的性能匹配,在大多数情况下,超过商用 MPP 发动机。
Impala 最初是 Cloudera 的一个项目。它被捐赠给了阿帕奇软件基金会,并于 2015 年 12 月 2 日被纳入阿帕奇孵化器。在加入 Cloudera 之前,Impala 的架构师和技术负责人 Marcel Kornacker 是谷歌的一名软件工程师,他领导了谷歌 F1 项目的分布式查询引擎的开发, ii 是一个分布式关系数据库系统,用于支持谷歌高度关键和非常受欢迎的 AdWords 业务, iii Cloudera 于 2012 年 10 月发布了 Impala 的测试版,并于 2013 年 5 月宣布正式上市。
本章不会为你提供 Impala 所有特性的详尽列表。我写这一章的目的是给你足够的关于 Impala 的信息,让你马上开始。
体系结构
Impala 和其他 Hadoop 组件一样有一个分布式架构(图 3-1 )。它利用守护进程在工作节点上直接本地执行查询。Impala 只是一个查询引擎,旨在集成多个存储引擎,如 HDFS、HBase、S3 和 Kudu。与 Oracle 和 SQL Server 等传统关系数据库系统相比,这是一种不同的体系结构,在这些系统中,查询和存储引擎紧密耦合在一起。
图 3-1
Impala Architecture
Impala 可以通过 ODBC 或 JDBC 访问流行的商业智能和数据可视化工具,如 Microsoft Power BI、Qlik、Tableau、Zoomdata、Business Objects 和 Microstrategy 等。
Impala 服务器组件
Impala 的架构由三个服务组成:Impala 守护进程、目录服务和 Statestore。
黑斑羚精灵
Impala 守护进程充当查询协调器,负责接受和管理集群中查询的执行。Impala 守护进程通常安装在数据节点上,利用数据局部性,允许它直接访问存储在 HDFS 的数据块。
英帕拉目录服务
Impala 的目录服务存储和聚合从其他元数据存储库中收集的元数据信息,如 Hive Metastore 和 HDFS Namenode。
黑斑羚国家商店
Statestore 向集群中的所有 Impala 进程分发和同步元数据信息。Statestore 是一个 pub-sub Impala 服务,它向 Impala 守护进程推送更新。
Impala 和 Hadoop 生态系统
Impala 是 Hadoop 生态系统的一部分,旨在与其他 Hadoop 组件协同工作。
黑斑羚和蜂巢
Hive 是 Hadoop 的第一个 SQL 接口。Hive 最初构建在 MapReduce 之上,但也运行在其他数据处理引擎上,如 Tez 和 Spark。尽管 Hive 是出了名的慢,但是已经有很多人在使用它,所以构建一个不兼容的 SQL 接口是没有意义的。当 Cloudera 工程师在设计 Impala 时,他们的一个主要目标是使它尽可能与 Hive 互操作。利用现有的 Hive 基础设施,比如 Hive Metastore,是使 Impala 能够访问 Hive 表的关键。Impala(和 Hive)可以通过 HUE(Hadoop 用户体验)访问,这是一个基于 web 的 Hadoop SQL 工作台。此外,Impala 有自己的 shell,称为 impala-shell,可以通过命令行访问。我将在本章的后面讨论黑斑羚的外壳和色调。
黑斑羚和 HDFS
在 Hive 和 Impala 中创建的表通常使用各种文件格式存储在 HDFS 中。要在 Impala 中创建 CSV 表格:
CREATE TABLE my_csv_table (
id BIGINT,
name STRING,
gender STRING
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
若要大容量插入数据,可以使用下面的 LOAD DATA 或 INSERT…SELECT 语句。请注意,您必须首先创建表。
LOAD DATA INPATH '/mydata/mytextfile.txt' INTO TABLE my_csv_table;INSERT INTO my_csv_table SELECT customerkey, concat(firstname,' ',lastname), gender FROM dimcustomer;
可以一次插入一个值,但不建议这样做。
INSERT INTO my_csv_table VALUES (200,'Norman Bates','M');
执行的每个 INSERT…VALUES 语句都将在 HDFS 中生成一个小文件,并可能导致碎片和性能问题。执行大容量插入时,建议使用 LOAD DATA 或 INSERT…SELECT。如果必须执行许多单行插入,最好使用不同的存储引擎,比如 HBase 或 Kudu。
也可以手动复制表的数据目录(/user/hive/warehouse/my _ CSV _ table)中的 CSV 文件。在运行 refresh 或 invalidate 语句之前,以这种方式添加的数据对 Impala 是不可见的。我们将在本章的后面介绍元数据的刷新和失效。
英帕拉和 HBase
Impala 可以通过 Hive 外部表查询 HBase 中存储的表。Impala 还可以将数据插入 Hive 外部表,数据存储在底层 HBase 表中。这里有一个例子。
启动 hbase shell。创建一个 HBase 表并插入一些测试数据。
create 'table1', 'colfam1'
put 'table1','firstrow','colfam1:name', 'Jeff Frazier'
put 'table1','firstrow','colfam1:city', 'Los Angeles'
put 'table1','firstrow','colfam1:age', '75'
put 'table1','secondrow','colfam1:name', 'Susan Fernandez'
put 'table1','secondrow','colfam1:city', 'New York'
put 'table1','secondrow','colfam1:age', '72'
put 'table1','thirdrow','colfam1:name', 'Tony Cheng'
put 'table1','thirdrow','colfam1:city','San Francisco'
put 'table1','thirdrow','colfam1:age','77'
在配置单元中创建外部表。
CREATE EXTERNAL TABLE hive_on_hbase (key string,name string, age int, city string)
STORED BY
'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES
('hbase.columns.mapping'=':key, colfam1:name,colfam1:age,colfam1:city')
TBLPROPERTIES('hbase.table.name'='table1');
启动黑斑羚壳。因为这个表是在 Impala 之外创建的,所以它不知道这个新表的任何信息。在从新表中选择数据之前,请执行 invalidate metadata。
invalidate metadata;
select * from hive_on_hbase;
key age city name
firstrow 75 Los Angeles Jeff Frazier
secondrow 72 New York Susan Fernandez
thirdrow 77 San Francisco Tony Cheng
INSERT INTO hive_on_hbase VALUES ('fourthrow', 65,'Palo Alto','Kevin Lee');
select * from hive_on_hbase;
key age city name
firstrow 75 Los Angeles Jeff Frazier
secondrow 72 New York Susan Fernandez
thirdrow 77 San Francisco Tony Cheng
fourthrow 65 Palo Alto Kevin Lee
黑斑羚和 S3
亚马逊 S3 是一个流行的对象存储,经常被用作临时集群的数据存储。它还是备份和冷数据的经济高效的存储方式。从 S3 读取数据就像从 HDFS 或任何其他文件系统读取数据一样。从 CDH 5.8 (Impala 2.6)及更高版本开始,Impala 支持对存储在 S3 上的数据进行查询和插入。
CREATE TABLE my_s3_table (
id BIGINT,
name STRING,
gender STRING
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
LOCATION 's3://impala01/csvfiles/';
黑斑羚和库杜
Kudu 是 Cloudera 的新关系数据存储。Kudu 是结构化数据的推荐存储引擎,可以与 Impala 无缝协作。我将在第四章中对黑斑羚和 Kudu 进行深入报道。
文件格式
Impala 支持多种文件格式,如 Text、Avro、RCFile、SequenceFile 和 Parquet。Impala 不支持 Hortonworks 支持的 ORC 文件格式。Impala 可以创建所有文件格式的表格,并从中进行查询。Impala 不允许插入 Avro、RCFile 和 SequenceFile 表。您必须从 Hive 运行 insert 语句或使用 LOAD DATA 导入数据。
Impala 在创建表格时的默认文件格式是用 ASCII 0x01 字符(Ctrl-A)分隔值的文本。支持多种压缩编解码器,如 Snappy(速度和压缩率的最佳*衡)、GZIP(最高压缩级别,但速度较慢)、Deflate、BZip2 和 LZO。一般大多数情况下用 Snappy。
拼花是 Impala 的推荐文件格式。Parquet 最初是 Twitter 和 Cloudera 的一个联合项目。Parquet 是一种分栏式文件格式,专门为大型全表扫描和分析查询而设计,这正是 Impala 的设计初衷。要在 Impala 中创建拼花表,只需在 CREATE TABLE 语句中包含 STORED AS PARQUET,例如:
CREATE TABLE customer (
id BIGINT,
name STRING)
STORED AS PARQUET;
Impala SQL
Impala 的行为就像一个传统的关系数据库管理系统。Impala 使用 SQL 来检索、管理和修改数据(与 Kudu 一起使用时)。表、视图和其他对象在数据库中进行逻辑组织。存储在表中的数据是按行和列组织的。这些列中的每一列都有一个关联的数据类型。
数据类型
就像典型的 RDBMS 一样,Impala 支持标准数据类型来存储不同种类的数据,如表 3-1 所示。
表 3-1
List of Data Types Supported by Impala
| 数据类型 | 描述 | | :-- | :-- | | 排列 | 可以包含一组元素的复杂数据类型。 | | 比吉斯本 | 8 字节整数数据类型。范围:-9223372036854775808..9223372036854775807. | | 布尔代数学体系的 | 代表真或假选择的数据类型。 | | 茶 | 固定长度的字符类型。您可以指定的最大长度为 255。 | | 小数 | 具有固定小数位数和精度的数值数据类型。适用于金融和其他算术计算。没有精度或小数位数值的 DECIMAL 等效于 DECIMAL(9,0)。 | | 两倍 | 双精度浮点数据类型。范围:4.94065645841246544e-324d..1.79769313486231570e+308,正或负 | | 浮动 | 单精度浮点数据类型。范围:1.40129846432481707e-45..3.40282346638528860e+38,正或负。 | | (同 Internationalorganizations)国际组织 | 4 字节整数数据类型。范围:-2147483648..2147483647.没有无符号子类型。 | | 地图 | 可以包含一组键值对的复杂数据类型。 | | 真实的 | DOUBLE 数据类型的别名。 | | 斯莫列特 | 双字节整数数据类型。范围:-32768..32767. | | 线 | 长度:最大 32,767 字节。 | | 结构体 | 可以包含单个项目的多个字段的复杂数据类型。 | | 时间戳 | 数据类型。范围:允许的日期值范围从 1400-01-01 到 9999-12-31。 | | Tinyint | 1 字节整数数据类型。范围:-128..127. | | 可变长字符串 | 可变长度字符类型。您可以指定的最大长度是 65,535。 |SQL 语句
如前所述,Impala 使用 SQL 作为查询、管理和操作数据的语言。Impala 遵循 SQL-92 标准,其中包括许多现代 SQL 实现中常见的标准特性和内置函数。下面是 Impala 支持的常用 SQL 语句的部分列表。查阅 Impala SQL 语言参考 iv 以获得对 Impala SQL 的完整介绍。
创建数据库
创建数据库在 Impala 中创建一个数据库。就像其他 RDBMS 一样,Impala 使用数据库的概念来存储它的表和其他对象。
CREATE DATABASE salesdb;
创建表格
默认情况下,Impala 创建“内部”表。内部表由 Impala 管理。如果删除一个内部表,组成该表的物理文件也会被删除。
CREATE TABLE salesdb.customers (
id BIGINT,
name STRING,
gender STRING
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
创建外部表
外部表允许您指定已经存储在存储引擎(如 HDFS、HBase、S3 或 Kudu)中的数据的位置。例如,我可以根据 HDFS 目录/user/bob/products 中存储的 CSV 数据创建一个名为 products 的外部表。外部表类似于视图。如果删除外部表,组成该表的物理文件不会被删除,只会删除表定义。您需要从底层存储中手动删除数据。
CREATE EXTERNAL TABLE salesdb.products (
(
id BIGINT,
productname STRING,
productdesc STRING,
cost DOUBLE
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
LOCATION '/user/bob/products/';
挑选
SELECT 允许您从表或视图中检索数据。您可以手动指定列名以仅返回特定的列,或者如果希望返回所有列,则指定星号。
例子
SELECT customerkey, firstname, lastname, birthdate
FROM dimcustomer;
Customerkey firstname lastname birthdate
11001 Eugene Huang 1976-05-10
11044 Adam Flores 1954-11-21
11063 Angela Murphy 1980-10-04
11075 Felicia Jimenez 1963-05-16
11099 Adam Ross 1966-09-05
11107 Bianca Lin 1976-03-04
11118 Alvin Zeng 1962-12-31
11156 Maria Roberts 1981-08-06
11169 Bryce Richardson 1973-12-20
11171 Jonathan Hill 1978-10-05
在哪里
基于条件过滤数据。
例子
SELECT customerkey, firstname, lastname, birthdate
FROM dimcustomer
WHERE firstname="Eugene";
Customerkey firstname lastname birthdate
11001 Eugene Huang 1976-05-10
与和或
AND 和 OR 允许用户在 WHERE 子句中组合多个条件。
例子
SELECT customerkey, firstname, lastname, birthdate
FROM dimcustomer
WHERE firstname = 'Adam'
AND lastname="Flores";
customerkey firstname lastname birthdate
11044 Adam Flores 1954-11-21
SELECT customerkey, firstname, lastname, birthdate
FROM dimcustomer
WHERE firstname = 'Adam'
OR firstname="Alvin";
customerkey firstname lastname birthdate
11044 Adam Flores 1954-11-21
11099 Adam Ross 1966-09-05
11118 Alvin Zeng 1962-12-31
喜欢
LIKE 允许用户指定通配符来搜索列中的模式。
例子
SELECT customerkey, firstname, lastname, birthdate
FROM dimcustomer
WHERE lastname
LIKE 'R%'
Customerkey firstname lastname birthdate
11099 Adam Ross 1966-09-05
11156 Maria Roberts 1981-08-06
11169 Bryce Richardson 1973-12-20
限制
LIMIT 限制 SQL 查询返回的行数。
例子
SELECT customerkey, firstname, lastname, birthdate
FROM dimcustomer
LIMIT 5;
customerkey firstname lastname birthdate
11001 Eugene Huang 1976-05-10
11044 Adam Flores 1954-11-21
11063 Angela Murphy 1980-10-04
11075 Felicia Jimenez 1963-05-16
11099 Adam Ross 1966-09-05
以...排序
排序依据按降序或升序对结果进行排序。
例子
SELECT customerkey, firstname, lastname, birthdate
FROM dimcustomer
ORDER BY customerkey;
Customerkey firstname lastname birthdate
11001 Eugene Huang 1976-05-10
11002 Ruben Torres 1971-02-09
11003 Christy Zhu 1973-08-14
11004 Elizabeth Johnson 1979-08-05
11005 Julio Ruiz 1976-08-01
11006 Janet Alvarez 1976-12-02
11007 Marco Mehta 1969-11-06
11008 Rob Verhoff 1975-07-04
11009 Shannon Carlson 1969-09-29
分组依据和拥有
GROUP BY 与 COUNT、AVG 和 SUM 等聚合函数一起使用,按一列或多列对结果进行分组。在汇总结果后过滤数据。它与 GROUP BY 一起使用。
例子
SELECT firstname, count(*)
FROM dimcustomer
GROUP BY firstname
HAVING firstname="Adam";
firstname count(*)
Adam 51
明显的
DISTINCT 返回列中的不同值。
例子
SELECT DISTINCT firstname
FROM dimcustomer;
联合和联合独特
UNION 和 UNION DISTINCT 组合两个或多个 SELECT 语句的结果。两者都只返回不同的值。
例子
SELECT firstname
FROM dimcustomer
UNION
SELECT firstname
FROM dimcustomer2;
联合所有
UNION ALL 类似于 UNION,只是它允许在结果中出现重复值。
例子
SELECT firstname
FROM dimcustomer
UNION ALL SELECT
firstname
FROM dimcustomer2;
加入
JOIN 使用两个表中的公共值合并两个或多个表中的行。Impala 支持不同类型的连接。更多细节请参考 Impala SQL 语言参考。
例子
SELECT t1.firstname, t1.lastname, t2.salesamount
FROM
dimcustomer AS t1
INNER JOIN
salesfact AS t2
WHERE
t1.customerkey = t2.customerkey;
子查询
子查询是另一个 SQL 查询中的 SQL 查询。
例子
SELECT firstname
FROM
dimcustomer
WHERE firstname
IN
(SELECT firstname FROM dimcustomer2);
形容
DESCRIBE 返回关于表的元数据信息。
例子
DESCRIBE dimcustomer;
DESCRIBE FORMATTED dimcustomer;
使元数据无效
无效元数据使一个或多个表的元数据无效。它迫使 Impala 在对表执行查询之前重新加载表的元数据。
例子
INVALIDATE METADATA dimcustomer;
加载数据
加载数据将存储在 HDFS 文件中的数据加载到表格中。
例子
LOAD DATA INPATH '/home/bob/customers.txt' INTO TABLE dimcustomer;
恢复精神
Refresh 立即重新加载表的元数据,但只加载新添加数据的部分元数据,与使元数据无效相比,这是一种开销较低的操作。
例子
REFRESH dimcustomer;
集合语句
SET 语句允许用户改变 SQL 语句的行为。这些设置只影响在 impala-shell 会话中执行的查询。
数量 _ 节点
NUM_NODES 限制将执行 SQL 查询的节点数量。
例子
SET NUM_NODES=1
MEM _ 极限
MEN_LIMIT 定义了一个 SQL 查询在每个节点上可以利用的最大内存量。
例子
set mem_limit=3G;
set mem_limit=3000M;
实时 _ 进度
LIVE_PROGRESS 显示一个实时进度条,显示已完成的执行百分比。
例子
SET LIVE_PROGRESS=TRUE;
批量大小
BATCH_SIZE 显示 SQL 查询一次处理的行数。较大的数量可以提高 SQL 查询的性能,但会消耗更多内存。默认值设置为 1024。
例子
SET BATCH_SIZE=2048
显示语句
SHOW 语句使用户能够获得关于 Impala 对象的信息。下面是 Impala 支持的最常见的 SHOW 语句列表。
显示数据库
显示数据库显示所有可用的数据库。
例子
SHOW DATABASES;
name comment
_impala_builtins System database for Impala builtin functions
default Default Hive database
显示表格
显示表格显示数据库中所有可用的表格。
例子
SHOW TABLES;
name
customers
dimcustomer
dimgeography
dimproductcategory
hbase_users
sample_07
sample_08
sensordata
testtable
testuser
users
web_logs
显示文件
显示文件显示组成表格的所有 HDFS 文件。
例子
SHOW FILES IN web_logs;
Path Size Partition
hdfs://kuducluster:8020/user/cloudera/2015_11_18/web_logs_1.csv 113.00KB date=2015-11-18
hdfs://kuducluster:8020/user/cloudera/2015_11_19/web_logs_2.csv 103.18KB date=2015-11-19
hdfs://kuducluster:8020/user/cloudera/2015_11_20/web_logs_3.csv 105.59KB date=2015-11-20
hdfs://kuducluster:8020/user/cloudera/2015_11_21/web_logs_4.csv 82.68KB date=2015-11-21
显示表格统计
显示表格 TATS 显示表格的低级信息。
例子
SHOW TABLE STATS web_logs;
date #Rows #Files Size Bytes Cached Cache Replication Format Incremental stats Location
2015-11-18 -1 1 113.00KB NOT CACHED NOT CACHED TEXT false hdfs://kuducluster:8020/user/cloudera/2015_11_18
2015-11-19 -1 1 103.18KB NOT CACHED NOT CACHED TEXT false hdfs://kuducluster:8020/user/cloudera/2015_11_19
2015-11-20 -1 1 105.59KB NOT CACHED NOT CACHED TEXT false hdfs://kuducluster:8020/user/cloudera/2015_11_20
2015-11-21 -1 1 82.68KB NOT CACHED NOT CACHED TEXT false hdfs://kuducluster:8020/user/cloudera/2015_11_21
Total -1 4 404.45KB 0B
内置函数
Impala 包含类似于现代 RDBMS 中的内置函数。这些函数可以在 SQL 语句中用于转换数据、操作字符串以及执行日期和数学计算。Impala 包括聚合函数、字符串函数、日期和时间函数以及数学函数等等。下面列出了 Impala 中包含的一些常用功能。参考 Impala SQL 语言参考 v 完整覆盖 Impala 的内置函数。
全局唯一识别
uuid
函数以字符串形式返回 UUID 或通用唯一标识符。此函数对于生成唯一值来标识对象非常有用。它也可以用作主键值。
select uuid();
uuid()
c14f1469-233b-4d95-9efe-8cf1d2205d7f
现在
now
函数返回当前日期和时间。
select now();
now()
2017-11-06 22:44:19.926002000
正则表达式 _like
regexp
函数返回 true 或 false 来表示字符串是否包含模式指定的正则表达式。
select regexp_like('water','wat');
regexp_like('water', 'wat')
true
丙烯腈-丁二烯-苯乙烯
abs
函数返回自变量的绝对值。
select abs(-100);
abs(-100)
100
fnv_hash(类型五)
基于输入参数返回 64 位 BIGINT 值。此函数实现了一个版本的 Fowler–Noll–Vo 哈希函数,该函数在加密方面并不安全。它可用于需要简单哈希功能的应用程序,如负载*衡和哈希分区。
select abs(fnv_hash((now())));
abs(fnv_hash((now())))
5911639952301673123
用户定义的函数
用户定义函数或 UDF 允许您编写自己的 Impala 函数。这在你需要一个 Impala 中没有的特性时很有用。Impala 支持用 Java 编写的 UDF,但是为了获得最佳性能,建议你用 C++编写 UDF。还可以创建用户定义的聚合函数(UDAFs ),根据一组值返回一个值。关于如何创建用户自定义函数,请参考 Cloudera 的文档。
黑斑羚的复杂类型
Impala 支持复杂类型,如 STRUCT、ARRAY 和 MAP。 vi 复杂数据类型有几个好处,比如通过消除对表连接的需要以及更简单的 ETL 和数据建模来提高查询性能。目前,对复杂类型的支持仅适用于通过 Hive 或 Spark 创建的拼花表。Impala 目前不能创建包含复杂类型的拼花桌。另外,Kudu 还不支持复杂类型;因此,在使用 Kudu 表时,需要传统的维度建模或反规范化。CDH 包括带有嵌套类型的示例表,您可以通过 HUE 安装这些表。让我们展示一些如何处理复杂类型的例子。
DESCRIBE customers;
+-------------------+------------------------+---------+
| name | type | comment |
+-------------------+------------------------+---------+
| id | int | |
| name | string | |
| email_preferences | struct< | |
| | email_format:string, | |
| | frequency:string, | |
| | categories:struct< | |
| | promos:boolean, | |
| | surveys:boolean | |
| | > | |
| | > | |
| addresses | map<string,struct< | |
| | street_1:string, | |
| | street_2:string, | |
| | city:string, | |
| | state:string, | |
| | zip_code:string | |
| | >> | |
| orders | array<struct< | |
| | order_id:string, | |
| | order_date:string, | |
| | items:array<struct< | |
| | product_id:int, | |
| | sku:string, | |
| | name:string, | |
| | price:double, | |
| | qty:int | |
| | >> | |
| | >> | |
+-------------------+------------------------+---------+
查询结构字段
嵌套字段通过点符号来访问。
SELECT
id,
name,
email_preferences.email_format ef,
email_preferences.categories.promos ecp
FROM customers
LIMIT 10;
+-------+---------------------+------+-------+
| id | name | ef | ecp |
+-------+---------------------+------+-------+
| 75012 | Dorothy Wilk | html | true |
| 17254 | Martin Johnson | text | true |
| 12532 | Melvin Garcia | html | true |
| 42632 | Raymond S. Vestal | html | true |
| 77913 | Betty J. Giambrone | text | false |
| 38807 | Rebecca T. Johnson | html | true |
| 71843 | David B. Allison | text | true |
| 67099 | Jay N. Weaver | text | false |
| 83510 | Carol B. Houser | html | false |
| 48072 | Octaviana Guiterrez | text | false |
+-------+---------------------+------+-------+
查询深度嵌套的集合
使用点标记法在 FROM 子句中指定嵌套数组。
SELECT
order_id,
order_date,
items.name,
items.qty
FROM
customers.orders,
customers.orders.items
LIMIT 10;
+----------------------+-----+
| name | qty |
+----------------------+-----+
| Evening Clutch | 1 |
| Large Tassel Pouch | 1 |
| Flameless Candle | 2 |
| Tea for One | 1 |
| Towel Set | 4 |
| Maple Dining Table | 1 |
| Paloma Accent Table | 1 |
| Trunk Coffee Table | 1 |
| Simple Scallop Table | 2 |
| Murano Glass Vase | 4 |
+----------------------+-----+
使用嵌套集合的 ANSI-92 SQL 连接进行查询
Impala 支持嵌套集合的 ANSI-92 SQL 连接。注意,由于父子关系上的隐式连接,您不需要使用 on 子句。
SELECT
id,
name,
co.order_id,
co.order_date
FROM
customers c
INNER JOIN
customers.orders co
LIMIT 10;
+-------+--------------+----------+---------------------------+
| id | name | order_id | order_date |
+-------+--------------+----------+---------------------------+
| 75012 | Dorothy Wilk | 4056711 | 2015-05-01T14:22:25-04:00 |
| 75012 | Dorothy Wilk | J882C2 | 2015-06-10T11:00:00-05:00 |
| 75012 | Dorothy Wilk | I72T39 | 2015-03-14T11:00:00-05:00 |
| 75012 | Dorothy Wilk | PB6268 | 2015-02-11T14:22:25-04:00 |
| 75012 | Dorothy Wilk | B8623C | 2015-04-21T11:00:00-05:00 |
| 75012 | Dorothy Wilk | R9S838 | 2015-07-09T11:00:00-05:00 |
| 75012 | Dorothy Wilk | HS3124 | 2015-10-14T00:00:00 |
| 75012 | Dorothy Wilk | BS5902 | 2014-10-23T00:00:00 |
| 75012 | Dorothy Wilk | DN8815 | 2015-09-07T00:00:00 |
| 75012 | Dorothy Wilk | XR2771 | 2015-12-25T00:00:00 |
+-------+--------------+----------+---------------------------+
黑斑羚壳
Impala shell (impala-shell)是一个命令行实用程序,可以用来与 Impala 交互。您可以运行 SQL 语句,将查询的输出保存到文件中,或者显示有关查询的低级性能信息。您可以在交互和非交互模式下使用 impala-shell。
在执行 impala-shell 命令时,您可以使用几个命令行选项。表 3-2 中列出了一些最重要的选项。要获得更详尽的列表,请参考 Impala 文档。VII
表 3-2
Impala Shell Command-Line Options
| 命令行选项 | 描述 | | :-- | :-- | | -i 主机名或-impalad =主机名 | impala-shell 将连接到运行 impala 守护进程的主机。 | | -o 文件名或-输出文件文件名 | 将查询结果存储在指定的文本文件中。 | | -d 默认数据库或-数据库=默认数据库 | 指定启动 impala-shell 时使用的默认数据库。 | | -q 查询或-query =查询 | 从命令行执行查询。 | | -f 查询文件或-查询文件=查询文件 | 执行存储在文件中的查询。 | | -h 或者-救命 | 显示帮助信息。 |让我们开始在交互模式下使用 impala-shell。让我们通过使用 impala-shell 选项使输出更容易显示。
impala-shell
Starting Impala Shell without Kerberos authentication
Connected to quickstart.cloudera:21000
Server version: impalad version 2.9.0-cdh5.12.0 RELEASE (build 03c6ddbdcec39238be4f5b14a300d5c4f576097e)
***************************************************************************
Welcome to the Impala shell.
(Impala Shell v2.9.0-cdh5.12.0 (03c6ddb) built on Thu Jun 29 04:17:31 PDT 2017)
Press TAB twice to see a list of available commands.
***************************************************************************
Pressing tab twice will list available impala-shell commands.
alter create describe explain insert quit shell src
unset use with compute delete drop help load
select show summary update values connect desc exit
history profile set source tip upsert version
You can use help to list the commands anytime you're inside impala-shell.
help;
Documented commands (type help <topic>):
========================================
compute describe explain profile select shell tip use version
connect exit history quit set show unset values with
Undocumented commands:
======================
alter delete drop insert source summary upsert
create desc help load src update
如果您想要了解特定命令的功能,可以键入 help“command”
help tip;
Print a random tip
执行 tip 命令。
tip;
When you set a query option it lasts for the duration of the Impala shell session.
您可以查看和查询可用的数据库和表。
show databases;
+------------------+----------------------------------------------+
| name | comment |
+------------------+----------------------------------------------+
| _impala_builtins | System database for Impala builtin functions |
| default | Default Hive database |
+------------------+----------------------------------------------+
use default;
show tables;
+-----------------+
| name |
+-----------------+
| customer_backup |
| customers |
| sample_07 |
| sample_08 |
| testtable |
| web_logs |
+-----------------+
select count(*) from customers;
+----------+
| count(*) |
+----------+
| 53 |
+----------+
select client_ip,city,country_name from web_logs limit 10;
+-----------------+---------------+---------------+
| client_ip | city | country_name |
+-----------------+---------------+---------------+
| 128.199.234.236 | Singapore | Singapore |
| 128.199.234.236 | Singapore | Singapore |
| 128.199.234.236 | Singapore | Singapore |
| 128.199.234.236 | Singapore | Singapore |
| 128.199.234.236 | Singapore | Singapore |
| 66.249.76.236 | Mountain View | United States |
| 222.85.131.87 | Guiyang | China |
| 222.85.131.87 | Guiyang | China |
| 101.226.168.225 | Shanghai | China |
| 66.249.76.225 | Mountain View | United States |
+-----------------+---------------+---------------+
describe web_logs;
+-------------------+----------+---------+
| name | type | comment |
+-------------------+----------+---------+
| _version_ | bigint | |
| app | string | |
| bytes | smallint | |
| city | string | |
| client_ip | string | |
| code | tinyint | |
| country_code | string | |
| country_code3 | string | |
| country_name | string | |
| device_family | string | |
| extension | string | |
| latitude | float | |
| longitude | float | |
| method | string | |
| os_family | string | |
| os_major | string | |
| protocol | string | |
| record | string | |
| referer | string | |
| region_code | bigint | |
| request | string | |
| subapp | string | |
| time | string | |
| url | string | |
| user_agent | string | |
| user_agent_family | string | |
| user_agent_major | string | |
| id | string | |
| date | string | |
+-------------------+----------+---------+
impala-shell 的一个便利特性是它能够运行 shell 命令。
shell iostat;
Linux 2.6.32-642.15.1.el6.x86_64 (kuducluster) 11/03/2017 _x86_64_ (4 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
40.91 0.00 2.75 0.12 0.00 56.22
Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
sda 16.11 643.23 619.74 6647326 6404562
dm-0 83.61 631.40 597.55 6525034 6175256
dm-1 0.03 0.25 0.00 2600 0
dm-2 3.74 10.91 22.18 112770 229256
历史记录允许您列出最*执行的命令的历史记录。
history;
[1]
[2]: show tables;
[3]: select * from users;
[4]: select count(*) from dimcustomer;
[5]: select customerkey, firstname, lastname, birthdate from dimcustomer limit 10;
[6]: describe dimcustomer;
[7]: show databases;
[8]: show tables;
[9]: help;
[10]: history;
性能调整和监控
Impala 是一个高性能的 SQL MPP 引擎,能够查询 1 万亿行或更多。 viii 然而,有时仍然需要一些性能调整和监控,以确保 Impala 发挥其最大潜力。幸运的是,Cloudera Enterprise 包括一些工具和命令,可以帮助您调优和监控 Impala。
解释
Explain 显示 SQL 查询的执行计划。执行计划显示了 Impala 为执行您的 SQL 查询而执行的低级操作的顺序。您可以使用执行计划来评估查询的性能。你从下到上阅读执行计划。
explain select count(*) from web_logs;
Query: explain select count(*) from web_logs
Explain String
Estimated Per-Host Requirements: Memory=90.00MB VCores=1
WARNING: The following tables are missing relevant table and/or column statistics.
default.web_logs
""
PLAN-ROOT SINK
|
03:AGGREGATE [FINALIZE]
| output: count:merge(*)
|
02:EXCHANGE [UNPARTITIONED]
|
01:AGGREGATE
| output: count(*)
|
00:SCAN HDFS [default.web_logs]
partitions=4/4 files=4 size=404.45KB
Fetched 16 row(s) in 3.50s
摘要
为查询执行的不同阶段提供计时。它帮助您确定潜在的瓶颈在哪里。下面的结果表明,大部分活动都用于执行 HDFS 扫描。
summary;
Operator #Hosts Avg Time Max Time #Rows
03:AGGREGATE 1 0ns 0ns 1
02:EXCHANGE 1 0ns 0ns 1
01:AGGREGATE 1 0ns 0ns 1
00:SCAN HDFS 1 44.00ms 44.00ms 1.00K
Est. #Rows Peak Mem Est. Peak Mem Detail
1 20.00 KB -1 B FINALIZE
1 0 B -1 B UNPARTITIONED
1 16.00 KB 10.00 MB
-1 280.00 KB 80.00 MB default.web_log
轮廓
Profile 显示关于 SQL 查询的更详细的性能报告,例如内存使用、网络和存储等待时间等。下面显示的是结果的摘录。
profile;
Query Runtime Profile:
Query (id=26414d8a59a8fff4:fd59c4f500000000):
Summary:
Session ID: 8e4d099336a7cabe:83d7327345384cb4
Session Type: BEESWAX
Start Time: 2017-11-05 22:21:59.218421000
End Time: 2017-11-05 22:21:59.345006000
Query Type: QUERY
Query State: FINISHED
Query Status: OK
Impala Version: impalad version 2.7.0-cdh5.10.0 RELEASE (build 785a073cd07e2540d521ecebb8b38161ccbd2aa2)
User: hadoop
Connected User: hadoop
Delegated User:
Network Address: ::ffff:10.0.1.101:53796
Default Db: default
Sql Statement: select count(*) from web_logs
Coordinator: kuducluster:22000
Query Options (non default):
Plan:
Cloudera 经理
Cloudera Manager 提供了一个图形用户界面,可以用来监控 Impala(图 3-2 )。
图 3-2
Cloudera Manager
您可以放大图表以获得不同性能指标的更详细视图,例如查询持续时间(图 3-3 )。
图 3-3
Query Duration
您还可以监视查询。在 Cloudera Manager 中,导航到集群➤黑斑羚查询,如图 3-4 所示。
图 3-4
Cloudera Manager – Impala Queries
您将看到集群中已经执行或正在执行的 Impala 查询的历史。Cloudera Manager 还显示持续时间、用户和其他指标等信息(图 3-5 )。
图 3-5
Impala Queries
Impala 有自己的 web 界面,提供关于其 StateStore 和目录服务器的底层细节(图 3-6 )。
图 3-6
Impala Web UI
Impala StateStore WebUI 提供了关于 Impala StateStore 的详细性能和诊断信息(图 3-7 )。
图 3-7
Impala StateStore WebUI
Impala 目录服务器 WebUI 提供了关于 Impala 目录服务器的详细性能和诊断信息(图 3-8 )。
图 3-8
Impala Catalog Server WebUI
Impala 性能建议
有些建议是特定于 Impala 的,而有些是大多数数据仓库共有的。
使用黑斑羚拼花地板
Parquet 是一种高度优化的柱状文件格式,设计用于 Impala。创建表时尽可能使用拼花文件格式(如果您的环境中没有 Kudu)。我会在下一章讨论 Impala 和 Kudu。
解决“小文件”问题
小文件对黑斑羚来说是个大问题。HDFS 没有优化处理大量的小文件。这通常是由于在一段时间内对一个表执行几个 INSERT…值造成的。解决这个问题的一种方法是通过创建另一个表并使用 INSERT…SELECT 将数据大容量插入该表来合并小文件。insert 语句的并行度取决于运行 Impala 的工作节点的数量。这意味着,如果集群中有 20 个工作节点,insert 语句可能会生成 20 个小文件。通过将 NUM_NODES 设置为 1,可以强制 Impala 写一个大文件,而不是 20 个小文件。这将强制 Impala 在 1 个节点上执行 insert 语句。因为您将并行度降低到 1,这可能会导致其他性能问题,所以请相应地进行测试。
表分区
分区是大型表的一项要求。如果您有大型表分区,它应该导致查询处理单个较小的分区,而不是读取整个表。这就是所谓的分区修剪。在大多数情况下,分区修剪可以显著缩短执行时间,减少 Impala 检索的数据量。我在第四章中讨论了表分区。
反规格化
反规范化是另一个优化选项。表连接通常是一些最低效的数据库操作,尤其是当您连接几个大表时。通过对表进行反规范化,可以提高读取性能,但代价是重复数据和降低写入性能。对于大多数 DSS 和报告系统来说,这是一个可以接受的折衷方案。
创建汇总或汇总表
从技术上讲,创建汇总或汇总表是一种反规范化形式。在大多数 RDBMS 中,这是使用物化视图实现的。Impala 不支持物化视图,但是您可以通过预先构建包含聚合数据的汇总表来模仿它们。
收集大型表连接中使用的表的统计信息
表统计数据需要是最新的,这样 Impala 就可以为包含在查询中的表计算出最有效的连接顺序。可以使用 COMPUTE STATS 定期收集频繁连接的表的统计信息。
关于如何调优 Impala 的更多细节,请参考 Impala 性能指南和最佳实践。 ix
工作量和资源管理
Impala 有自己的资源和工作负载管理功能,不像 Spark 和 MapReduce 等其他 Hadoop 组件那样与 YARN 集成。
Note
在早期版本的 CDH 中有一个称为骆驼的组件,它提供了与黑斑羚的纱线集成,但从 CDH 5.5 /黑斑羚 2.3 开始停止使用。
在 Impala 中,如果启用了静态分区,那么 CPU、网络和磁盘 IO 上的资源限制是通过使用 cgroups 来实现的。在内存使用方面,Impala 的内存限制(设置 MEM _ 限制)是用来限制内存使用的。动态资源池允许基于池中可用资源对 Impala 查询进行动态资源管理。
准入控制
准入控制是 Impala 中的一个资源管理特性,它限制 SQL 查询的并发性,以防止大量用户同时使用而导致的过度使用。准入控制将用户查询排队,直到资源被释放并变得可用。
有关 Impala 工作负载和资源管理特性的更多信息,请参考 Impala 的在线文档。 x
Hadoop 用户体验
Hadoop 用户体验或 HUE 是一个基于 web 的用户界面,为 Impala 和 Hive 提供了一个易于使用的 SQL 编辑器和 Metastore 管理器。HUE 还包括其他功能,可以让您与 HBase、文件浏览器和作业浏览器进行交互。
SQL 编辑器允许用户运行 Impala 查询,是一个比 impala-shell 更友好的工具(图 3-9 )。
图 3-9
HUE – Impala
Metastore 管理器允许用户管理 Impala 和 Hive 表以及其他对象(图 3-10 )。
图 3-10
HUE – Metastore Manager
企业中的黑斑羚
由于其 JDBC 和 ODBC 驱动程序,Impala 与市场上流行的商业智能、数据整理和数据可视化工具兼容,例如 Microstrategy、Business Objects、Cognos、Oracle 商业智能企业版(OBIEE)、Tableau、Qlik、Power BI 和 Zoomdata 等。我在第九章中用 Impala 介绍了数据可视化和商业智能。
摘要
Impala 是 Cloudera Enterprise 最重要的组件之一。虽然 Hive 使 Hadoop 可以为大众所用,但正是 Impala 等 SQL MPP 引擎使 Hadoop 入侵企业成为可能。我们将在下一章讨论黑斑羚和库杜。
参考
- 马塞尔·科尔纳克;“Impala:面向 Hadoop 的现代开源 SQL 引擎”,Cloudera,2015,
http://cidrdb.org/cidr2015/Papers/CIDR15_Paper28.pdf
。 - 马塞尔·科尔纳克;《遇见工程师:Marcel Kornacker》,Cloudera,2013,
https://blog.cloudera.com/blog/2013/01/meet-the-engineer-marcel-kornacker/
。 - 杰夫·舒特;F1:“可扩展的分布式 SQL 数据库”,谷歌,2013 年,
https://research.google.com/pubs/pub41344.html
。 - Cloudera《Impala SQL 语言参考》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/latest/topics/impala_langref.html
。 - Cloudera《Impala SQL 语言参考》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/latest/topics/impala_langref.html
。 http://blog.cloudera.com/blog/2015/11/new-in-cloudera-enterprise-5-5-support-for-complex-types-in-impala/
。- Cloudera《impala-shell 命令行选项》,Cloudera,2018,
http://www.cloudera.com/documentation/cdh/5-1-x/Impala/Installing-and-Using-Impala/ciiu_shell_options.html
。 - Cloudera《黑斑羚常见问题》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/latest/topics/impala_faq.html
。 - Cloudera《Impala 性能指南和最佳实践》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/latest/topics/impala_perf_cookbook.html#perf_cookbook__perf_cookbook_stats
。 - Cloudera《黑斑羚的资源管理》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/latest/topics/impala_resource_management.html
。
四、使用 Impala 和 Kudu 的高性能数据分析
Impala 是 Kudu 默认的 MPP SQL 引擎。Impala 允许您使用 SQL 与 Kudu 进行交互。如果您有使用 SQL 和存储引擎紧密集成的传统关系数据库的经验,您可能会发现 Kudu 和 Impala 相互解耦并不常见。Impala 旨在与其他存储引擎(如 HDFS、HBase 和 S3)一起工作,而不仅仅是 Kudu。将其他 SQL 引擎如 Apache Drill (DRILL-4241)和 Hive (HIVE-12971)与 Kudu 集成的工作也在进行中。在开源社区中,分离存储、SQL 和处理引擎是很常见的。
Impala-Kudu 的整合非常成功,但仍有工作要做。虽然它在性能和可伸缩性方面达到或超过了传统的数据仓库*台,但 Impala-Kudu 仍然缺乏大多数传统数据仓库*台中的一些企业特性。Kudu 是一个年轻的项目。我们将在本章后面讨论其中的一些限制。
主关键字
每个 Kudu 表都需要有一个主键。创建 Kudu 表时,必须首先列出用作主键的一列或多列。Kudu 的主键是作为聚集索引实现的。使用聚集索引,行以与索引相同的顺序物理存储在*板中。还要注意,Kudu 没有自动递增特性,所以在向 Kudu 表中插入行时,必须包含唯一的主键值。如果没有主键值,可以使用 Impala 内置的 uuid()函数或者更高效的生成唯一值的方法。
数据类型
和其他关系数据库一样,Kudu 支持各种数据类型(表 4-1 )。
您可能会注意到 Kudu 不支持 decimal 数据类型。这是 Kudu 的一个关键限制。float 和 double 数据类型仅存储非常接*的值,而不是 IEEE 754 规范中定义的精确值。 我
表 4-1
List of Data Types, with Available and Default Encoding
| 数据类型 | 编码 | 默认 | | :-- | :-- | :-- | | 布尔 | 普通,游程长度 | 运行长度 | | 8 位有符号整数 | 普通、位洗牌、游程长度 | 比特洗牌 | | 16 位有符号整数 | 普通、位洗牌、游程长度 | 比特洗牌 | | 32 位有符号整数 | 普通、位洗牌、游程长度 | 比特洗牌 | | 64 位有符号整数 | 普通、位洗牌、游程长度 | 比特洗牌 | | unixtime_micros(自 Unix 纪元以来的 64 位微秒) | 普通、位洗牌、游程长度 | 比特洗牌 | | 单精度(32 位)IEEE-754 浮点数 | 普通,位图 | 位混洗 | | 双精度(64 位)IEEE-754 浮点数 | 普通,位图 | 比特洗牌 | | UTF-8 编码字符串(未压缩时最大 64KB) | 普通,前缀,字典 | 词典 | | 二进制(最多 64KB 未压缩) | 普通,前缀,字典 | 词典 |因此,行为 float 和 double 不适合存储财务数据。在撰写本文时,对十进制数据类型的支持仍在开发中(Apache Kudu 1.5 / CDH 5.13)。更多详情请查看 KUDU-721。周围有各种各样的工作。您可以将财务数据存储为 string,然后在每次需要读取数据时使用 Impala 将值转换为 decimal。因为 Parquet 支持小数,所以另一个解决方法是对事实表使用 Parquet,对维度表使用 Kudu。Kudu 提交者正在致力于增加十进制支持,并且可能会包含在 Kudu 的新版本中。
如表 4-1 所示,根据列的类型,Kudu 列可以使用不同的编码类型。支持的编码类型包括普通、位混洗、游程、字典和前缀。默认情况下,Kudu 列是未压缩的。Kudu 支持使用 Snappy、zlib 或 LZ4 压缩编解码器进行列压缩。压缩和编码可以显著减少空间开销并提高性能。有关编码和压缩的更多信息,请参考 Kudu 的在线文档。
Note
在 Kudu 的早期版本中,日期和时间被表示为 BIGINT。从 Impala 2.9/CDH 5.12 开始,可以在 Kudu 表中使用时间戳数据类型。然而,有几件事要记住。Kudu 使用 64 位值表示日期和时间列,而 Impala 使用 96 位值表示日期和时间。存储在 Kudu 中时,Impala 生成的纳秒值四舍五入。在读写时间戳列时,Kudu 的 64 位表示和 Impala 的 96 位表示之间存在转换开销。有两种解决方法:使用 Kudu 客户端 API 或 Spark 来插入数据,或者继续使用 BIGINT 来表示日期和时间。 ii
内部和外部 Impala 表
您可以在 Impala 中创建内部和外部表。
内部表格
内部表由 Impala 创建和管理。内部表一创建,Impala 就能立即看到。删除和重命名表等管理任务是使用 Impala 执行的。下面是一个如何在 Impala 中创建内部表的例子。
CREATE TABLE users
(
id BIGINT,
name STRING,
age TINYINT,
salary FLOAT,
PRIMARY KEY(id)
)
PARTITION BY HASH PARTITIONS 8
STORED AS KUDU;
外部表格
通过 Kudu API 和 Spark 创建的 Kudu 表对 Impala 来说并不直接可见。这个表存在于 Kudu 中,但是因为它不是通过 Impala 创建的,所以它不知道这个表的任何信息。必须在 Impala 中创建一个引用 Kudu 表的外部表。删除外部表只会删除 Impala 和 Kudu 表之间的映射,而不会删除 Kudu 中的物理表。下面是一个关于如何在 Impala 中创建一个外部表到一个现有的 Kudu 表的例子。
CREATE EXTERNAL TABLE users
STORED AS KUDU
TBLPROPERTIES (
'kudu.table_name' = 'kudu_users'
);
更改数据
您可以通过 Impala 使用 SQL 语句更改存储在 Kudu 表中的数据,就像传统的关系数据库一样。这就是为什么你会使用 Kudu 而不是不可变的存储格式,比如 ORC 或 Parquet 的主要原因之一。
插入行
您可以使用标准的 SQL insert 语句。
INSERT INTO users VALUES (100, "John Smith", 25, 50000);
插入具有多个值的行子子句。
INSERT INTO users VALUES (100, "John Smith", 25, 50000), (200, "Cindy Nguyen", 38, 120000), (300, "Steve Mankiw", 60, 75000);
也支持大容量插入。
INSERT INTO users SELECT * from users_backup;
更新行
标准 SQL 更新语句。
UPDATE users SET age=39 where name = 'Cindy Nguyen';
还支持批量更新。
UPDATE users SET salary=150000 where id > 100;
打乱行
支持向上插入。如果表中不存在主键,则插入整行。
UPSERT INTO users VALUES (400, "Mike Jones", 21, 80000);
但是,如果主键已经存在,则列将使用新值进行更新。
UPSERT INTO users VALUES (100, "John Smith", 27, 70000);
删除行
标准 SQL 删除语句。
DELETE FROM users WHERE id < 3;
还支持更复杂的 Delete 语句。
DELETE FROM users WHERE id in (SELECT id FROM users WHERE name = 'Steve Mankiw');
Note
如第二章所述,Kudu 不支持符合 ACID 的事务。如果更新中途失败,将不会回滚。更新后必须执行额外的数据验证,以确保数据完整性。
更改模式
Kudu 支持典型的数据库管理任务,比如重命名表、添加和删除范围分区以及删除表。更多信息,请查阅 Kudu 的在线文档。
分割
表分区是增强 Kudu 表的性能、可用性和可管理性的常用方法。分区允许将表细分成更小的部分,即片。分区使 Kudu 能够以更精细的粒度访问*板电脑,从而利用分区修剪。所有 Kudu 表都需要进行表分区,表分区对应用程序是完全透明的。Kudu 支持散列、范围、复合散列-范围和散列-散列分区。下面是 Kudu 中分区的几个例子。分区在第二章中有更详细的讨论。
哈希分区
散列分区使用散列键将行均匀地分布在不同的*板上。
CREATE TABLE myTable (
id BIGINT NOT NULL,
name STRING,
PRIMARY KEY(id)
)
PARTITION BY HASH PARTITIONS 4
STORED AS KUDU;
范围划分
范围分区将数据范围分别存储在不同的*板电脑中。
CREATE TABLE myTable (
year INT,
deviceid INT,
totalamt INT,
PRIMARY KEY (deviceid, year)
)
PARTITION BY RANGE (year) (
PARTITION VALUE = 2016,
PARTITION VALUE = 2017,
PARTITION VALUE = 2018
)
STORED AS KUDU;
哈希范围分区
哈希分区将写操作均匀地分布在*板电脑上。范围分区支持分区修剪,并允许添加和删除分区。复合分区结合了两种分区方案的优点,同时限制了它的缺点。对于物联网用例来说,这是一个很好的划分方案。
CREATE TABLE myTable (
id BIGINT NOT NULL,
sensortimestamp BIGINT NOT NULL,
sensorid INTEGER,
temperature INTEGER,
pressure INTEGER,
PRIMARY KEY(id, sensortimestamp)
)
PARTITION BY HASH (id) PARTITIONS 16,
RANGE (sensortimestamp)
(
PARTITION unix_timestamp('2017-01-01') <= VALUES < unix_timestamp('2018-01-01'),
PARTITION unix_timestamp('2018-01-01') <= VALUES < unix_timestamp('2019-01-01'),
PARTITION unix_timestamp('2019-01-01') <= VALUES < unix_timestamp('2020-01-01')
)
STORED AS KUDU;
哈希-哈希分区
您的表中可以有多个级别的散列分区。每个分区级别应该使用不同的散列列。
CREATE TABLE myTable (
id BIGINT,
city STRING,
name STRING
age TINYINT,
PRIMARY KEY (id, city)
)
PARTITION BY HASH (id) PARTITIONS 8,
HASH (city) PARTITIONS 8
STORED AS KUDU;
列表分区
如果您熟悉 Oracle 等其他关系数据库管理系统,您可能已经使用过列表分区。虽然 Kudu 在技术上没有列表分区,但是您可以使用范围分区来模仿它的行为。
CREATE TABLE myTable (
city STRING
name STRING,
age TINYINT,
PRIMARY KEY (city, name)
)
PARTITION BY RANGE (city)
(
PARTITION VALUE = 'San Francisco',
PARTITION VALUE = 'Los Angeles',
PARTITION VALUE = 'San Diego',
PARTITION VALUE = 'San Jose'
)
STORED AS KUDU;
Note
分区不是万能的。它只是优化 Kudu 的众多方法之一。如果使用默认的 Kudu 设置,在将数据摄取到 Kudu 中时,您仍可能会遇到性能问题。确保调整参数 maintenance _ manager _ num _ threads, iii ,这是维护线程的数量,有助于加速压缩和刷新。您可以监视 bloom_lookups_per_op 指标和内存压力拒绝,以查看压缩和刷新是否会影响性能。您可能需要调整的另一个设置是 memory_limit_hard_bytes,它控制分配给 Kudu 守护进程的内存总量。 iv 更多详情参考在线 Kudu 文档。
使用 JDBC 与阿帕奇黑斑羚和库杜
流行的 BI 和数据可视化工具,如 Power BI、Tableau、Qlik、OBIEE 和 MicroStrategy(仅举几个例子)可以使用 JDBC/ODBC 访问 Apache Impala 和 Kudu。黑斑羚 JDBC 的驱动程序可以从 Cloudera 的网站上下载。Progress DataDirect JDBC 驱动程序是另一种选择。 v 在某些情况下,来自不同公司的一些 JDBC 驱动程序具有额外的性能特征。你的经历可能会有所不同。图 4-1 显示了一个 Zoomdata 仪表板的样本截图,该仪表板通过 Impala JDBC/ODBC 可视化存储在 Kudu 中的数据。
图 4-1
Kudu data accessible from ZoomData in real time
与 SQL Server 链接服务器和 Oracle 网关的联盟
您可以创建从 SQL Server 和 Oracle 到 Impala 的数据库链接。如果来回复制数据太麻烦并且数据相对较小,这有时会很有用。此外,如果您需要从 SQL Server 或 Oracle 接*实时地访问存储在 Kudu 中的最新数据,而 ETL 太慢,那么通过数据库链接访问数据是一种选择。下面是一个如何在 SQL Server 中创建数据库链接的示例。然后,用户可以访问存储在远程 Impala 环境中的数据(如清单 4-1 所示)。注意,通常建议在执行查询时使用 OpenQuery,以确保查询在远程服务器上执行。
EXEC master.dbo.sp_addlinkedserver
@server = 'ClouderaImpala', @srvproduct='ClouderaImpala',
@provider='MSDASQL', @datasrc='ClouderaImpala',
@provstr='Provider=MSDASQL.1;Persist Security Info=True;User ID=;Password=';
SELECT * FROM OpenQuery(ClouderaImpala, 'SELECT * FROM order_items');
SELECT * FROM OpenQuery(ClouderaImpala, 'SELECT * FROM orders');
SELECT count(*)
FROM [ClouderaImpala].[IMPALA].[default].order_items
SELECT count(*)
FROM [ClouderaImpala].[IMPALA].[default].orders
create view OrderItems_v as
SELECT * FROM OpenQuery(ClouderaImpala, 'SELECT * from order_items');
create view Orders_v as
SELECT * FROM OpenQuery(ClouderaImpala, 'SELECT * from orders');
create view OrderDetail_v as
SELECT * FROM OpenQuery(ClouderaImpala, 'SELECT o.order_id,oi.order_item_id, o.order_date,o.order_status
FROM [IMPALA].[default].orders o, [IMPALA].[default].order_items oi
where o.order_id=oi.order_item_order_id')
Listing 4-1Creating and using linked server from SQL Server to Impala
您可以使用 Oracle 的 ODBC 异构网关创建从 Oracle 到 Impala 的数据库链接。有关更多详细信息,请参考 Oracle 文档。
如果访问中小型数据集,数据库链接是合适的。如果您需要一个成熟的数据虚拟化工具,您可能会考虑使用 Polybase、Denodo 或 TIBCO 数据虚拟化(以前由 Cisco 拥有)。
摘要
Impala 为 Kudu 提供了强大的 MPP SQL 引擎。总之,它们在性能和可伸缩性方面可以与传统的数据仓库*台相媲美。Kudu 提交者和贡献者正在努力增加更多的特性和功能。第二章提供了对 Kudu 的深入讨论,包括它的局限性。想了解更多关于黑斑羚的信息,我建议你参考第三章。第八章讲述了使用 Impala 和 Kudu 进行大数据仓储。第九章向用户展示如何使用 Impala 和 Kudu 进行实时数据可视化。
参考
- 微软;《理解 IEEE 浮点错误完整教程》,微软,2018,
https://support.microsoft.com/en-us/help/42980/-complete-tutorial-to-understand-ieee-floating-point-errors
- 阿帕奇黑斑羚;“时间戳数据类型”,Apache Impala,2018,
https://impala.apache.org/docs/build/html/topics/impala_timestamp.html
- Cloudera《阿帕奇库度后台维护任务》,Cloudera,2018,
https://www.cloudera.com/documentation/kudu/latest/topics/kudu_background_tasks.html
- 托德·利普孔;“Re:如何计算
维护 _ 管理器 _ 线程数'的最优值”,Todd Lipcon,2017,
https://www.mail-archive.com/user@kudu.apache.org/msg00358.html` - Saikrishna Teja Bobba《教程:用 Apache Kudu 使用 Impala JDBC 和 SQL》,《进度》2017,
https://www.progress.com/blogs/tutorial-using-impala-jdbc-and-sql-with-apache-kudu
五、Spark 简介
Spark 是下一代大数据处理框架,用于处理和分析大型数据集。Spark 具有统一的处理框架,提供 Scala、Python、Java 和 R 的高级 API 以及强大的库,包括用于 SQL 支持的 Spark SQL、用于机器学习的 MLlib、用于实时流的 Spark Streaming 和用于图形处理的 GraphX。 i Spark 由马泰·扎哈里亚(Matei Zaharia)在加州大学伯克利分校的 AMPLab 创立,后捐赠给阿帕奇软件基金会,于 2014 年 2 月 24 日成为顶级项目。 ii 第一版于 2014 年 5 月 30 日发布。 iii
整本书都是关于 Spark 的。本章将向您简要介绍 Spark,足以让您掌握执行常见数据处理任务所需的技能。我的目标是让你尽快变得高效。对于更彻底的治疗,霍尔登·卡劳、安迪·孔温斯基、帕特里克·温德尔和马泰·扎哈里亚(O'Reilly,2015 年)的《学习 Spark》仍然是对 Spark 的最佳介绍。Sandy Ryza、Uri Laserson、Sean Owen 和 Josh Wills (O'Reilly,2015 年)撰写的《Spark 高级分析》第二版涵盖了高级 Spark 主题,也是强烈推荐的。我假设你以前没有 Spark 的知识。然而,了解一些 Scala 知识是有帮助的。Jason Swartz (O'Reilly,2014)的《学习 Scala》和 Martin Odersky、Lex Spoon 和 Bill Venners (Artima,2011)的《Scala 第二版编程》是帮助您学习 Scala 的好书。有关 Hadoop 和 Hadoop 组件(如 HDFS 和 YARN)的初级读本,请访问 Apache Hadoop 网站。第六章讨论了与 Kudu 的集成。
概观
Spark 的开发是为了解决 Hadoop 最初的数据处理框架 MapReduce 的局限性。Matei Zaharia 在加州大学伯克利分校和脸书大学(他在那里实习)看到了 MapReduce 的许多局限性,并试图创建一个更快、更通用、多用途的数据处理框架,可以处理迭代和交互式应用程序。 iv 马泰成功实现了他的目标,让 Spark 几乎在各个方面都优于 MapReduce。由于其简单而强大的 API,Spark 更容易访问和使用。Spark 提供了一个统一的*台(图 5-1 ),支持更多类型的工作负载,如流式、交互式、图形处理、机器学习和批处理。 v Spark 作业的运行速度比同等的 MapReduce 作业快 10-100 倍,这是因为它具有快速的内存功能和高级 DAG(定向非循环图)执行引擎。数据科学家和工程师通常使用 Spark 比使用 MapReduce 更有效率。
图 5-1
Apache Spark Ecosystem
集群管理器
群集管理器管理和分配群集资源应用程序。Spark 支持 Spark(独立调度程序)、YARN 和 Mesos 自带的独立集群管理器。有一个实验性的项目,为 Spark 提供本地支持,将 Kubernetes 用作集群管理器。查看 SPARK-18278 了解更多详情。
体系结构
在高层次上,Spark 将 Spark 应用程序任务的执行分布在集群节点上(图 5-2 )。每个 Spark 应用程序在其驱动程序中都有一个 SparkContext 对象。SparkContext 表示到集群管理器的连接,集群管理器为 Spark 应用程序提供计算资源。在连接到集群之后,Spark 在您的 worker 节点上获取执行器。然后 Spark 将您的应用程序代码发送给执行器。一个应用程序通常会运行一个或多个作业来响应一个 Spark 动作。然后 Spark 将每个任务划分成更小的有向无环图(Dag ),表示任务的各个阶段。然后,每个任务被分发并发送给工作节点上的执行器来执行。
图 5-2
Apache Spark Architecture
每个 Spark 应用程序都有自己的一组执行器。因为来自不同应用程序的任务在不同的 JVM 中运行,所以一个 Spark 应用程序不会干扰另一个 Spark 应用程序。这也意味着,如果不使用像 HDFS 或 S3 这样的慢速外部数据源,Spark 应用程序很难共享数据[38]。使用 Tachyon(又名 Alluxio)等堆外内存存储可以使数据共享更快更容易。我在第十章讨论了 Alluxio。
执行 Spark 应用程序
我假设您正在使用 Cloudera Enterprise 作为您的大数据*台。在 CDH,Spark 1.x 和 2.x 可以在同一个集群上共存,不会出现任何问题。您可以使用交互式 shell (spark-shell 或 pyspark)或提交应用程序(spark-submit)来执行 Spark 1.x 应用程序。你需要使用不同的命令来访问 Spark 2.x(表 5-1 )。
表 5-1
Spark 1.x and Spark 2.x Commands
| Spark 1.x | Spark 2.x | | :-- | :-- | | Spark-提交 | Spark 2-提交 | | Spark 壳 | Spark 2-外壳 | | 派斯巴基派斯巴基派斯巴基派斯巴基派斯巴基派斯巴基派斯巴基派斯巴基派斯巴基派斯巴基派斯巴基派斯巴基 | 派斯巴基 2 号 |纱线上的 Spark
YARN 是大多数基于 Hadoop 的*台(如 Cloudera 和 Hortonworks)的默认集群管理器。有两种部署模式可用于在 YARN 中启动 Spark 应用程序。
集群模式
在集群模式下,驱动程序运行在由 YARN 管理的主应用程序中。客户端可以退出而不影响应用程序的执行。以集群模式启动应用程序或 spark-shell:
spark-shell --master yarn --deploy-mode cluster
spark-submit --class mypath.myClass --master yarn --deploy-mode cluster
客户端模式
在客户端模式下,驱动程序在客户端上运行。应用程序主机仅用于向 YARN 请求资源。要在客户端模式下启动应用程序或 spark-shell:
spark-shell --master yarn --deploy-mode client
spark-submit --class mypath.myClass --master yarn --deploy-mode client
Spark 壳简介
您通常使用交互式 shell 进行特定的数据分析或探索。也是学习 Spark API 的好工具。Spark 的交互 shell 有 Scala 或 Python 版本。在下面的例子中,我们将创建一个城市列表,并将它们全部转换为大写。
spark2-shell
Setting default log level to "WARN".
To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).
Spark context Web UI available at http://10.0.1.101:4040
Spark context available as 'sc' (master = yarn, app id = application_1513771857144_0002).
Spark session available as 'spark'.
Welcome to
____ __
/ __/__ ___ _____/ /__
_\ \/ _ \/ _ `/ __/ '_/
/___/ .__/\_,_/_/ /_/\_\ version 2.2.0.cloudera1
/_/
Using Scala version 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151)
Type in expressions to have them evaluated.
Type :help for more information.
scala> val myCities = sc.parallelize(List("tokyo","new york","sydney","san francisco"))
myCities: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[0] at parallelize at <console>:24
scala> val uCities = myCities.map {x => x.toUpperCase}
uCities: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[1] at map at <console>:26
scala> uCities.collect.foreach(println)
TOKYO
NEW YORK
SYDNEY
SAN FRANCISCO
Listing 5-1Introduction to spark-shell
你将在整章中使用 Spark 壳。当您启动如清单 5-1 所示的 spark2-shell 时,会自动创建一个名为“spark”的 SparkSession。
Spark 会议
如图 5-2 所示,SparkContext 支持访问所有 Spark 特性和功能。驱动程序使用 SparkContext 来访问其他上下文,如 StreamingContext、SQLContext 和 HiveContext。从 Spark 2.0 开始,SparkSession 提供了与 Spark 交互的单一入口点。SparkContext 提供的所有功能,如 Spark 1.x 中的 SQLContext、HiveContext 和 StreamingContext,现在都可以通过 SparkSession 访问。VI
在 Spark 1.x 中,您可以编写类似这样的代码。
val sparkConf = new SparkConf().setAppName("MyApp").setMaster("local")
val sc = new SparkContext(sparkConf).set("spark.executor.cores", "4")
val sqlContext = new org.apache.spark.sql.SQLContext(sc)
在 Spark 2.x 中,您不必显式创建 SparkConf、SparkContext 或 SQLContext,因为它们的所有功能都已经包含在 SparkSession 中。
val spark = SparkSession.
builder().
appName("MyApp").
config("spark.executor.cores", "4").
getOrCreate()
蓄电池
累加器是只被“添加”的变量。它们通常用于实现计数器。在示例中,我使用累加器将数组的元素相加:
val accum = sc.longAccumulator("Accumulator 01")
sc.parallelize(Array(10, 20, 30, 40)).foreach(x => accum.add(x))
accum.value
res2: Long = 100
广播变量
广播变量是存储在每个执行器节点内存中的只读变量。Spark 使用高速广播算法来减少复制广播变量的网络延迟。使用广播变量在每个节点上存储数据集的副本是一种更快的方法,而不是将数据存储在 HDFS 或 S3 这样的慢速存储引擎中。
val broadcastVar = sc.broadcast(Array(10, 20, 30))
broadcastVar.value
res0: Array[Int] = Array(10, 20, 30)
放射性散布装置
RDD 是一个有弹性的不可变分布式对象集合,跨集群中的一个或多个节点进行分区。RDD 可以通过两种类型的操作并行处理和操作:转换和动作。
Note
RDD 是 Spark 1.x 中 Spark 的主要编程接口。从 Spark 2.0 开始,数据集已经取代 RDD 成为主要的 API。由于更丰富的编程界面和更好的性能,强烈建议用户从 RDD 切换到数据集。我们将在本章后面讨论数据集和数据帧。
创建 RDD
创建 RDD 非常简单。你可以从现有的 Scala 集合中创建一个 RDD,或者从存储在 HDFS 或 S3 的外部文件中读取。
*行放置
并行化从 Scala 集合创建一个 RDD。
val myList = (1 to 5).toList
val myRDD = sc.parallelize(myList)
val myCitiesRDD = sc.parallelize(List("tokyo","new york","sydney","san francisco"))
文本文件
文本文件从储存在 HDFS 或 S3 的文本文件创建 RDD。
val myRDD = sc.textFile("hdfs://master01:9000/files/mydirectory")
val myRDD = sc.textFile("s3a://mybucket/files/mydata.csv")
请注意,RDD 是不可变的。需要执行任何类型的数据转换的操作将需要创建另一个 RDD。RDD 操作可以分为两类:转换和行动。
转换
转换是创建新 RDD 的操作。我描述了一些最常见的转换。有关完整的列表,请参考在线 Spark 文档。
地图
Map 对 RDD 中的每个元素执行一个函数。它创建并返回结果的新 RDD。地图的返回类型不必与原始 RDD 的类型相同。
val myCities = sc.parallelize(List("tokyo","new york","paris","san francisco"))
val uCities = myCities.map {x => x.toUpperCase}
uCities.collect.foreach(println)
TOKYO
NEW YORK
PARIS
SAN FRANCISCO
让我们展示另一个地图的例子。
val lines = sc.parallelize(List("Michael Jordan", "iPhone"))
val words = lines.map(line => line.split(" "))
words.collect
res2: Array[Array[String]] = Array(Array(Michael, Jordan), Array(iPhone))
*面地图
Flatmap 对 RDD 中的每个元素执行一个函数,然后对结果进行拼合。
val lines = sc.parallelize(List("Michael Jordan", "iPhone"))
val words = lines.flatMap(line => line.split(" "))
words.collect
res3: Array[String] = Array(Michael, Jordan, iPhone)
过滤器
返回仅包含符合指定条件的元素的 RDD。
val lines = sc.parallelize(List("Michael Jordan", "iPhone","Michael Corleone"))
val words = lines.map(line => line.split(" "))
val results = words.filter(w => w.contains("Michael"))
results.collect
res9: Array[Array[String]] = Array(Array(Michael, Jordan), Array(Michael, Corleone))
明显的
仅返回不同的值。
val myCities1 = sc.parallelize(List("tokyo","tokyo","paris","sydney"))
val myCities2 = sc.parallelize(List("perth","tokyo","canberra","sydney"))
使用 union 合并结果。
val citiesFromBoth = myCities1.union(myCities2)
仅显示不同的值。
citiesFromBoth.distinct.collect.foreach(println)
sydney
perth
canberra
tokyo
paris
ReduceByKey
用同一个键组合值。使用指定的 reduce 函数。
val pairRDD = sc.parallelize(List(("a", 1), ("b",2), ("c",3), ("a", 30), ("b",25), ("a",20)))
val sumRDD = pairRDD.reduceByKey((x,y) => x+y)
sumRDD.collect
res15: Array[(String, Int)] = Array((b,27), (a,51), (c,3))
键
返回一个只包含键的 RDD。
val myRDD = sc.parallelize(List(("a", "Larry"), ("b", "Curly"), ("c", "Moe")))
val keysRDD = myRDD.keys
keysRDD.collect.foreach(println)
a
b
c
价值观念
返回只包含值的 RDD。
val myRDD = sc.parallelize(List(("a", "Larry"), ("b", "Curly"), ("c", "Moe")))
val valRDD = myRDD.values
valRDD.collect.foreach(println)
Larry
Curly
Moe
内部连接
基于连接谓词返回两个 RDD 中所有元素的 RDD。
val employee = Array((100,"Jim Hernandez"), (101,"Shane King"))
val employeeRDD = sc.parallelize(employee)
val employeeCity = Array((100,"Glendale"), (101,"Burbank"))
val employeeCityRDD = sc.parallelize(employeeCity)
val employeeState = Array((100,"CA"), (101,"CA"), (102,"NY"))
val employeeStateRDD = sc.parallelize(employeeState)
val employeeRecordRDD = employeeRDD.join(employeeCityRDD).join(employeeStateRDD)
employeeRecordRDD.collect.foreach(println)
(100,((Jim Hernandez,Glendale),CA))
(101,((Shane King,Burbank),CA))
RightOuterJoin / LeftOuterJoin
返回右 RDD 中所有元素的 RDD,即使左 RDD 中没有匹配的行。左外连接相当于右外连接,只是列的顺序不同。
val employeeRecordRDD = employeeRDD.join(employeeCityRDD).rightOuterJoin(employeeStateRDD)
employeeRecordRDD.collect.foreach(println)
(100,(Some((Jim Hernandez,Glendale)),CA))
(102,(None,NY))
(101,(Some((Shane King,Burbank)),CA))
联盟
返回包含两个或更多 RDD 组合的 RDD。
val employee2 = Array((103,"Mark Choi","Torrance","CA"), (104,"Janet Reyes","Rolling Hills","CA"))
val employee2RDD = sc.parallelize(employee2)
val employee3 = Array((105,"Lester Cruz","Van Nuys","CA"), (106,"John White","Inglewood","CA"))
val employee3RDD = sc.parallelize(employee3)
employeesRDD.collect.foreach(println)
(103,Mark Choi,Torrance,CA)
(104,Janet Reyes,Rolling Hills,CA)
(105,Lester Cruz,Van Nuys,CA)
(106,John White,Inglewood,CA)
减去
返回仅包含第一个 RDD 中的元素的 RDD。
val listEmployees = Array((103,"Mark Choi","Torrance","CA"), (104,"Janet Reyes","Rolling Hills","CA"),(105,"Lester Cruz","Van Nuys","CA"))
val listEmployeesRDD = sc.parallelize(listEmployees)
val exEmployees = Array((103,"Mark Choi","Torrance","CA"))
val exEmployeesRDD = sc.parallelize(exEmployees)
val currentEmployeesRDD = listEmployeesRDD.subtract(exEmployeesRDD)
currentEmployeesRDD.collect.foreach(println)
(105,Lester Cruz,Van Nuys,CA)
(104,Janet Reyes,Rolling Hills,CA)
联合
联合减少了 RDD 中的分区数量。在大型 RDD 上执行过滤后,您可能需要使用合并。虽然过滤减少了新 RDD 消耗的数据量,但它继承了原始 RDD 的分区数量。如果新的 RDD 比原来的 RDD 小得多,它可能会有成百上千个小分区,这可能会导致性能问题。
当您希望在写入 HDFS 时减少 Spark 生成的文件数量,防止可怕的“小文件”问题时,使用 coalesce 也很有用。每个分区作为单独的文件写入 HDFS。请注意,使用 coalesce 时,您可能会遇到性能问题,因为在写入 HDFS 时,您会有效地降低并行度。如果发生这种情况,请尝试增加分区的数量。这也适用于数据帧,我将在后面讨论。在下面的例子中,我们只写了一个拼花文件给 HDFS。
DF.coalesce(1).write.mode("append").parquet("/user/hive/warehouse/Mytable")
再分
重新分区可以减少或增加 RDD 中的分区数量。减少分区时通常会使用联合,因为它比重新分区更有效。在写入 HDFS 时,增加分区数量可能有助于提高并行度。这也适用于数据帧,我将在后面讨论。在下面的例子中,我们写了 6 个拼花文件给 HDFS。
DF.repartition (6).write.mode("append").parquet("/user/hive/warehouse/Mytable")
Note
合并通常比重新分区快。重新分区将执行完全洗牌,创建新分区并在工作节点之间*均分配数据。通过使用现有分区,联合最大限度地减少了数据移动并避免了完全洗牌。
行动
动作是向驱动程序返回值的 RDD 操作。我列出了一些最常见的动作。有关 RDD 行动的完整列表,请参考在线 Spark 文档。
收集
将 RDD 的全部内容返回给驱动程序。不适合大型数据集。
val myCities = sc.parallelize(List("tokyo","new york","paris","san francisco"))
myCities.collect
res2: Array[String] = Array(tokyo, new york, paris, san francisco)
拿
将 RDD 的子集返回给驱动程序。
val myCities = sc.parallelize(List("tokyo","new york","paris","san francisco"))
myCities.take(2)
res4: Array[String] = Array(tokyo, new york)
数数
返回 RDD 中的项目数。
val myCities = sc.parallelize(List("tokyo","new york","paris","san francisco"))
myCities.count
res3: Long = 4
为每一个
对 RDD 的每一项执行所提供的功能。
val myCities = sc.parallelize(List("tokyo","new york","paris","san francisco"))
myCities.collect.foreach(println)
tokyo
new york
paris
san Francisco
懒惰评估
Spark 支持惰性求值,这对于大数据处理至关重要。Spark 中的所有转换都是延迟计算的。Spark 不会立即执行转换。您可以继续定义更多的转换。当您最终想要最终结果时,您执行一个动作,这将导致转换被执行。
贮藏
默认情况下,每次运行操作时,都会重新执行每个转换。您可以使用 cache 或 persist 方法在内存中缓存 RDD,以避免多次重复执行转换。有几个持久性级别可供选择,如 MEMORY_ONLY、MEMORY_ONLY_SER、MEMORY_AND_DISK、MEMORY_AND_DISK_SER。和仅磁盘。有关缓存的更多细节,请参考 Apache Spark 的在线文档。使用 Alluxio(以前称为 Tachyon)的堆外缓存在第十章中讨论。
Spark SQL、数据集和数据帧 API
虽然目前大多数令人兴奋的事情都集中在涉及非结构化和半结构化数据(视频分析、图像处理和文本挖掘等)的用例上,但大多数实际的数据分析和处理仍然是在结构化的关系数据上完成的。公司的大多数重要商业决策仍然基于对关系数据的分析。
SparkSQL 的开发是为了让 Spark 数据工程师和数据科学更容易处理和分析结构化数据。数据集类似于 RDD,因为它支持强类型,但是在它的内部有一个更高效的引擎。从 Spark 2.0 开始,数据集 API 现在是主要的编程接口。DataFrame 只是一个带有命名列的数据集,非常类似于关系表。Spark SQL 和 DataFrames 一起为处理和分析结构化数据提供了强大的编程接口。这里有一个关于如何使用 DataFrames API 的简单例子。稍后我将更详细地讨论 DataFrames API。
val jsonDF = spark.read.json("/jsondata")
jsonDF.show
+---+------+--------------+-----+------+-----+
|age| city| name|state|userid| zip|
+---+------+--------------+-----+------+-----+
| 35|Frisco| Jonathan West| TX| 200|75034|
| 28|Dallas|Andrea Foreman| TX| 201|75001|
| 69| Plano| Kirsten Jung| TX| 202|75025|
| 52| Allen|Jessica Nguyen| TX| 203|75002|
+---+------+--------------+-----+------+-----+
jsonDF.select ("age", "city").show
+---+------+
|age| city|
+---+------+
| 35|Frisco|
| 28|Dallas|
| 69| Plano|
| 52| Allen|
+---+------+
jsonDF.filter($"userid" < 202).show()
+---+------+--------------+-----+------+-----+
|age| city| name|state|userid| zip|
+---+------+--------------+-----+------+-----+
| 35|Frisco| Jonathan West| TX| 200|75034|
| 28|Dallas|Andrea Foreman| TX| 201|75001|
+---+------+--------------+-----+------+-----+
jsonDF.createOrReplaceTempView("jsonDF")
val uzDF = spark.sql("SELECT userid, zip FROM jsonDF")
uzDF.show
+------+-----+
|userid| zip|
+------+-----+
| 200|75034|
| 201|75001|
| 202|75025|
| 203|75002|
+------+-----+
Note
Spark 2.0 中统一了数据帧和数据集 API。DataFrame 现在只是行数据集的类型别名,其中行是通用的非类型化对象。相比之下,Dataset 是强类型对象 Dataset[T]的集合。Scala 支持强类型和非类型 API,而在 Java 中,Dataset[T]是主要的抽象。DataFrames 是 R 和 Python 的主要编程接口,因为它缺乏对编译时类型安全的支持。
Spark 数据源
数据处理中一些最常见的任务是读取和写入不同的数据源。在接下来的几页中,我提供了几个例子。我在第六章中介绍了 Spark 和 Kudu integration。
战斗支援车
Spark 为您提供了从 CSV 文件中读取数据的不同方法。您可以先将数据读入 RDD,然后将其转换为 DataFrame。
val dataRDD = sc.textFile("/sparkdata/customerdata.csv")
val parsedRDD = dataRDD.map{_.split(",")}
case class CustomerData(customerid: Int, name: String, city: String, state: String, zip: String)
val dataDF = parsedRDD.map{ a => CustomerData (a(0).toInt, a(1).toString, a(2).toString,a(3).toString,a(4).toString) }.toDF
您也可以使用 Databricks CSV 包。该方法直接读取数据帧中的数据。
spark-shell --packages com.databricks:spark-csv_2.10:1.5.0
val dataDF = sqlContext.read.format("csv")
.option("header", "true")
.option("inferSchema", "true")
.load("/sparkdata/customerdata.csv")
从 Spark 2.0 开始,CSV 连接器已经内置,因此无需使用 Databrick 的第三方包。
val dataDF = spark.read
.option("header", "true")
.option("inferSchema", "true")
.csv("/sparkdata/customerdata.csv")
可扩展置标语言
Databricks 有一个 Spark xml 包,可以轻松读取 xml 数据。
cat users.xml
<userid>100</userid><name>Wendell Ryan</name><city>San Diego</city><state>CA</state><zip>92102</zip>
<userid>101</userid><name>Alicia Thompson</name><city>Berkeley</city><state>CA</state><zip>94705</zip>
<userid>102</userid><name>Felipe Drummond</name><city>Palo Alto</city><state>CA</state><zip>94301</zip>
<userid>103</userid><name>Teresa Levine</name><city>Walnut Creek</city><state>CA</state><zip>94507</zip>
hadoop fs -mkdir /xmldata
hadoop fs -put users.xml /xmldata
spark-shell --packages com.databricks:spark-xml:2.10:0.4.1
使用 Spark XML 创建一个数据帧。在本例中,我们指定了行标记和 XML 文件所在的 HDFS 路径。
val xmlDF = sqlContext.read.format("com.databricks.spark.xml").option("rowTag", "user").load("/xmldata/");
xmlDF: org.apache.spark.sql.DataFrame = [city: string, name: string, state: string, userid: bigint, zip: bigint]
我们也来看看数据。
xmlDF.show
+------------+---------------+-----+------+-----+
| city| name|state|userid| zip|
+------------+---------------+-----+------+-----+
| San Diego| Wendell Ryan| CA| 100|92102|
| Berkeley|Alicia Thompson| CA| 101|94705|
| Palo Alto|Felipe Drummond| CA| 102|94301|
|Walnut Creek| Teresa Levine| CA| 103|94507|
+------------+---------------+-----+------+-----+
数据
我们将创建一个 JSON 文件作为这个例子的样本数据。确保该文件位于 HDFS 名为/jsondata 的文件夹中。
cat users.json
{"userid": 200, "name": "Jonathan West", "city":"Frisco", "state":"TX", "zip": "75034", "age":35}
{"userid": 201, "name": "Andrea Foreman", "city":"Dallas", "state":"TX", "zip": "75001", "age":28}
{"userid": 202, "name": "Kirsten Jung", "city":"Plano", "state":"TX", "zip": "75025", "age":69}
{"userid": 203, "name": "Jessica Nguyen", "city":"Allen", "state":"TX", "zip": "75002", "age":52}
从 JSON 文件创建一个数据帧。
val jsonDF = sqlContext.read.json("/jsondata")
jsonDF: org.apache.spark.sql.DataFrame = [age: bigint, city: string, name: string, state: string, userid: bigint, zip: string]
检查日期
jsonDF.show
+---+------+--------------+-----+------+-----+
|age| city| name|state|userid| zip|
+---+------+--------------+-----+------+-----+
| 35|Frisco| Jonathan West| TX| 200|75034|
| 28|Dallas|Andrea Foreman| TX| 201|75001|
| 69| Plano| Kirsten Jung| TX| 202|75025|
| 52| Allen|Jessica Nguyen| TX| 203|75002|
+---+------+--------------+-----+------+-----+
使用 JDBC 的关系数据库
我们在这个例子中使用 MySQL,但是也支持其他关系数据库,例如 Oracle、SQL Server、Teradata 和 PostgreSQL 等等。只要关系数据库有 JDBC/ODBC 驱动程序,就应该可以从 Spark 访问它。性能取决于您的 JDBC/ODBC 驱动程序对批处理操作的支持。请查看您的 JDBC 驱动程序文档以了解更多详细信息。
mysql -u root -pmypassword
create databases salesdb;
use salesdb;
create table customers (
customerid INT,
name VARCHAR(100),
city VARCHAR(100),
state CHAR(3),
zip CHAR(5));
spark-shell --driver-class-path mysql-connector-java-5.1.40-bin.jar --jars mysql-connector-java-5.1.40-bin.jar
Note
在某些版本的 Spark - jars 中,没有在驱动程序的类路径中添加 JAR。 vii 建议您将 JDBC 驱动程序包含在您的- jars 和 Spark 类路径中。VIII
启动 Spark 壳。请注意,我必须将 MySQL 驱动程序作为参数包含在–driver-class-path 和–jar 中。在较新版本的 Spark 中,您可能不需要这样做。
将 csv 文件读入数据帧
val dataRDD = sc.textFile("/home/hadoop/test.csv")
val parsedRDD = dataRDD.map{_.split(",")}
case class CustomerData(customerid: Int, name: String, city: String, state: String, zip: String)
val dataDF = parsedRDD.map{ a => CustomerData (a(0).toInt, a(1).toString, a(2).toString,a(3).toString,a(4).toString)}.toDF
将数据帧注册为临时表,以便我们可以对其运行 SQL 查询。在 Spark 2.x 中,使用 createOrReplaceTempView。
dataDF.registerTempTable("dataDF")
让我们设置连接属性。
val jdbcUsername = "myuser"
val jdbcPassword = "mypass"
val jdbcHostname = "10.0.1.112"
val jdbcPort = 3306
val jdbcDatabase ="salesdb"
val jdbcrewriteBatchedStatements = "true"
val jdbcUrl = s"jdbc:mysql://${jdbcHostname}:${jdbcPort}/${jdbcDatabase}?user=${jdbcUsername}&password=${jdbcPassword}&rewriteBatchedStatements=${jdbcrewriteBatchedStatements}"
val connectionProperties = new java.util.Properties()
这将允许我们指定正确的保存模式——追加、覆盖等。
import org.apache.spark.sql.SaveMode
将 SELECT 语句返回的数据插入到 MySQL salesdb 数据库中存储的 customer 表中。
sqlContext.sql("select * from dataDF").write.mode(SaveMode.Append).jdbc(jdbcUrl, "customers", connectionProperties)
让我们用 JDBC 读一个表格。让我们用一些测试数据填充 MySQL 中的 users 表。确保 salesdb 数据库中存在 users 表。
mysql -u root -pmypassword
use salesdb;
describe users;
+--------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+-------+
| userid | bigint(20) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| city | varchar(100) | YES | | NULL | |
| state | char(3) | YES | | NULL | |
| zip | char(5) | YES | | NULL | |
| age | tinyint(4) | YES | | NULL | |
+--------+--------------+------+-----+---------+-------+
select * from users;
Empty set (0.00 sec)
insert into users values (300,'Fred Stevens','Torrance','CA',90503,23);
insert into users values (301,'Nancy Gibbs','Valencia','CA',91354,49);
insert into users values (302,'Randy Park','Manhattan Beach','CA',90267,21);
insert into users values (303,'Victoria Loma','Rolling Hills','CA',90274,75);
select * from users;
+--------+---------------+-----------------+-------+-------+------+
| userid | name | city | state | zip | age |
+--------+---------------+-----------------+-------+-------+------+
| 300 | Fred Stevens | Torrance | CA | 90503 | 23 |
| 301 | Nancy Gibbs | Valencia | CA | 91354 | 49 |
| 302 | Randy Park | Manhattan Beach | CA | 90267 | 21 |
| 303 | Victoria Loma | Rolling Hills | CA | 90274 | 75 |
+--------+---------------+-----------------+-------+-------+------+
spark-shell --driver-class-path mysql-connector-java-5.1.40-bin.jar --jars mysql-connector-java-5.1.40-bin.jar
让我们设置 jdbc url 和连接属性。
val jdbcURL = s"jdbc:mysql://10.0.1.101:3306/salesdb?user=myuser&password=mypass"
val connectionProperties = new java.util.Properties()
我们可以从整个表中创建一个数据帧。
val mysqlDF = sqlContext.read.jdbc(jdbcURL, "users", connectionProperties)
mysqlDF.show
+------+-------------+---------------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+-------------+---------------+-----+-----+---+
| 300| Fred Stevens| Torrance| CA|90503| 23|
| 301| Nancy Gibbs| Valencia| CA|91354| 49|
| 302| Randy Park|Manhattan Beach| CA|90267| 21|
| 303|Victoria Loma| Rolling Hills| CA|90274| 75|
+------+-------------+---------------+-----+-----+---+
镶木地板
在拼花地板上读写很简单。
val employeeDF = spark.read.load("/sparkdata/employees.parquet")
employeeDF.select("id","firstname","lastname","salary").write.format("parquet").save("/sparkdata/myData.parquet")
您可以直接在 Parquet 文件上运行 SELECT 语句。
val myDF = spark.sql("SELECT * FROM parquet.`/sparkdata/myData.parquet`")
巴什
从 Spark 访问 HBase 有不同的方法。如前所述,Scala 可以访问所有 Java 库,包括 HBase 客户端 API。这并不是从 Spark 访问 HBase 的首选方式,但一些开发人员可能会发现它们很方便。另一种访问 HBase 的方式是通过 Impala,我将在第六章中讨论。
Note
HBase 上的 Spark 是从 Spark 访问 HBase 的首选方式。然而,在撰写本文时,它只能在 Spark 1.x 上运行。HBase 上的 Spark 在 Spark 2.x 上不受支持。
您可以使用 SaveAsHadoopDataset 将数据写入 HBase。
启动 HBase shell。创建一个 HBase 表并用测试数据填充它。
hbase shell
create 'users', 'cf1'
启动 Spark 壳。
spark-shell
导入所有必需的包。
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.{HBaseConfiguration, HTableDescriptor}
import org.apache.hadoop.hbase.client.HBaseAdmin
import org.apache.hadoop.hbase.mapreduce.TableInputFormat
import org.apache.hadoop.hbase.HColumnDescriptor
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
val hconf = HBaseConfiguration.create()
val jobConf = new JobConf(hconf, this.getClass)
jobConf.setOutputFormat(classOf[TableOutputFormat])
jobConf.set(TableOutputFormat.OUTPUT_TABLE,"users")
val num = sc.parallelize(List(1,2,3,4,5,6))
val theRDD = num.filter.map(x=>{
val rowkey = "row" + x
val put = new Put(Bytes.toBytes(rowkey))
put.add(Bytes.toBytes("cf1"), Bytes.toBytes("fname"), Bytes.toBytes("my fname" + x))
(new ImmutableBytesWritable, put)
})
theRDD.saveAsHadoopDataset(jobConf)
您还可以使用 Spark 中的 HBase 客户端 API 来读写 HBase 中的数据。
启动 HBase shell。创建另一个 HBase 表,并用测试数据填充它。
hbase shell
create 'employees', 'cf1'
put 'employees','400','cf1:name', 'Patrick Montalban'
put 'employees','400','cf1:city', 'Los Angeles'
put 'employees','400','cf1:state', 'CA'
put 'employees','400','cf1:zip', '90010'
put 'employees','400','cf1:age', '71'
put 'employees','401','cf1:name', 'Jillian Collins'
put 'employees','401','cf1:city', 'Santa Monica'
put 'employees','401','cf1:state', 'CA'
put 'employees','401','cf1:zip', '90402'
put 'employees','401','cf1:age', '45'
put 'employees','402','cf1:name', 'Robert Sarkisian'
put 'employees','402','cf1:city', 'Glendale'
put 'employees','402','cf1:state', 'CA'
put 'employees','402','cf1:zip', '91204'
put 'employees','402','cf1:age', '29'
put 'employees','403','cf1:name', 'Warren Porcaro'
put 'employees','403','cf1:city', 'Burbank'
put 'employees','403','cf1:state', 'CA'
put 'employees','403','cf1:zip', '91523'
put 'employees','403','cf1:age', '62'
让我们验证数据是否成功地插入到我们的 HBase 表中。
scan 'employees'
ROW COLUMN+CELL
400 column=cf1:age, timestamp=1493105325812, value=71
400 column=cf1:city, timestamp=1493105325691, value=Los Angeles
400 column=cf1:name, timestamp=1493105325644, value=Patrick Montalban
400 column=cf1:state, timestamp=1493105325738, value=CA
400 column=cf1:zip, timestamp=1493105325789, value=90010
401 column=cf1:age, timestamp=1493105334417, value=45
401 column=cf1:city, timestamp=1493105333126, value=Santa Monica
401 column=cf1:name, timestamp=1493105333050, value=Jillian Collins
401 column=cf1:state, timestamp=1493105333145, value=CA
401 column=cf1:zip, timestamp=1493105333165, value=90402
402 column=cf1:age, timestamp=1493105346254, value=29
402 column=cf1:city, timestamp=1493105345053, value=Glendale
402 column=cf1:name, timestamp=1493105344979, value=Robert Sarkisian
402 column=cf1:state, timestamp=1493105345074, value=CA
402 column=cf1:zip, timestamp=1493105345093, value=91204
403 column=cf1:age, timestamp=1493105353650, value=62
403 column=cf1:city, timestamp=1493105352467, value=Burbank
403 column=cf1:name, timestamp=1493105352445, value=Warren Porcaro
403 column=cf1:state, timestamp=1493105352513, value=CA
403 column=cf1:zip, timestamp=1493105352549, value=91523
启动 Spark 壳。
spark-shell
导入所有必需的包。
val configuration = HBaseConfiguration.create()
指定 HBase 表和行键。
val table = new HTable(configuration, "employees");
val g = new Get(Bytes.toBytes("401"))
val result = table.get(g);
从表中提取值。
val val2 = result.getValue(Bytes.toBytes("cf1"),Bytes.toBytes("name"));
val val3 = result.getValue(Bytes.toBytes("cf1"),Bytes.toBytes("city"));
val val4 = result.getValue(Bytes.toBytes("cf1"),Bytes.toBytes("state"));
val val5 = result.getValue(Bytes.toBytes("cf1"),Bytes.toBytes("zip"));
val val6 = result.getValue(Bytes.toBytes("cf1"),Bytes.toBytes("age"));
将这些值转换为适当的数据类型。
val id = Bytes.toString(result.getRow())
val name = Bytes.toString(val2);
val city = Bytes.toString(val3);
val state = Bytes.toString(val4);
val zip = Bytes.toString(val5);
val age = Bytes.toShort(val6);
打印数值。
println("employee id: " + id + "name: " + name + "city: " + city + "state: " + state + "zip: " + zip + "age: " + age);
employee id: 401 name: Jillian Collins city: Santa Monica state: CA zip: 90402 age: 13365
让我们使用 HBase API 写入 HBase。
val configuration = HBaseConfiguration.create()
val table = new HTable(configuration, "employees");
指定新的行键。
val p = new Put(new String("404").getBytes());
用新值填充单元格。
p.add("cf1".getBytes(), "name".getBytes(), new String("Denise Shulman").getBytes());
p.add("cf1".getBytes(), "city".getBytes(), new String("La Jolla").getBytes());
p.add("cf1".getBytes(), "state".getBytes(), new String("CA").getBytes());
p.add("cf1".getBytes(), "zip".getBytes(), new String("92093").getBytes());
p.add("cf1".getBytes(), "age".getBytes(), new String("56").getBytes());
写入 HBase 表。
table.put(p);
table.close();
确认值已成功插入 HBase 表中。
启动 HBase shell。
hbase shell
scan 'employees'
ROW COLUMN+CELL
400 column=cf1:age, timestamp=1493105325812, value=71
400 column=cf1:city, timestamp=1493105325691, value=Los Angeles
400 column=cf1:name, timestamp=1493105325644, value=Patrick Montalban
400 column=cf1:state, timestamp=1493105325738, value=CA
400 column=cf1:zip, timestamp=1493105325789, value=90010
401 column=cf1:age, timestamp=1493105334417, value=45
401 column=cf1:city, timestamp=1493105333126, value=Santa Monica
401 column=cf1:name, timestamp=1493105333050, value=Jillian Collins
401 column=cf1:state, timestamp=1493105333145, value=CA
401 column=cf1:zip, timestamp=1493105333165, value=90402
402 column=cf1:age, timestamp=1493105346254, value=29
402 column=cf1:city, timestamp=1493105345053, value=Glendale
402 column=cf1:name, timestamp=1493105344979, value=Robert Sarkisian
402 column=cf1:state, timestamp=1493105345074, value=CA
402 column=cf1:zip, timestamp=1493105345093, value=91204
403 column=cf1:age, timestamp=1493105353650, value=62
403 column=cf1:city, timestamp=1493105352467, value=Burbank
403 column=cf1:name, timestamp=1493105352445, value=Warren Porcaro
403 column=cf1:state, timestamp=1493105352513, value=CA
403 column=cf1:zip, timestamp=1493105352549, value=91523
404 column=cf1:age, timestamp=1493123890714, value=56
404 column=cf1:city, timestamp=1493123890714, value=La Jolla
404 column=cf1:name, timestamp=1493123890714, value=Denise Shulman
404 column=cf1:state, timestamp=1493123890714, value=CA
404 column=cf1:zip, timestamp=1493123890714, value=92093
亚马逊 S3
亚马逊 S3 是一个流行的对象存储,经常被用作临时集群的数据存储。它还是备份和冷数据的经济高效的存储方式。从 S3 读取数据就像从 HDFS 或任何其他文件系统读取数据一样。
阅读来自亚马逊 S3 的 CSV 文件。请确保您已经配置了 S3 凭据。
val myCSV = sc.textFile("s3a://mydata/customers.csv")
将 CSV 数据映射到 RDD。
import org.apache.spark.sql.Row
val myRDD = myCSV.map(_.split(',')).map(e ⇒ Row(r(0).trim.toInt, r(1), r(2).trim.toInt, r(3)))
创建一个模式。
import org.apache.spark.sql.types.{StructType, StructField, StringType, IntegerType};
val mySchema = StructType(Array(
StructField("customerid",IntegerType,false),
StructField("customername",StringType,false),
StructField("age",IntegerType,false),
StructField("city",StringType,false)))
val myDF = sqlContext.createDataFrame(myRDD, mySchema)
使用
Solr 是一个流行的搜索*台,提供全文搜索和实时索引功能。您可以使用 SolrJ 从 Spark 与 Solr 进行交互。 ix
import java.net.MalformedURLException;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocumentList;
val solr = new HttpSolrServer("http://master02:8983/solr/mycollection");
val query = new SolrQuery();
query.setQuery("*:*");
query.addFilterQuery("userid:3");
query.setFields("userid","name","age","city");
query.setStart(0);
query.set("defType", "edismax");
val response = solr.query(query);
val results = response.getResults();
println(results);
从 Spark 访问 Solr 集合的一个更好的方法是使用 spark-solr 包。Lucidworks 启动了 spark-solr 项目来提供 Spark-Solr 集成。与 solrJ 相比,使用 spark-solr 要简单和强大得多,它允许你从 Solr 集合中创建数据帧,并使用 SparkSQL 与它们进行交互。
首先从 spark-shell 导入 jar 文件。您可以从 Lucidworks 的网站下载 jar 文件。
spark-shell --jars spark-solr-3.0.1-shaded.jar
指定集合和连接信息。
val myOptions = Map( "collection" -> "mycollection","zkhost" -> "{ master02:8983/solr}")
创建一个数据帧。
val solrDF = spark.read.format("solr")
.options(myOptions)
.load
微软优越试算表
我遇到过几个关于如何从 Spark 访问 Excel 工作表的请求。虽然这不是我通常会做的事情,但使用 Excel 是几乎每个企业 IT 环境中的现实。
一家名为 Crealytics 的公司开发了一个用于与 Excel 交互的 Spark 插件。该库需要 Spark 2.x。可以使用- packages 命令行选项添加该包。Xi
spark-shell --packages com.crealytics:spark-excel_2.11:0.9.12
从 Excel 工作表创建数据帧。
val ExcelDF = spark.read
.format("com.crealytics.spark.excel")
.option("sheetName", "sheet1")
.option("useHeader", "true")
.option("inferSchema", "true")
.option("treatEmptyValuesAsNulls", "true")
.load("budget.xlsx")
将数据帧写入 Excel 工作表。
ExcelDF2.write
.format("com.crealytics.spark.excel")
.option("sheetName", "sheet1")
.option("useHeader", "true")
.mode("overwrite")
.save("budget2.xlsx")
你可以在他们的 github 页面上找到更多的细节:github.com/crealytics.
安全 FTP
从 SFTP 下载文件并将数据帧写入 SFTP 的服务器也是一个流行的请求。SpringML 提供了一个 Spark SFTP 连接器库。该库需要 Spark 2.x 并利用 jsch,这是 SSH2 的一个 Java 实现。对 SFTP 服务器的读写将作为单个进程执行。
可以使用- packages 命令行选项添加软件包。XII
spark-shell --packages com.springml:spark-sftp_2.11:1.1.1
从 SFTP 服务器中的文件创建一个数据帧。
val SftpDF = spark.read.
format("com.springml.spark.sftp").
option("host", "sftpserver.com").
option("username", "myusername").
option("password", "mypassword").
option("inferSchema", "true").
option("fileType", "csv").
option("delimiter", ",").
load("/myftp/myfile.csv")
将数据帧作为 CSV 文件写入 FTP 服务器。
SftpDF2.write.
format("com.springml.spark.sftp").
option("host", "sftpserver.com").
option("username", "myusername").
option("password", "mypassword").
option("fileType", "csv").
option("delimiter", ",").
save("/myftp/myfile.csv")
你可以在他们的 github 页面上找到更多的细节:github.com/springml/spark-sftp.
Spark MLlib(基于数据帧的 API)
机器学习是 Spark 的主要应用之一。基于数据帧的 API (Spark ML 或 Spark ML 管道)现在是 Spark 的主要 API。基于 RDD 的 API (Spark MLlib)正在进入维护模式。我们不会讨论旧的基于 RDD 的 API。以前,基于数据帧的 API 被非正式地称为 Spark ML 和 Spark ML 管道(spark.ml 包),以区别于基于 RDD 的 API,后者是基于原始 spark.mllib 包命名的。一旦基于数据帧的 API 达到特性对等,基于 RDD 的 API 在 Spark 2.3 中将被弃用。 xiii 基于 RDD 的 API 将在 Spark 3.0 中被移除。目前,Spark MLlib 包含这两种 API。
基于 DataFrames 的 API 比基于 RDD 的 API 更快更容易使用,允许用户使用 SQL 并利用 Catalyst 和钨优化。基于 DataFrames 的 API 通过提供更高级别的抽象来表示类似于关系数据库表的表格数据,使转换功能变得容易,这使它成为实现管道的自然选择。
在典型的机器学习流水线中执行的大多数操作是特征转换。如图 5-3 所示,DataFrames 便于执行特征转换。 xiv 记号赋予器将文本分解成单词包,将单词附加到输出的第二数据帧中。TF-IDF 将第二个数据帧作为输入,将单词包转换为特征向量,并将它们添加到第三个数据帧中。
图 5-3
Example Feature Transformation in a typical Spark ML pipeline
管道
流水线只是创建机器学习工作流的一系列相连的阶段。一个阶段可以是一个转换器或估计器。
变压器
转换器将一个数据帧作为输入,并输出一个新的数据帧,其中附加了附加列。新数据帧包括来自输入数据帧的列和附加列。
估计量
估计是一种机器学习算法,适合训练数据的模型。估计器估计器接受训练数据并产生机器学习模型。
ParamGridBuilder
ParamGridBuilder 用于构建参数网格。CrossValidator 执行网格搜索,并用参数网格中用户指定的超参数组合来训练模型。
交叉验证器
CrossValidator 交叉评估拟合的机器学习模型,并通过尝试用用户指定的超参数组合拟合底层估计器来输出最佳模型。
求值程序
评估者计算你的机器学习模型的性能。评估器输出精度或召回率等指标来衡量拟合模型的表现。
例子
让我们来看一个例子。我们将使用来自 UCI 机器学习库中的心脏病数据集【XV】来预测心脏病的存在。这些数据是由罗伯特德特拉诺,医学博士,博士,从弗吉尼亚州医学中心,长滩和克利夫兰诊所基金会收集的。历史上,克利夫兰数据集一直是众多研究的主题,因此我们将使用该数据集。原始数据集有 76 个属性,但其中只有 14 个是 ML 研究人员传统使用的(表 5-2 )。我们将简单地执行二项式分类,并确定患者是否患有心脏病。
表 5-2
Cleveland Heart Disease Data Set Attribute Information
| 属性 | 描述 | | :-- | :-- | | 年龄 | 年龄 | | 性 | 性 | | 丙酸纤维素 | 胸痛型 | | treatbps | 静息血压 | | 胆固醇 | 血清胆固醇(毫克/分升) | | 前沿系统 | 空腹血糖> 120 毫克/分升 | | 尊重 | 静息心电图结果 | | 塔尔巴赫 | 达到最大心率 | | 考试 | 运动诱发的心绞痛 | | 旧峰 | 相对于静息运动诱发的 ST 段压低 | | 倾斜 | 运动 ST 段峰值的斜率 | | 大约 | 荧光镜染色的主要血管数量(0-3) | | 塔尔 | 唐松草压力测试结果 | | 数字 | 预测属性——心脏病的诊断 |我们开始吧。开始之前,将列名添加到 CSV 文件中。我们需要下载文件并拷贝到 HDFS。
wget http://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/cleveland.data
head -n 10 processed.cleveland.data
63.0,1.0,1.0,145.0,233.0,1.0,2.0,150.0,0.0,2.3,3.0,0.0,6.0,0
67.0,1.0,4.0,160.0,286.0,0.0,2.0,108.0,1.0,1.5,2.0,3.0,3.0,2
67.0,1.0,4.0,120.0,229.0,0.0,2.0,129.0,1.0,2.6,2.0,2.0,7.0,1
37.0,1.0,3.0,130.0,250.0,0.0,0.0,187.0,0.0,3.5,3.0,0.0,3.0,0
41.0,0.0,2.0,130.0,204.0,0.0,2.0,172.0,0.0,1.4,1.0,0.0,3.0,0
56.0,1.0,2.0,120.0,236.0,0.0,0.0,178.0,0.0,0.8,1.0,0.0,3.0,0
62.0,0.0,4.0,140.0,268.0,0.0,2.0,160.0,0.0,3.6,3.0,2.0,3.0,3
57.0,0.0,4.0,120.0,354.0,0.0,0.0,163.0,1.0,0.6,1.0,0.0,3.0,0
63.0,1.0,4.0,130.0,254.0,0.0,2.0,147.0,0.0,1.4,2.0,1.0,7.0,2
53.0,1.0,4.0,140.0,203.0,1.0,2.0,155.0,1.0,3.1,3.0,0.0,7.0,1
hadoop fs -put processed.cleveland.data /tmp/data
然后使用 spark-shell 通过 Spark MLlib 交互式地创建我们的模型,如清单 5-2 所示。
spark-shell --packages com.databricks:spark-csv_2.10:1.5.0
import org.apache.spark._
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SQLContext
import org.apache.spark.sql.functions._
import org.apache.spark.sql.types._
import org.apache.spark.sql._
import org.apache.spark.ml.classification.RandomForestClassifier
import org.apache.spark.ml.evaluation.BinaryClassificationEvaluator
import org.apache.spark.ml.feature.StringIndexer
import org.apache.spark.ml.feature.VectorAssembler
import org.apache.spark.ml.tuning.{ParamGridBuilder, CrossValidator}
import org.apache.spark.ml.{Pipeline, PipelineStage}
import org.apache.spark.mllib.evaluation.RegressionMetrics
import org.apache.spark.ml.param.ParamMap
import org.apache.kudu.spark.kudu._
val dataDF = sqlContext.read.format("csv")
.option("header", "true")
.option("inferSchema", "true")
.load("/tmp/data/processed.cleveland.data")
dataDF.printSchema
root
|-- id: string (nullable = false)
|-- age: float (nullable = true)
|-- sex: float (nullable = true)
|-- cp: float (nullable = true)
|-- trestbps: float (nullable = true)
|-- chol: float (nullable = true)
|-- fbs: float (nullable = true)
|-- restecg: float (nullable = true)
|-- thalach: float (nullable = true)
|-- exang: float (nullable = true)
|-- oldpeak: float (nullable = true)
|-- slope: float (nullable = true)
|-- ca: float (nullable = true)
|-- thal: float (nullable = true)
|-- num: float (nullable = true)
val myFeatures = Array("age", "sex", "cp", "trestbps", "chol", "fbs",
"restecg", "thalach", "exang", "oldpeak", "slope",
"ca", "thal", "num")
val myAssembler = new VectorAssembler().setInputCols(myFeatures).setOutputCol("features")
val dataDF2 = myAssembler.transform(dataDF)
val myLabelIndexer = new StringIndexer().setInputCol("num").setOutputCol("label")
val dataDF3 = mylabelIndexer.fit(dataDF2).transform(dataDF2)
val dataDF4 = dataDF3.where(dataDF3("ca").isNotNull).where(dataDF3("thal").isNotNull).where(dataDF3("num").isNotNull)
val Array(trainingData, testData) = dataDF4.randomSplit(Array(0.8, 0.2), 101)
val myRFclassifier = new RandomForestClassifier().setFeatureSubsetStrategy("auto").setSeed(101)
val myEvaluator = new BinaryClassificationEvaluator().setLabelCol("label")
val myParamGrid = new ParamGridBuilder()
.addGrid(myRFclassifier.maxBins, Array(10, 20, 30))
.addGrid(myRFclassifier.maxDepth, Array(5, 10, 15))
.addGrid(myRFclassifier.numTrees, Array(20, 30, 40))
.addGrid(myRGclassifier.impurity, Array("gini", "entropy"))
.build()
val myPipeline = new Pipeline().setStages(Array(myRFclassifier))
val myCV = new CrosValidator()
.setEstimator(myPipeline)
.setEvaluator(myEvaluator)
.setEstimatorParamMaps(myParamGrid)
.setNumFolds(3)
Listing 5-2Performing binary classification using Random Forest
我们现在可以拟合模型了
val myCrossValidatorModel = myCV.fit(trainingData)
我们来评价一下模型。
val myEvaluatorParamMap = ParamMap(myEvaluator.metricName -> "areaUnderROC")
val aucTrainingData = myEvaluator.evaluate(CrossValidatorPrediction, myEvaluatorParamMap)
你现在可以根据我们的数据做一些预测。
val myCrossValidatorPrediction = myCrossValidatorModel.transform(testData)
Spark MLlib 提供了构建管道、特征化和流行的机器学习算法的功能,用于回归、分类、聚类和协作过滤。Sandy Ryza、Uri Laserson、Sean Owen 和 Josh Wills (O'Reilly,2017 年)的《Spark 高级分析第二版》对 Spark 的机器学习进行了更深入的处理。我们将在第六章中使用 Kudu 作为功能商店。
图形 x
Spark 包括一个名为 GraphX 的图形处理框架。有一个名为 GraphFrames 的基于 DataFrames 的独立包。GraphFrames 目前不是核心 Apache Spark 的一部分。在撰写本文时,GraphX 和 GraphFrames 仍被认为是不成熟的,不被 Cloudera Enterprise 支持。 xvi 我不会在本书中涉及它们,但可以随意访问 Spark 的在线文档了解更多细节。
Spark 流
我在第六章中介绍了 Spark 流。Spark 2.0 包括一个新的流处理框架,称为结构化流,这是一个构建在 Spark SQL 之上的高级流 API。撰写本文时,Cloudera 还不支持结构化流。XVII
Spark 上的蜂巢
Cloudera 支持 Hive on Spark,以实现更快的批处理。早期基准测试显示,在 MapReduce 上,性能比 Hive *均快 3 倍。 xviii 对于那些希望利用 Spark 的性能而无需学习 Scala 或 Python 的组织来说,Hive on Spark 非常有用。由于 HiveQL 查询的数量和复杂性,有些人可能会发现重构数据处理管道并不容易。Hive for Spark 非常适合这些场景。
Spark 1.x vs Spark 2.x
尽管大量代码仍然在 Spark 1.x 上运行,但现在您的大部分开发应该在 Spark 2.x 上进行。Spark 2.x API 的大部分与 1.x 相似,但 2.x 中有一些改变破坏了 API 兼容性。Spark 2 不兼容 Scala 2.10 仅支持 Scala 2.11。JDK 8 也是 Spark 2.2 的一个要求。更多细节请参考 Spark 的在线文档。
监控和配置
有几个工具可以用来监控和配置 Apache Spark。Cloudera Manager 是 Cloudera Enterprise 事实上的管理工具。Spark 还包括系统管理和监控功能。
Cloudera 经理
Cloudera Manager 是 Cloudera Enterprise 附带的集群管理工具。您可以使用 Cloudera Manager 执行各种管理任务,例如更新配置(图 5-4 )和监控其性能(图 5-5 )。
图 5-5
Monitoring Spark Jobs
图 5-4
Using Cloudera Manager to configure Spark
Spark Web 用户界面
Spark 提供了几种监控 Spark 应用程序的方法。对于当前正在运行的 Spark 应用程序,您可以在端口 4040 上访问其性能信息。如果在同一节点上运行多个作业,您可以通过端口 4041、4042 等访问 web 用户界面。
Spark 历史服务器提供关于已经完成执行的 Spark 应用程序的详细信息。Spark 历史服务器可以通过端口 18088 访问(图 5-6 )。可以查看详细的性能指标(图 5-7 和图 5-8 )和关于 Spark 环境的信息(图 5-9 )以帮助监控和故障排除。
图 5-9
Information about the current Spark environment
图 5-8
Performance metrics on Spark executors
图 5-7
Detailed performance information about a particular Spark job
图 5-6
Spark History Server
摘要
Apache Spark 已经取代 MapReduce 成为事实上的大数据处理框架。数据工程师和科学家欣赏 Spark 简单易用的 API、处理多种工作负载的能力以及快速的性能。它对 SparkSQL、Dataset 和 DataFrame APIs 的关注是受欢迎的改进,使 Spark 更容易访问和使用。Spark 是 Kudu 的理想数据处理引擎。我在第六章讨论 Spark 和 Kudu 集成。
参考
- 阿帕奇 Spark《Spark 概览》,阿帕奇 Spark,2018,
https://spark.apache.org/docs/2.2.0/
- “阿帕奇软件基金会”Apache 软件基金会宣布 Apache Spark 为顶级项目,“Apache 软件基金会,2018,
https://blogs.apache.org/foundation/entry/the_apache_software_foundation_announces50
- 阿帕奇 Spark《阿帕奇星火新闻》,阿帕奇星火,2018,
https://spark.apache.org/news/
- 马泰·扎哈里亚;“我是 Matei Zaharia,Spark 的创始人,Databricks 的首席技术官。AMA!,“Reddit,2018,
https://www.reddit.com/r/IAmA/comments/31bkue/im_matei_zaharia_creator_of_spark_and_cto_at/?st=j1svbrx9&sh=a8b9698e
- 数据块;“什么是阿帕奇 Spark?,“Databricks,2018,
https://databricks.com/spark/about
- 朱尔斯·丹吉;《如何在 Apache Spark 2.0 中使用 SparkSession》,Databricks,2018,
https://databricks.com/blog/2016/08/15/how-to-use-sparksession-in-apache-spark-2-0.html
- 霍尔登·卡劳,雷切尔·沃伦;“高性能 Spark”,奥莱利,2017 年 6 月,
https://www.safaribooksonline.com/library/view/high-performance-spark/9781491943199/
- 阿帕奇 Spark《JDBC 到其他数据库》,阿帕奇 Spark,2018,
https://spark.apache.org/docs/latest/sql-programming-guide.html#jdbc-to-other-databases
- 阿帕奇 Lucene《使用 SolrJ》,阿帕奇·卢斯,2018,
https://lucene.apache.org/solr/guide/6_6/using-solrj.html
- Lucidworks“从 Solr 中读取数据作为 Spark RDD 并使用 SolrJ 将 Spark 中的对象索引到 Solr 中的工具”,Lucidworks,2018,
https://github.com/lucidworks/spark-solr
- Crealytics“一个通过 Apache POI 读取 Excel 文件的 Spark 插件”,Crealytics,2018,
https://github.com/crealytics/spark-excel
- SpringML《星火 SFTP 连接器库》,SpringML,2018,
https://github.com/springml/spark-sftp
- 阿帕奇 Spark《机器学习库(MLlib)指南》,Apache Spark,2018,
https://spark.apache.org/docs/latest/ml-guide.html
- 孟祥瑞、约瑟夫·布拉德利、埃文·斯帕克斯和希瓦拉姆·文卡塔拉曼;《ML Pipelines:ML lib 的一种新的高层 API》,Databricks,2018,
https://databricks.com/blog/2015/01/07/ml-pipelines-a-new-high-level-api-for-mllib.html
- 大卫啊哈;《心脏病数据集》,UCI 机器学习资源库,1988 年,
https://archive.ics.uci.edu/ml/datasets/heart%2BDisease
- Cloudera“不支持 GraphX”,Cloudera,2018,
https://www.cloudera.com/documentation/spark2/latest/topics/spark2_known_issues.html#ki_graphx
- Cloudera“不支持结构化流”,Cloudera,2018,
https://www.cloudera.com/documentation/spark2/latest/topics/spark2_known_issues.html#ki_structured_streaming
- 桑托什·库马尔;“利用 Hive-on-Spark 实现更快的批处理”,Cloudera,2016,
https://vision.cloudera.com/faster-batch-processing-with-hive-on-spark/
六、使用 Spark 和 Kudu 的高性能数据处理
Kudu 只是一个存储引擎。你需要一种方法将数据输入输出。作为 Cloudera 默认的大数据处理框架,Spark 是 Kudu 理想的数据处理和摄取工具。Spark 不仅提供了出色的可伸缩性和性能,Spark SQL 和 DataFrame API 使得与 Kudu 的交互变得很容易。
如果您来自数据仓库背景,或者熟悉 Oracle 和 SQL Server 等关系数据库,那么您可以考虑 Spark,它是 SQL(如 PL/SQL 和 T-SQL)的过程扩展的更强大、更通用的等价物。
Spark 和酷都
您可以使用数据源 API 将 Spark 与 Kudu 结合使用。您可以使用 spark-shell 或 spark-submit 中的 packages 选项来包含 kudu-spark 依赖项。您也可以从 central.maven.org 手动下载 jar 文件,并将其包含在—jars 选项中。有关如何使用 sbt 和 Maven 作为项目构建工具的更多细节,请参考 Apache Spark 在线文档。
Spark 1.6.x
如果您在 Scala 2.10 中使用 spark,请使用 kudu-spark_2.10 工件。例如:
spark-shell -packages org.apache.kudu:kudu-spark_2.10:1.1.0
spark-shell -jars kudu-spark_2.10-1.1.0.jar
Spark 2.x
如果您在 Scala 2.11 中使用 spark2,请使用 kudu-spark2_2.11 工件。例如:
spark-shell –-packages org.apache.kudu:kudu-spark2_2.11:1.1.0
spark-shell -jars kudu-spark2_2.11-1.1.0.jar
酷都语境
您使用 Kudu 上下文来对 Kudu 表执行 DML 语句。 i 你要指定 Kudu 主服务器和端口。在下面的例子中,我们只有一个 Kudu 上下文。一个生产环境中通常有多个 Kudu masters 在这种情况下,您必须指定一个逗号分隔的列表,列出所有 Kudu 主机的主机名。我给出了如何使用 Spark 和 Kudu 的例子。我还展示了如何使用 Spark 将 Kudu 与其他数据源集成的例子。
import org.apache.kudu.spark.kudu._
val kuduContext = new KuduContext("kudumaster01:7051")
在开始之前,我们需要创建我们的表。
impala-shell
CREATE TABLE customers
(
id BIGINT PRIMARY KEY,
name STRING,
age SMALLINT
)
PARTITION BY HASH PARTITIONS 4
STORED AS KUDU;
创建一个 case 类,为我们的示例数据提供一个模式。
case class CustomerData(id: Long, name: String, age: Short)
插入数据
创建一些示例数据。
val data = Array(CustomerData(101,"Lisa Kim",60), CustomerData(102,"Casey Fernandez",45))
val insertRDD = sc.parallelize(data)
val insertDF = sqlContext.createDataFrame(insertRDD)
insertDF.show
+----------+---------------+---+
|customerid| name|age|
+----------+---------------+---+
| 101| Lisa Kim| 60|
| 102|Casey Fernandez| 45|
+----------+---------------+---+
插入数据帧。记下表的名称。如果表是在 Impala 中创建的,就需要这种格式。在本例中,default 是数据库的名称,customers 是表的名称。采用这种约定是为了防止在 Impala 中创建的表和使用 Kudu API 或 Spark 本地创建的表之间的表名冲突。
kuduContext.insertRows(insertDF, "impala::default.customers")
确认数据已成功插入。
val df = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.customers")).kudu
df.select("id","name","age").show()
+---+---------------+---+
| id| name|age|
+---+---------------+---+
|102|Casey Fernandez| 45|
|101| Lisa Kim| 60|
+---+---------------+---+
更新 Kudu 表
创建更新的数据集。请注意,我们修改了姓氏和年龄。
val data = Array(CustomerData(101,"Lisa Kim",120), CustomerData(102,"Casey Jones",90))
val updateRDD = sc.parallelize(data)
val updateDF = sqlContext.createDataFrame(updateRDD)
updateDF.show
+--+------+---------+
| id| name|age|
+--+------+---------+
|101| Lisa Kim|120|
|102|Casey Jones| 90|
+--+------+---------+
更新表格。
kuduContext.updateRows(updateDF, "impala::default.customers");
确认该表已成功更新。
val df = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.customers")).kudu
df.select("id","name","age").show()
+--+------+---------+
| id| name|age|
+--+------+---------+
|102|Casey Jones| 90|
|101| Lisa Kim|120|
+--+------+---------+
令人不安的数据
创建一些示例数据。
val data = Array(CustomerData(101,"Lisa Kim",240), CustomerData(102,"Casey Cullen",90),CustomerData(103,"Byron Miller",25))
val upsertRDD = sc.parallelize(data)
val upsertDF = sqlContext.createDataFrame(upsertRDD)
upsertDF.show
+--+------+----------+
| id| name|age|
+--+------+----------+
|101| Lisa Kim|240|
|102|Casey Cullen| 90|
|103|Byron Miller| 25|
+--+------+----------+
up sert data–如果主键存在,则更新所有列,如果主键不存在,则插入行。
kuduContext.upsertRows(upsertDF, "impala::default.customers")
确认数据是否成功更新。
val df = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.customers")).kudu
df.select("id","name","age").show()
+--+------+----------+
| id| name|age|
+--+------+----------+
|102|Casey Cullen| 90|
|103|Byron Miller| 25|
|101| Lisa Kim|240|
+--+------+----------+
删除数据
检查表中的数据。
val df = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.customers")).kudu
df.select("id","name","age").show()
+--+------+----------+
| id| name|age|
+--+------+----------+
|102|Casey Cullen| 90|
|103|Byron Miller| 25|
|101| Lisa Kim|240|
+--+------+----------+
注册该表,以便我们可以在 SQL 查询中使用该表。
df.registerTempTable("customers")
根据我们的查询删除数据。
kuduContext.deleteRows(sqlContext.sql("select id from customers where name like 'Casey%'"), "impala::default.customers")
确认数据已成功删除。
val df = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.customers")).kudu
df.select("id","name","age").show()
+--+------+----------+
| id| name|age|
+--+------+----------+
|103|Byron Miller| 25|
|101| Lisa Kim|240|
+--+------+----------+
选择数据
选择表格中的数据。
val df = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.customers")).kudu
df.select("id","name","age").show()
+--+------+----------+
| id| name|age|
+--+------+----------+
|103|Byron Miller| 25|
|101| Lisa Kim|240|
+--+------+----------+
还可以通过注册该表并在 SQL 查询中使用它来运行查询。注意,如果使用 Spark 2.x,应该使用 createOrReplaceTempView。
df.registerTempTable("customers")
val df2 = sqlContext.sql("select * from customers where age=240")
df2.show
+--+----+--------+
| id| name|age|
+--+----+--------+
|101|Lisa Kim|240|
+--+----+--------+
创建 Kudu 表
Impala 看不到这个表,因为这个表是在 Spark 中创建的。你必须在 Impala 中创建一个外部表,并引用这个表。
Import org.apache.kudu.client.CreateTableOptions;
kuduContext.createTable("customer2", df.schema, Seq("customerid"), new CreateTableOptions().setRangePartitionColumns(List("customerid").asJava).setNumReplicas(1))
将 CSV 插入 Kudu
让我们从 CSV 文件中插入多行。
val dataRDD = sc.textFile("/sparkdata/customerdata.csv")
val parsedRDD = dataRDD.map{_.split(",")}
case class CustomerData(customerid: Long, name: String, age: Short)
val dataDF = parsedRDD.map{a => CustomerData (a(0).toLong, a(1), a(2).toShort) }.toDF
kuduContext.insertRows(dataDF, "customer2");
因为我们通过 Spark 创建了 customer2 表,所以我们只需要指定表名(customer2)而不是 impala::default.customer2。
使用 spark-csv 包将 CSV 插入 Kudu
我们还可以使用 spark-csv 包来处理样本 csv 数据。下面的命令将自动下载 spark-csv 依赖项,因此请确保您的集群节点中有 Internet 连接。请注意使用逗号分隔软件包列表。
spark-shell -packages com.databricks:spark-csv_2.10:1.5.0,org.apache.kudu:kudu-spark_2.10:1.1.0
val dataDF = sqlContext.read.format("csv")
.option("header", "true")
.option("inferSchema", "true")
.load(“/sparkdata/customerdata.csv ")
kuduContext.insertRows(dataDF, "customer2");
因为我们通过 Spark 创建了 customer2 表,所以我们只需要指定表名(customer2)而不是 impala::default.customer2。
通过以编程方式指定模式,将 CSV 插入 Kudu
您可以使用 StructType 为您的数据集定义一个架构。当无法提前确定模式时,以编程方式指定模式很有帮助。
阅读来自 HDFS 的 CSV 文件。
val myCSV = sc.textFile("/tmp/mydata/customers.csv")
将 CSV 数据映射到 RDD
import org.apache.spark.sql.Row
val myRDD = myCSV.map(_.split(',')).map(e ⇒ Row(r(0).trim.toInt, r(1), r(2).trim.toInt, r(3)))
创建一个模式。
import org.apache.spark.sql.types.{StructType, StructField, StringType, IntegerType};
val mySchema = StructType(Array(
StructField("customerid",IntegerType,false),
StructField("customername",StringType,false),
StructField("age",IntegerType,false),
StructField("city",StringType,false)))
val myDF = sqlContext.createDataFrame(myRDD, mySchema)
将数据帧插入 Kudu。
kuduContext.insertRows(myDF, "impala::default.customers")
还记得我们通过 Impala 创建了 customers 表。因此,在引用 customers 表时,我们需要使用格式 impala::database.table。
使用 spark-xml 包将 XML 插入 Kudu
我们将创建一个 XML 文件作为这个例子的样本数据。我们需要把文件复制到 HDFS。
cat users.xml
<userid>100</userid><name>Wendell Ryan</name><city>San Diego</city><state>CA</state><zip>92102</zip>
<userid>101</userid><name>Alicia Thompson</name><city>Berkeley</city><state>CA</state><zip>94705</zip>
<userid>102</userid><name>Felipe Drummond</name><city>Palo Alto</city><state>CA</state><zip>94301</zip>
<userid>103</userid><name>Teresa Levine</name><city>Walnut Creek</city><state>CA</state><zip>94507</zip>
hadoop fs -mkdir /xmldata
hadoop fs -put users.xml /xmldata
我们将使用 spark-xml 包来处理样本 xml 数据。这个包的工作方式类似于 spark-csv 包。下面的命令将自动下载 spark-xml 包,因此请确保您的集群节点中有 Internet 连接。包括 kudu-spark 依赖性。
spark-shell -packages com.databricks:spark-xml:2.10:0.4.1,org.apache.kudu:kudu-spark_2.10:1.1.0
使用 Spark XML 创建一个数据帧。在本例中,我们指定了行标记和 XML 文件所在的 HDFS 路径。
val xmlDF = sqlContext.read.format("com.databricks.spark.xml").option("rowTag", "user").load("/xmldata/");
xmlDF: org.apache.spark.sql.DataFrame = [city: string, name: string, state: string, userid: bigint, zip: bigint]
我们也来看看数据。
xmlDF.show
+------------+---------------+-----+------+-----+
| city| name|state|userid| zip|
+------------+---------------+-----+------+-----+
| San Diego| Wendell Ryan| CA| 100|92102|
| Berkeley|Alicia Thompson| CA| 101|94705|
| Palo Alto|Felipe Drummond| CA| 102|94301|
|Walnut Creek| Teresa Levine| CA| 103|94507|
+------------+---------------+-----+------+-----+
让我们检查一下模式。
xmlDF.printSchema
root
|- age: long (nullable = true)
|- city: string (nullable = true)
|- name: string (nullable = true)
|- state: string (nullable = true)
|- userid: long (nullable = true)
|- zip: long (nullable = true)
现在让我们回到 impala-shell,将模式与 users 表的结构进行比较。如您所见,用户表中年龄和邮政编码列的数据类型不同于数据帧中的相应列。如果我们试图将这个数据帧插入到 Kudu 表中,我们会得到一个错误消息。
describe users;
+--------+---------+---------+-------------+
| name | type | comment | primary_key |
+--------+---------+---------+-------------+
| userid | bigint | | true |
| name | string | | false |
| city | string | | false |
| state | string | | false |
| zip | string | | false |
| age | tinyint | | false |
+--------+---------+---------+-------------+
在将数据帧插入 Kudu 表之前,我们需要转换数据类型。我们在这里介绍使用 selectExpr 方法来转换数据类型,但是另一种选择是使用 StructType 以编程方式指定模式。
val convertedDF = xmlDF.selectExpr("userid","name","city","state","cast(zip as string) zip","cast(age as tinyint) age");
convertedDF: org.apache.spark.sql.DataFrame = [usersid: bigint, name: string, city: string, state: string, zip: string, age: tinyint]
创建 kudu 上下文并将数据帧插入目标表。
import org.apache.kudu.spark.kudu._
val kuduContext = new KuduContext("kudumaster01:7051")
kuduContext.insertRows(convertedDF, "impala::default.users")
看起来 DataFrame 已经成功地插入到 Kudu 表中。使用 impala-shell,检查表格中的数据进行确认。
select * from users;
+--------+-----------------+--------------+-------+-------+-----+
| userid | name | city | state | zip | age |
+--------+-----------------+--------------+-------+-------+-----+
| 102 | Felipe Drummond | Palo Alto | CA | 94301 | 33 |
| 103 | Teresa Levine | Walnut Creek | CA | 94507 | 47 |
| 100 | Wendell Ryan | San Diego | CA | 92102 | 24 |
| 101 | Alicia Thompson | Berkeley | CA | 94705 | 52 |
+--------+-----------------+--------------+-------+-------+-----+
将 JSON 插入 Kudu
我们将创建一个 JSON 文件作为这个例子的样本数据。确保该文件位于 HDFS 名为/jsondata 的文件夹中。
cat users.json
{"userid": 200, "name": "Jonathan West", "city":"Frisco", "state":"TX", "zip": "75034", "age":35}
{"userid": 201, "name": "Andrea Foreman", "city":"Dallas", "state":"TX", "zip": "75001", "age":28}
{"userid": 202, "name": "Kirsten Jung", "city":"Plano", "state":"TX", "zip": "75025", "age":69}
{"userid": 203, "name": "Jessica Nguyen", "city":"Allen", "state":"TX", "zip": "75002", "age":52}
从 JSON 文件创建一个数据帧。
val jsonDF = sqlContext.read.json("/jsondata")
jsonDF: org.apache.spark.sql.DataFrame = [age: bigint, city: string, name: string, state: string, userid: bigint, zip: string]
检查日期
jsonDF.show
+---+------+--------------+-----+------+-----+
|age| city| name|state|userid| zip|
+---+------+--------------+-----+------+-----+
| 35|Frisco| Jonathan West| TX| 200|75034|
| 28|Dallas|Andrea Foreman| TX| 201|75001|
| 69| Plano| Kirsten Jung| TX| 202|75025|
| 52| Allen|Jessica Nguyen| TX| 203|75002|
+---+------+--------------+-----+------+-----+
请检查架构。
jsonDF.printSchema
root
|- age: long (nullable = true)
|- city: string (nullable = true)
|- name: string (nullable = true)
|- state: string (nullable = true)
|- userid: long (nullable = true)
|- zip: string (nullable = true)
将 age 列的数据类型转换为 tinyint,以匹配表的数据类型。
val convertedDF = jsonDF.selectExpr("userid","name","city","state","zip","cast(age as tinyint) age");
convertedDF: org.apache.spark.sql.DataFrame = [userid: bigint, name: string, city: string, state: string, zip: string, age: tinyint]
创建 kudu 上下文并将数据帧插入目标表。
import org.apache.kudu.spark.kudu._
val kuduContext = new KuduContext("kudumaster01:7051")
kuduContext.insertRows(convertedDF, "impala::default.users")
使用 impala-shell,检查行是否成功插入。
select * from users order by userid;
+--------+-----------------+--------------+-------+-------+-----+
| userid | name | city | state | zip | age |
+--------+-----------------+--------------+-------+-------+-----+
| 100 | Wendell Ryan | San Diego | CA | 92102 | 24 |
| 101 | Alicia Thompson | Berkeley | CA | 94705 | 52 |
| 102 | Felipe Drummond | Palo Alto | CA | 94301 | 33 |
| 103 | Teresa Levine | Walnut Creek | CA | 94507 | 47 |
| 200 | Jonathan West | Frisco | TX | 75034 | 35 |
| 201 | Andrea Foreman | Dallas | TX | 75001 | 28 |
| 202 | Kirsten Jung | Plano | TX | 75025 | 69 |
| 203 | Jessica Nguyen | Allen | TX | 75002 | 52 |
+--------+-----------------+--------------+-------+-------+-----+
从 MySQL 插入到 Kudu
让我们用一些测试数据填充 MySQL 中的 users 表。确保 salesdb 数据库中存在 users 表。我们将把这些数据插入到 Kudu 中的一个表中。
mysql -u root –p mypassword
use salesdb;
describe users;
+--------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+-------+
| userid | bigint(20) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| city | varchar(100) | YES | | NULL | |
| state | char(3) | YES | | NULL | |
| zip | char(5) | YES | | NULL | |
| age | tinyint(4) | YES | | NULL | |
+--------+--------------+------+-----+---------+-------+
select * from users;
Empty set (0.00 sec)
insert into users values (300,'Fred Stevens','Torrance','CA',90503,23);
insert into users values (301,'Nancy Gibbs','Valencia','CA',91354,49);
insert into users values (302,'Randy Park','Manhattan Beach','CA',90267,21);
insert into users values (303,'Victoria Loma','Rolling Hills','CA',90274,75);
select * from users;
+--------+---------------+-----------------+-------+-------+------+
| userid | name | city | state | zip | age |
+--------+---------------+-----------------+-------+-------+------+
| 300 | Fred Stevens | Torrance | CA | 90503 | 23 |
| 301 | Nancy Gibbs | Valencia | CA | 91354 | 49 |
| 302 | Randy Park | Manhattan Beach | CA | 90267 | 21 |
| 303 | Victoria Loma | Rolling Hills | CA | 90274 | 75 |
+--------+---------------+-----------------+-------+-------+------+
Note
在某些版本的 Spark -jars 中,没有在驱动程序的类路径中添加 JAR。 ii 建议您将 JDBC 驱动程序包含在您的-jars 和 Spark 类路径中。 iii
启动 Spark 壳。请注意,我必须将 MySQL 驱动程序作为参数包含在–driver-class-path 和–jar 中。
spark-shell -packages org.apache.kudu:kudu-spark_2.10:1.1.0 -driver-class-path mysql-connector-java-5.1.40-bin.jar -jars mysql-connector-java-5.1.40-bin.jar
让我们设置 jdbc url 和连接属性。
val jdbcURL = s"jdbc:mysql://10.0.1.101:3306/salesdb?user=root&password=cloudera"
val connectionProperties = new java.util.Properties()
我们可以从整个表中创建一个数据帧。
val mysqlDF = sqlContext.read.jdbc(jdbcURL, "users", connectionProperties)
mysqlDF.show
+------+-------------+---------------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+-------------+---------------+-----+-----+---+
| 300| Fred Stevens| Torrance| CA|90503| 23|
| 301| Nancy Gibbs| Valencia| CA|91354| 49|
| 302| Randy Park|Manhattan Beach| CA|90267| 21|
| 303|Victoria Loma| Rolling Hills| CA|90274| 75|
+------+-------------+---------------+-----+-----+---+
让我们利用下推优化,在数据库中运行查询,只返回所需的结果。
val mysqlDF = sqlContext.read.jdbc(jdbcURL, "users", connectionProperties).select("userid", "city", "state","age").where("age < 25")
mysqlDF.show
+------+---------------+-----+---+
|userid| city|state|age|
+------+---------------+-----+---+
| 300| Torrance| CA| 23|
| 302|Manhattan Beach| CA| 21|
+------+---------------+-----+---+
让我们指定一个完整的查询。这是一种更方便、更灵活的方法。此外,与前面的方法不同,如果在 WHERE 子句中指定了列,则不需要在选择列表中指定列。
val query = "(SELECT userid,name FROM users WHERE city IN ('Torrance','Rolling Hills')) as users"
val mysqlDF = sqlContext.read.jdbc(jdbcURL, query, connectionProperties)
mysqlDF.show
+------+-------------+
|userid| name|
+------+-------------+
| 300| Fred Stevens|
| 303|Victoria Loma|
+------+-------------+
我们刚刚尝试了从 MySQL 表中选择数据的不同方法。
让我们直接将整个表插入到 Kudu 中。
val mysqlDF = sqlContext.read.jdbc(jdbcURL, "users", connectionProperties)
mysqlDF: org.apache.spark.sql.DataFrame = [userid: bigint, name: string, city: string, state: string, zip: string, age: int]
mysqlDF.show
+------+-------------+---------------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+-------------+---------------+-----+-----+---+
| 300| Fred Stevens| Torrance| CA|90503| 23|
| 301| Nancy Gibbs| Valencia| CA|91354| 49|
| 302| Randy Park|Manhattan Beach| CA|90267| 21|
| 303|Victoria Loma| Rolling Hills| CA|90274| 75|
+------+-------------+---------------+-----+-----+---+
请验证架构。
mysqlDF.printSchema
root
|- userid: long (nullable = true)
|- name: string (nullable = true)
|- city: string (nullable = true)
|- state: string (nullable = true)
|- zip: string (nullable = true)
|- age: integer (nullable = true)
但是首先让我们把年龄从整数转换成 TINYINT。否则,您将无法将此数据帧插入 Kudu。同样,我们可以使用 StructType 定义一个模式。
val convertedDF = mysqlDF.selectExpr("userid","name","city","state","zip","cast(age as tinyint) age");
convertedDF: org.apache.spark.sql.DataFrame = [userid: bigint, name: string, city: string, state: string, zip: string, age: tinyint]
如您所见,age 列的数据类型现在是 TINYINT。让我们继续将数据插入到 Kudu 中。
import org.apache.kudu.spark.kudu._
val kuduContext = new KuduContext("kudumaster01:7051")
kuduContext.insertRows(convertedDF, "impala::default.users")
现在转到 impala-shell,检查数据是否成功插入。
select * from users order by userid
+--------+-----------------+-----------------+-------+-------+-----+
| userid | name | city | state | zip | age |
+--------+-----------------+-----------------+-------+-------+-----+
| 100 | Wendell Ryan | San Diego | CA | 92102 | 24 |
| 101 | Alicia Thompson | Berkeley | CA | 94705 | 52 |
| 102 | Felipe Drummond | Palo Alto | CA | 94301 | 33 |
| 103 | Teresa Levine | Walnut Creek | CA | 94507 | 47 |
| 200 | Jonathan West | Frisco | TX | 75034 | 35 |
| 201 | Andrea Foreman | Dallas | TX | 75001 | 28 |
| 202 | Kirsten Jung | Plano | TX | 75025 | 69 |
| 203 | Jessica Nguyen | Allen | TX | 75002 | 52 |
| 300 | Fred Stevens | Torrance | CA | 90503 | 23 |
| 301 | Nancy Gibbs | Valencia | CA | 91354 | 49 |
| 302 | Randy Park | Manhattan Beach | CA | 90267 | 21 |
| 303 | Victoria Loma | Rolling Hills | CA | 90274 | 75 |
+--------+-----------------+-----------------+-------+-------+-----+
或者,您也可以使用 Spark 进行检查。
import org.apache.kudu.spark.kudu._
val kuduDF = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.users")).kudu
kuduDF.select("userid","name","city","state","zip","age").sort($"userid".asc).show()
+------+---------------+---------------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+---------------+---------------+-----+-----+---+
| 100| Wendell Ryan| San Diego| CA|92102| 24|
| 101|Alicia Thompson| Berkeley| CA|94705| 52|
| 102|Felipe Drummond| Palo Alto| CA|94301| 33|
| 103| Teresa Levine| Walnut Creek| CA|94507| 47|
| 200| Jonathan West| Frisco| TX|75034| 35|
| 201| Andrea Foreman| Dallas| TX|75001| 28|
| 202| Kirsten Jung| Plano| TX|75025| 69|
| 203| Jessica Nguyen| Allen| TX|75002| 52|
| 300| Fred Stevens| Torrance| CA|90503| 23|
| 301| Nancy Gibbs| Valencia| CA|91354| 49|
| 302| Randy Park|Manhattan Beach| CA|90267| 21|
| 303| Victoria Loma| Rolling Hills| CA|90274| 75|
+------+---------------+---------------+-----+-----+---+
从 SQL Server 插入到 Kudu
您需要做的第一件事是下载用于 SQL Server 的微软 JDBC 驱动程序。你可以在这里下载 JDBC 驱动: https://docs.microsoft.com/en-us/sql/connect/jdbc/microsoft-jdbc-driver-for-sql-server
。
您应该会看到类似于图 6-1 中的页面。单击“下载 JDBC 驱动程序”链接。
图 6-1
Microsoft JDBC Driver for SQL Server
选择语言并点击“下载”
解开并拉开 Tarball。根据 JRE 的版本选择驱动程序。
tar zxvf sqljdbc_6.0.8112.100_enu.tar.gz
sqljdbc_6.0/enu/auth/x64/sqljdbc_auth.dll
sqljdbc_6.0/enu/auth/x86/sqljdbc_auth.dll
sqljdbc_6.0/enu/install.txt
sqljdbc_6.0/enu/jre7/sqljdbc41.jar
sqljdbc_6.0/enu/jre8/sqljdbc42.jar
sqljdbc_6.0/enu/license.txt
sqljdbc_6.0/enu/release.txt
sqljdbc_6.0/enu/samples/adaptive/executeStoredProcedure.java
sqljdbc_6.0/enu/samples/adaptive/readLargeData.java
sqljdbc_6.0/enu/samples/adaptive/updateLargeData.java
sqljdbc_6.0/enu/samples/alwaysencrypted/AlwaysEncrypted.java
sqljdbc_6.0/enu/samples/connections/connectDS.java
sqljdbc_6.0/enu/samples/connections/connectURL.java
sqljdbc_6.0/enu/samples/datatypes/basicDT.java
sqljdbc_6.0/enu/samples/datatypes/sqlxmlExample.java
sqljdbc_6.0/enu/samples/resultsets/cacheRS.java
sqljdbc_6.0/enu/samples/resultsets/retrieveRS.java
sqljdbc_6.0/enu/samples/resultsets/updateRS.java
sqljdbc_6.0/enu/samples/sparse/SparseColumns.java
sqljdbc_6.0/enu/xa/x64/sqljdbc_xa.dll
sqljdbc_6.0/enu/xa/x86/sqljdbc_xa.dll
sqljdbc_6.0/enu/xa/xa_install.sql
我将在本书中通篇使用 SQL Server 2016。您还需要单独安装 SQL Server Management Studio(图 6-2 )。
图 6-2
SQL Server Management Studio
我们将创建 salesdb 数据库和 users 表。在对象资源管理器中,右键单击数据库节点,然后单击“新建数据库”(图 6-3 )。将显示一个窗口,您可以在其中指定数据库名称和其他数据库配置选项。输入数据库名称“salesdb ”,然后单击确定。出于测试目的,我们将其他选项保留默认值。
图 6-3
Create new database
展开 salesdb 节点。右键单击“表格”,单击“新建”,然后单击“表格”填写列名和数据类型。为了便于您理解书中的示例,请确保列名和数据类型与 MySQL 和 Kudu 表相同。点击窗口右上角附*的“保存”图标,然后输入表名“用户”(图 6-4 )。
图 6-4
Create new table
让我们将一些测试数据插入到刚刚创建的 users 表中。点击标准工具栏上的“新建查询”按钮,打开一个新的编辑器窗口(图 6-5 )。
图 6-5
Insert test data
我们将把这四行从 SQL Server 复制到 Kudu。
启动 Spark 壳。不要忘记在-driver-class-path 和-jar 中将 SQL Server 驱动程序作为参数传递。
spark-shell -packages org.apache.kudu:kudu-spark_2.10:1.1.0 -driver-class-path sqljdbc41.jar -jars sqljdbc41.jar
让我们设置 jdbc url 和连接属性。
val jdbcURL = "jdbc:sqlserver://192.168.56.102;databaseName=salesdb;user=salesuser;password=salespassword"
val connectionProperties = new java.util.Properties()
我们可以从整个表中创建一个数据帧。
val sqlDF = sqlContext.read.jdbc(jdbcURL, "users", connectionProperties)
sqlDF.show
+------+-------------+--------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+-------------+--------+-----+-----+---+
| 500| Eric Steele| Seattle| WA |98109| 91|
| 501|Brian Ambrose|Portland| OR |97035| 53|
| 502| Tim Parsons| Tucson| AZ |85704| 49|
| 503| Lee Greene| Miami| FL |33018| 30|
+------+-------------+--------+-----+-----+---+
让我们利用下推优化在数据库中运行查询,并且只返回结果。
val sqlDF = sqlContext.read.jdbc(jdbcURL, "users", connectionProperties).select("userid", "city", "state","age").where("age < 50")
sqlDF.show
+------+------+-----+---+
|userid| city|state|age|
+------+------+-----+---+
| 502|Tucson| AZ | 49|
| 503| Miami| FL | 30|
+------+------+-----+---+
我们可以指定整个查询。
val query = "(SELECT userid,name FROM users WHERE city IN ('Seattle','Portland')) as users"
val sqlDF = sqlContext.read.jdbc(jdbcURL, query, connectionProperties)
sqlDF.show
+------+-------------+
|userid| name|
+------+-------------+
| 500| Eric Steele|
| 501|Brian Ambrose|
+------+-------------+
让我们直接将整个表插入到 Kudu 中。
val sqlDF = sqlContext.read.jdbc(jdbcURL, "users", connectionProperties)
sqlDF.show
+------+-------------+--------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+-------------+--------+-----+-----+---+
| 500| Eric Steele| Seattle| WA |98109| 91|
| 501|Brian Ambrose|Portland| OR |97035| 53|
| 502| Tim Parsons| Tucson| AZ |85704| 49|
| 503| Lee Greene| Miami| FL |33018| 30|
+------+-------------+--------+-----+-----+---+
检查模式,看起来年龄被转换为整数。
sqlDF.printSchema
root
|- userid: long (nullable = true)
|- name: string (nullable = true)
|- city: string (nullable = true)
|- state: string (nullable = true)
|- zip: string (nullable = true)
|- age: integer (nullable = true)
我们需要将年龄从整数转换为整数。否则,您将无法将此数据帧插入 kudu。
val convertedDF = sqlDF.selectExpr("userid","name","city","state","zip","cast(age as tinyint) age");
convertedDF: org.apache.spark.sql.DataFrame = [userid: bigint, name: string, city: string, state: string, zip: string, age: tinyint]
如您所见,age 列的数据类型现在是 TINYINT。让我们将数据帧插入到 Kudu 中。
import org.apache.kudu.spark.kudu._
val kuduContext = new KuduContext("kudumaster01:7051")
kuduContext.insertRows(convertedDF, "impala::default.users")
现在让我们转到 impala-shell 并确认数据是否被成功插入。
select * from users order by userid
+--------+-------------------+-----------------+-------+-------+-----+
| userid | name | city | state | zip | age |
+--------+-------------------+-----------------+-------+-------+-----+
| 100 | Wendell Ryan | San Diego | CA | 92102 | 24 |
| 101 | Alicia Thompson | Berkeley | CA | 94705 | 52 |
| 102 | Felipe Drummond | Palo Alto | CA | 94301 | 33 |
| 103 | Teresa Levine | Walnut Creek | CA | 94507 | 47 |
| 200 | Jonathan West | Frisco | TX | 75034 | 35 |
| 201 | Andrea Foreman | Dallas | TX | 75001 | 28 |
| 202 | Kirsten Jung | Plano | TX | 75025 | 69 |
| 203 | Jessica Nguyen | Allen | TX | 75002 | 52 |
| 300 | Fred Stevens | Torrance | CA | 90503 | 23 |
| 301 | Nancy Gibbs | Valencia | CA | 91354 | 49 |
| 302 | Randy Park | Manhattan Beach | CA | 90267 | 21 |
| 303 | Victoria Loma | Rolling Hills | CA | 90274 | 75 |
| 400 | Patrick Montalban | Los Angeles | CA | 90010 | 71 |
| 401 | Jillian Collins | Santa Monica | CA | 90402 | 45 |
| 402 | Robert Sarkisian | Glendale | CA | 91204 | 29 |
| 403 | Warren Porcaro | Burbank | CA | 91523 | 62 |
| 500 | Eric Steele | Seattle | WA | 98109 | 91 |
| 501 | Brian Ambrose | Portland | OR | 97035 | 53 |
| 502 | Tim Parsons | Tucson | AZ | 85704 | 49 |
| 503 | Lee Greene | Miami | FL | 33018 | 30 |
+--------+-------------------+-----------------+-------+-------+-----+
或者,您也可以使用 Spark 进行检查。
import org.apache.kudu.spark.kudu._
val kuduDF = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.users")).kudu
kuduDF.select("userid","name","city","state","zip","age").sort($"userid".asc).show()
+------+-----------------+---------------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+-----------------+---------------+-----+-----+---+
| 100| Wendell Ryan| San Diego| CA|92102| 24|
| 101| Alicia Thompson| Berkeley| CA|94705| 52|
| 102| Felipe Drummond| Palo Alto| CA|94301| 33|
| 103| Teresa Levine| Walnut Creek| CA|94507| 47|
| 200| Jonathan West| Frisco| TX|75034| 35|
| 201| Andrea Foreman| Dallas| TX|75001| 28|
| 202| Kirsten Jung| Plano| TX|75025| 69|
| 203| Jessica Nguyen| Allen| TX|75002| 52|
| 300| Fred Stevens| Torrance| CA|90503| 23|
| 301| Nancy Gibbs| Valencia| CA|91354| 49|
| 302| Randy Park|Manhattan Beach| CA|90267| 21|
| 303| Victoria Loma| Rolling Hills| CA|90274| 75|
| 400|Patrick Montalban| Los Angeles| CA|90010| 71|
| 401| Jillian Collins| Santa Monica| CA|90402| 45|
| 402| Robert Sarkisian| Glendale| CA|91204| 29|
| 403| Warren Porcaro| Burbank| CA|91523| 62|
| 500| Eric Steele| Seattle| WA |98109| 91|
| 501| Brian Ambrose| Portland| OR |97035| 53|
| 502| Tim Parsons| Tucson| AZ |85704| 49|
| 503| Lee Greene| Miami| FL |33018| 30|
+------+-----------------+---------------+-----+-----+---+
从 HBase 插入 Kudu
有几种方法可以将数据从 HBase 传输到 Kudu。我们可以使用 HBase 客户端 API。有 Hortonworks 开发的 Spark-HBase 连接器。 iv Astro,由虎娃微开发,使用 Spark SQL 提供 SQL 层 HBase。来自 Cloudera 的 SparkOnHBase 项目最*被集成到 HBase 中,但在它成为 HBase 的一个版本之前,可能还需要一段时间。 vi
我们将使用最简单的方法,通过 JDBC。这可能不是将数据从 HBase 移动到 Kudu 的最快方式,但对于大多数任务来说应该足够了。我们将在 HBase 表的顶部创建一个 Hive 表,然后我们将通过 Impala 使用 JDBC 创建一个 Spark 数据帧。一旦我们有了数据帧,我们可以很容易地将其插入到 Kudu 中。
我们需要做的第一件事是下载黑斑羚 JDBC 驱动程序。将浏览器指向 https://www.cloudera.com/downloads.html
(图 6-6 )。
图 6-6
Cloudera Enterprise download page
点击页面右下角附*的黑斑羚 JDBC 驱动程序下载链接,您将进入黑斑羚 JDBC 驱动程序最新版本的下载页面。下载并解压缩文件。
ls -l
-rw-r-r- 1 hadoop hadoop 693530 Mar 9 10:04 Cloudera-JDBC-Driver-for-Impala-Install-Guide.pdf
-rw-r-r- 1 hadoop hadoop 43600 Mar 8 11:11 Cloudera-JDBC-Driver-for-Impala-Release-Notes.pdf
-rw-r-r- 1 hadoop hadoop 46725 Mar 4 13:12 commons-codec-1.3.jar
-rw-r-r- 1 hadoop hadoop 60686 Mar 4 13:12 commons-logging-1.1.1.jar
-rw-r-r- 1 hadoop hadoop 7670596 Mar 4 13:16 hive_metastore.jar
-rw-r-r- 1 hadoop hadoop 596600 Mar 4 13:16 hive_service.jar
-rw-r-r- 1 hadoop hadoop 352585 Mar 4 13:12 httpclient-4.1.3.jar
-rw-r-r- 1 hadoop hadoop 181201 Mar 4 13:12 httpcore-4.1.3.jar
-rw-r-r- 1 hadoop hadoop 1562600 Mar 4 13:19 ImpalaJDBC41.jar
-rw-r-r- 1 hadoop hadoop 275186 Mar 4 13:12 libfb303-0.9.0.jar
-rw-r-r- 1 hadoop hadoop 347531 Mar 4 13:12 libthrift-0.9.0.jar
-rw-r-r- 1 hadoop hadoop 367444 Mar 4 13:12 log4j-1.2.14.jar
-rw-r-r- 1 hadoop hadoop 294796 Mar 4 13:16 ql.jar
-rw-r-r- 1 hadoop hadoop 23671 Mar 4 13:12 slf4j-api-1.5.11.jar
-rw-r-r- 1 hadoop hadoop 9693 Mar 4 13:12 slf4j-log4j12-1.5.11.jar
-rw-r-r- 1 hadoop hadoop 1307923 Mar 4 13:16 TCLIServiceClient.jar
-rw-r-r- 1 hadoop hadoop 792964 Mar 4 13:12 zookeeper-3.4.6.jar
我们需要做的第一件事是启动“hbase shell”并创建 hbase 表。我们还需要将测试数据添加到 HBase 表中。如果您不熟悉 HBase 命令,请在线查阅 Apache HBase 参考指南。
hbase shell
create 'hbase_users', 'cf1'
put 'hbase_users','400','cf1:name', 'Patrick Montalban'
put 'hbase_users','400','cf1:city', 'Los Angeles'
put 'hbase_users','400','cf1:state', 'CA'
put 'hbase_users','400','cf1:zip', '90010'
put 'hbase_users','400','cf1:age', '71'
put 'hbase_users','401','cf1:name', 'Jillian Collins'
put 'hbase_users','401','cf1:city', 'Santa Monica'
put 'hbase_users','401','cf1:state', 'CA'
put 'hbase_users','401','cf1:zip', '90402'
put 'hbase_users','401','cf1:age', '45'
put 'hbase_users','402','cf1:name', 'Robert Sarkisian'
put 'hbase_users','402','cf1:city', 'Glendale'
put 'hbase_users','402','cf1:state', 'CA'
put 'hbase_users','402','cf1:zip', '91204'
put 'hbase_users','402','cf1:age', '29'
put 'hbase_users','403','cf1:name', 'Warren Porcaro'
put 'hbase_users','403','cf1:city', 'Burbank'
put 'hbase_users','403','cf1:state', 'CA'
put 'hbase_users','403','cf1:zip', '91523'
put 'hbase_users','403','cf1:age', '62'
在 HBase 表的顶部创建配置单元外部表。
create external table hbase_users
(userid bigint,
name string,
city string,
state string,
zip string,
age tinyint)
stored by
'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
with
SERDEPROPERTIES ('hbase.columns.mapping'=':key, cf1:name, cf1:city, cf1:state, cf1:zip, cf1:age')
TBLPROPERTIES ('hbase.table.name'='hbase_users');
使用 impala-shell,验证您可以看到 Hive 外部表。
show tables;
+-----------+
| name |
+-----------+
| customers |
| sample_07 |
| sample_08 |
| users |
| web_logs |
+-----------+
它没有出现。我们需要使元数据无效来刷新 Impala 的内存。
invalidate metadata;
show tables;
+-------------+
| name |
+-------------+
| customers |
| hbase_users |
| sample_07 |
| sample_08 |
| users |
| web_logs |
+-------------+
select * from hbase_users;
+--------+-----+--------------+-------------------+-------+-------+
| userid | age | city | name | state | zip |
+--------+-----+--------------+-------------------+-------+-------+
| 400 | 71 | Los Angeles | Patrick Montalban | CA | 90010 |
| 401 | 45 | Santa Monica | Jillian Collins | CA | 90402 |
| 402 | 29 | Glendale | Robert Sarkisian | CA | 91204 |
| 403 | 62 | Burbank | Warren Porcaro | CA | 91523 |
+--------+-----+--------------+-------------------+-------+-------+
启动 Spark 壳。
spark-shell -driver-class-path ImpalaJDBC41.jar -jars ImpalaJDBC41.jar -packages org.apache.kudu:kudu-spark_2.10:1.1.0
从 HBase 表创建一个数据帧
val jdbcURL = s"jdbc:impala://10.0.1.101:21050;AuthMech=0"
val connectionProperties = new java.util.Properties()
val hbaseDF = sqlContext.read.jdbc(jdbcURL, "hbase_users", connectionProperties)
hbaseDF: org.apache.spark.sql.DataFrame = [userid: bigint, age: int, city: string, name: string, state: string, zip: string]
hbaseDF.show
+------+---+------------+-----------------+-----+-----+
|userid|age| city| name|state| zip|
+------+---+------------+-----------------+-----+-----+
| 400| 71| Los Angeles|Patrick Montalban| CA|90010|
| 401| 45|Santa Monica| Jillian Collins| CA|90402|
| 402| 29| Glendale| Robert Sarkisian| CA|91204|
| 403| 62| Burbank| Warren Porcaro| CA|91523|
+------+---+------------+-----------------+-----+-----+
在将数据插入 Kudu users 表之前,我们仍然需要将 age 转换为 TINYINT。使用 StructType 定义模式是这里的一个选项。
val convertedDF = hbaseDF.selectExpr("userid","name","city","state","zip","cast(age as tinyint) age");
convertedDF: org.apache.spark.sql.DataFrame = [userid: bigint, name: string, city: string, state: string, zip: string, age: tinyint]
我们现在可以将数据插入到 Kudu 中。
import org.apache.kudu.spark.kudu._
val kuduContext = new KuduContext("kudumaster01:7051")
kuduContext.insertRows(convertedDF, "impala::default.users")
确认数据是否成功插入。
val kuduDF = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.users")).kudu
kuduDF.select("userid","name","city","state","zip","age").sort($"userid".asc).show()
+------+-----------------+---------------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+-----------------+---------------+-----+-----+---+
| 100| Wendell Ryan| San Diego| CA|92102| 24|
| 101| Alicia Thompson| Berkeley| CA|94705| 52|
| 102| Felipe Drummond| Palo Alto| CA|94301| 33|
| 103| Teresa Levine| Walnut Creek| CA|94507| 47|
| 200| Jonathan West| Frisco| TX|75034| 35|
| 201| Andrea Foreman| Dallas| TX|75001| 28|
| 202| Kirsten Jung| Plano| TX|75025| 69|
| 203| Jessica Nguyen| Allen| TX|75002| 52|
| 300| Fred Stevens| Torrance| CA|90503| 23|
| 301| Nancy Gibbs| Valencia| CA|91354| 49|
| 302| Randy Park|Manhattan Beach| CA|90267| 21|
| 303| Victoria Loma| Rolling Hills| CA|90274| 75|
| 400|Patrick Montalban| Los Angeles| CA|90010| 71|
| 401| Jillian Collins| Santa Monica| CA|90402| 45|
| 402| Robert Sarkisian| Glendale| CA|91204| 29|
| 403| Warren Porcaro| Burbank| CA|91523| 62|
+------+-----------------+---------------+-----+-----+---+
行已成功插入。
从 Solr 插入 Kudu
正如第五章所讨论的,你可以使用 SolrJ 从 Spark 访问 Solr。VII
import java.net.MalformedURLException;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocumentList;
val solr = new HttpSolrServer("http://master02:8983/solr/mycollection");
val query = new SolrQuery();
query.setQuery("*:*");
query.addFilterQuery("userid:3");
query.setFields("userid","name","age","city");
query.setStart(0);
query.set("defType", "edismax");
val response = solr.query(query);
val results = response.getResults();
println(results);
从 Spark 访问 Solr 集合的一个更好的方法是使用 spark-solr 包。Lucidworks 启动了 spark-solr 项目来提供 Spark-Solr 集成。与 solrJ 相比,使用 spark-solr 要简单和强大得多,它允许你从 Solr 集合中创建数据帧,并使用 SparkSQL 与它们进行交互。您可以从 Lucidworks 的网站下载 jar 文件。
首先从 spark-shell 导入 jar 文件。
spark-shell -jars spark-solr-3.0.1-shaded.jar
指定集合和连接信息。
val myOptions = Map("collection" -> "mycollection","zkhost" -> "{master02:8983/solr}")
创建一个数据帧。
val solrDF = spark.read.format("solr")
.options(myOptions)
.load
将数据插入 Kudu。
kuduContext.insertRows(solrDF, "impala::default.users")
从亚马逊 S3 插入到 Kudu
亚马逊 S3 是一个流行的对象存储,经常被用作临时集群的数据存储。它还是备份和冷数据的经济高效的存储方式。从 S3 读取数据就像从 HDFS 或任何其他文件系统读取数据一样。
阅读来自亚马逊 S3 的 CSV 文件。请确保您已经配置了 S3 凭据。
val myCSV = sc.textFile("s3a://mydata/customers.csv")
将 CSV 数据映射到 RDD。
import org.apache.spark.sql.Row
val myRDD = myCSV.map(_.split(‘,’)).map(e ⇒ Row(r(0).trim.toInt, r(1), r(2).trim.toInt, r(3)))
创建一个模式。
import org.apache.spark.sql.types.{StructType, StructField, StringType, IntegerType};
val mySchema = StructType(Array(
StructField("customerid",IntegerType,false),
StructField("customername",StringType,false),
StructField("age",IntegerType,false),
StructField("city",StringType,false)))
val myDF = sqlContext.createDataFrame(myRDD, mySchema)
将数据帧插入 Kudu。
kuduContext.insertRows(myDF, "impala::default.customers")
您已经成功地将 S3 的数据插入到 Kudu 中。
我们已经将不同数据源的数据插入到 Kudu 中。现在让我们将 Kudu 中的数据插入到不同的数据源中。
从 Kudu 插入 MySQL
启动 Spark 壳。
spark-shell -packages org.apache.kudu:kudu-spark_2.10:1.1.0 -driver-class-path mysql-connector-java-5.1.40-bin.jar -jars mysql-connector-java-5.1.40-bin.jar
连接到 Kudu master 并检查 users 表中的数据。我们将同步这个 Kudu 表和一个 MySQL 表。
import org.apache.kudu.spark.kudu._
val kuduDF = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.users")).kudu
kuduDF.select("userid","name","city","state","zip","age").sort($"userid".asc).show()
+------+---------------+---------------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+---------------+---------------+-----+-----+---+
| 100| Wendell Ryan| San Diego| CA|92102| 24|
| 101|Alicia Thompson| Berkeley| CA|94705| 52|
| 102|Felipe Drummond| Palo Alto| CA|94301| 33|
| 103| Teresa Levine| Walnut Creek| CA|94507| 47|
| 200| Jonathan West| Frisco| TX|75034| 35|
| 201| Andrea Foreman| Dallas| TX|75001| 28|
| 202| Kirsten Jung| Plano| TX|75025| 69|
| 203| Jessica Nguyen| Allen| TX|75002| 52|
| 300| Fred Stevens| Torrance| CA|90503| 23|
| 301| Nancy Gibbs| Valencia| CA|91354| 49|
| 302| Randy Park|Manhattan Beach| CA|90267| 21|
| 303| Victoria Loma| Rolling Hills| CA|90274| 75|
+------+---------------+---------------+-----+-----+---+
注册数据帧,以便我们可以对其运行 SQL 查询。
kuduDF.registerTempTable("kudu_users")
设置 MySQL 数据库的 JDBC URL 和连接属性。
val jdbcURL = s"jdbc:mysql://10.0.1.101:3306/salesdb?user=root&password=cloudera"
val connectionProperties = new java.util.Properties()
import org.apache.spark.sql.SaveMode
Check the data in the MySQL table using the MySQL command-line tool.
select * from users;
+--------+---------------+-----------------+-------+-------+------+
| userid | name | city | state | zip | age |
+--------+---------------+-----------------+-------+-------+------+
| 300 | Fred Stevens | Torrance | CA | 90503 | 23 |
| 301 | Nancy Gibbs | Valencia | CA | 91354 | 49 |
| 302 | Randy Park | Manhattan Beach | CA | 90267 | 21 |
| 303 | Victoria Loma | Rolling Hills | CA | 90274 | 75 |
+--------+---------------+-----------------+-------+-------+------+
让我们通过将 userid < 300 的所有行从 Kudu 插入 MySQL 来同步这两个表。
sqlContext.sql("select * from kudu_users where userid < 300").write.mode(SaveMode.Append).jdbc(jdbcUrl, "users", connectionProperties)
再次检查 MySQL 表,验证是否添加了行。
select * from users order by userid;
+--------+-----------------+-----------------+-------+-------+------+
| userid | name | city | state | zip | age |
+--------+-----------------+-----------------+-------+-------+------+
| 100 | Wendell Ryan | San Diego | CA | 92102 | 24 |
| 101 | Alicia Thompson | Berkeley | CA | 94705 | 52 |
| 102 | Felipe Drummond | Palo Alto | CA | 94301 | 33 |
| 103 | Teresa Levine | Walnut Creek | CA | 94507 | 47 |
| 200 | Jonathan West | Frisco | TX | 75034 | 35 |
| 201 | Andrea Foreman | Dallas | TX | 75001 | 28 |
| 202 | Kirsten Jung | Plano | TX | 75025 | 69 |
| 203 | Jessica Nguyen | Allen | TX | 75002 | 52 |
| 300 | Fred Stevens | Torrance | CA | 90503 | 23 |
| 301 | Nancy Gibbs | Valencia | CA | 91354 | 49 |
| 302 | Randy Park | Manhattan Beach | CA | 90267 | 21 |
| 303 | Victoria Loma | Rolling Hills | CA | 90274 | 75 |
+--------+-----------------+-----------------+-------+-------+------+
看起来行已成功插入。
从 Kudu 插入 SQL Server
启动 Spark 壳。
spark-shell -packages org.apache.kudu:kudu-spark_2.10:1.1.0 -driver-class-path sqljdbc41.jar -jars sqljdbc41.jar
从默认数据库中的 users 表创建一个 DataFrame。
import org.apache.kudu.spark.kudu._
val kuduDF = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.users")).kudu
验证数据帧的内容。
kuduDF.select("userid","name","city","state","zip","age").sort($"userid".asc).show()
+------+---------------+------------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+---------------+------------+-----+-----+---+
| 100| Wendell Ryan| San Diego| CA|92102| 24|
| 101|Alicia Thompson| Berkeley| CA|94705| 52|
| 102|Felipe Drummond| Palo Alto| CA|94301| 33|
| 103| Teresa Levine|Walnut Creek| CA|94507| 47|
+------+---------------+------------+-----+-----+---+
注册数据帧,以便我们可以对其运行 SQL 查询。
kuduDF.registerTempTable("kudu_users")
设置 SQL Server 数据库的 JDBC URL 和连接属性。
val jdbcURL = "jdbc:sqlserver://192.168.56.103;databaseName=salesdb;user=sa;password=cloudera"
val connectionProperties = new java.util.Properties()
import org.apache.spark.sql.SaveMode
为了确保我们的测试是一致的,使用 SQL Server Management Studio 确保 SQL Server 中的 users 表是空的(图 6-7 )。
图 6-7
Make sure the table is empty
将 Kudu 中的数据插入 SQL Server。
sqlContext.sql("select * from kudu_users").write.mode(SaveMode.Append).jdbc(jdbcURL, "users", connectionProperties)
再次检查 SQL Server 表,验证是否添加了行(图 6-8 )。
图 6-8
Check the table
恭喜你!数据已成功插入。
从 Kudu 插入 Oracle
我们需要做的第一件事是设置 Oracle 环境。我们将在名为 EDWPDB 的现有可插拔数据库中创建用户表。以 sysdba 身份登录并启动实例。如果您不熟悉 Oracle,请参考联机 Oracle 文档。
sqlplus / as sysdba
SQL*Plus: Release 12.2.0.1.0 Production on Sat May 6 18:12:45 2017
Copyright (c) 1982, 2016, Oracle. All rights reserved.
Connected to an idle instance.
SQL> startup
ORACLE instance started.
Total System Global Area 1845493760 bytes
Fixed Size 8793976 bytes
Variable Size 553648264 bytes
Database Buffers 1275068416 bytes
Redo Buffers 7983104 bytes
Database mounted.
Database opened.
SELECT name, open_mode FROM v$pdbs;
NAME OPEN_MODE
-------------------- ---------
PDB$SEED READ ONLY
ORCLPDB MOUNTED
EDWPDB MOUNTED
打开 EDWPDB 可插拔数据库,并将其设置为当前容器。
ALTER PLUGGABLE DATABASE EDWPDB OPEN;
SELECT name, open_mode FROM v$pdbs;
NAME OPEN_MODE
-------------------- -----
PDB$SEED READ ONLY
ORCLPDB MOUNTED
EDWPDB READ WRITE
ALTER SESSION SET container = EDWPDB;
创建 Oracle 表。
CREATE TABLE users (
userid NUMBER(19),
name VARCHAR(50),
city VARCHAR(50),
state VARCHAR (50),
zip VARCHAR(50),
age NUMBER(3));
启动 Spark 壳。不要忘记包括 oracle 驱动程序。在这个例子中,我使用的是 ojdbc6.jar。
Note
使用 ojdbc6.jar 驱动程序连接到 Oracle 12c R2 时,您可能会遇到错误“ORA-28040:没有匹配的认证协议异常”。这很可能是由 Oracle12c 中的一个错误导致的,错误 14575666。解决方法是设置 SQLNET。Oracle/network/admin/sqlnet . ora 文件中的 ALLOWED_LOGON_VERSION=8。
spark-shell -packages org.apache.kudu:kudu-spark_2.10:1.1.0 -driver-class-path ojdbc6.jar -jars ojdbc6.jar
从默认数据库中的 users 表创建一个 DataFrame。
import org.apache.kudu.spark.kudu._
val kuduDF = sqlContext.read.options(Map("kudu.master" ->
"kudumaster01:7051","kudu.table" -> "impala::default.users")).kudu
验证数据帧的内容。
kuduDF.select("userid","name","city","state","zip","age").sort($"userid".asc).show()
+------+---------------+------------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+---------------+------------+-----+-----+---+
| 100| Wendell Ryan| San Diego| CA|92102| 24|
| 101|Alicia Thompson| Berkeley| CA|94705| 52|
| 102|Felipe Drummond| Palo Alto| CA|94301| 33|
| 103| Teresa Levine|Walnut Creek| CA|94507| 47|
+------+---------------+------------+-----+-----+---+
注册数据帧,以便我们可以对其运行 SQL 查询。
kuduDF.registerTempTable("kudu_users")
设置 Oracle 数据库的 JDBC URL 和连接属性。
val jdbcURL = "jdbc:oracle:thin:sales/cloudera@//192.168.56.30:1521/EDWPDB"
val connectionProperties = new java.util.Properties()
import org.apache.spark.sql.SaveMode
使用 Oracle SQL Developer 确保 Oracle 中的用户表为空(图 6-9 )。
图 6-9
Make sure Oracle table is empty
将 Kudu 中的数据插入 Oracle。
sqlContext.sql("select * from kudu_users").write.mode(SaveMode.Append).jdbc(jdbcURL, "users", connectionProperties)
再次检查 Oracle 表,验证是否添加了行(图 6-10 )。
图 6-10
Check Oracle table
恭喜你!您已成功地将行从 Kudu 复制到 Oracle。
从 Kudu 插入到 HBase
我们将通过 Impala 插入数据 HBase,以便使用 SQL。这不是写入 HBase 的最快方法。如果性能很关键,我建议你使用 saveAsHadoopDataset 方法或者 HBase Java 客户端 API 来写入 HBase。将数据导入 HBase 还有其他各种方法。 ix
启动 spark-shell 并从 kudu 表创建一个数据帧。
spark-shell -driver-class-path ImpalaJDBC41.jar -jars ImpalaJDBC41.jar -packages org.apache.kudu:kudu-spark_2.10:1.1.0
import org.apache.kudu.client.CreateTableOptions;
import org.apache.kudu.spark.kudu._
val kuduDF = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.users")).kudu
验证表格的内容。
kuduDF.sort($"userid".asc).show()
+------+-----------------+---------------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+-----------------+---------------+-----+-----+---+
| 100| Wendell Ryan| San Diego| CA|92102| 24|
| 101| Alicia Thompson| Berkeley| CA|94705| 52|
| 102| Felipe Drummond| Palo Alto| CA|94301| 33|
| 103| Teresa Levine| Walnut Creek| CA|94507| 47|
| 200| Jonathan West| Frisco| TX|75034| 35|
| 201| Andrea Foreman| Dallas| TX|75001| 28|
| 202| Kirsten Jung| Plano| TX|75025| 69|
| 203| Jessica Nguyen| Allen| TX|75002| 52|
| 300| Fred Stevens| Torrance| CA|90503| 23|
| 301| Nancy Gibbs| Valencia| CA|91354| 49|
| 302| Randy Park|Manhattan Beach| CA|90267| 21|
| 303| Victoria Loma| Rolling Hills| CA|90274| 75|
| 400|Patrick Montalban| Los Angeles| CA|90010| 71|
| 401| Jillian Collins| Santa Monica| CA|90402| 45|
| 402| Robert Sarkisian| Glendale| CA|91204| 29|
| 403| Warren Porcaro| Burbank| CA|91523| 62|
+------+-----------------+---------------+-----+-----+---+
让我们注册该表,以便在查询中使用它。
kuduDF.registerTempTable("kudu_users")
使用 impala-shell,验证目标 HBase 表的内容。
select * from hbase_users order by userid;
+--------+-----+--------------+-------------------+-------+-------+
| userid | age | city | name | state | zip |
+--------+-----+--------------+-------------------+-------+-------+
| 400 | 71 | Los Angeles | Patrick Montalban | CA | 90010 |
| 401 | 45 | Santa Monica | Jillian Collins | CA | 90402 |
| 402 | 29 | Glendale | Robert Sarkisian | CA | 91204 |
| 403 | 62 | Burbank | Warren Porcaro | CA | 91523 |
+--------+-----+--------------+-------------------+-------+-------+
回到 Spark 壳,建立黑斑羚连接。
val jdbcURL = s"jdbc:impala://10.0.1.101:21050;AuthMech=0"
val connectionProperties = new java.util.Properties()
仅将所选行插入目标 HBase 表。
import org.apache.spark.sql.SaveMode
sqlContext.sql("select * from kudu_users where userid in (300,301,302,303)").write.mode(SaveMode.Append).jdbc(jdbcURL, "hbase_users", connectionProperties)
回到 impala-shell,确认这些行已经添加到目标 HBase 表中。
select * from hbase_users order by userid;
+--------+-----+-----------------+-------------------+-------+-------+
| userid | age | city | name | state | zip |
+--------+-----+-----------------+-------------------+-------+-------+
| 300 | 23 | Torrance | Fred Stevens | CA | 90503 |
| 301 | 49 | Valencia | Nancy Gibbs | CA | 91354 |
| 302 | 21 | Manhattan Beach | Randy Park | CA | 90267 |
| 303 | 75 | Rolling Hills | Victoria Loma | CA | 90274 |
| 400 | 71 | Los Angeles | Patrick Montalban | CA | 90010 |
| 401 | 45 | Santa Monica | Jillian Collins | CA | 90402 |
| 402 | 29 | Glendale | Robert Sarkisian | CA | 91204 |
| 403 | 62 | Burbank | Warren Porcaro | CA | 91523 |
+--------+-----+-----------------+-------------------+-------+-------+
数据已成功插入 HBase 表。
将 Kudu 中的行插入拼花地板
读表格。
spark-shell -packages org.apache.kudu:kudu-spark_2.10:1.1.0
import org.apache.kudu.client.CreateTableOptions;
import org.apache.kudu.spark.kudu._
val df = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.customers")).kudu
df.show
+---+------------+---+
| id| name|age|
+---+------------+---+
|103|Byron Miller| 25|
|101| Lisa Kim|240|
+---+------------+---+
注册该表,然后根据查询结果创建另一个数据帧。
df.registerTempTable("customers")
val df2 = sqlContext.sql("select * from customers where age=240")
检查数据。
df2.show
+---+--------+---+
| id| name|age|
+---+--------+---+
|101|Lisa Kim|240|
+---+--------+---+
将数据帧附加到镶木地板桌子上。您也可以使用关键词“覆盖”而不是“追加”来覆盖目的位置
df2.write.mode("SaveMode.Append").parquet("/user/hive/warehouse/Mytable")
你会发现 Spark 在给 HDFS 写信时会生成几十或几百个小文件。这就是所谓的“小文件”问题。 x 这最终会导致集群出现各种性能问题。如果发生这种情况,您可能需要使用联合或重新分区来指定要写入 HDFS 的文件数量。例如,您可能希望 Spark 向 HDFS 写一个拼花文件。
df2.coalesce(1).write.mode("SaveMode.Append").parquet("/user/hive/warehouse/Mytable")
使用合并和重新分区可能会导致性能问题,因为写入数据时实际上是降低了并行度。根据您正在处理的数据量,合并和重新分区还会触发可能导致性能问题的洗牌。您需要*衡生成文件的数量和处理性能。一段时间后,您可能仍然需要定期压实镶木地板。这是一个你在 Kudu 身上不会遇到的问题。
将 SQL Server 和 Oracle 数据帧插入 Kudu
我们将连接来自 SQL 和 Oracle 的数据,并将其插入 Kudu。
启动 Spark 壳。不要忘记包括必要的驱动程序和依赖项。
spark-shell -packages org.apache.kudu:kudu-spark_2.10:1.1.0 -driver-class-path ojdbc6.jar:sqljdbc41.jar -jars ojdbc6.jar,sqljdbc41.jar
设置 Oracle 连接。
val jdbcURL = "jdbc:oracle:thin:sales/cloudera@//192.168.56.30:1521/EDWPDB"
val connectionProperties = new java.util.Properties()
从 Oracle 表创建一个数据帧。
val oraDF = sqlContext.read.jdbc(jdbcURL, "users", connectionProperties)
oraDF.show
+------+---------------+------------+-----+-----+---+
|USERID| NAME| CITY|STATE| ZIP|AGE|
+------+---------------+------------+-----+-----+---+
| 102|Felipe Drummond| Palo Alto| CA|94301| 33|
| 103| Teresa Levine|Walnut Creek| CA|94507| 47|
| 100| Wendell Ryan| San Diego| CA|92102| 24|
| 101|Alicia Thompson| Berkeley| CA|94705| 52|
+------+---------------+------------+-----+-----+---+
注册该表,以便我们可以对其运行 SQL。
oraDF.registerTempTable("ora_users")
设置 SQL Server 连接。
val jdbcURL = "jdbc:sqlserver://192.168.56.103;databaseName=salesdb;user=sa;password=cloudera"
val connectionProperties = new java.util.Properties()
从 SQL Server 表创建数据帧。
val sqlDF = sqlContext.read.jdbc(jdbcURL, "userattributes", connectionProperties)
sqlDF.show
+------+------+------+------------------+
|userid|height|weight| occupation|
+------+------+------+------------------+
| 100| 175| 170| Electrician|
| 101| 180| 120| Librarian|
| 102| 180| 215| Data Scientist|
| 103| 178| 132|Software Developer|
+------+------+------+------------------+
注册该表,以便我们可以将其连接到 Oracle 表。
sqlDF.registerTempTable("sql_userattributes")
连接两张桌子。我们将把结果插入到 Kudu 表中。
val joinDF = sqlContext.sql("select ora_users.userid,ora_users.name,ora_users.city,ora_users.state,ora_users.zip,ora_users.age,sql_userattributes.height,sql_userattributes.weight,sql_userattributes.occupation from ora_users INNER JOIN sql_userattributes ON ora_users.userid=sql_userattributes.userid")
joinDF.show
+------+---------------+------------+-----+-----+---+------+------+-----------+
|userid| name| city|state| zip|age|height|weight| occupation|
+------+---------------+------------+-----+-----+---+------+------+-----------+
| 100| Wendell Ryan| San Diego| CA|92102| 24| 175| 170|Electrician|
| 101|Alicia Thompson| Berkeley| CA|94705| 52| 180| 120| Librarian|
| 102|Felipe Drummond| Palo Alto| CA|94301| 33| 180| 215| Data Scientist|
| 103| Teresa Levine|Walnut Creek| CA|94507| 47| 178| 132| Software Developer|
+------+---------------+------------+-----+-----+---+------+------+-----------+
您也可以使用此方法连接两个数据帧。
val joinDF2 = oraDF.join(sqlDF,"userid")
joinDF2.show
+------+---------------+------------+-----+-----+---+------+------+------------+
|userid| NAME| CITY|STATE| ZIP|AGE|height|weight| occupation|
+------+---------------+------------+-----+-----+---+------+------+------------+
| 100| Wendell Ryan| San Diego| CA|92102| 24| 175| 170| Electrician|
| 101|Alicia Thompson| Berkeley| CA|94705| 52| 180| 120| Librarian|
| 102|Felipe Drummond| Palo Alto| CA|94301| 33| 180| 215| Data Scientist|
| 103| Teresa Levine|Walnut Creek| CA|94507| 47| 178| 132| Software Developer|
+------+---------------+------------+-----+-----+---+------+------+------------+
在 Impala 中创建目标 Kudu 表。
impala-shell
create table users2 (
userid BIGINT PRIMARY KEY,
name STRING,
city STRING,
state STRING,
zip STRING,
age STRING,
height STRING,
weight STRING,
occupation STRING
)
PARTITION BY HASH PARTITIONS 16
STORED AS KUDU;
回到 spark-shell 并建立 Kudu 连接
import org.apache.kudu.spark.kudu._
val kuduContext = new KuduContext("kudumaster01:7051")
将数据插入 Kudu。
kuduContext.insertRows(JoinDF, "impala::default.users2")
确认数据已成功插入 Kudu 表。
impala-shell
select * from users2;
+------+---------------+------------+-----+------+---+------+------+----------+
|userid|name |city |state|zip |age|height|weight|occupation|
+------+---------------+------------+-----+------+---+------+------+----------+
|102 |Felipe Drummond|Palo Alto |CA |94301 |33 |180 |215 | Data Scientist|
|103 |Teresa Levine |Walnut Creek|CA |94507 |47 |178 |132 | Software Developer|
|100 |Wendell Ryan |San Diego |CA |92102 |24 |175 |170 |Electrician|
|101 |Alicia Thompson|Berkeley |CA |94705 |52 |180 |120 |Librarian |
+------+---------------+------------+-----+------+---+------+------+----------+
我觉得不错。
将 Kudu 和 SQL Server 数据帧插入 Oracle
使用 Oracle SQL Developer 在 Oracle 中创建目标表(图 6-11 )。
图 6-11
Create an Oracle table
启动 Spark 壳。不要忘记包括必要的驱动程序和依赖项。
spark-shell -packages org.apache.kudu:kudu-spark_2.10:1.1.0 -driver-class-path ojdbc6.jar:sqljdbc41.jar -jars ojdbc6.jar,sqljdbc41.jar
从默认数据库中的 Kudu users 表创建一个 DataFrame。
import org.apache.kudu.spark.kudu._
val kuduDF = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.users")).kudu
验证数据帧的内容。
kuduDF.select("userid","name","city","state","zip","age").sort($"userid".asc).show()
+------+---------------+------------+-----+-----+---+
|userid| name| city|state| zip|age|
+------+---------------+------------+-----+-----+---+
| 100| Wendell Ryan| San Diego| CA|92102| 24|
| 101|Alicia Thompson| Berkeley| CA|94705| 52|
| 102|Felipe Drummond| Palo Alto| CA|94301| 33|
| 103| Teresa Levine|Walnut Creek| CA|94507| 47|
+------+---------------+------------+-----+-----+---+
注册数据帧,以便我们可以对其运行 SQL 查询。
kuduDF.registerTempTable("kudu_users")
val jdbcURL = "jdbc:sqlserver://192.168.56.103;databaseName=salesdb;user=sa;password=cloudera"
val connectionProperties = new java.util.Properties()
从 SQL Server 表创建数据帧。
val sqlDF = sqlContext.read.jdbc(jdbcURL, "userattributes", connectionProperties)
sqlDF.show
+------+------+------+------------------+
|userid|height|weight| occupation|
+------+------+------+------------------+
| 100| 175| 170| Electrician|
| 101| 180| 120| Librarian|
| 102| 180| 215| Data Scientist|
| 103| 178| 132|Software Developer|
+------+------+------+------------------+
将数据帧注册为临时表。
sqlDF.registerTempTable("sql_userattributes")
连接两张桌子。我们将把结果插入 Oracle 数据库。
val joinDF = sqlContext.sql("select
kudu_users.userid,kudu_users.name,kudu_users.city,kudu_users.state,kudu_users.zip,kudu_users.age,sql_userattributes.height,sql_userattributes.weight,sql_userattributes.occupation from kudu_users INNER JOIN sql_userattributes ON kudu_users.userid=sql_userattributes.userid")
joinDF.show
+------+---------------+------------+-----+-----+---+------+------+-----------+
|userid| name| city|state| zip|age|height|weight|occupation |
+------+---------------+------------+-----+-----+---+------+------+-----------+
| 100| Wendell Ryan| San Diego| CA|92102| 24| 175| 170|Electrician|
| 101|Alicia Thompson| Berkeley| CA|94705| 52| 180| 120|Librarian |
| 102|Felipe Drummond| Palo Alto| CA|94301| 33| 180| 215| Data Scientist|
| 103| Teresa Levine|Walnut Creek| CA|94507| 47| 178| 132|Software Developer|
+------+---------------+------------+-----+-----+---+------+------+-----------+
使用这种方法可以获得相同的结果。
val joinDF2 = kuduDF.join(sqlDF,"userid")
joinDF2.show
+------+---------------+------------+-----+-----+---+------+------+-----------+
|userid| name| city|state| zip|age|height|weight| occupation|
+------+---------------+------------+-----+-----+---+------+------+-----------+
| 100| Wendell Ryan| San Diego| CA|92102| 24| 175| 170|Electrician|
| 101|Alicia Thompson| Berkeley| CA|94705| 52| 180| 120| Librarian|
| 102|Felipe Drummond| Palo Alto| CA|94301| 33| 180| 215| Data Scientist|
| 103| Teresa Levine|Walnut Creek| CA|94507| 47| 178| 132| Software Developer|
+------+---------------+------------+-----+-----+---+------+------+-----------+
设置 Oracle 数据库的 JDBC URL 和连接属性。
val jdbcURL = "jdbc:oracle:thin:sales/cloudera@//192.168.56.30:1521/EDWPDB"
val connectionProperties = new java.util.Properties()
import org.apache.spark.sql.SaveMode
将数据帧插入 Oracle。
joinDF.write.mode(SaveMode.Append).jdbc(jdbcURL, "users2", connectionProperties)
验证这些行是否已成功添加到 Oracle 数据库中(图 6-12 )。
图 6-12
Verify rows
Spark 流和 Kudu
在一些用例中,您需要*乎实时地将数据接收到 Kudu 中。例如,这是物联网(IoT)用例的要求。在清单 6-1 中,我们展示了一个从 Flume 获取传感器数据的样本 Spark 流应用程序。我们执行基本的事件流处理,根据传感器返回的温度将每个事件的状态标记为正常、警告或严重。然后,状态与其余数据一起保存在 Kudu 表中。
用户可以在向 Kudu 表中插入数据时对其进行查询。在第九章,我们讨论一个叫做 Zoomdata 的实时数据可视化工具。您可以使用 Zoomdata 实时可视化存储在 Kudu 中的数据。
import java.io.IOException;
import org.apache.spark._
import org.apache.spark.rdd.NewHadoopRDD
import org.apache.spark.SparkConf
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.flume._
import org.apache.spark.streaming.Seconds
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.util.IntParam
import org.apache.spark.sql.SQLContext
object FlumeStreaming {
case class SensorData(tableid: String, deviceid: String, thedate: String, thetime: String, temp: Short, status: String)
def parseSensorData(str: String): SensorData = {
val myData = str.split(",")
val myTableid = myData(0)
val myDeviceid = myData(1)
val myDate = myData(2)
val myTime = myData(3)
val myTemp = myData(4)
val myStatus = myData(5)
SensorData(myTableid, myDeviceid, myDate, myTime, myTemp.toShort, myStatus)
}
def main(args: Array[String]) {
val sparkConf = new SparkConf().setMaster("local[2]").setAppName("FlumeStreaming")
val sc = new SparkContext(sparkConf)
val ssc = new StreamingContext(sc, Seconds(1))
val flumeStream = FlumeUtils.createPollingStream(ssc,args(0),args(1).toInt)
val sensorDStream = flumeStream.map (x => new String(x.event.getBody.array)).map(parseSensorData)
sensorDStream.foreachRDD { rdd =>
val sqlContext = new SQLContext(sc)
import sqlContext.implicits._
val kuduContext = new KuduContext("kudumaster01:7051")
// convert the RDD into a DataFrame and insert it into the Kudu table
val DataDF = rdd.toDF
kuduContext.insertRows(DataDF, "impala::default.sensortable")
DataDF.registerTempTable("currentDF")
// Update the table based on the thresholds
val WarningFilteredDF = sqlContext.sql("select * from currentDF where temp > 50 and temp <= 60")
WarningFilteredDF.registerTempTable("warningtable")
val UpdatedWarningDF = sqlContext.sql("select tableid,deviceid,thedate,thetime,temp,'WARNING' as status from warningtable")
kuduContext.updateRows(UpdatedWarningDF, "impala::default.sensortable")
val CriticalFilteredDF = sqlContext.sql("select * from currentDF where temp > 61")
CriticalFilteredDF.registerTempTable("criticaltable")
val UpdatedCriticalDF = sqlContext.sql("select tableid,deviceid,thedate,thetime,temp,'CRITICAL' as status from criticaltable")
kuduContext.updateRows(UpdatedCriticalDF, "impala::default.sensortable")
}
ssc.start()
ssc.awaitTermination()
}
}
Listing 6-1Spark Streaming with Kudu
这是我的 SBT 档案。有关建筑应用的更多信息,请参考第五章。如果您正在使用 Maven,请查阅 Maven 的在线文档。
name := "My Test App"
version := "1.0"
scalaVersion := "2.10.5"
resolvers ++= Seq(
"Apache Repository" at "https://repository.apache.org/content/repositories/releases/",
"Cloudera repo" at "https://repository.cloudera.com/artifactory/cloudera-repos/"
)
libraryDependencies ++= Seq (
"org.apache.spark" % "spark-core_2.10" % "1.5.0",
"org.apache.spark" % "spark-streaming_2.10" % "1.5.0",
"org.apache.spark" % "spark-streaming-flume_2.10" % "1.5.0",
"org.apache.spark" % "spark-sql_2.10" % "1.5.0"
)
在执行 sbt package 来打包 spark 应用程序之后,现在可以使用 spark 附带的 spark-submit 工具启动应用程序。这些参数仅用于测试目的。根据您的数据接收要求更改参数。注意,我使用 jar 文件来包含 kudu-spark 依赖项。参数 localhost 和 9999 将被用作 Flume sink 目的地。
spark-submit \
-class FlumeStreaming \
-jars kudu-spark_2.10-0.10.0.jar \
-master yarn-client \
-driver-memory=512m \
-executor-memory=512m \
-executor-cores 4 \
/mydir/spark/flume_streaming_kudu/target/scala-2.10/butch-app_2.10-1.0.jar \ localhost 9999
这是一个使用 spooldir 作为数据源的样例 flume.conf 文件。这些设置足以满足测试目的。
agent1.sources = source1
agent1.channels = channel1
agent1.sinks = spark
agent1.sources.source1.type = spooldir
agent1.sources.source1.spoolDir = /tmp/streaming
agent1.sources.source1.channels = channel1
agent1.channels.channel1.type = memory
agent1.channels.channel1.capacity = 10000
agent1.channels.channel1.transactionCapacity = 1000
agent1.sinks.spark.type = org.apache.spark.streaming.flume.sink.SparkSink
agent1.sinks.spark.hostname = 127.0.0.1
agent1.sinks.spark.port = 9999
agent1.sinks.spark.channel = channel1
agent1.sinks.spark.batchSize=5
Kudu 作为 Spark MLlib 的特性库
Kudu 让数据科学家更容易准备和清理数据。Kudu 可以作为机器学习应用程序的快速、高度可扩展和可变的特征库。它还可以与 Spark SQL 和 DataFrame API 顺利集成。
让我们来看一个例子。我们将使用来自 UCI 机器学习知识库的心脏病数据集【Xi】来预测心脏病的存在。这些数据是由罗伯特德特拉诺,医学博士,博士,从弗吉尼亚州医学中心,长滩和克利夫兰诊所基金会收集的。历史上,克利夫兰数据集一直是众多研究的主题,因此我们将使用该数据集。原始数据集有 76 个属性,但其中只有 14 个是 ML 研究人员传统上使用的(表 6-1 )。我们将简单地执行二项式分类,并确定患者是否患有心脏病(列表 6-2 )。这是我们在第五章中使用的同一个例子,但是这次我们将使用 Kudu 来存储我们的特性。
表 6-1
Cleveland Heart Disease Data Set Attribute Information
| 属性 | 描述 | | :-- | :-- | | 年龄 | 年龄 | | 性 | 性 | | 丙酸纤维素 | 胸痛型 | | treatbps | 静息血压 | | 胆固醇 | 血清胆固醇(毫克/分升) | | 前沿系统 | 空腹血糖> 120 毫克/分升 | | 尊重 | 静息心电图结果 | | 塔尔巴赫 | 达到最大心率 | | 考试 | 运动诱发的心绞痛 | | 旧峰 | 相对于静息运动诱发的 ST 段压低 | | 倾斜 | 运动 ST 段峰值的斜率 | | 大约 | 荧光镜染色的主要血管数量(0-3) | | 塔尔 | 唐松草压力测试结果 | | 数字 | 预测属性——心脏病的诊断 |我们开始吧。我们需要下载该文件,将其复制到 HDFS,并在其上创建一个外部表。然后,我们将数据复制到 Kudu 表中。
wget http://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/cleveland.data
head -n 10 processed.cleveland.data
63.0,1.0,1.0,145.0,233.0,1.0,2.0,150.0,0.0,2.3,3.0,0.0,6.0,0
67.0,1.0,4.0,160.0,286.0,0.0,2.0,108.0,1.0,1.5,2.0,3.0,3.0,2
67.0,1.0,4.0,120.0,229.0,0.0,2.0,129.0,1.0,2.6,2.0,2.0,7.0,1
37.0,1.0,3.0,130.0,250.0,0.0,0.0,187.0,0.0,3.5,3.0,0.0,3.0,0
41.0,0.0,2.0,130.0,204.0,0.0,2.0,172.0,0.0,1.4,1.0,0.0,3.0,0
56.0,1.0,2.0,120.0,236.0,0.0,0.0,178.0,0.0,0.8,1.0,0.0,3.0,0
62.0,0.0,4.0,140.0,268.0,0.0,2.0,160.0,0.0,3.6,3.0,2.0,3.0,3
57.0,0.0,4.0,120.0,354.0,0.0,0.0,163.0,1.0,0.6,1.0,0.0,3.0,0
63.0,1.0,4.0,130.0,254.0,0.0,2.0,147.0,0.0,1.4,2.0,1.0,7.0,2
53.0,1.0,4.0,140.0,203.0,1.0,2.0,155.0,1.0,3.1,3.0,0.0,7.0,1
hadoop fs -put processed.cleveland.data /tmp/data
impala-shell
CREATE EXTERNAL TABLE cleveland_csv (
age float,
sex float,
cp float,
trestbps float,
chol float,
fbs float,
restecg float,
thalach float,
exang float,
oldpeak float,
slope float ,
ca float,
thal float,
num float
)
ROW FORMAT
DELIMITED FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n' STORED AS TEXTFILE
LOCATION '/tmp/data';
CREATE TABLE cleveland_kudu (
id string,
age float,
sex float,
cp float,
trestbps float,
chol float,
fbs float,
restecg float,
thalach float,
exang float,
oldpeak float,
slope float ,
ca float,
thal float,
num float,
primary key(id)
)
PARTITION BY HASH PARTITIONS 4
STORED AS KUDU
TBLPROPERTIES ('kudu.num_tablet_replicas' = '1');
INSERT INTO cleveland_kudu
SELECT
uuid(),
age,
sex
cp,
trestbps,
chol,
fbs,
restecg,
thalach,
exang,
oldpeak,
slope,
ca,
thal,
num
FROM
cleveland_csv;
注意,我们使用了 Impala 函数 uuid()为我们的 Kudu 表生成一个惟一的主键。Kudu 表现在应该有一些数据了。让我们使用 spark-shell 来拟合使用 Spark MLlib 的模型。
spark-shell -packages org.apache.kudu:kudu-spark_2.10:1.1.0
import org.apache.spark._
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SQLContext
import org.apache.spark.sql.functions._
import org.apache.spark.sql.types._
import org.apache.spark.sql._
import org.apache.spark.ml.classification.RandomForestClassifier
import org.apache.spark.ml.evaluation.BinaryClassificationEvaluator
import org.apache.spark.ml.feature.StringIndexer
import org.apache.spark.ml.feature.VectorAssembler
import org.apache.spark.ml.tuning.{ParamGridBuilder, CrossValidator}
import org.apache.spark.ml.{Pipeline, PipelineStage}
import org.apache.spark.mllib.evaluation.RegressionMetrics
import org.apache.spark.ml.param.ParamMap
import org.apache.kudu.spark.kudu._
val kuduDF = sqlContext.read.options(Map("kudu.master" -> "kudumaster01:7051","kudu.table" -> "impala::default.cleveland_kudu")).kudu
kuduDF.printSchema
root
|- id: string (nullable = false)
|- age: float (nullable = true)
|- sex: float (nullable = true)
|- cp: float (nullable = true)
|- trestbps: float (nullable = true)
|- chol: float (nullable = true)
|- fbs: float (nullable = true)
|- restecg: float (nullable = true)
|- thalach: float (nullable = true)
|- exang: float (nullable = true)
|- oldpeak: float (nullable = true)
|- slope: float (nullable = true)
|- ca: float (nullable = true)
|- thal: float (nullable = true)
|- num: float (nullable = true)
val myFeatures = Array("age", "sex", "cp", "trestbps", "chol", "fbs",
"restecg", "thalach", "exang", "oldpeak", "slope",
"ca", "thal", "num")
val myAssembler = new VectorAssembler().setInputCols(myFeatures).setOutputCol("features")
val kuduDF2 = myAssembler.transform(kuduDF)
val myLabelIndexer = new StringIndexer().setInputCol("num").setOutputCol("label")
val kuduDF3 = mylabelIndexer.fit(kuduDF2).transform(kuduDF2)
val kuduDF4 = kuduDF3.where(kuduDF3("ca").isNotNull).where(kuduDF3("thal").isNotNull).where(kuduDF3("num").isNotNull)
val Array(trainingData, testData) = kuduDF4.randomSplit(Array(0.8, 0.2), 101)
val myRFclassifier = new RandomForestClassifier().setFeatureSubsetStrategy("auto").setSeed(101)
val myEvaluator = new BinaryClassificationEvaluator().setLabelCol("label")
val myParamGrid = new ParamGridBuilder()
.addGrid(myRFclassifier.maxBins, Array(10, 20, 30))
.addGrid(myRFclassifier.maxDepth, Array(5, 10, 15))
.addGrid(myRFclassifier.numTrees, Array(20, 30, 40))
.addGrid(myRGclassifier.impurity, Array("gini", "entropy"))
.build()
val myPipeline = new Pipeline().setStages(Array(myRFclassifier))
val myCV = new CrosValidator()
.setEstimator(myPipeline)
.setEvaluator(myEvaluator)
.setEstimatorParamMaps(myParamGrid)
.setNumFolds(3)
Listing 6-2Performing binary classifcation using Kudu as a feature store
我们现在可以拟合模型了。
val myCrossValidatorModel = myCV.fit(trainingData)
我们来评价一下模型。
val myEvaluatorParamMap = ParamMap(myEvaluator.metricName -> "areaUnderROC")
val aucTrainingData = myEvaluator.evaluate(CrossValidatorPrediction, myEvaluatorParamMap)
你现在可以根据我们的数据做一些预测。
val myCrossValidatorPrediction = myCrossValidatorModel.transform(testData)
在我们的例子中,我们使用了一个非常小的数据集(300 多个观察值),但是想象一下,如果数据集包含数十亿行。如果需要添加或更新训练数据,只需对 Kudu 表运行 DML 语句。更新高度可扩展的存储引擎(如 Kudu)的能力极大地简化了数据准备和功能工程。
Note
Kudu 允许每个表最多 300 列。如果需要存储 300 个以上的特性,HBase 是更合适的存储引擎。HBase 表可以包含成千上万的列。使用 HBase 的缺点是,与 Kudu 相比,它在处理全表扫描时效率不高。Apache Kudu 社区正在讨论在 Kudu 的未来版本中解决 300 列的限制。
严格来说,可以通过设置不安全标志来绕过 Kudu 的 300 列限制。例如,如果您需要创建一个包含 1000 列的 Kudu 表,您可以使用以下标志启动 Kudu master:-unlock-unsafe-flags-max-num-columns = 1000。这还没有经过 Kudu 开发团队的彻底测试,因此不建议用于生产。
摘要
Spark,Impala,和 Kudu 是完美的搭配。Spark 提供了高度可扩展的数据处理框架,而 Kudu 提供了高度可扩展的存储引擎。有了 Impala 提供的快速 SQL 接口,您就拥有了实现经济高效的企业数据管理和分析*台所需的一切。当然,您并不局限于这三个 Apache 开源项目。在随后的章节中,我将介绍可以进一步增强您的大数据*台的其他开源和商业应用程序。
参考
- 阿帕奇库杜;《Kudu 与 Spark 的融合》,阿帕奇 Kudu,2018,
https://kudu.apache.org/docs/developing.html#_kudu_integration_with_spark
- 霍尔登·卡劳,雷切尔·沃伦;“高性能 Spark”,奥莱利,2017 年 6 月
https://www.safaribooksonline.com/library/view/high-performance-spark/9781491943199/
- 阿帕奇 Spark《JDBC 到其他数据库》,阿帕奇 Spark,2018,
http://spark.apache.org/docs/latest/sql-programming-guide.html#jdbc-to-other-databases
- 张占;《HBASE 星火:基于数据帧的 HBASE 连接器》,霍顿作品,2016,
https://hortonworks.com/blog/spark-hbase-dataframe-based-hbase-connector/
- 华为;《Astro:通过使用 Spark SQL 框架在 HBase 上实现高性能 SQL 层》,华为,2018,
http://huaweibigdata.github.io/astro/
- 泰德·马拉斯卡;“Apache Spark 携 HBase-Spark 模块来到 Apache HBase”,Cloudera,2018
https://blog.cloudera.com/blog/2015/08/apache-spark-comes-to-apache-hbase-with-hbase-spark-module/
- 阿帕奇 Lucene《使用 SolrJ》,阿帕奇·卢斯,2018,
https://lucene.apache.org/solr/guide/6_6/using-solrj.html
- Lucidworks《Lucidworks Spark/Solr 集成》,Lucidworks,2018,
https://github.com/lucidworks/spark-solr
- Cloudera《将数据导入 HBase》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/latest/topics/admin_hbase_import.html
- 汤姆·怀特;《小文件问题》,Cloudera,2009,
https://blog.cloudera.com/blog/2009/02/the-small-files-problem/
- 大卫·w·阿哈;“心脏病数据集”,加州大学欧文分校,1988 年,
http://archive.ics.uci.edu/ml/datasets/heart+Disease
七、批量和实时数据接收和处理
数据接收是将数据传输、加载和处理到数据管理或存储*台的过程。本章讨论了各种工具和方法,说明如何将数据批量和实时地摄取到 Kudu 中。我将介绍流行的 Hadoop 发行版自带的原生工具。我将举例说明如何使用 Spark 通过数据源 API 以及 Java、Python 和 C++中的 Kudu 客户端 API 将数据接收到 Kudu 中。有一组下一代商业数据摄取工具提供了原生 Kudu 支持。物联网也是一个热门话题。我将在本章中从流集开始详细讨论它们。
流集数据收集器
StreamSets Data Collector 是一个强大的企业级流*台,可用于接收、路由和处理来自各种来源的实时流和批处理数据。StreamSets 由 Informatica 前首席产品官 Girish Pancha 创立;以及 Arvind Prabhakar,Cloudera 的早期员工,他领导了 Apache Flume 和 Apache Sqoop 的开发。 i StreamSets 被 CBS Interactive、Cox Automotive、沃达丰等公司使用。 ii
Data Collector 可以执行各种数据浓缩、转换和流内清理,然后将数据写入大量目的地,如 HDFS、索尔、卡夫卡或库杜,而无需编写任何代码。对于更复杂的数据处理,可以用以下支持的语言和框架之一编写代码:Java、JavaScript、Jython (Python)、Groovy、Java 表达式语言(EL)和 Spark。数据收集器可以独立或集群模式运行,以支持最大的环境。
管道
为了接收数据,Data Collector 要求您设计一个管道。管道由多个阶段组成,您可以配置这些阶段来定义数据源(起点)、所需的任何数据转换或路由(处理器)以及要写入数据的位置(目标)。
设计好管道后,您可以启动它,它将立即开始接收数据。数据收集器将处于待机状态,一直静静地等待数据到达,直到您停止管道。您可以通过在数据被接收时检查数据或通过查看有关管道的实时指标来监视数据收集器。
起源
在 StreamSets 管道中,数据源称为源。它们是可配置的组件,无需任何编码就可以添加到画布中。StreamSets 包括几个源代码,可以节省您的开发时间和精力。一些可用的源包括 MQTT 订户、目录、文件尾、HDFS、S3、MongoDB、Kafka、RabbitMQ、MySQL 二进制博客、SQL Server 和 Oracle CDC 客户端等等。访问 StreamSets.com 获得支持来源的完整列表。
处理器
Processors 允许您对数据执行数据转换。一些可用的处理器是字段哈希器、字段掩码器、表达式计算器、记录去重器、JSON 解析器、XML 解析器和 JDBC 查找等等。有些处理器,比如流选择器,可以让您根据条件轻松地路由数据。此外,您可以使用能够基于自定义代码处理数据的赋值器。支持的语言和框架包括 JavaScript、Groovy、Jython 和 Spark。访问 StreamSets.com 获得支持的处理器的完整列表。
目的地
目的地是管道的目标。可用的目的地包括 Kudu、S3、Azure Data Lake Store、Solr、Kafka、JDBC Producer、Elasticsearch、Cassandra、HBase、MQTT Publisher 和 HDFS 等等。请访问 StreamSets.com,查看支持的目的地的完整列表。
实现者
执行器允许您在收到事件时运行 MapReduce 作业、Hive 查询、Shell 脚本或 Spark 应用程序等任务。
数据收集器控制台
控制台是数据采集器的主要用户界面(见图 7-1 )。这是所有活动发生的地方。这是您设计管道、配置阶段、运行管道和排除管道故障等的地方。
图 7-1
StreamSets Data Collector console
StreamSets 数据收集器可以实时或批量摄取数据(参见图 7-2 )。实时数据源或起源包括 MQTT、Kafka 和 Kinesis 等等。批量数据源包括 HDFS、S3、Oracle、SQL Server、MongoDB 等。数据最终会到达一个或多个目的地。在数据源和目的地之间是转换和处理流中数据的处理器。
图 7-2
Typical StreamSets Architecture
对于一些最常见的数据转换和丰富,有几个预构建的处理器。处理器的例子包括列拆分器和散列器、数据类型转换器、XML 解析器、JDBC 查找和流处理器等等。这些处理器无需编写一行代码就可以进行配置。但是,有时您需要编写代码,主要是因为现有的处理器都不能处理您需要的特定类型的转换。StreamSets 提供了支持 JavaScript、Java、Java EL、Spark、Jython 和 Groovy 的评估器。StreamSets 将所有这些阶段(起点、处理器、数据源)连接起来,形成一个管道。StreamSets 在内存中执行管道,提供最大的性能和可伸缩性。
实时流
如前几章所述,Kudu 特别适合于实时接收和处理,因为它支持高效的插入和更新以及快速的列数据扫描。 iii 对于实时工作负载,用户需要选择合适的目的地类型。有些存储引擎不是为实时流设计的。这些存储引擎的例子包括 HDFS 和 S3。
HDFS 是为面向批处理的工作负载而设计的。它以 128MB 块大小(可配置)拆分和写入数据,以实现高吞吐量并行处理。当 StreamSets 或 Flume 等实时数据接收工具开始以接收数据的速度向 HDFS 写入数据,以便让用户实时或接*实时地使用数据时,问题就出现了。这将导致 HDFS 产生大量的小文件。如前所述,HDFS 不是为处理小文件而设计的。ivHDFS 的每个文件、目录或块都有元数据存储在名称节点的存储器中。这些小文件消耗的内存量迟早会淹没 name 节点。小文件的另一个问题是访问它们的物理效率很低。应用程序需要执行比读取小文件所需更多的 IO。常见的解决方法是在 Spark 中使用 coalesce 或 repartition 来限制写入 HDFS 的文件数量,或者使用 Impala 定期压缩文件,但这并不总是可行的。
S3 有自己的 IO 特点,工作方式与 HDFS 不同。S3 是一个最终一致的对象存储。对于最终一致的文件系统,用户可能不会总是立即看到他们的更新。 v 这可能会导致从 S3 读写数据时出现不一致。通常,数据在写入 S3 后几毫秒或几秒钟内就会出现,但在某些情况下,某些数据可能需要长达 12 个小时才会出现并变得一致。在极少数情况下,一些物体可能需要 24 小时才能出现。VIS3 的另一个限制是延迟。与 HDFS 和其他存储系统相比,访问存储在 S3 上的数据要慢得多。
每个 S3 操作都是一个 API 调用,可能需要几十到几百毫秒。如果您正在处理流集中的数百万个对象,延迟将会增加,并可能成为性能瓶颈。 vii S3 非常适合存储备份或历史数据,但不适合存储流媒体数据。除了 Kudu,其他适合实时数据摄取的目的地包括 HBase 和 MemSQL 等等。
面向批处理的数据摄取
如前所述,StreamSets 还支持批处理工作负载。作为始发地和目的地,我建议大多数面向批量的工作使用 Kudu。Kudu 提供了 HDFS 的大部分好处,而且没有管理费用。Kudu 开发团队执行的性能基准表明,在某些操作中,Kudu 仍然比 HDFS(拼花地板)稍慢。如果性能差异对您的应用程序很重要(或者如果您正在处理非结构化数据),那么我建议您使用 HDFS。对于大多数应用程序来说,性能上的差异可能并不重要。在这种情况下,我建议你坚持使用 Kudu(见图 7-3 )。使用流集,批处理管道的构建方式与实时管道相同。唯一的区别是,当您使用面向批处理的源(如 S3、HDFS、RDBMS 或文件目录)时,StreamSets 会检测源的类型,并自动以批处理模式读取数据。
图 7-3
Batch data ingestion with StreamSets and Kudu
物联网
StreamSets 是数据摄取的瑞士军刀。除了众多的特性和功能,它还支持物联网或物联网 viii (见图 7-4 )。StreamSets 包括一个 MQTT 订阅者源和 MQTT 发布者目的地,这允许它被用作物联网网关。为了从 SCADA 网络和 OPC 历史记录中读取数据,StreamSets 包括一个 OPC UA 客户端源。最后,StreamSets 支持 CoAP(受限应用协议)。 ix CoAP 是一种用于低功耗和低带宽环境的协议,专为机器对机器设备通信而设计。在第九章中,我们使用 StreamSets、Kudu 和 Zoomdata 实现了一个完整的物联网数据摄取和可视化应用。
图 7-4
StreamSets IoT Architecture
部署选项
StreamSets 支持不同的部署选项,独立模式或集群模式。在独立模式下,StreamSets 数据收集器安装在一个或多个边缘节点上。这些边缘节点可以位于同一个数据中心,也可以位于地理位置不同的站点,只要网络能够支持管道的延迟和吞吐量要求。在集群模式下,StreamSets 管道作为 Spark 流应用进行部署和执行,利用 YARN 或 Mesos 作为其集群管理器,以充分利用 Hadoop 集群的可扩展性(参见图 7-5 )。
图 7-5
StreamSets Deployment Options
有关从 Tar ball(服务启动)、RPM 包(服务启动)和 Cloudera Manager(集群模式)安装流集的信息,请参考 Data Collector 用户指南。
使用流集数据收集器
让我们从创建第一个管道开始。导航到 StreamSets 数据收集器 URL,您将看到一个登录页面。默认用户名是“admin”默认密码是“admin”登录,然后点击“创建新管道”出现提示时,输入新管道的标题和描述(见图 7-6 )。
现在你可以设计你的第一个管道了。
图 7-6
StreamSets Console
向 kudu 投资 xml
在这个例子中,我们将把 XML 格式的传感器数据接收到一个 Kudu 表中。登录 StreamSets。在管道页面上,点击“创建新管道”按钮(见图 7-7 )。
图 7-7
New Pipeline
在数据采集器控制台中,单击位于“帮助”图标附*的“暂存库”图标(参见图 7-8 )。您将看到您可以使用的可用阶段列表。在阶段列表中选择“起源”。在这个例子中,我们将使用目录原点。有了目录源,复制到指定目录的文件将被流集接收。将目录原点拖到画布的右侧。
图 7-8
Directory Origin
接下来,我们需要添加一个处理器。在阶段库中选择“处理器”。我们将使用 XML 解析器来解析我们的 XML 数据,并将其转换成可以插入 Kudu 的格式。向下滚动,直到看到 XML 解析器图标。将 XML 解析器处理器拖到目录原点附*的画布上。将目录源连接到画布中的 XML 解析器处理器,如图 7-9 所示。您会注意到一个黄色的管道创建帮助栏。您可以使用帮助栏来帮助您选择阶段。现在,我们将忽略帮助栏。
图 7-9
XML Parser
我们需要一个目的地来完成我们的管道。在舞台库中选择“目的地”。我们将使用一个库都作为我们的目的地。将 Kudu 目的地拖到画布上,靠* XML 解析器处理器。将 XML 解析器连接到画布中的 Kudu 目的地,如图 7-10 所示。
图 7-10
Kudu destination
在开始配置我们的阶段之前,我们需要创建一个目录,它将作为我们的数据源和目标 Kudu 表的来源。我们还会准备一些测试数据。
打开 impala-shell 并创建一个表格。
CREATE TABLE sensordata
(
rowid BIGINT,
sensorid SMALLINT,
sensortimestamp STRING,
temperature TINYINT,
pressure TINYINT,
PRIMARY KEY(rowid)
)
PARTITION BY HASH PARTITIONS 16
STORED AS KUDU;
登录到 Cloudera 集群,创建一个包含我们的传感器数据的目录。
mkdir /sensordata
chown hadoop:hadoop /sensordata
让我们创建一些测试数据,我们将在稍后的练习中使用。
<sensordata><rowid>1000</rowid><sensorid>12</sensorid><sensortimestamp>20170409001904</sensortimestamp><temperature>23</temperature><pressure>30</pressure></sensordata>
<sensordata><rowid>1001</rowid><sensorid>39</sensorid><sensortimestamp>20170409001927</sensortimestamp><temperature>25</temperature><pressure>28</pressure></sensordata>
配置管道
通过单击管道的名称,可以为整个管道设置配置选项;在我们的例子中,点击面板左上方附*的“传感器数据 Kudu Pipeline”链接(见图 7-11 )。
图 7-11
Configure Pipeline
在“错误记录”选项卡中,将“错误记录”更改为“丢弃(库:基本)”(参见图 7-12 )。现在,保留其余的默认参数。
图 7-12
Error Records
配置目录来源
在管道画布中单击目录原始阶段。在属性面板中,导航到“配置”选项卡。在“配置”面板中,您会看到另一组选项卡(参见图 7-13 )。我们会仔细检查每个标签。
图 7-13
Configure Directory origin
“通用”允许用户为原点指定一个名称和描述。有一个选项可以让原点生成事件。如果启用“产生事件”,则每次目录源开始或结束读取文件时,目录源都会产生事件记录。您现在可以忽略这个配置项。“记录错误”允许您选择对发送至错误的记录采取的操作。默认选项是“发送到错误”其他选项是“放弃”和“停止管道”暂时保留默认选项。
“文件”选项卡包含与文件接收相关的各种配置选项。在我们的示例中,对我们来说最有趣的选项是“文件目录”,这是您想要放置想要摄取的文件的本地目录。“文件名模式”允许您选择指定的文件名模式是使用 regex 语法还是 glob 语法。暂时将其设置为“Glob”。“文件名模式”是定义目录中文件名模式的表达式。因为我们正在接收 XML 文件,所以让我们将模式设置为“*”。xml”。“读取顺序”控制数据收集器读取和处理文件的顺序。有两个选项,“最后修改的时间戳”和“按字典顺序升序排列的文件名”当使用按字典顺序升序排列的文件名时,将根据文件名按字典顺序升序读取文件。 x 需要注意的是,这个排序会把数字 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 排序为 1,10,11,12,13,14,15,2,3,4,5,6,7,8,9。如果必须按字典升序读取文件,请考虑在文件名前添加前导零,例如:file_001.xml、file_002.xml、file_003.xml、file_004.xml、file_010.xml、file_011.xml、file_012.xml。
当使用最后修改的时间戳时,使用该时间戳读取文件。时间戳相同的文件根据文件名排序。在本练习中,让我们将读取顺序设置为“上次修改时间戳”(见图 7-14 )。
图 7-14
Last Modified Timestamp
“后处理”选项卡在文件被读取和处理后为文件提供配置选项。“错误目录”可以配置为无法读取的文件的目的地。“文件后处理”提供了文件处理后要采取的选项。您可以在处理后存档或删除文件,也可以什么都不做。如果您选择“存档”,将会显示附加选项,例如“存档目录”和“存档保留时间(分钟)”(参见图 7-15 )。对于本例,我们将“文件后处理”设置为空。
图 7-15
Archive
“数据格式”选项卡允许您设置与数据格式相关的配置参数(参见图 7-16 )。我们最感兴趣的参数是“数据格式”注意,即使我们正在接收 XML 数据,我们也将把这个参数设置为“Text”我们接收的 XML 数据不是严格有效的 XML,因为它不包含根元素或 XML prolog。所以我们必须使用自定义分隔符来处理 XML 数据。Xi让我们勾选“使用客户分隔符”并指定一个客户分隔符;在我们的例子中,让我们将它设置为"< /sensordata >"最后,让我们检查“包括客户清单”我们暂时将其他参数保留为默认值。
图 7-16
Data Format
配置 XML 解析器处理器
单击画布中的 XML 解析器处理器,并导航到属性面板的“配置”部分。“General”选项卡将允许您为 XML 解析器处理器设置名称和描述。导航至“解析”选项卡,并将“要解析的字段”和“目标字段”设置为“/文本”(参见图 7-17 )。如前所述,目录源被配置为处理文本数据。它将记录写入一个名为“text”的文本字段 xii 这是 SDC 惯用的惯例。
图 7-17
Parse tab
当 Directory origins 处理我们的 XML 数据时,它将创建两个记录,用“.”分隔
<sensordata><rowid>1000</rowid><sensorid>12</sensorid><sensortimestamp>20170409001904</sensortimestamp><temperature>23</temperature><pressure>30</pressure></sensordata>
<sensordata><rowid>1001</rowid><sensorid>39</sensorid><sensortimestamp>20170409001927</sensortimestamp><temperature>25</temperature><pressure>28</pressure></sensordata>
在“配置”选项卡中,导航至 Kudu 并填写所需参数。“库都大师”的主机名和端口注意,因为我们在 Impala 中创建了“sensordata”表,所以它被认为是一个内部管理的表。在 Impala 中创建的 Kudu 表遵循命名约定“impala::database.table_name”将“表名”设置为“impala::default.sensordata”。您还需要设置字段到列的映射,如图 7-18 所示。SDC 字段的格式类似于“/text/column[0]/value”最后,您将“默认操作”设置为“插入”请注意,您可以选择执行其他操作,如向上插入、更新和删除。
图 7-18
Kudu tab
验证和预览管道
现在您已经完成了管道的设计,您需要检查并确保所有的配置都是有效的。单击画布右上角的 validate 图标来验证管道。一旦您完成了验证,您就可以预览您的管道来测试它,而不用实际运行它。预览管道是调试和单步调试管道的简单方法,允许您在每个阶段检查数据。 xiii 点击预览图标。将出现一个“预览配置”窗口(参见图 7-19 )。接受默认值并单击“运行预览”按钮。
图 7-19
Preview Configuration
您现在处于预览模式。单击目录原点。在预览阶段窗格中,您可以看到测试记录的列表(参见图 7-20 )。折叠每个记录会显示实际数据。请注意,第三条记录是空的。这将导致 XML 解析器处理器出错。
图 7-20
Preview – inspecting records
单击 XML 解析器处理器。在这里,您可以看到输入数据是如何处理的,以及产生的输出数据是什么样的。如您所见,XML 数据得到了正确处理,但正如所料,在处理空记录时出现了错误(参见图 7-21 )。
图 7-21
Preview – error processing empty record
正如所料,Kudu 目的地的输入数据是 XML 解析器的输出(见图 7-22 )。
图 7-22
Preview – Kudu destination
您也可以预览多个阶段。单击预览阶段面板顶部的“多个”。“预览多个阶段”面板允许您检查记录从一个阶段到另一个阶段的流程(参见图 7-23 )。
图 7-23
Preview – Multiple Stages
启动管道
现在,您已经准备好启动管道了。单击 start 图标,然后将我们的测试数据(file001.xml)复制到/sparkdata。
cat file001.xml
<sensordata><rowid>1000</rowid><sensorid>12</sensorid><sensortimestamp>20170409001904</sensortimestamp><temperature>23</temperature><pressure>30</pressure></sensordata>
<sensordata><rowid>1001</rowid><sensorid>39</sensorid><sensortimestamp>20170409001927</sensortimestamp><temperature>25</temperature><pressure>28</pressure></sensordata>
cp file001.xml /sparkdata
几秒钟后,您应该在监控面板上看到一些运行时统计数据,例如记录计数、记录吞吐量和批处理吞吐量等(参见图 7-24 )。
图 7-24
Monitoring pipeline
单击画布中的每个阶段,查看特定于该阶段的运行时统计信息。让我们点击位于画布中的目录原点(参见图 7-25 )。
图 7-25
Monitoring– Directory Origin
单击 XML 解析器处理器会得到一组不同的指标(参见图 7-26 )。
图 7-26
Monitoring – XML Parser
Kudu 目的地也有一套不同的指标。根据图 7-27 中的截图,在 sensordata Kudu 表中插入了两行。
图 7-27
Monitoring – Kudu destination
让我们验证这些行是否确实成功地插入到 Kudu 中。启动 impala-shell 并查询 sensordata 表。
select * from sensordata;
+-------+----------+-----------------+-------------+----------+
| rowid | sensorid | sensortimestamp | temperature | pressure |
+-------+----------+-----------------+-------------+----------+
| 1001 | 39 | 20170409001927 | 25 | 28 |
| 1000 | 12 | 20170409001904 | 23 | 30 |
+-------+----------+-----------------+-------------+----------+
行已成功添加到表中。尝试添加更多数据(参见图 7-28 )。添加更多数据后,您将看到图表和其他指标得到更新。
图 7-28
Monitoring pipeline after one hour
恭喜你!您已经成功构建并运行了第一个 StreamSets 数据管道。
流选择器
有时候,您需要根据条件将数据路由到某些流。例如,您可以根据“国家”向某些表中插入数据有一个默认流处理不匹配任何用户定义条件的记录。 十四
Stream 1: ${record:value("/Country")==’USA’}
Stream 2: ${record:value("/Country")==’CANADA’}
Stream 3: ${record:value("/Country")==’BRAZIL’}
Stream 4: default
在这个管道中,所有“Country”匹配美国的记录都进入流 1,加拿大进入流 2,巴西进入流 3。其他的都到流 4。您将能够使用流选择器来指定这些条件。
让我们设计另一个管道。让我们复制刚刚创建的管道,并修改它以使用流选择器,而不是从头开始。在这个例子中,我们将把三个不同版本的 XML 数据写到三个不同的 Kudu 表中。这是我们新的 XML 数据的样子。流选择器将检查 firmwareversion 的值。
cat file002.xml
<sensordata>
<rowid>1000</rowid>
<sensorid>12</sensorid>
<sensortimestamp>20170409001904</sensortimestamp>
<temperature>23</temperature>
<pressure>30</pressure>
<firmwareversion>1</firmwareversion>
</sensordata>
<sensordata>
<rowid>1001</rowid>
<sensorid>39</sensorid>
<sensortimestamp>20170409001927</sensortimestamp>
<temperature>25</temperature>
<pressure>28</pressure>
<firmwareversion>2</firmwareversion>
<humidity>110</humidity>
<ozone>31</ozone>
</sensordata>
<sensordata>
<rowid>1001</rowid>
<sensorid>39</sensorid>
<sensortimestamp>20170409001927</sensortimestamp>
<temperature>25</temperature>
<pressure>28</pressure>
<firmwareversion>3</firmwareversion>
<humidity>115</humidity>
<ozone>12</ozone>
<location>
<altitude>121</altitude>
<lat>37.8136</lat>
<long>144.9631</long>
</location>
</sensordata>
我们将创建另外两个 Kudu 表:sensordata2 和 sensordata3。我们还将在现有的 sensordata 表中添加 firmwareversion 列。我们总共有三个 sensordata 表,每个表都有一组不同的列。
CREATE TABLE sensordata2
(
rowid BIGINT,
sensorid SMALLINT,
sensortimestamp STRING,
temperature TINYINT,
pressure TINYINT,
firmwareversion TINYINT,
humidity TINYINT,
ozone TINYINT,
PRIMARY KEY(rowid)
)
PARTITION BY HASH PARTITIONS 16
STORED AS KUDU;
CREATE TABLE sensordata3
(
rowid BIGINT,
sensorid SMALLINT,
sensortimestamp STRING,
temperature TINYINT,
pressure TINYINT,
firmwareversion TINYINT,
humidity TINYINT,
ozone TINYINT,
altitude TINYINT,
lat FLOAT,
long FLOAT,
PRIMARY KEY(rowid)
)
PARTITION BY HASH PARTITIONS 16
STORED AS KUDU;
ALTER TABLE sensordata ADD COLUMNS (firmwareversion TINYINT);
将一个流选择器处理器和另外两个 Kudu 目的地拖到画布上。将 XML 解析器连接到流连接器。你需要定义三个条件:firmwareversion 为 1(或者 2 或 3 以外的任何值)的 XML 数据到缺省流(Kudu 1),firmwareversion 为 2 的 XML 数据到流 2 (Kudu 2),最后 firmwareversion 为 3 的 XML 数据到流 3 (Kudu 3)。
用这些条件向流选择器处理器添加另外两个条件。
${record:value('/text/firmwareversion[0]/value') == "3"}
${record:value('/text/firmwareversion[0]/value') == "2"}
您的新管道应该如图 7-29 所示。
图 7-29
Stream Selector
对于每个 Kudu 目的地,确保将正确的 SDC 字段映射到相应的列名(图 7-30 )。
图 7-30
First Kudu destination
请注意,第二个和第三个 Kudu 目的地将有额外的列(图 7-31 )。不要忘记将固件版本添加到第一个 Kudu 目的地(图 7-32 )。
图 7-32
Third Kudu destination
图 7-31
Second Kudu destination
验证并预览您刚刚创建的管道(图 7-33 )。确认每个 Kudu 目的地正在接收正确的 XML 数据(图 7-34 和 7-35 )。
图 7-35
Preview – Third Kudu destination
图 7-34
Preview – Second Kudu destination
图 7-33
Preview – First Kudu destination
在开始流水线之前,确认目的地 Kudu 表是空的。
select count(*) from sensordata;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
select count(*) from sensordata2;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
select count(*) from sensordata3;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
启动管道,然后将 file002.xml 复制到/sparkdata。几秒钟后,您将在 monitoring panel 中的图表上看到一些更新。单击流选择器处理器。正如您在记录计数图表中看到的,输入有三条记录,而三个输出各有一条记录。
图 7-36
Monitor Stream Selector (Figure 7-36)
检查每个 Kudu 目的地,查看记录是否被成功摄取。
图 7-37
Monitor Kudu destination (Figure 7-37)
检查 Kudu 表,确认记录已被成功摄取。
select * from sensordata;
+-------+----------+-----------------+-------------+
| rowid | sensorid | sensortimestamp | temperature |
+-------+----------+-----------------+-------------+
| 1000 | 12 | 20170409001904 | 23 |
+-------+----------+-----------------+-------------+
+-----------+-----------------+
| pressure | firmwareversion |
+-----------+-----------------+
| 30 | 1 |
+-----------+-----------------+
select * from sensordata2;
+-------+----------+-----------------+-------------+
| rowid | sensorid | sensortimestamp | temperature |
+-------+----------+-----------------+-------------+
| 1001 | 39 | 20170409001927 | 25 |
+-------+----------+-----------------+-------------+
+----------+-----------------+----------+-------+
| pressure | firmwareversion | humidity | ozone |
+----------+-----------------+----------+-------+
| 28 | 2 | 110 | 31 |
+----------+-----------------+----------+-------+
select * from sensordata3;
+-------+----------+-----------------+-------------+
| rowid | sensorid | sensortimestamp | temperature |
+-------+----------+-----------------+-------------+
| 1001 | 39 | 20170409001927 | 25 |
+-------+----------+-----------------+-------------+
+----------+-----------------+----------+
| pressure | firmwareversion | humidity |
+----------+-----------------+----------+
| 28 | 3 | 115 |
+----------+-----------------+----------+
+-------+----------+------------------+-------------------+
| ozone | altitude | lat | long |
+-------+----------+------------------+-------------------+
| 12 | 121 | 37.8135986328125 | 144.9631042480469 |
+-------+----------+------------------+-------------------+
表达式评估器
表达式计算器允许用户执行数据转换和计算,并将结果写入新的或现有的字段。用户还可以添加或更改字段属性和记录标题属性。 xv 我经常使用表达式计算器作为规则引擎。
让我们做一个新的例子。我们将使用表达式计算器来执行两个数据转换。我们将把温度从摄氏温度转换为华氏温度,并保存到现有的温度场。对于第二个转换,我们将根据压力字段的值,用值“正常”、“警告”或“严重”来更新一个新的状态字段。如果压力超过 40,则状态字段更新为“警告”,如果压力超过 50,则更新为“危急”我们开始吧。
向 Kudu sensordata 表中添加另一个“status”列。
ALTER TABLE sensordata ADD COLUMNS (status STRING);
DESCRIBE sensordata;
+-----------------+----------+-------------+
| name | type | primary_key |
+-----------------+----------+-------------+
| rowid | bigint | true |
| sensorid | smallint | false |
| sensortimestamp | string | false |
| temperature | tinyint | false |
| pressure | tinyint | false |
| firmwareversion | tinyint | false |
| status | string | false |
+-----------------+----------+-------------+
在这个例子中,我们将使用下面的 XML 数据。
cat file003.xml
<sensordata>
<rowid>1000</rowid>
<sensorid>12</sensorid>
<sensortimestamp>20170409001904</sensortimestamp>
<temperature>23</temperature>
<pressure>30</pressure>
<firmwareversion>1</firmwareversion>
</sensordata>
<sensordata>
<rowid>1001</rowid>
<sensorid>39</sensorid>
<sensortimestamp>20170409001927</sensortimestamp>
<temperature>25</temperature>
<pressure>28</pressure>
<firmwareversion>2</firmwareversion>
<humidity>110</humidity>
<ozone>31</ozone>
</sensordata>
<sensordata>
<rowid>1002</rowid>
<sensorid>39</sensorid>
<sensortimestamp>20170409001927</sensortimestamp>
<temperature>25</temperature>
<pressure>28</pressure>
<firmwareversion>3</firmwareversion>
<humidity>115</humidity>
<ozone>12</ozone>
<location>
<altitude>121</altitude>
<lat>37.8136</lat>
<long>144.9631</long>
</location>
</sensordata>
<sensordata>
<rowid>1003</rowid>
<sensorid>58</sensorid>
<sensortimestamp>20170409001930</sensortimestamp>
<temperature>22</temperature>
<pressure>44</pressure>
<firmwareversion>2</firmwareversion>
<humidity>112</humidity>
<ozone>17</ozone>
</sensordata>
<sensordata>
<rowid>1004</rowid>
<sensorid>72</sensorid>
<sensortimestamp>20170409001934</sensortimestamp>
<temperature>26</temperature>
<pressure>59</pressure>
<firmwareversion>2</firmwareversion>
<humidity>115</humidity>
<ozone>12</ozone>
</sensordata>
复制原始管道,并在 XML 解析器处理器和 Kudu 目的地之间添加一个表达式求值器处理器。连接阶段。你的画布看起来应该如图 7-40 所示。
图 7-38
Expression Evaluator
点击表达式求值器处理器(图 7-38 )并导航至“表达式”选项卡。我们将在“字段表达式”部分添加两个条目。对于第一个条目,在“输出字段”中输入以下值:
/text/temperature[0]/value
在“字段表达式”中,输入表达式:
${record:value('/text/temperature[0]/value') * 1.8 + 32}
这是把摄氏温度转换成华氏温度的公式。在“输出字段”中指定现有字段将覆盖字段中的值。
对于第二个条目,我们将使用 if-then-else 表达式。在“输出字段”中输入一个新字段:
/text/status
在“字段表达式”中输入表达式:
${record:value('/text/pressure[0]/value') > 50?'CRITICAL': (record:value('/text/pressure[0]/value') > 40?'WARNING':'NORMAL')}
你的屏幕应该如图 7-41 所示。
图 7-39
Expression Evaluator - Expression
将“/text/status”映射到 Kudu 目的地的新状态字段。
图 7-40
Map SDC field to Kudu field
验证和预览管道。单击表达式赋值器阶段,并确认输入和输出记录的值。请注意,输出记录 4 和记录 5 中的温度值已经转换为华氏温度。基于压力字段的值,输出记录 4 和记录 5 上的状态值分别更新为“警告”和“严重”。
图 7-41
Preview – Expression Evaluator
确认 Kudu 目的地中的值。
图 7-42
Preview – Kudu destination
运行管道并将 XML 文件复制到/sparkdata。
看起来所有五条记录都成功地插入到了 Kudu sensordata 表中。
图 7-43
Five records inserted into Kudu destination
确认数据已成功插入 Kudu。如您所见,状态字段已基于压力成功更新。另外,现在的温度是华氏温度。
SELECT * FROM sensordata;
+-------+----------+-----------------+-------------+
| rowid | sensorid | sensortimestamp | temperature |
+-------+----------+-----------------+-------------+
| 1002 | 39 | 20170409001927 | 77 |
| 1000 | 12 | 20170409001904 | 73 |
| 1001 | 39 | 20170409001927 | 77 |
| 1003 | 58 | 20170409001930 | 71 |
| 1004 | 72 | 20170409001934 | 78 |
+-------+----------+-----------------+-------------+
+----------+-----------------+----------+
|pressure | firmwareversion | status |
+----------+-----------------+----------+
|28 | 3 | NORMAL |
|30 | 1 | NORMAL |
|28 | 2 | NORMAL |
|44 | 2 | WARNING |
|59 | 2 | CRITICAL |
+----------+-----------------+----------+
使用 JavaScript 计算器
StreamSets 数据收集器包括一个 JavaScript 计算器,您可以使用它进行更复杂的处理和转换。它使用 JavaScript 代码一次处理一条记录或一批记录。 xvi 可以想象,与一次处理一批数据相比,一次处理一条记录很可能会更慢。在生产环境中,建议使用批处理。
虽然我们在这个例子中使用 JavaScript,但是 Data Collector 也支持其他语言。除了 JavaScript,您还可以使用 Java、Jython (Python)、Groovy 和 Spark 评估器。这些评估器使用户能够执行更高级的复杂流处理,充分利用每种编程语言的全部功能。
让我们从一个简单的例子开始。我们将创建两个附加字段,action 和 uniqueid。我们将基于 status 字段的值生成 action 的值。接下来,我们将使用定制的 JavaScript 函数生成一个通用唯一标识符(UUID)。我们将把这个 UUID 保存在我们唯一的字段中。
我们不必从头开始。我们将复制前面的管道,然后将一个 JavaScript 赋值器处理器拖到画布中,并放置在表达式赋值器处理器和 Kudu 目的地之间。重新连接舞台。你的画布看起来应该如图 7-46 所示。
图 7-44
JavaScript Evaluator
现在让我们配置 JavaScript 评估器,以便在管道中使用我们的定制 JavaScript 代码。在“属性”面板的“JavaScript”选项卡上,确保“记录处理模式”设置为“逐批”“脚本”将包含您的客户 JavaScript 代码。如前所述,我们将创建一个函数来生成 UUID。XVIIJavaScript 求值器将批处理作为数组传递给脚本。要访问单个字段,请使用以下格式:records[array index]. value . text . columname。要引用 status 字段的值,请使用“records[i].value.text.status”。我们还使用此格式创建了一个新字段“record[i].value.text.action”。见清单 7-1 。
function generateUUID() {
var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
});
return uuid;
};
for(var i = 0; i < records.length; i++) {
try {
var myUUID = generateUUID()
if (records[i].value.text.status == "NORMAL")
records[i].value.text.action = "None. Pressure is normal.";
else if (records[i].value.text.status == "WARNING")
records[i].value.text.action = "File a support ticket.";
else if (records[i].value.text.status == "CRITICAL")
records[i].value.text.action = "Inform your supervisor immediately!";
records[i].value.text.uniqueid = myUUID;
output.write(records[i]);
} catch (e) {
error.write(records[i], e);
}
}
Listing 7-1Javascript Evaluator code
你的屏幕应该如图 7-47 所示。
图 7-45
JavaScript Evaluator code
将 action 和 uniqueid 列添加到 Kudu sensordata 列中。
ALTER TABLE sensordata ADD COLUMNS (action STRING);
ALTER TABLE sensordata ADD COLUMNS (uniqueid STRING);
DESCRIBE sensordata;
+-----------------+----------+-------------+
| name | type | primary_key |
+-----------------+----------+-------------+
| rowid | bigint | true |
| sensorid | smallint | false |
| sensortimestamp | string | false |
| temperature | tinyint | false |
| pressure | tinyint | false |
| firmwareversion | tinyint | false |
| status | string | false |
| action | string | false |
| uniqueid | string | false |
+-----------------+----------+-------------+
将 SDC 字段与其对应的 Kudu 表列进行映射。
图 7-46
Map SDC field
验证和预览您的管道。单击 JavaScript 赋值器并确认输入和输出记录的值。请注意,已经填充了 action 和 unique 字段。
图 7-47
Preview values
确认 Kudu 目的地中的值。
图 7-48
Confirm values in Kudu destination
运行管道并将 XML 文件复制到/sparkdata。
看起来 JavaScript 评估器成功地处理了所有五条记录。
图 7-49
Monitor JavaScript Evaluator
所有五个记录也成功地插入到 Kudu sensordata 表中。
图 7-50
Monitor Kudu destination
确认数据已成功插入 Kudu。如您所见,action 和 uniqueid 字段已成功填充。
SELECT rowid, status, action, substr(1,10,uniqueid) as uniqueid FROM sensordata;
+-------+----------+-----------------------------------------+------------+
| rowid | status | action |uniqueid |
+-------+----------+-----------------------------------------+------------+
| 1002 | NORMAL | None. Pressure is normal. |35c47cb3-e9 |
| 1000 | NORMAL | None. Pressure is normal. |d3017138-09 |
| 1001 | NORMAL | None. Pressure is normal. |d91416e0-1b |
| 1003 | WARNING | WARNING. file a support ticket. |30aa86be-71 |
| 1004 | CRITICAL | CRITICAL. inform your supervisor immediately!|f7161ea3-eb |
+-------+----------+----------------------------------------+-------------+
恭喜你!您已经在管道中成功地使用了 JavaScript 评估器。
摄入多个 Kudu 集群
有时,出于高可用性和可扩展性的原因,您需要将数据接收到两个或更多主动-主动集群中。使用 Data Collector,只需将 Kudu 目的地添加到画布并将它们连接到处理器即可轻松完成。在本例中,我们将同时将 XML 数据接收到两个 Kudu 集群 kuducluster01 和 kuducluster02 中。
将第二个 Kudu 目的位置拖到画布上。您现在应该有两个 Kudu 目的地。确保它们都连接到 XML 处理器。
图 7-51
Multiple Kudu destinations
单击第一个 Kudu 目的地。确保在 Kudu Masters 字段中指定了正确的主机名和端口。确保 SDC 字段也映射到 Kudu 表字段。
图 7-52
Configure first Kudu destination
接下来,单击第二个 Kudu 目的地。执行与第一个 Kudu 目的地相同的任务。
图 7-53
Configure second Kudu destination
验证和预览管道。完成后,启动管道并开始添加数据。监视 XML 解析器。注意输入和输出数据的数量。
图 7-54
Monitor XML Parser
监控第一个库都目的地。请注意,它处理了 1,582,554 条记录。
图 7-55
Monitor first Kudu destination
检查第二个 Kudu 目的地,注意它也接收了相同数量的记录。
图 7-56
Monitor second Kudu destination
在黑斑羚验证。对第一个 Kudu 集群上的 sensordata 表执行选择计数。
select count(*) from sensordata;
+----------+
| count(*) |
+----------+
| 1582554 |
+----------+
对第二个 Kudu 集群执行相同的选择计数。
select count(*) from sensordata;
+----------+
| count(*) |
+----------+
| 1582554 |
+----------+
你同时摄入了两个库度星团的数据。您可以摄取到不同的*台组合,如 HBase、Cassandra、Solr、Kafka、S3、MongoDB 等。有关详细信息,请参考数据采集器用户指南。
应用程序接口
Data Collector 有一个易于使用的基于 web 的 GUI,用于设计、运行和管理管道。但是,如果您希望以编程方式与数据收集器进行交互,例如出于自动化的目的,您也可以使用内置的 REST API。REST API 允许您访问数据收集器的所有方面,从启动和停止管道、返回配置信息和监控管道指标。
您可以通过单击帮助图标,然后选择“RESTful API”来访问 REST API
图 7-57
REST API
您将看到一组可用的操作。点击“经理”展开经理列表您可以在“manager”组下探索不同的操作,例如返回管道状态、启动和停止管道等等。
图 7-58
List of available operations
展开“返回所有管道状态”选择响应内容类型“application/json ”,然后单击“试用”按钮。
“响应体”将包含 JSON 格式的管道细节,类似于清单 7-2 。根据管道数量和返回的错误消息类型,响应正文可能会非常大。
{
"47234db3-7a94-40ab-9465-2dc799e132e6": {
"user": "admin",
"name": "47234db3-7a94-40ab-9465-2dc799e132e6",
"rev": "0",
"status": "EDITED",
"message": "Pipeline edited",
"timeStamp": 1491742372750,
"attributes": {
"IS_REMOTE_PIPELINE": false
},
"executionMode": "STANDALONE",
"metrics": null,
"retryAttempt": 0,
"nextRetryTimeStamp": 0
},
"6c92cc6d-bdef-4b2b-ad62-69537e057128": {
"user": "admin",
"name": "6c92cc6d-bdef-4b2b-ad62-69537e057128",
"rev": "0",
"status": "EDITED",
"message": "Pipeline edited",
"timeStamp": 1491739709715,
"attributes": {
"IS_REMOTE_PIPELINE": false
},
"executionMode": "STANDALONE",
"metrics": null,
"retryAttempt": 0,
"nextRetryTimeStamp": 0
},
"de3b27ed-0a92-47cb-8400-da5aa4cdf43e": {
"user": "admin",
"name": "de3b27ed-0a92-47cb-8400-da5aa4cdf43e",
"rev": "0",
"status": "STOPPED",
"message": "The pipeline was stopped. The last committed source offset is 'file006.xml::-1'.",
"timeStamp": 1492067839465,
"attributes": {
"IS_REMOTE_PIPELINE": false
},
"executionMode": "STANDALONE",
"metrics": null,
"retryAttempt": 0,
"nextRetryTimeStamp": 0
},
"e4ded330-c573-4ab0-8fa8-004991493398": {
"user": "admin",
"name": "e4ded330-c573-4ab0-8fa8-004991493398",
"rev": "0",
"status": "STOPPED",
"message": "The pipeline was stopped. The last committed source offset is 'file006.xml::-1'.",
"timeStamp": 1492176332877,
"attributes": {
"IS_REMOTE_PIPELINE": false
},
"executionMode": "STANDALONE",
"metrics": null,
"retryAttempt": 0,
"nextRetryTimeStamp": 0
}
}
Listing 7-2Return all pipeline status response body
您可以使用 curl 发出 REST API 请求,curl 是一个可以使用标准协议下载数据的实用程序。用户名和密码以及自定义 HTTP 头属性(X-Requested-By)是必需的。
curl -u admin:mypassword http://localhost:18630/rest/v1/pipelines/status -H "X-Requested-By:myscript"
事件框架
StreamSets 有一个事件框架,允许用户启动任务来响应管道中发生的触发器或事件。StreamSets 使用数据流触发器来执行任务,如发送电子邮件、执行 shell 脚本、启动 JDBC 查询或在事件后启动 Spark 作业,例如,在管道成功完成 JDBC 查询后。
数据流性能管理器
StreamSets 最强大的功能之一是 StreamSets 数据流性能管理器。StreamSets data flow Performance Manager(DPM)是一个管理控制台,允许您管理复杂的数据流,为您的环境中所有正在运行的管道提供统一的视图。DPM 在管道监控和故障排除方面非常有用。当你的任务是监控数百甚至数千条管道时,你会更加体会到它的价值。介绍 DPM 超出了本书的范围。有关详细信息,请参阅数据采集器用户指南。
我只介绍了 StreamSets 特性和功能的一小部分。有关流集的详细信息,请参阅《流集数据采集器用户指南》。在第九章中,我使用 StreamSets、Zoomdata 和 Kudu 创建了一个完整的物联网(IoT)应用程序,该应用程序实时接收和可视化传感器数据。
其他下一代大数据集成工具
市场上还有其他下一代数据集成工具。我讨论了在木桶数据应用程序*台中一些最流行的选项。
木桶数据应用*台(CDAP)是一个开源*台,您可以使用 Spark 和 Hadoop 堆栈来开发大数据应用和 ETL 作业。CDAP 提供了一个基于 GUI 的数据摄取工作室,用于开发、部署和管理数据管道。其数据准备功能提供了一种交互式的数据清理和转换方法,这是一组通常称为数据整理的任务。CDAP 还有一个应用程序开发框架、用于快速应用程序开发和部署的高级 Java APIs。除此之外,它还具有对企业环境非常重要的元数据、数据沿袭和安全特性。和 StreamSets 一样,它也有对 Kudu 的本地支持。让我们开发一个 CDAP 管道将数据输入到 Kudu。
使用 Kudu 摄取数据
我们需要做的第一件事是为我们的例子准备测试数据。
cat test01.csv
1,Jeff Wells,San Diego,71
2,Nancy Maher,Van Nuys,34
3,Thomas Chen,Rolling Hills,62
4,Earl Brown,Artesia,29
hadoop fs -put test01.csv /mydata
要访问 CDAP,请使用端口 11011 将您的浏览器定向到安装该应用程序的服务器的主机名。第一次访问 CDAP 时,你会看到一个欢迎页面。
图 7-59
CDAP welcome page
关闭窗口,然后单击按钮“从向 CDAP 添加实体开始”你有五个选择。让我们创建一个管道。
图 7-60
Add Entity
将出现一个画布。这是您设计、部署和管理数据管道的地方。在画布的右侧,您可以选择不同的源、转换和接收器来设计您的管道。
图 7-61
CDAP canvas
你可能会注意到 Kudu 源和汇都不见了。我们需要添加酒桶市场的 Kudu 源和汇。单击应用程序窗口右上角附*的“木桶市场”链接。
向下滚动木桶市场的物品列表,直到你找到库杜源头和水槽。
图 7-62
Cask Market
单击 Kudu 源和接收器图标,然后单击“部署”
图 7-63
Cask Market – Kudu Source and Sink
点击“完成”
图 7-64
Finish installation
单击“创建管道”请注意,Kudu 图标现在可以用作数据源和接收器。
图 7-65
Kudu data source and sink
我们的示例 CDAP 管道将一个 CSV 文件接收到一个 Kudu 表中。但是在插入数据之前,我们将使用流行的散列算法对名称进行散列。让我们将一个“文件”源拖到画布上。
图 7-66
File source
双击文件源图标。输入文件属性,如文件名和文件在 HDFS 的路径。
图 7-67
File properties
CDAP 使得访问关于特定汇、源或转换的文档变得容易。
图 7-68
File Batch Source documentation
接下来,将一个 CSVParser 转换拖到画布上,并将其连接到文件源。
图 7-69
CSVParser transformation
输入 CSVParser 属性。确保输出模式具有正确的列。
图 7-70
Configure CSVParser
如果您需要帮助,CSV 解析器转换的文档是现成的。
图 7-71
CSVParser documentation
将 Hasher 转换拖到画布上,并将其与 CSVParser 连接起来。
图 7-72
Hasher transformation
通过选择哈希算法并指定要哈希的字段来配置哈希器。对于这个例子,我们将选择 MD5 作为我们的散列算法。
图 7-73
Hasher configuration
拖动 Kudu 水槽并将其连接到哈希器。
图 7-74
Kudu sink
配置 Kudu 接收器。注意,CDAP 使用 Kudu 原生 API 将数据插入到表中,而不是通过 Impala。CDAP 还将创建目标表,因此您只需要指定表名,而不是使用“impala::database _ name . table _ name”格式。在本例中,我们将使用表名“users_table”稍后,我们将在这个 Kudu 表的顶部创建一个外部 Impala 表。
图 7-75
Kudu sink configuration
Kudu sink 文档可供使用,以防您在某些选项上需要帮助。
图 7-76
Kudu sink documentation
你的画布看起来应该如图 7-77 所示。现在可以部署管道了。您可以先预览和验证管道,以确保没有错误。
图 7-77
Complete pipeline
最后,画布将向您显示从文件源一直到 Kudu 接收器传输和处理的记录数量。您会在画布的左上角附*看到一个指示器,指示作业是否成功。参见图 7-78 。
图 7-78
Number of records transferred and processed
检查日志并确认作业成功,如图 7-79 所示。
图 7-79
Check CDAP logs
确认行已成功插入到 Kudu 表中。首先,我们必须在 CDAP 创建的 Kudu 表之上用 Impala 创建一个外部表。确保名称字段是散列的。
impala-shell
CREATE EXTERNAL TABLE cdap_users
STORED AS KUDU
TBLPROPERTIES (
'kudu.table_name' = 'users_table'
);
SELECT * FROM cdap_users;
+----+----------------------------------+---------------+-----+
| id | name | city | age |
+----+----------------------------------+---------------+-----+
| 3 | dd500fc6d39cde55b6b4858e9854a21d | Rolling Hills | 62 |
| 1 | 228b855279d81c5251cff62e2b503079 | San Diego | 71 |
| 4 | 332035b1942026174865ede2021dad2a | Artesia | 29 |
| 2 | 8283a7fa1a09657dcc62125f8d734a7e | Van Nuys | 34 |
+----+----------------------------------+---------------+-----+
您已经成功地将数据接收到一个 Kudu 表中,使用一个字段散列器对数据执行流内转换。
Pentaho 数据集成
Pentaho 为数据集成、大数据处理和商业分析提供完整的产品系列。在本章中,我们将重点介绍 Pentaho 数据集成(PDI)。有一个社区版叫 Pentaho 社区版(CE),里面有 Kettle,Pentaho 数据集成的开源版本。有一个企业版,包括原生纱集成、分析器和仪表板增强、高级安全性和高可用性特性。 xix
Pentaho PDI 有一个直观的用户界面,具有现成的和易于使用的组件,以帮助您开发数据摄取管道。与市场上大多数 ETL 工具类似,Pentaho PDI 允许您连接到不同类型的数据源,从 Oracle、SQL Server、MySQL 和 Teradata 等流行的 RDBMS 到 HDFS、HBase、Cassandra 和 MongoDB 等大数据和 NoSQL *台。PDI 包括用于协调和管理工作流的集成企业流程编排和调度功能。您可以毫不费力地在 Pentaho 的本地执行引擎和 Apache Spark 之间切换,以扩展您的管道来处理大量数据。
将 CSV 摄入 HDFS 和库都
与本章描述的其他数据集成工具不同,Pentaho 还没有包含对 Kudu 的本地支持。为了将数据插入到 Kudu 中,我们需要使用 Pentaho 的通用表格输出组件和 Impala 的 JDBC 驱动程序。根据数据的大小和数据接收要求,直接使用表输出可能不够快。提高性能的一种方法是首先将数据转移到 HDFS,然后使用表输出将数据从 HDFS 接收到 Kudu。在某些情况下,这可能比直接摄入 Kudu 更快。
我们需要做的第一件事是为我们的例子准备测试数据。
cd /mydata
cat test01.csv
id,name,city,age
1,Jeff Wells,San Diego,71
2,Nancy Maher,Van Nuys,34
3,Thomas Chen,Rolling Hills,62
4,Earl Brown,Artesia,29
开始彭塔霍 PDI。在这个例子中,我们将使用社区版(Kettle)。导航到安装二进制文件的位置并执行。见图 7-80 。
图 7-80
Start Pentaho Data Integration
勺子图形用户界面如图 7-81 所示。这是你设计和构建工作和转型的地方。
图 7-81
Graphical view
在窗口的左侧,您可以找到 ETL 开发中所有支持的输入、输出和转换步骤的列表。展开“输入”并将“CSV 文件输入”步骤拖动到画布上,如图 7-82 所示。
图 7-82
CSV file input
双击该图标。输入配置详细信息,如文件名、分隔符等。参见图 7-83 。
图 7-83
Configure CSV file input
点击“获取字段”让 Pentaho 推断字段类型和字段大小。参见图 7-84 。
图 7-84
Get fields
展开“大数据”,拖动“Hadoop 文件输出”到画布上,如图 7-85 所示。
图 7-85
Hadoop file output
将“CSV 文件输入”连接到“Hadoop 文件输出”,如图 7-86 所示。
图 7-86
Connect CSV file input to Hadoop file output
双击“Hadoop 文件输出”组件来配置目标。输入所有必需的信息,例如集群的名称、HDFS 命名节点的地址等等。参见图 7-87 。
图 7-87
Configure Hadoop file output
向 Hadoop 文件输出阶段输入附加参数。在我们的例子中,我们将根据自己的日期时间格式重命名文件。参见图 7-88 。
图 7-88
Rename file based on date time format
指定字段的详细信息,如数据类型、格式、长度、精度等。参见图 7-89 。
图 7-89
Configure fields
您可以预览和检查您的数据。参见图 7-90 。
图 7-90
Preview data
PDI 最有用的功能之一是能够监控工作的执行指标。试着做一次测试,确保数据从源传输到目的地。见图 7-91 。
图 7-91
Run the job
导航到执行结果面板中的“指标”,如图 7-92 所示。
图 7-92
Execution Results
仔细看,它显示了工作中每一步需要多长时间。在本例中,整个作业执行了 412 毫秒。初始化转换耗时 122 毫秒,其中一部分耗时 122 毫秒,初始化 CSV 文件输入步骤耗时 3 毫秒,而初始化 Hadoop 文件输出步骤耗时 96 毫秒。CSV 文件输入步骤的实际执行耗时 15 毫秒,而 Hadoop 文件输出的执行耗时 251 毫秒。参见图 7-93 。
图 7-93
Metrics – Execution Results
导航到“日志记录”选项卡以检查应用程序日志。如图 7-94 所示,可以看到作业成功执行。
图 7-94
Logging – Execution Results
确认文件确实被复制到 HDFS。
hadoop fs -ls /proddata
-rw-r--r-- 3 hadoop supergroup 129 2017-05-13 00:05/proddata/20170513000514.txt
hadoop fs -cat /proddata/20170513000514.txt
id,name,city,age
1,Jeff Wells,San Diego,71
2,Nancy Maher,Van Nuys,34
3,Thomas Chen,Rolling Hills,62
4,Earl Brown,Artesia,29
让我们配置最终目的地。将表格输出步骤拖到画布上。双击图标并开始配置步骤。我们来配置一下 Impala 驱动。从 Cloudera.com 下载黑斑羚 JDBC 驱动程序,并复制到
图 7-95
配置数据库连接
配置 JDBC 驱动程序。输入 Impala 服务器的主机名或 IP 地址、TCP/UP 端口和数据库名称,如图 7-96 所示。
图 7-96
Configure JDBC settings
测试数据库连接,如图 7-97 所示。
图 7-97
Test the database connection
创建目标表。
impala-shell
CREATE TABLE pentaho_users
(
id BIGINT,
name STRING,
city STRING,
age TINYINT,
PRIMARY KEY(id)
)
PARTITION BY HASH PARTITIONS 4
STORED AS KUDU;
输入目标表和其他相关配置选项。参见图 7-98 。
图 7-98
Configure table output
点击“获取字段”并确保源和目标字段匹配,如图 7-99 所示。
图 7-99
Get fields
如图 7-100 所示,对数据进行预览和完整性检查。
图 7-100
Preview data
执行作业。监控日志并确保作业成功执行。参见图 7-101 。
图 7-101
Run the job
作业运行成功。
2017/05/13 00:21:16 - Spoon - Transformation opened.
2017/05/13 00:21:16 - Spoon - Launching transformation [csv_to_kudu1]...
2017/05/13 00:21:16 - Spoon - Started the transformation execution.
2017/05/13 00:21:16 - csv_to_kudu1 - Dispatching started for transformation [csv_to_kudu1]
2017/05/13 00:21:16 - Table output.0 - Connected to database [Impala_Kudu] (commit=1000)
2017/05/13 00:21:16 - CSV file input.0 - Header row skipped in file '/mydata/test01.csv'
2017/05/13 00:21:16 - CSV file input.0 - Finished processing (I=5, O=0, R=0, W=4, U=0, E=0)
2017/05/13 00:21:16 - Hadoop File Output.0 - Finished processing (I=0, O=5, R=4, W=4, U=0, E=0)
2017/05/13 00:21:16 - Table output.0 - Finished processing (I=0, O=4, R=4, W=4, U=0, E=0)
2017/05/13 00:21:16 - Spoon - The transformation has finished!!
确认数据已成功插入 Kudu 表。
impala-shell
select * from pentaho_users;
+----+-------------+---------------+-----+
| id | name | city | age |
+----+-------------+---------------+-----+
| 2 | Nancy Maher | Van Nuys | 34 |
| 3 | Thomas Chen | Rolling Hills | 62 |
| 1 | Jeff Wells | San Diego | 71 |
| 4 | Earl Brown | Artesia | 29 |
+----+-------------+---------------+-----+
通过转换将数据摄取到 Kudu
先说另一个例子。这次我们将使用字符串替换转换将字符串“Yonkers”替换为“Berkeley”
准备数据。
ls /mydata
test01.csv test02.csv
cat test01.csv
id,name,city,age
1,Jeff Wells,San Diego,71
2,Nancy Maher,Van Nuys,34
3,Thomas Chen,Rolling Hills,62
4,Earl Brown,Artesia,29
cat test02.csv
id,name,city,age
5,Damian Lee,Yonkers,27
6,John Lohan,Encino,55
7,Lily Tran,Reseda,50
8,Sam Estevez,Tucson,81
输入源目录和正则表达式(通配符)来搜索文件。参见图 7-102 。
图 7-102
Specify source directory and file
预览和健全检查数据。参见图 7-103 。
图 7-103
Preview data
将“替换字符串”转换步骤拖到画布上。双击图标,通过指定要搜索的字符串和要替换的字符串来配置它。如前所述,我们将在 city 字段中用“Berkeley”替换字符串“Yonkers”。您可以暂时忽略其他选项。参见图 7-104 。
图 7-104
Replace a string transformation
如图 7-105 所示运行作业。
图 7-105
Run the job
检查表格,确保将“扬克斯”替换为“伯克利”将 id 5 行中的城市值与原始源文本文件进行比较。
impala-shell
select * from pentaho_users order by id;
+----+-------------+---------------+-----+
| id | name | city | age |
+----+-------------+---------------+-----+
| 1 | Jeff Wells | San Diego | 71 |
| 2 | Nancy Maher | Van Nuys | 34 |
| 3 | Thomas Chen | Rolling Hills | 62 |
| 4 | Earl Brown | Artesia | 29 |
| 5 | Damian Lee | Berkeley | 27 |
| 6 | John Lohan | Encino | 55 |
| 7 | Lily Tran | Reseda | 50 |
| 8 | Sam Estevez | Tucson | 81 |
+----+-------------+---------------+-----+
SQL Server 到 Kudu
在本例中,我们将展示如何将数据从 RDBMS (SQL Server 2016)接收到 Kudu。
请确保源 SQL Server 2016 数据库中有数据。我们将使用在其他示例中使用的相同的表。在 salesdb 数据库中,有一个名为 users 的表,包含以下行。请使用 SQL Server Management Studio 进行确认。参见图 7-106 。
图 7-106
Check the table in SQL Server Management Studio
在 Spoon 中,将一个表格输入步骤拖到画布上,如图 7-107 所示。
图 7-107
Table input
配置表格输入步骤。参见图 7-108 。
图 7-108
Configure Table input
确保通过将 sqljdbc41.jar (JDK 1.7)或 sqljdbc42.jar (JDK 1.8)复制到
图 7-109
配置数据库连接
配置 JDBC 设置(图 7-110 )。
图 7-110
Configure JDBC settings
测试与源 SQL Server 2016 数据库的连接(图 7-111 )。
图 7-111
Test database connection
现在您已经配置了到源数据库的连接,指定一个 SQL 查询作为您的数据源。参见图 7-112 。
图 7-112
Specify SQL query as data source
预览您的数据以确保您的 SQL 查询有效(图 7-113 )。
图 7-113
Preview data
将一个表格输出步骤拖到您的画布上,并将其连接到表格输入步骤(图 7-114 )。
图 7-114
Table output
配置您的目的地。输入目标模式和目标表。如果你还没有这样做,配置你的黑斑羚 JDBC 连接以及图 7-115 。
图 7-115
Configure Table output
运行作业(图 7-116 )。
图 7-116
Run the job
检查日志中的错误。
2017/06/03 20:31:17 - Spoon - Transformation opened.
2017/06/03 20:31:17 - Spoon - Launching transformation [SQL Server to Kudu]...
2017/06/03 20:31:17 - Spoon - Started the transformation execution.
2017/06/03 20:31:17 - SQL Server to Kudu - Dispatching started for transformation [SQL Server to Kudu]
2017/06/03 20:31:17 - Table output.0 - Connected to database [Impala] (commit=1000)
2017/06/03 20:31:17 - Table input.0 - Finished reading query, closing connection.
2017/06/03 20:31:17 - Table input.0 - Finished processing (I=4, O=0, R=0, W=4, U=0, E=0)
2017/06/03 20:31:18 - Table output.0 - Finished processing (I=0, O=4, R=4, W=4, U=0, E=0)
2017/06/03 20:31:18 - Spoon - The transformation has finished!!
确认数据已成功插入 Kudu 表。
impala-shell
select * from users;
+--------+-----------------+--------------+-------+-------+-----+
| userid | name | city | state | zip | age |
+--------+-----------------+--------------+-------+-------+-----+
| 102 | Felipe Drummond | Palo Alto | CA | 94301 | 33 |
| 100 | Wendell Ryan | San Diego | CA | 92102 | 24 |
| 101 | Alicia Thompson | Berkeley | CA | 94705 | 52 |
| 103 | Teresa Levine | Walnut Creek | CA | 94507 | 47 |
+--------+-----------------+--------------+-------+-------+-----+
恭喜你!您已成功将数据从 SQL Server 2016 导入 Kudu。
拓蓝
Talend 是专注于大数据集成的领先软件公司之一。Talend 提供免费的开源数据摄取工具,称为 Open Studio for Big Data 和 Open Studio for Data Integration。这两个工具都提供了现代图形用户界面、YARN 支持、HDFS、HBase、Hive 和 Kudu 支持、到 Oracle、SQL Server 和 Teradata 的连接器,并在 Apache 许可证 v2 下完全开源。xxOpen Studio for Data Integration 和 Open Studio for Big Data 的主要区别在于,数据集成只能生成原生 Java 代码,而 Big Data 可以同时生成原生 Java、Spark 和 MapReduce 代码。
商业版为您提供实时 Talend 支持、有保证的响应时间、升级和产品补丁。开源版本只提供社区支持。 xxi 如果你处理的是 TB 级或者 Pb 级的数据,我建议你用大数据版。对于传统的 ETL 类型的工作负载,比如将数据从 RDBMS 移动到 Kudu,中间有一些简单的数据转换,Data Integration edition 就足够了。本章我们将使用 Talend Open Studio 进行数据集成。
Note
Talend Kudu 组件由第三方公司 One point Ltd .提供。这些组件可从 Talend Exchange-https://exchange.talend.com/
免费下载。在将 Talend 与 Kudu 配合使用之前,需要安装 Kudu 输出和输入组件。
将 csv 文件输入到 kudu
让我们从一个熟悉的将 CSV 文件摄取到 Kudu 的例子开始。
准备测试数据。
ls /mydata
test01.csv
cat test01.csv
id,name,city,age
1,Jeff Wells,San Diego,71
2,Nancy Maher,Van Nuys,34
3,Thomas Chen,Rolling Hills,62
4,Earl Brown,Artesia,29
在 Impala 中创建目标 Kudu 表。
impala-shell
CREATE TABLE talend_users
(
id INTEGER,
name STRING,
city STRING,
age INTEGER,
PRIMARY KEY(id)
)
PARTITION BY HASH PARTITIONS 4
STORED AS KUDU;
启动 Talend Open Studio 进行数据集成,如图 7-117 所示。
图 7-117
Start Talend Open Studio for Data Integration
您可以选择创建新项目或打开现有项目。让我们开始一个新的项目(图 7-118 )。
图 7-118
Select an existing project or create a new project
你会看到一个类似于图 7-119 的图形用户界面。
图 7-119
Talend Open Studio Graphical User Interface
让我们创建一个新任务(图 7-120 )。
图 7-120
Create job
指定作业的名称。您可以指定其他属性,如目的、描述、作者等。完成后点击“完成”(图 7-121 )。
图 7-121
Specify job name and other job properties
你会看到一个类似于图 7-122 的画布。这是您设计和运行作业的地方。
图 7-122
Job canvas
与本章前面讨论的数据接收工具类似,在设计数据接收管道时,您必须指定源、转换步骤和目标。在画布的右侧,您会看到一个可以用作数据源的输入列表。因为我们正在接收 CSV 文件,所以将 tFileInputDelimited 源拖放到画布中。通过指定 CSV 文件的文件名来配置源文件。参见图 7-123 。
图 7-123
tFileInputDelimited source
接下来,通过将 Kudu 输出拖放到画布中来指定输出。通过指定表名、连接信息等来配置 Kudu 输出(图 7-124 )。
图 7-124
Kudu output
不要忘记连接输入和输出图标(图 7-125 )。
图 7-125
Connect File Input delimited and Kudu output
如图 7-126 所示运行作业。
图 7-126
Run the job
日志将显示作业运行时的相关信息。当作业完成时,您会看到一个退出代码。退出代码为零表示作业成功完成。
Starting job CSV_to_Impala at 23:20 14/05/2017.
[statistics] connecting to socket on port 3725
[statistics] connected
330 [New I/O worker #1] INFO org.apache.kudu.client.AsyncKuduClient - Discovered tablet Kudu Master for table 'Kudu Master' with partition [<start>, <end>)
399 [New I/O worker #1] INFO org.apache.kudu.client.AsyncKuduClient - Discovered tablet 4bcfc5b62a284ea0b572d8201aea0aa5 for table 'impala::default.talend_users' with partition [0x00000001, 0x00000002)
400 [New I/O worker #1] INFO org.apache.kudu.client.AsyncKuduClient - Discovered tablet 42d775f9402b45d18e1d1c22ca61ed22 for table 'impala::default.talend_users' with partition [0x00000002, 0x00000003)
400 [New I/O worker #1] INFO org.apache.kudu.client.AsyncKuduClient - Discovered tablet 6a0e39ac33ff433e8d8242ca0ea2bee8 for table 'impala::default.talend_users' with partition [0x00000003, <end>)
453 [New I/O worker #1] INFO org.apache.kudu.client.AsyncKuduClient - Discovered tablet ffbf9021409f445fae04b1f35c318567 for table 'impala::default.talend_users' with partition [<start>, 0x00000001)
[statistics] disconnected
Job CSV_to_Impala ended at 23:20 14/05/2017\. [exit code=0]
确认这些行已成功插入 Kudu 表中。
impala-shell
select * from talend_users;
+----+-------------+---------------+-----+
| id | name | city | age |
+----+-------------+---------------+-----+
| 3 | Thomas Chen | Rolling Hills | 62 |
| 4 | Earl Brown | Artesia | 29 |
| 1 | Jeff Wells | San Diego | 71 |
| 2 | Nancy Maher | Van Nuys | 34 |
+----+-------------+---------------+-----+
SQL Server 到 Kudu
对于我们的第二个例子,让我们使用 Talend Open Studio 将数据从 SQL Server 接收到 Kudu。在 Impala 中创建 Kudu 表 DimGeography,如果您还没有这样做的话。
创建一个新任务(图 7-127 )。
图 7-128
tMSSQLinput. See Figure 7-128.
图 7-127
New job
通过确保指定了正确的数据类型等来配置组件(图 7-129 )。
图 7-129
Configure MS SQL input
将 Kudu 输出组件拖放到画布中。如图 7-130 所示,将其连接到 tMSSQLinput 组件。
图 7-130
Kudu output
配置 Kudu 输出,确保数据类型与 SQL Server 表匹配,如图 7-131 所示。
图 7-131
Configure Kudu output
如果模式不匹配,则同步模式(图 7-132 )。
图 7-132
Sync the schema
如图 7-133 所示运行作业。
图 7-133
Run the job
通过比较源和目标行数来验证作业是否成功运行(图 7-134 )。
图 7-134
Verify if the job ran successfully
impala-shell
SELECT count(*) FROM DimGeography;
+----------+
| count(*) |
+----------+
| 655 |
+----------+
数据转换
现在让我们使用 Talend 的一些内置特性进行数据转换。我将使用 tReplace 组件替换指定输入列中的值。我们将用“英国”替换值“英国”我还将使用 tFilterRow 组件来过滤结果,只包括城市等于“伦敦”或“伯克希尔”的记录
将 tReplace 和 tFilterRow 组件拖放到画布中,在输入和输出之间,如图 7-136 所示。
通过选择包含您想要替换的值的字段来配置 tReplace 组件(图 7-135 )。
图 7-135
Select field
通过指定要替换的值和替换值来配置 tReplace 组件。在这种情况下,我们将用“英国”替换“英国”(图 7-136 )。
图 7-136
Configure tReplace
配置 tFilterRow 组件。我们将只返回城市字段中等于“伦敦”和“伯克希尔”的行(图 7-137 )。
图 7-137
Configure tFilterRow
不要忘记如图 7-138 所示连接所有组件。
图 7-138
Connect tFilterRow to Kudu output
如图 7-139 所示运行作业。
图 7-139
Run the job
检查 Kudu 表中的数据,以确保作业成功执行。从结果中可以看出,只返回了 city 等于 London 和 Berkshire 的记录。英国也被英国取代。
impala-shell
SELECT geographykey as gkey, city, stateprovincecode as spc, stateprovincename as spn, countryregioncode as crc, englishcountryregionname as ecr, postalcode as pc FROM DimGeography;
+------+-----------+-----+---------+-----+-----+----------+
| gkey | city | spc | spn | crc | ecr | pc |
+------+-----------+-----+---------+-----+-----+----------+
| 246 | London | ENG | England | GB | UK | EC1R 0DU |
| 250 | London | ENG | England | GB | UK | SW6 SBY |
| 254 | London | ENG | England | GB | UK | W1N 9FA |
| 257 | London | ENG | England | GB | UK | W1Y 3RA |
| 230 | Berkshire | ENG | England | GB | UK | RG11 5TP |
| 244 | London | ENG | England | GB | UK | C2H 7AU |
| 248 | London | ENG | England | GB | UK | SW19 3RU |
| 249 | London | ENG | England | GB | UK | SW1P 2NU |
| 251 | London | ENG | England | GB | UK | SW8 1XD |
| 252 | London | ENG | England | GB | UK | SW8 4BG |
| 253 | London | ENG | England | GB | UK | W10 6BL |
| 256 | London | ENG | England | GB | UK | W1X3SE |
| 245 | London | ENG | England | GB | UK | E17 6JF |
| 247 | London | ENG | England | GB | UK | SE1 8HL |
| 255 | London | ENG | England | GB | UK | W1V 5RN |
+------+-----------+-----+---------+-----+-----+----------+
Fetched 15 row(s) in 4.50s
其他大数据集成参与者
如果我没有提到传统的 ETL 播放器,这一章将是不完整的。它们是经过增强的工具,包括大数据集成。即便如此,与我刚刚讨论的较新的大数据集成工具相比,它们在原生功能方面仍然落后。例如,大多数都缺乏本地 Spark 支持和到流行大数据源的连接器。
信息
Informatica 是世界上最大的软件开发公司,专门从事数据集成。该公司成立于 1993 年,总部位于加利福尼亚州的红木城。Informatica 还为主数据管理、数据质量、b2b 数据交换、数据虚拟化等开发软件。XXIIInformatica power center 大数据版是公司的大数据集成旗舰产品。与本章描述的其他 ETL 工具一样,Informatica PowerCenter 大数据版具有易于使用的可视化开发环境。Informatica PowerCenter 大数据版与领先的传统和大数据*台紧密集成,使您可以轻松地计划、管理和监控整个企业的流程和工作流。XXIII在撰写本文时,Informatica PowerCenter 还没有原生的 Kudu 支持;但是你可以通过 Impala 和 JDBC/ODBC 使用 Informatica PowerCenter 将数据导入 Kudu。
Microsoft SQL Server 集成服务
如果我没有提到前三大企业软件开发公司,微软、IBM 和 Oracle,这个列表将是不完整的。SQL Server Integration Services(SSIS)包括支持 Hadoop 和 HDFS 数据集成的功能。SSIS 提供了 Hadoop 连接管理器和以下控制流任务:Hadoop 文件系统任务、Hadoop Hive 任务和 Hadoop Pig 任务。SSIS 支持以下数据源和目标:HDFS 文件源和 HDFS 文件目标。和本章描述的所有 ETL 工具一样,SSIS 也有一个 GUI 开发环境。 xxiv 但是你可以通过 Impala 和 JDBC/ODBC 将数据导入 Kudu。
面向大数据的 Oracle 数据集成器
Oracle Data Integrator for Big Data 为大数据*台提供了高级数据集成功能。它支持各种工作负载,包括 Spark、Spark Streaming 和 Pig 转换,并连接到各种大数据源,如 Kafka 和 Cassandra。对于 ODI 作业的编排,用户可以选择使用 ODI 代理或 Oozie 作为编排引擎。 xxv 在撰写本文时,Oracle Data Integrator 还没有原生的 Kudu 支持;但是,您可以通过 Impala 和 JDBC/ODBC 使用 Oracle Data Integrator 将数据导入 Kudu。
IBM InfoSphere DataStage
IBM InfoSphere DataStage 是 IBM InfoSphere Information Server 附带的数据集成工具。它支持提取、转换和加载(ETL)以及提取、加载和转换(ELT)模式。 xxvi 它提供一些大数据支持,比如访问和处理 HDFS 上的文件,将表从 Hive 移动到 RDBMS。XXVIIIBM infosphereinformationserver 操作控制台可用于监控和管理 DataStage 作业,包括监控作业日志和资源使用情况。出现运行时问题时,操作控制台还有助于解决问题。 xxviii 截至本文撰写之时,DataStage 还没有原生的 Kudu 支持;但是,您可以使用 DataStage 通过 Impala 和 JDBC/ODBC 将数据导入 Kudu。
备份表达
Syncsort 是另一家专注于大数据集成的领先软件开发公司。他们以对大型机的广泛支持而闻名。到 2013 年,Syncsort 将自己定位为“大铁到大数据”的数据集成公司。XXIX
Syncsort 的大数据集成*台 Syncsort DMX-h 有一个批处理和流式接口,可以从多个来源收集不同类型的数据,如 Kafka、HBase、RDBMS、S3 和大型机。与本章描述的其他 ETL 解决方案一样,Syncsort 为 ETL 开发提供了一个易于使用的 GUI。 xxxi 截至本文写作时,DMX-h 还没有原生 Kudu 支持;但是你可以通过 Impala 和 JDBC/ODBC 使用 DMX-h 将数据导入 Kudu。
阿帕奇尼菲
Apache NIFI 是一个开源的实时数据摄取工具,主要用于 Hortonworks 环境,尽管它也可以用于从 Cloudera 和 MapR 等其他*台摄取数据。它在许多方面与 StreamSets 相似。NIFI 的主要限制之一是缺少纱线支撑。NIFI 作为一个独立的 JVM 进程或多个 JVM 进程运行,如果配置为集群模式的话。在撰写本文时,NIFI 还没有本地的 Kudu 支持,尽管开源社区正在努力。查看 NIFI-3973 了解更多详情。XXXIII
使用本机工具摄取数据
十年前,你必须是 Java 开发人员,才能用 Hadoop 接收和处理数据。通常需要几十行代码来执行简单的数据接收或处理。今天,由于各种 Apache 项目的成千上万的提交者和贡献者,Hadoop 生态系统有了一大堆(有人说太多了)用于数据摄取和处理的原生工具。其中一些工具可用于将数据导入 Kudu。我将使用 Flume、Kafka 和 Spark 展示示例。如果您想开发自己的数据摄取例程,Kudu 提供了 Spark、Java、C++和 Python 的 API。
Kudu 和 Spark
Kudu 提供了一个 Spark API,您可以使用它将数据接收到 Kudu 表中。在下面的示例中,我们将连接存储在 SQL Server 数据库中的一个表和存储在 Oracle 数据库中的另一个表,并将连接的数据插入到 Kudu 表中。第六章对 Kudu 和 Spark 进行了更深入的讨论。
启动 Spark 壳。不要忘记包括必要的驱动程序和依赖项。
spark-shell --packages org.apache.kudu:kudu-spark_2.10:1.1.0 --driver-class-path ojdbc6.jar:sqljdbc41.jar --jars ojdbc6.jar,sqljdbc41.jar
设置 Oracle 连接
val jdbcURL = "jdbc:oracle:thin:sales/cloudera@//192.168.56.30:1521/EDWPDB"
val connectionProperties = new java.util.Properties()
从 Oracle 表创建一个数据帧。
val oraDF = sqlContext.read.jdbc(jdbcURL, "users", connectionProperties)
oraDF.show
+------+---------------+------------+-----+-----+---+
|USERID| NAME| CITY|STATE| ZIP|AGE|
+------+---------------+------------+-----+-----+---+
| 102|Felipe Drummond| Palo Alto| CA|94301| 33|
| 103| Teresa Levine|Walnut Creek| CA|94507| 47|
| 100| Wendell Ryan| San Diego| CA|92102| 24|
| 101|Alicia Thompson| Berkeley| CA|94705| 52|
+------+---------------+------------+-----+-----+---+
注册该表,以便我们可以对其运行 SQL。
oraDF.registerTempTable("ora_users")
设置 SQL Server 连接。
val jdbcURL = "jdbc:sqlserver://192.168.56.103;databaseName=salesdb;user=sa;password=cloudera"
val connectionProperties = new java.util.Properties()
从 SQL Server 表创建数据帧。
val sqlDF = sqlContext.read.jdbc(jdbcURL, "userattributes", connectionProperties)
sqlDF.show
+------+------+------+------------------+
|userid|height|weight| occupation|
+------+------+------+------------------+
| 100| 175| 170| Electrician|
| 101| 180| 120| Librarian|
| 102| 180| 215| Data Scientist|
| 103| 178| 132|Software Developer|
+------+------+------+------------------+
注册该表,以便我们可以将其连接到 Oracle 表。
sqlDF.registerTempTable("sql_userattributes")
连接两张桌子。我们将把结果插入到 Kudu 表中。
val joinDF = sqlContext.sql("select ora_users.userid,ora_users.name,ora_users.city,ora_users.state,ora_users.zip,ora_users.age,sql_userattributes.height,sql_userattributes.weight,sql_userattributes.occupation from ora_users INNER JOIN sql_userattributes ON ora_users.userid=sql_userattributes.userid")
joinDF.show
+------+---------------+------------+-----+-----+---+------+------+-----------+
|userid| name| city|state| zip|age|height|weight| occupation|
+------+---------------+------------+-----+-----+---+------+------+-----------+
| 100| Wendell Ryan| San Diego| CA|92102| 24| 175| 170|Electrician|
| 101|Alicia Thompson| Berkeley| CA|94705| 52| 180| 120| Librarian|
| 102|Felipe Drummond| Palo Alto| CA|94301| 33| 180| 215|Data Scientist|
| 103| Teresa Levine|Walnut Creek| CA|94507| 47| 178| 132|Software Developer|
+------+---------------+------------+-----+-----+---+------+------+-----------+
您也可以使用以下方法连接数据帧。
val joinDF2 = oraDF.join(sqlDF,"userid")
joinDF2.show
+------+---------------+------------+-----+-----+---+------+------+-----------+
|userid| NAME| CITY|STATE| ZIP|AGE|height|weight|occupation |
+------+---------------+------------+-----+-----+---+------+------+-----------+
| 100| Wendell Ryan| San Diego| CA|92102| 24| 175| 170|Electrician|
| 101|Alicia Thompson| Berkeley| CA|94705| 52| 180| 120|Librarian |
| 102|Felipe Drummond| Palo Alto| CA|94301| 33| 180| 215|Data Scientist|
| 103| Teresa Levine|Walnut Creek| CA|94507| 47| 178| 132|Software Developer|
+------+---------------+------------+-----+-----+---+------+------+-----------+
在 Impala 中创建目标 Kudu 表。
impala-shell
create table users2 (
userid BIGINT PRIMARY KEY,
name STRING,
city STRING,
state STRING,
zip STRING,
age STRING,
height STRING,
weight STRING,
occupation STRING
)
PARTITION BY HASH PARTITIONS 16
STORED AS KUDU;
回到 spark-shell 并建立 Kudu 连接。
import org.apache.kudu.spark.kudu._
val kuduContext = new KuduContext("kuducluster:7051")
将数据插入 Kudu。
kuduContext.insertRows(JoinDF, "impala::default.users2")
确认数据已成功插入 Kudu 表。
impala-shell
select * from users2;
+------+--------------+------------+-----+------+---+------+------+-----------+
|userid|name |city |state|zip |age|height|weight|occupation|
+------+--------------+------------+-----+------+---+------+------+-----------+
|102 |Felipe Drummond|Palo Alto |CA |94301 |33 |180 |215 |Data Scientist|
|103 |Teresa Levine |Walnut Creek|CA |94507 |47 |178 |132 |Software Developer|
|100 |Wendell Ryan |San Diego |CA |92102 |24 |175 |170 |Electrician|
|101 |Alicia Thompson|Berkeley |CA |94705 |52 |180 |120 |Librarian |
+-----+---------------+------------+-----+------+---+------+------+-----------+
水槽,卡夫卡和 Spark 流
使用 Flume、Kafka 和 Spark 流进行实时数据接收和事件处理是一种常见的架构模式。阿帕奇水槽
允许用户收集、转换和移动大量的流媒体和事件数据到 HDFS、HBase 和 Kafka 等等。 xxxiv 它是大多数 Hadoop 发行版的集成组件。Flume 的架构可以分为源、通道和接收器。源是你的数据源,信道在源和接收器之间提供中间缓冲并提供可靠性,接收器代表你的目的地。Flume 有一个简单的架构,如图 7-140 所示。
图 7-140
Flume Architecture
使用一个简单的配置文件来配置 Flume。示例配置文件允许您从脚本生成事件,然后将它们记录到控制台。
# Name the components
agent1.sources = source1
agent1.sinks = sinks1
agent1.channels = channel1
# Configure the source
agent1.sources.source1.type = exec
agent1.sources.source1.command = /tmp/myscript.sh
agent1.sources.source1.channels = channel1
# Configure the channel
agent1.channels.channel1.type = memory
agent1.channels.channel1.capacity = 10000
agent1.channels.channel1.transactionCapacity = 1000
# Configure the sink
agent1.sinks.sinks1.type = logger
agent1.sinks.sinks1.channel = channel1
要了解有关 Flume 的更多信息,Hari Shreedharan 的《使用 Flume 》( O ' Reilly,2014)是权威指南。
阿帕奇卡夫卡
Kafka 是一个快速、可伸缩、可靠的分布式发布-订阅消息系统。Kafka 现在是需要实时数据摄取和流式传输的架构的标准组件。尽管不是必需的,Kafka 经常与 Apache Flume、Storm、Spark Streaming 和 StreamSets 一起使用。
Kafka 作为一个集群在一个或多个代理上运行,并具有内置的复制和分区,如图 7-141 所示。
图 7-141
Kafka Producers and Consumers
卡夫卡的唱片是作为主题出版的。每个主题都被分割并不断追加。分区中的每条记录都被分配一个唯一的偏移量来标识每条记录。 xxxv 把题目想成表格,把偏移量想成主键(图 7-142 )。
图 7-142
Kafka Topic
要了解更多关于卡夫卡的知识,夏皮罗、纳克赫德和帕林的《卡夫卡:权威指南》(O'Reilly,2017 年)是最好的资源。
弗拉斯卡
当 Kafka 在 2011 年首次发布时,用户必须编写 Java 代码才能将数据输入 Kafka 或从中读取数据。不幸的是,这个需求减缓了 Kafka 的采用,因为不是每个人都能用 Java 编写代码。必须有一种更简单的方式让应用程序与 Kafka 交互。如前所述,使用 Flume 可以轻松完成这些任务(图 7-143 )。
图 7-143
A typical Flafka pipeline
Cloudera 的工程师和开源社区的其他人认识到了 Flume 与 Kafka 集成的好处,因此他们开发了 Flume 和 Kafka 集成,通常称为 Flafka。从 CDH 5.2 开始,Flume 可以充当卡夫卡的消费者和生产者。随着水槽 1.6 和 CDH 5.3 的进一步发展,卡夫卡作为水槽通道的功能已经被添加。XXXVI
Spark 流
从历史上看,Kafka 没有为流处理提供任何机制。Kafka Streams 最*发布,提供基本的流处理功能。Flume 也有拦截器,可以用于轻量级的流处理。然而,由于 Apache Spark 的流行,水槽拦截器最*已经过时了。由于 Spark Streaming 与 Spark SQL、Spark MLlib 和 Spark 的其他功能相集成,Spark Streaming 比 Kafka Streams 和 Flume 拦截器更强大(图 7-144 )。与拦截器不同,Spark Streaming 不需要 Flume,可以与 MQTT、Kafka 和 Kinesis 等其他数据源集成。如果您有一个不支持的数据源,并希望与 Spark Streaming 集成,那么您可以实现一个定制的接收器来处理这个特定的数据源。参考第二章,了解如何使用 Spark 流读写 Kudu 的示例。
图 7-144
A typical Flafka pipeline with Spark Streaming and Kudu
Sqoop
Sqoop 在技术上与 Kudu 不兼容。您不能使用 Sqoop 将数据从 RDBMS 传输到 Kudu,反之亦然。然而,Sqoop 可能是一些用户从 RDBMS 获取数据的唯一工具。
您可以使用 Sqoop 将数据从 RDBMS 导入 HDFS,然后使用 Spark 或 Impala 从 HDFS 读取数据并插入 Kudu。这里有几个使用 Sqoop 的例子。确保您安装并配置了正确的驱动程序。
获取 SQL Server 中可用数据库的列表。
sqoop list-databases --connect jdbc:sqlserver://10.0.1.124:1433 --username myusername --password mypassword
将表从 SQL Server 复制到 Hadoop。
sqoop import --connect "jdbc:sqlserver://10.0.1.124:1433;database=AdventureWorksDW2012;username=myusername;password=mypassword" --table DimProduct --hive-import --hive-overwrite
将表从配置单元复制到 SQL Server。
sqoop export --connect "jdbc:sqlserver://10.0.1.124:1433;database=AdventureWorksDW2012;username=myusername;password=mypassword" --table salesfact_hadoop --hcatalog-database default --hcatalog-table sales_data
然后,您可以简单地执行 Impala insert into…select 语句:
INSERT INTO my_kudu_table SELECT * FROM sales_data;
kudu 客户端
Kudu 提供了 NoSQL 风格的 Java、C++和 Python 客户端 API。需要 Kudu 提供最佳性能的应用程序应该使用客户端 API。事实上,前面讨论的一些数据摄取工具,比如 StreamSets、CDAP 和 Talend,利用客户端 API 将数据摄取到 Kudu 中。通过 API 进行的 DML 更改可以立即在 Impala 中查询,而不需要执行 INVALIDATE 元数据。有关 Kudu 客户端 API 的更多详细信息,请参考第二章。
MapReduce 和 Kudu
如果您的组织仍然使用 MapReduce,您可能会很高兴地知道 Kudu 集成了 MapReduce。示例 MapReduce 代码可以在 Kudu 的官方网站上找到。XXXVII
摘要
有几种方法可以将数据摄取到 Kudu 中。可以使用 StreamSets、Talend 等第三方商业工具。您可以使用 Apache Spark 和 Kudu 的客户端 API 等本地工具创建自己的应用程序。Kudu 使用户能够批量或实时摄取数据,同时运行分析查询,使其成为物联网和高级分析的理想*台。既然您已经将数据吸收到 Kudu 中,您需要从中提取价值。在第 8 和 9 章中,我将讨论分析存储在 Kudu 表中的数据的常用方法。
参考
- 商业电线;“StreamSets 在由 Battery Ventures 和 NEA 牵头的 A 轮融资中筹集了 1250 万美元,”商业资讯,2015 年,
https://www.businesswire.com/news/home/20150924005143/en/StreamSets-Raises-12.5-Million-Series-Funding-Led
- 流集;《征服数据流混沌》,StreamSets,2018,
https://streamsets.com/
- 阿帕奇库杜;《实例用例》,阿帕奇库杜,2018,
https://kudu.apache.org/docs/#kudu_use_cases
- 汤姆·怀特;《小文件问题》,Cloudera,2009,
https://blog.cloudera.com/blog/2009/02/the-small-files-problem/
- 亚伦·法夫里;《S3Guard 简介:Apache Hadoop 的 S3 一致性》,Cloudera,2017,
http://blog.cloudera.com/blog/2017/08/introducing-s3guard-s3-consistency-for-apache-hadoop/
- 伊尔科·多尔斯特拉;“S3BinaryCacheStore 最终是一致的”,Github,2017,
https://github.com/NixOS/nix/issues/1420
- Sumologic《使用 S3 你可能不知道的 10 件事》,Sumologic,2018,
https://www.sumologic.com/aws/s3/10-things-might-not-know-using-s3/
- 蒸汽装置;《MQTT 订户》,StreamSets,2018,
https://streamsets.com/documentation/datacollector/latest/help/index.html#Origins/MQTTSubscriber.html#concept_ukz_3vt_lz
- 流集;“CoAP 客户端”,流集,2018 年,
- 流集;《读序》,StreamSets,2018,
https://streamsets.com/documentation/datacollector/latest/help/#datacollector/UserGuide/Origins/Directory.html#concept_qcq_54n_jq
- 流集;《用自定义分隔符处理 XML 数据》,StreamSets,2018,
https://streamsets.com/documentation/datacollector/latest/help/#Pipeline_Design/TextCDelim.html#concept_okt_kmg_jx
- 流集;《用自定义分隔符处理 XML 数据》,StreamSets,2018,
https://streamsets.com/documentation/datacollector/latest/help/#Pipeline_Design/TextCDelim.html#concept_okt_kmg_jx
- 阿尔温德·普拉巴卡尔;“用流集驯服不羁的大数据流”,InfoWorld,2016,
http://www.infoworld.com/article/3138005/analytics/tame-unruly-big-data-flows-with-streamsets.html
- 流集;《流选择器》,StreamSets,2018,
https://streamsets.com/documentation/datacollector/latest/help/#Processors/StreamSelector.html#concept_tqv_t5r_wq
- 流集;”表情,“StreamSets,2018,
https://streamsets.com/documentation/datacollector/latest/help/#Processors/Expression.html#concept_zm2_pp3_wq
- 流集;《JavaScript Evaluator》,StreamSets,2018,
https://streamsets.com/documentation/datacollector/latest/help/index.html#Processors/JavaScript.html#concept_n2p_jgf_lr
- jsfiddle“生成 UUID”,JSFiddle,2018 年,
- 帕特·帕特森;“通过 StreamSets 数据收集器 REST API 检索指标”,StreamSets,2016,
https://streamsets.com/blog/retrieving-metrics-via-streamsets-data-collector-rest-api/
- Pentaho《产品对比》,Pentaho,2017,
https://support.pentaho.com/hc/en-us/articles/205788659-PENTAHO-COMMUNITY-COMMERCIAL-PRODUCT-COMPARISON
- Talend《开源集成软件》,Talend,2018,
https://www.talend.com/download/talend-open-studio/
- Talend“为什么升级?,“Talend,2018,
https://www.talend.com/products/why-upgrade/
- crunchbase;crunchbase;“计算机”,Crunchbase,2018 年,
- 计算机科学;《计算机科学动力中心大数据版》,《计算机科学》,2018 年,
- 病毒科塔里语;《微软 SSIS 与 Cloudera BIGDATA》,YouTube,2016,
https://www.youtube.com/watch?v=gPLfcL2zDX8
- 甲骨文;《面向大数据的甲骨文数据集成商》,甲骨文,2018,
http://www.oracle.com/us/products/middleware/data-integration/odieebd-ds-2464372.pdf
; - IBM《InfoSphere DataStage 概述》,IBM,2018,
https://www.ibm.com/support/knowledgecenter/SSZJPZ_11.5.0/com.ibm.swg.im.iis.ds.intro.doc/topics/what_is_ds.html
- IBM《大数据处理》,IBM,2018,
https://www.ibm.com/support/knowledgecenter/SSZJPZ_11.5.0/com.ibm.swg.im.iis.ds.intro.doc/topics/ds_samples_bigdata.html
- IBM 分析技能;“使用 IBM InfoSphere Information Server 操作控制台监控 DataStage 作业”,IBM,2013,
https://www.youtube.com/watch?v=qOl_6HqyVes
- SyncSort《从大铁到大数据的创新软件》,SyncSort,2018,
http://www.syncsort.com/en/About/Syncsort-History
- SyncSort《Syncsort DMX-h》,Syncsort,2018,
http://www.syncsort.com/en/Products/BigData/DMXh
- SyncSort《Syncsort DMX-h 8 简介》,Syncsort,2015,
https://www.youtube.com/watch?v=7e_8YadLa9E
- 霍顿作品;《APACHE NIFI》,《Hortonworks,2018 年》,
- 吉拉;“创建一个新的 Kudu 处理器来摄取数据”,吉拉,2017,
https://issues.apache.org/jira/browse/NIFI-3973
- 阿帕奇水槽;《Flume 1.8.0 用户指南》,阿帕奇 Flume,2018,
https://flume.apache.org/FlumeUserGuide.html
- 阿帕契卡夫卡;“导言,”apache kafka,2018 年,
https://kafka.apache.org/intro
- 格温·沙皮拉,杰夫·霍洛曼;《Flafka: Apache Flume 遇上 Apache Kafka 进行事件处理》,Cloudera,2014,
http://blog.cloudera.com/blog/2014/11/flafka-apache-flume-meets-apache-kafka-for-event-processing/
- dan burkert" ImportCsv.java," Apache Kudu,2017 年,
八、大数据仓库
几十年来,企业数据仓库一直是企业数据的中央存储库。它是每个组织的商业智能基础设施不可或缺的一部分。
数据仓库有各种形状和形式。多年来,微软和甲骨文公司销售用于 SMP(对称多处理)系统上的数据仓库的关系数据库管理软件。其他公司,如 Teradata,销售大型集成 MPP(大规模并行处理)系统。微软等云提供商提供完全托管的云数据仓库,如 Azure SQL 数据仓库。开发 Apache Hive 项目是为了提供构建在 Hadoop 之上的数据仓库基础设施。无论采用何种形式,数据仓库的目的都是一样的——提供一个可以高效存储、处理和分析结构化数据的*台。
在本章中,我将讨论大数据(更具体地说是 Cloudera Enterprise)如何颠覆数据仓库。我假设读者熟悉数据仓库,所以我不会讨论数据仓库的基础知识和理论。有关数据仓库的权威指南,请参考 Ralph Kimball 的《数据仓库工具包》。 ii
Note
我将在本章(以及整本书)中交替使用术语“大数据”和“Hadoop”。从技术上讲,大数据指的是整个技术和框架生态系统,Hadoop 是其中的一部分。在整本书中,我还将交替使用术语“数据湖”、“数据中心”和“大数据*台”。
大数据时代的企业数据仓库
正如我们所知,大数据时代已经颠覆了数据仓库。大数据的先驱发明了新的数据处理和分析方法,使传统的数据仓库显得过时。大数据*台处理和分析结构化和非结构化数据的能力以及 ELT(提取、加载和转换)、读取模式、数据整理和自助数据分析等新技术暴露了数据仓库的局限性,使传统的数据建模和 ETL 开发生命周期看起来缓慢而僵化。
从成本和技术角度来看,大数据*台通常比传统数据仓库*台更具可扩展性。一般来说,最大的数据仓库最多只能处理数百 TB 的数据,需要大量的硬件投资来扩展。大数据*台可以使用低成本的商用服务器轻松处理和分析数 Pb 的数据。对于大多数组织来说,最实际的问题可能是实现和维护商业数据仓库的财务负担。为了拥有一个全功能的企业数据仓库,公司在许可、基础设施和开发成本上花费了数百万美元。像 Impala、Drill 和 Presto 这样的开源 MPP Apache 项目以很小的成本提供了相当的性能和可伸缩性。
结构化数据仍然占主导地位
对于数据仓库来说,并不完全是厄运和黑暗,远非如此。尽管围绕大数据大肆宣传,但戴尔进行的一项调查显示,超过三分之二的公司报告结构化数据占正在处理的数据的 75%,三分之一的公司报告根本不分析非结构化数据。该调查还显示,结构化数据的增长速度快于非结构化数据。 iii
EDW 现代化
尽管 Cloudera Enterprise 等 Hadoop *台接*复制数据仓库的一些功能,但商业数据仓库*台仍然是分析结构化数据的最佳*台。Impala 和 Kudu(以及 Hive)仍然缺乏商业数据仓库*台的许多(专有)功能和特性。您会发现一些流行的商业智能和 OLAP(在线分析处理)工具依赖于这些专有特性。Impala 和 Kudu 中没有的一些功能包括多行和多表事务、OLAP 支持、高级备份和恢复特性、二级索引、物化视图、对十进制数据类型的支持以及自动增加列等等。表 8-1 包含了 Impala 和 Kudu 与传统数据仓库*台的对比。
表 8-1
Impala and Kudu vs. a Traditional Data Warehouse
| 特征 | 黑斑羚和库杜 | 数据仓库*台 | | :-- | :-- | :-- | | 多行多表交易 | 不 | 是 | | 自动递增列 | 不 | 是 | | 外键约束 | 不 | 是 | | 次要索引 | 不 | 是 | | 物化视图 | 不 | 是 | | 扳机 | 不 | 是 | | 存储过程 | 不 | 是 | | 数据库缓存 | 不 | 是 | | OLAP 支持 | 不 | 是 | | SQL 的过程扩展 | 不 | 是 | | 高级备份和恢复 | 不 | 是 | | 十进制和复杂数据类型 | 不 | 是 |Hadoop 供应商通常将他们的*台作为“数据中心”或“数据湖”进行营销,这些*台可用于扩充或更新数据仓库,而不是取代它。事实上,现在大多数公司都有数据仓库和数据湖。有多种方法可以使用数据湖来实现企业数据仓库的现代化:ETL 卸载、活动归档和分析卸载以及数据整合。这些用例相对容易实现。投资回报也很容易衡量并向管理层证明。
ETL 卸载
在典型的数据仓库环境中,IO 和 CPU 密集型数据转换和加载在数据仓库上执行(图 8-1 )。随着时间的推移,随着越来越多的数据被接收并存储在数据仓库中,其 ETL 窗口将开始占用业务时间,导致业务用户无法使用重要的报告和仪表板。一个明显的解决方法是升级数据仓库。这是一个短期的解决方法。随着时间的推移,当您将更多的数据接收到数据仓库中时,您会遇到同样的性能和可伸缩性问题。
图 8-1
Traditional ETL
正确的解决方案是通过 ETL 卸载用数据湖来扩充您的数据仓库。如图 8-2 所示,大部分繁重的数据转换是在大数据*台上执行的,由于 Spark 和 MapReduce 等高度分布式数据处理框架,大数据*台可以在相对较短的时间内处理大量数据。数据湖可以充当临时区域或操作数据存储。然后,经过处理和聚合的数据被加载到数据仓库中,现在可以免费用于数据分析。
图 8-2
ETL Offloading with Big Data
分析卸载和活动归档
您还可以使用您的数据湖来卸载一些昂贵的报告和即席查询。通过卸载一些分析,您可以腾出数据仓库来处理更重要的任务。分析卸载还允许您的数据仓库处理更多数据和并发用户,同时保持较小的数据仓库占用空间,从而为您的组织节省数百万的数据仓库升级费用。
如果您已经使用大数据*台进行 ETL 卸载,那么您已经在大数据*台中存储了数据的副本。您还可以通过将旧数据移动或归档到大数据*台来帮助提高数据仓库的效率。一旦您在大数据*台中获得了所需的数据,您就可以通过 JDBC 将您的 BI 或数据可视化工具(如 Tableau 或 MS Power BI)指向 Impala(图 8-3 )。在某些情况下,BI tools 有一个语义层,它提供了一种连接、读取和缓存来自不同数据源的数据的方法。它向用户隐藏了数据源的详细信息,提供了数据的统一视图。
图 8-3
Some of the analytics are offloaded to the big data platform
数据整合
Hadoop 最好的特性之一是它存储大量数据的能力。通过结合来自商用硬件的存储并提供分布式存储引擎,如 HDFS、HBase 和 Kudu,Hadoop 使大型数据集的存储和处理不仅成为可能,而且变得切实可行。与只能存储结构化数据的数据仓库不同,数据湖可以存储和处理结构化和非结构化数据集,使其成为真正的企业数据存储库。
将数据整合到一个集中的数据湖中有几个好处。首先,它通过使连接和关联数据变得更容易来增强数据分析。第二,因为您的数据在一个中心位置,所以用户可以更容易地跨各种数据集运行 SQL 连接,例如,提供客户的 360 度视图。第三,特征工程–选择和处理属性以及为预测模型创建特征集也变得更加容易。第四,一旦数据被整合到一个中心位置,数据治理和主数据管理就变得简单明了,为您提供一个数据的黄金副本,而不是将同一数据的多个副本分散到多个数据仓库中。
整合数据带来的最切实的好处可能是许可、运营和基础架构成本的大幅降低。对于大型组织来说,积累分散在整个企业中的数百甚至数千个数据库是相当常见的。通过将这些数据库整合到一个集中的数据湖中(图 8-4 ),您可以淘汰托管这些数据库的旧服务器,并停止支付数据库软件许可证的费用。
图 8-4
Consolidating data in a data lake or data hub
重新构建企业数据仓库
这可能是你阅读这本书或这一章的原因。用 Cloudera Enterprise 替换或重新构建数据仓库在技术上是可能的。许多公司已经成功地做到了这一点,在本章的其余部分,我们将讨论如何做到这一点。
Note
提醒一句。除非你有真正令人信服的理由,否则我建议你在用 Cloudera Enterprise 重新*台化你的数据仓库之前三思。有许多事情要考虑。您需要仔细评估贵公司的数据管理和分析需求。您还需要评估 Cloudera Enterprise 的能力和弱点。你的商业智能和 OLAP 工具能和 Impala 一起工作吗?您需要迁移数千个 ETL 作业吗?把 PL/SQL 或者 T-SQL 移植到 Impala SQL 有多难?在经历将数据仓库迁移到 Cloudera Enterprise 的磨难之前,这些只是您需要问自己的一些问题。
大数据仓库 101
对于我们的示例,我们将使用来自微软 iv 的 AdventureWorksDW 免费示例数据库。如果你想学习我们的例子,你可以从微软的网站下载这个数据库。
维度建模
维度建模是对数据库表进行逻辑建模的一种方式,以便于使用和提高 SQL 查询性能。维度模型是当今市场上大多数商业智能和 OLAP 系统的逻辑基础。事实上,大多数数据仓库*台在设计和优化时都考虑了维度建模。维度建模有几个概念:
事实
维度模型有事实数据表,其中包含表示业务指标(如销售额或成本)的度量值或数值。一个数据仓库通常有几个用于不同业务流程的事实表。在 Kudu 之前,出于性能原因,事实表通常以 Parquet 格式存储。
维度表
维度表包含描述事实表中存储的度量或为其提供上下文的属性。典型的属性包括日期、地点、年龄、宗教、国籍和性别等等。
星形模式
有两种物理实现维度模型的方法:使用星型或雪花型模式。星型模式在中间包含一个事实表,周围是维度表。事实表和维度表通过表的外键和主键连接起来。图 8-5 中的 ER 图是星型模式的一个例子。
图 8-5
Star Schema
雪花模式
雪花模式非常类似于星型模式,它有一个中央事实表,但维度表被进一步规范化为子维度表。数据仓库专家 Ralph Kimball 通常推荐使用星型模式。他建议在一些度量引用许多其他度量不共享的属性的情况下使用雪花模式。 v 图 8-6 所示的 ER 图就是雪花图式的一个例子。
图 8-6
Snowflake Schema
尺寸缓慢变化
有效使用 Hive 或 Impala 进行数据仓库存储的最大障碍之一是它无法执行一件简单的事情——更新表。无法更新纵栏文件格式,例如存储在 HDFS 的拼花地板。这可能会让有 RDBMS 背景的人感到震惊。用户需要覆盖整个表或分区,以便更改存储在镶木地板表中的值。当处理缓慢变化的维度时,这种限制成为一个令人头疼的问题。在 Kudu 之前,处理缓慢变化的维度的最佳方式是将维度表存储在 HBase 中,然后在表的顶部创建一个外部 Hive 表,以便从 Impala 访问它。Impala 不支持 SQL update 语句,但是因为 HBase 支持版本控制,所以可以通过使用相同的 rowkey 执行 insert 语句来模拟更新。 vi
使用 Impala 和 Kudu 的大数据仓库
多年来,Hive 被用来在 Hadoop 中实现基本的数据仓库。它慢得令人痛苦,也很笨拙,但它比 MapReduce 更容易使用,而且它能完成工作,所以用户能容忍它。
几年后,Impala 和其他开源 MPP 引擎出现了。与 Parquet 等列式文件格式一起使用,它为企业中的 Hadoop 铺*了道路,使 BI 和 OLAP 工具能够连接到 Hadoop 集群并获得快速性能。使用 Impala 和 Parquet 进行 ETL 和维度建模仍然很麻烦,但是好处超过了成本,所以用户忍受使用它。
Impala 和 Kudu 使得在 Cloudera Enterprise 上存储、处理和分析关系数据变得更加容易。虽然它还不具备商业数据仓库*台的所有高级功能,但它的功能已经足够接*关系数据库了。
让我们通过在 Kudu 中实现一个简单的维度星型模式模型来研究一个例子。我将把 AdventureWorksDW 数据库的子集从 SQL Server 复制到 Kudu,并使用 Impala 对其运行一些 SQL 查询。我们将复制三个维度表和一个事实表。我们还将复制列的子集。该模式应该如图 8-7 所示。
图 8-7
Example schema
如图 8-8 所示,我将从 FactInternetSales 表中转移以下各列:ProductKey、OrderDateKey、CustomerKey、OrderQuantity、UnitPrice、SalesAmount、TaxAmt、TotalProductCost、Freight、OrderDate、SalesOrderNumber 和 SalesOrderLineNumber。
图 8-8
Fact Internet Sales
我将从 DimCustomer 表中复制以下各列:CustomerKey、FirstName、LastName、BirthDate、YearlyIncome、TotalChildren、EnglishEducation、EnglishOccupation、HouseOwnerFlag 和 NumberofCarsOwned,如图 8-9 所示。
图 8-9
DimCustomer
我们将从 DimProduct 表中选择四列:ProductKey、EnglishProductName、EnglishDescription 和 Color,如图 8-10 所示。
图 8-10
DimProduct
我们将从 DimDate 表中选取几列:DateKey、FullDateAlternateKey、DayNumberOfWeek、EnlighsDayNameOfWeek、DayNumberOfMonth、DayNumberOfYear、WeekNumberOfYear、EnglishMonthName、MonthNumberOfYear、CalendarQuarter、CalendarYear、FiscalQuarter 和 FiscalYear,如图 8-11 所示。
图 8-11
DimDate
我需要将表从 SQL Server 复制到 Impala。为此,我为每个表手动生成一个 CSV 文件。你可以使用我们在第七章中提到的任何 ETL 工具。在这种情况下,我使用 SQL Server 的数据导出和导入工具来生成 CSV 文件。不要忘记排除列标题。然后,我将文件移动到 HDFS,并在每个文件的顶部创建一个外部表。清单 8-1 显示了步骤。
hadoop fs -mkdir /tmp/factinternetsales
hadoop fs -mkdir /tmp/dimcustomer
hadoop fs -mkdir /tmp/dimproduct
hadoop fs -mkdir /tmp/dimdate
hadoop fs -put FactInternetSales.csv /tmp/factinternetsales/
hadoop fs -put DimCustomer.csv /tmp/dimcustomer/
hadoop fs -put DimProduct.csv /tmp/dimproduct/
hadoop fs -put DimDate.csv /tmp/dimdate/
impala-shell
CREATE EXTERNAL TABLE FactInternetSales_CSV (
OrderDate STRING,
ProductKey BIGINT,
OrderDateKey BIGINT,
CustomerKey BIGINT,
OrderQuantity FLOAT,
UnitPrice FLOAT,
SalesAmount FLOAT,
TaxAmt FLOAT,
TotalProductCost FLOAT,
Freight FLOAT
)
ROW FORMAT
DELIMITED FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n' STORED AS TEXTFILE
LOCATION '/tmp/factinternetsales';
CREATE EXTERNAL TABLE DimCustomer_CSV (
CustomerKey BIGINT,
FirstName STRING,
LastName STRING,
BirthDate STRING,
YearlyIncome FLOAT,
TotalChildren INT,
EnglishEducation STRING,
EnglishOccupation STRING,
HouseOwnerFlag INT,
NumberCarsOwned INT
)
ROW FORMAT
DELIMITED FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n' STORED AS TEXTFILE
LOCATION '/tmp/dimcustomer';
CREATE EXTERNAL TABLE DimDate_CSV (
DateKey BIGINT,
FullDateAlternateKey STRING,
DayNumberOfWeek INT,
EnglishDayNameOfWeek STRING,
DayNumberOfMonth INT,
DayNumberOfYear INT,
WeekNumberOfYear INT,
EnglishMonthName STRING,
MonthNumberOfYear INT,
CalendarQuarter INT,
CalendarYear INT,
FiscalQuarter INT,
FiscalYear INT
)
ROW FORMAT
DELIMITED FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n' STORED AS TEXTFILE
LOCATION '/tmp/dimdate';
CREATE EXTERNAL TABLE DimProduct_CSV (
ProductKey BIGINT,
EnglishProductName STRING,
EnglishDescription STRING,
Color STRING
)
ROW FORMAT
DELIMITED FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n' STORED AS TEXTFILE
LOCATION '/tmp/dimproduct';
invalidate metadata;
select count(*) from FactInternetSales_csv;
60398
select count(*) from DimCustomer_csv;
18484
select count(*) from DimProduct_csv;
606
select count(*) from DimDate_csv;
3652
Listing 8-1Copy the tables from SQL Server to Impala
既然可以从 Impala 访问数据,我们就可以创建 Kudu 表,并用外部表中的数据填充它。步骤如清单 8-2 所示。
Note
如第二章所述,Kudu 不支持多行多表的 ACID 兼容事务。即使某些行由于违反主键或约束而被拒绝,ETL 作业也将继续成功运行。在 ETL 过程之后,必须执行额外的数据验证,以确保数据处于一致的状态。此外,考虑对您的表进行反规范化,以减少 Kudu 中数据不一致的可能性。VII
CREATE TABLE FactInternetSales
(
ID STRING NOT NULL,
OrderDate BIGINT NOT NULL,
ProductKey BIGINT,
OrderDateKey BIGINT,
CustomerKey BIGINT,
OrderQuantity FLOAT,
UnitPrice FLOAT,
SalesAmount FLOAT,
TaxAmt FLOAT,
TotalProductCost FLOAT,
Freight FLOAT,
SalesOrderNumber STRING,
SalesOrderLineNumber TINYINT,
PRIMARY KEY(ID, OrderDate)
)
PARTITION BY HASH (ID) PARTITIONS 16,
RANGE (OrderDate)
(
PARTITION unix_timestamp('2017-01-01') <= VALUES < unix_timestamp('2017-02-01'),
PARTITION unix_timestamp('2017-02-01') <= VALUES < unix_timestamp('2017-03-01'),
PARTITION unix_timestamp('2017-03-01') <= VALUES < unix_timestamp('2017-04-01'),
PARTITION unix_timestamp('2017-04-01') <= VALUES < unix_timestamp('2017-05-01'),
PARTITION unix_timestamp('2017-05-01') <= VALUES < unix_timestamp('2017-06-01'),
PARTITION unix_timestamp('2017-06-01') <= VALUES < unix_timestamp('2017-07-01'),
PARTITION unix_timestamp('2017-07-01') <= VALUES < unix_timestamp('2017-08-01'),
PARTITION unix_timestamp('2017-08-01') <= VALUES < unix_timestamp('2017-09-01'),
PARTITION unix_timestamp('2017-09-01') <= VALUES < unix_timestamp('2017-10-01'),
PARTITION unix_timestamp('2017-10-01') <= VALUES < unix_timestamp('2017-11-01'),
PARTITION unix_timestamp('2017-11-01') <= VALUES < unix_timestamp('2017-12-01'),
PARTITION unix_timestamp('2017-12-01') <= VALUES < unix_timestamp('2018-01-01')
)
STORED AS KUDU;
INSERT INTO FactInternetSales
SELECT
uuid(),
unix_timestamp('2017-01-20'),
ProductKey,
OrderDateKey,
CustomerKey,
OrderQuantity,
UnitPrice,
SalesAmount,
TaxAmt,
TotalProductCost,
Freight,
SalesOrderNumber,
SalesOrderLineNumber
FROM
FactInternetSales_CSV;
SELECT COUNT(*) FROM FactInternetSales_csv
+----------+
| count(*) |
+----------+
| 60398 |
+----------+
SELECT COUNT(*) FROM FactInternetSales;
+----------+
| count(*) |
+----------+
| 60398 |
+----------+
CREATE TABLE DimCustomer (
ID STRING,
CustomerKey BIGINT,
FirstName STRING,
LastName STRING,
BirthDate STRING,
YearlyIncome FLOAT,
TotalChildren INT,
EnglishEducation STRING,
EnglishOccupation STRING,
HouseOwnerFlag INT,
NumberCarsOwned INT,
PRIMARY KEY(ID)
)
PARTITION BY HASH (ID) PARTITIONS 16
STORED AS KUDU);
INSERT INTO DimCustomer
SELECT
uuid(),
CustomerKey,
FirstName,
LastName,
BirthDate,
YearlyIncome,
TotalChildren,
EnglishEducation,
EnglishOccupation,
HouseOwnerFlag,
NumberCarsOwned
FROM
DimCustomer_CSV;
SELECT COUNT(*) FROM DimCustomer;
+----------+
| count(*) |
+----------+
| 18484 |
+----------+
SELECT COUNT(*) FROM DimCustomer_CSV;
+----------+
| count(*) |
+----------+
| 18484 |
+----------+
CREATE TABLE DimDate (
ID STRING,
DateKey BIGINT,
FullDateAlternateKey STRING,
DayNumberOfWeek INT,
EnglishDayNameOfWeek STRING,
DayNumberOfMonth INT,
DayNumberOfYear INT,
WeekNumberOfYear INT,
EnglishMonthName STRING,
MonthNumberOfYear INT,
CalendarQuarter INT,
CalendarYear INT,
FiscalQuarter INT,
FiscalYear INT,
PRIMARY KEY(ID)
)
PARTITION BY HASH (ID) PARTITIONS 16
STORED AS KUDU;
INSERT INTO DimDate
SELECT
uuid(),
DateKey,
FullDateAlternateKey,
DayNumberOfWeek,
EnglishDayNameOfWeek,
DayNumberOfMonth,
DayNumberOfYear,
WeekNumberOfYear,
EnglishMonthName,
MonthNumberOfYear,
CalendarQuarter,
CalendarYear,
FiscalQuarter,
FiscalYear
FROM DimDate_CSV;
SELECT count(*) from DimDate;
+----------+
| count(*) |
+----------+
| 3652 |
+----------+
SELECT count(*) from Dimdate_CSV;
+----------+
| count(*) |
+----------+
| 3652 |
+----------+
CREATE TABLE DimProduct (
ID STRING,
ProductKey BIGINT,
EnglishProductName STRING,
EnglishDescription STRING,
Color STRING
)
PARTITION BY HASH (ID) PARTITIONS 16
STORED AS KUDU;
INSERT INTO DimProduct
SELECT
uuid(),
productkey,
englishproductname,
englishdescription,
color
FROM
DimProduct_CSV
select COUNT(*) from DimProduct
+----------+
| count(*) |
+----------+
| 606 |
+----------+
select COUNT(*) from DimProduct_CSV
+----------+
| count(*) |
+----------+
| 606 |
+----------+
Listing 8-2Build Kudu tables and populate it with data
这里有几件重要的事情要讨论。注意,我使用了 Impala 内置函数 uuid()为事实表和维度表生成一个惟一的 id。Impala 和 Kudu 不包含自动增量特性,这实际上被认为是分布式环境中的反模式。使用 Kudu、Spanner 和类似的分布式系统,对值单调递增的列进行范围分区会将所有插入一次写入一个*板,导致*板比其他*板大得多,并限制了插入的可伸缩性。这也被称为热点。当插入具有单调递增行键的行时,HBase 中也会出现热点。
虽然在单个 SMP 服务器上运行的数据库上使用 UUIDs 作为主键是一种灾难,但是在分布式 MPP 环境中,使用不是从表中包含的业务或数据派生的唯一键进行哈希分区会将工作负载分散到集群中的各个节点上。哈希分区可防止数据和工作负载不对称,并通过将插入并行发送到多个*板电脑来提高数据接收的可伸缩性。 viii
一些数据库系统,如 Microsoft SQL Server,具有本地 UUID 数据类型,为 uuid 提供了更有效的存储。Kudu 没有 UUID 数据类型,因此您必须使用字符串数据类型来存储 UUID。36 个字符的 UUID 确实会占用一些空间;然而,基准测试表明,在 Kudu 中使用 36 个字符的主键不会影响大多数查询的性能,尽管您的体验可能会有所不同。如果您关心 UUID 的使用,那么另一个选择是散列 UUID 来生成一个非连续的 BIGINT 值作为您的主键。您可以使用 Impala 的内置哈希函数 fnv_hash()(它不是加密安全的,这意味着在生成唯一 ID 时可能会发生冲突)或一个加密安全的哈希算法。只要确保您测试了 insert 语句的性能。每个 insert 语句都调用内置函数可能会导致性能瓶颈。当然,还有其他方法可以生成非顺序的唯一键。
如您所见,事实表是散列和范围分区的。通过结合这两种分区策略,我们获得了两者的优点,同时减少了各自的局限性。 ix 通过对日期列使用范围分区,您的查询可以在按日期范围扫描时利用分区修剪。此外,随着新数据添加到表中,可以添加新的分区(这将导致 16 个额外的*板电脑)。通过在 ID 列上使用散列分区(这是一种非顺序的 UUID),插入被均匀地分布在多个*板上,直到散列分区的数量(在我们的例子中是 16),从而提高了数据接收的吞吐量和性能。将此策略与当前的 AdventureWorksDW 架构进行比较,您会发现一些不同之处。首先,FactInternetSales 的主键在 SalesOrderNumber 和 SalesOrderLineNumber 上。这对于在一台服务器上运行的 SQL Server 数据库来说已经足够了。然而,这打破了我们刚刚讨论的关于分布式系统上的分区和选择主键值的几个规则。SalesOrderNumber 是单调递增的,SalesOrderLineNumber 的大部分值是 1。如果我们使用这两列作为主键,所有插入都将被定向到一个分区,从而导致数据和工作负载不对称,并降低数据接收速度。
维度表通常很小,并且大多是只读的。缓慢变化的维度通常不需要像事实表那样考虑性能,所以我们将使用唯一的 UUID 作为主键对维度表进行哈希分区。较大的维度表可能需要不同的分区策略。
虽然 Kudu 要求所有表都有主键,但它没有外键约束。因此,在 Kudu 数据仓库中,数据不一致是可能的。但是,出于性能和可管理性的原因,数据仓库通常会禁用外键约束。清单 8-3 显示了我们刚刚创建的 Kudu 表的结构。
describe FactInternetSales;
+----------------------+---------+---------+-------------+----------+
| name | type | comment | primary_key | nullable |
+----------------------+---------+---------+-------------+----------+
| id | string | | true | false |
| orderdate | bigint | | true | false |
| productkey | bigint | | false | true |
| orderdatekey | bigint | | false | true |
| customerkey | bigint | | false | true |
| orderquantity | float | | false | true |
| unitprice | float | | false | true |
| salesamount | float | | false | true |
| taxamt | float | | false | true |
| totalproductcost | float | | false | true |
| freight | float | | false | true |
| salesordernumber | string | | false | true |
| salesorderlinenumber | tinyint | | false | true |
+----------------------+---------+---------+-------------+----------+
describe DimDate;
+----------------------+--------+---------+-------------+----------+
| name | type | comment | primary_key | nullable |
+----------------------+--------+---------+-------------+----------+
| id | string | | true | false |
| datekey | bigint | | false | true |
| fulldatealternatekey | string | | false | true |
| daynumberofweek | int | | false | true |
| englishdaynameofweek | string | | false | true |
| daynumberofmonth | int | | false | true |
| daynumberofyear | int | | false | true |
| weeknumberofyear | int | | false | true |
| englishmonthname | string | | false | true |
| monthnumberofyear | int | | false | true |
| calendarquarter | int | | false | true |
| calendaryear | int | | false | true |
| fiscalquarter | int | | false | true |
| fiscalyear | int | | false | true |
+----------------------+--------+---------+-------------+----------+
describe DimCustomer;
+-------------------+--------+---------+-------------+----------+
| name | type | comment | primary_key | nullable |
+-------------------+--------+---------+-------------+----------+
| id | string | | true | false |
| customerkey | bigint | | false | true |
| firstname | string | | false | true |
| lastname | string | | false | true |
| birthdate | string | | false | true |
| yearlyincome | float | | false | true |
| totalchildren | int | | false | true |
| englisheducation | string | | false | true |
| englishoccupation | string | | false | true |
| houseownerflag | int | | false | true |
| numbercarsowned | int | | false | true |
+-------------------+--------+---------+-------------+----------+
describe DimProduct;
+--------------------+--------+---------+-------------+----------+
| name | type | comment | primary_key | nullable |
+--------------------+--------+---------+-------------+----------+
| id | string | | true | false |
| productkey | bigint | | false | true |
| englishproductname | string | | false | true |
| englishdescription | string | | false | true |
| color | string | | false | true |
+--------------------+--------+---------+-------------+----------+
Listing 8-3Structure of Kudu tables
Note
正如本章前面提到的,Kudu 不支持 decimal 数据类型。float 和 double 数据类型仅存储非常接*的值,而不是 IEEE 754 规范中定义的精确值。 x 正因为如此,behavior float 和 double 都不适合存储财务数据。在撰写本文时,对十进制数据类型的支持仍在开发中(Apache Kudu 1.5 / CDH 5.13)。更多详情请参考 KUDU-721。一种解决方法是将财务数据存储为字符串,然后在每次需要读取数据时使用 Impala 将值转换为十进制。因为 Parquet 支持小数,所以另一个解决方法是对事实表使用 Parquet,对维度表使用 Kudu。
我们现在可以对 Kudu 的数据仓库运行 Impala 查询。
例如,这里有一个查询,获取收入超过 100,000 美元且拥有住房的所有人的前 20 名列表。
SELECT
DC.FirstName,
DC.LastName,
DC.BirthDate,
DC.HouseOwnerFlag,
DC.YearlyIncome
FROM FactInternetSales FIS
INNER JOIN DimCustomer DC ON FIS.customerkey = DC.customerkey
WHERE
DC.YearlyIncome >= 100000
AND
DC.HouseOwnerFlag = 1
ORDER BY
DC.YearlyIncome DESC
LIMIT 20;
+-----------+------------+------------+----------------+--------------+
| firstname | lastname | birthdate | houseownerflag | yearlyincome |
+-----------+------------+------------+----------------+--------------+
| Shannon | Navarro | 1971-05-03 | 1 | 170000 |
| Devin | Anderson | 1959-08-22 | 1 | 170000 |
| Luis | Washington | 1946-05-07 | 1 | 170000 |
| Ian | Watson | 1950-12-08 | 1 | 170000 |
| Jenny | Chander | 1955-02-04 | 1 | 170000 |
| Nathan | Wright | 1965-12-20 | 1 | 170000 |
| Trisha | Wu | 1959-01-03 | 1 | 170000 |
| Dale | Holt | 1952-02-01 | 1 | 170000 |
| Craig | Gutierrez | 1952-04-11 | 1 | 170000 |
| Devin | Martin | 1960-02-01 | 1 | 170000 |
| Katrina | Luo | 1950-04-22 | 1 | 170000 |
| Alvin | Nara | 1972-11-16 | 1 | 170000 |
| Megan | Morgan | 1966-02-13 | 1 | 170000 |
| Armando | Munoz | 1961-10-31 | 1 | 170000 |
| Arturo | Zheng | 1975-04-06 | 1 | 170000 |
| Vincent | Sun | 1959-11-03 | 1 | 170000 |
| Colleen | Anand | 1963-09-23 | 1 | 170000 |
| Caleb | Jenkins | 1970-04-22 | 1 | 170000 |
| Devin | Johnson | 1965-12-16 | 1 | 170000 |
| Shannon | Liu | 1966-03-08 | 1 | 170000 |
+-----------+------------+------------+----------------+--------------+
列出所有买了“Sport-100 头盔”并且拥有两辆以上汽车的人。
SELECT
DC.FirstName,
DC.LastName,
DC.BirthDate,
DC.NumberCarsOwned,
DP.EnglishProductName
FROM FactInternetSales FIS
INNER JOIN DimCustomer DC ON FIS.customerkey = DC.customerkey
INNER JOIN DimProduct DP ON FIS.productkey = DP.productkey
WHERE
DP.EnglishProductName = 'Sport-100 Helmet'
AND
DC.NumberCarsOwned > 2
LIMIT 20;
+-----------+-----------+------------+-----------------+------------------+
| firstname | lastname | birthdate | numbercarsowned | englishproductname |
+-----------+-----------+------------+-----------------+------------------+
| Colleen | Goel | 1954-04-06 | 4 | Sport-100 Helmet |
| Timothy | Stewart | 1948-12-22 | 4 | Sport-100 Helmet |
| Janelle | Arthur | 1971-01-14 | 3 | Sport-100 Helmet |
| Colin | Lal | 1976-11-30 | 4 | Sport-100 Helmet |
| Melody | Ruiz | 1979-08-01 | 4 | Sport-100 Helmet |
| Nicolas | Andersen | 1972-11-10 | 3 | Sport-100 Helmet |
| Hunter | Lopez | 1958-02-06 | 3 | Sport-100 Helmet |
| Jake | Zhu | 1978-06-24 | 3 | Sport-100 Helmet |
| Caleb | Yang | 1985-08-13 | 3 | Sport-100 Helmet |
| Jeffery | Ma | 1969-12-03 | 3 | Sport-100 Helmet |
| Miranda | Patterson | 1952-01-07 | 4 | Sport-100 Helmet |
| Isaac | Ward | 1957-11-09 | 3 | Sport-100 Helmet |
| Roy | Chandra | 1953-02-09 | 4 | Sport-100 Helmet |
| William | Harris | 1979-09-30 | 3 | Sport-100 Helmet |
| Xavier | Williams | 1974-01-18 | 3 | Sport-100 Helmet |
| Mason | Mitchell | 1952-11-03 | 4 | Sport-100 Helmet |
| Claudia | Zhou | 1961-12-04 | 3 | Sport-100 Helmet |
| Tamara | Lal | 1961-06-17 | 3 | Sport-100 Helmet |
| Misty | Tang | 1947-08-30 | 3 | Sport-100 Helmet |
| Alisha | Zeng | 1977-10-31 | 3 | Sport-100 Helmet |
+-----------+-----------+------------+-----------------+------------------+
摘要
恭喜你!您刚刚使用了 Impala 和 Kudu 进行数据仓库存储!您可以通过 JDBC/ODBC 将您最喜欢的商业智能、数据可视化和 OLAP 工具连接到 Impala,根据我们刚刚创建的表创建仪表板、报告和 OLAP 立方体。我建议您参考第九章,了解更多关于数据可视化工具的信息。您可以使用 Spark 来处理海量数据并将其接收到 Kudu 中。这将在第六章中详细讨论。还可以使用 StreamSets 和 Talend 等商业第三方 ETL 应用程序将数据加载到 Kudu 中。这在第七章中有所涉及。看起来您已经拥有了构建数据仓库所需的一切!
本章讨论了利用 Impala 和 Kudu 的几种方法。您可以更新您的数据仓库,或者完全取代它。只要确保您已经仔细评估了它的优势和劣势,以及它是否是您组织的正确解决方案。
参考
- Hortonworks《用 Apache Hive 进行数据仓储》,Hortonworks,2018,
https://docs.hortonworks.com/HDPDocuments/HDP2/HDP-2.6.2/bk_data-access/content/ch_using-hive.html
- 金博尔集团;《数据仓库工具包,第 3 版》,金博尔集团,2018,
http://www.kimballgroup.com/data-warehouse-business-intelligence-resources/books/data-warehouse-dw-toolkit/
- 戴尔;“戴尔调查:尽管信息管理格局快速变化,结构化数据仍是焦点”,戴尔,2018,
http://www.dell.com/learn/us/en/uscorp1/press-releases/2015-04-15-dell-survey
- 微软;《用于 SQL Server 2016 CTP3 的 AdventureWorks 数据库和脚本》,微软,2018,
https://www.microsoft.com/en-us/download/details.aspx?id=49502
- 甲骨文;《星形与雪花图式》,甲骨文,2018,
http://www.oracle.com/webfolder/technetwork/tutorials/obe/db/10g/r2/owb/owb10gr2_gs/owb/lesson3/starandsnowflake.htm
- Cloudera《使用 Impala 查询 HBase 表》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/latest/topics/impala_hbase.html
- Cloudera《使用 Impala 查询 Kudu 表》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/5-12-x/topics/impala_kudu.html
- 阿帕奇库杜;《Kudu:常见问题》阿帕奇 Kudu,2018,
https://kudu.apache.org/faq.html
- 阿帕奇库杜;《哈希和范围划分示例》,Apache Kudu,2018,
https://kudu.apache.org/docs/schema_design.html#hash-range-partitioning-example
- 微软;“使用十进制、浮点和实数数据”,微软,2018,
https://technet.microsoft.com/en-us/library/ms187912(v=sql.105).aspx
九、大数据可视化和数据整理
很容易理解为什么自助式数据分析和可视化在过去几年变得流行。它使用户能够执行自己的分析,并允许他们根据自己的需求交互式地探索和操作数据,而无需依赖传统的商业智能开发人员来开发报告和仪表板,这一任务可能需要几天、几周或更长时间,从而提高了用户的工作效率。用户可以执行即席分析并运行后续查询来回答他们自己的问题。他们也不受静态报告和仪表板的限制。根据分析的类型,自助数据分析的输出可以采用多种形式。输出可以采用交互式图表和仪表板、数据透视表、OLAP 立方体、来自机器学习模型的预测或由 SQL 查询返回的查询结果的形式。
大数据可视化
已经出版了几本关于流行的数据可视化工具的书,比如 Tableau、Qlik 和 Power BI。这些工具都与 Impala 很好地集成在一起,其中一些对大数据有基本的支持。对于典型的数据可视化任务来说,它们已经足够了,而且在大多数情况下,这也是所有典型用户所需要的。然而,当组织的分析需求超出传统工具所能处理的范围时,就该考虑专门为大数据设计的工具了。请注意,当我谈到大数据时,我指的是大数据的三个 V。我指的不仅仅是数据的大小(体积),还有数据的多样性(这些工具能否分析结构化、非结构化、半结构化数据)和速度(这些工具能否进行实时或接*实时的数据可视化?).我在本章中探讨了专门为大数据设计的数据可视化工具。
SAS 可视化分析
40 多年来,SAS 一直是高级分析领域的领导者之一。SAS 软件套件有数百个组件,设计用于各种用例,从数据挖掘和计量经济学到六西格玛和临床试验分析。对于这一章,我们感兴趣的是被称为 SAS 可视化分析的自助式分析工具。
SAS Visual Analytics 是来自 SAS 的基于 web 的自助式交互式数据分析应用程序。SAS 可视化分析允许用户探索他们的数据,写报告,处理和加载数据到 SAS 环境中。它具有内存功能,专为大数据而设计。SAS 可视化分析可以在分布式和非分布式模式下部署。在分布式模式下,高性能 SAS LASR 分析服务器守护程序安装在 Hadoop 工作节点上。它可以利用您的 Hadoop 集群的全部处理能力,使其能够处理存储在 HDFS 的大量数据。
SAS 可视化分析分布式部署支持 Cloudera Enterprise、Hortonworks HDP 和 Teradata 数据仓库设备。非分布式 SAS VA 部署在一台本地安装了 SAS LASR 分析服务器的机器上运行,并通过 JDBC 连接到 Cloudera Impala。 我
Zoomdata
Zoomdata 是一家位于弗吉尼亚州莱斯顿的数据可视化和分析公司。 ii 它旨在利用 Apache Spark,使其成为分析超大数据集的理想选择。Spark 的内存和流特性使 Zoomdata 能够支持实时数据可视化功能。Zoomdata 支持各种数据源,例如 Oracle、SQL Server、Hive、Solr、Elasticsearch 和 PostgreSQL 等等。Zoomdata 最令人兴奋的特性之一是它对 Kudu 和 Impala 的支持,这使它非常适合大数据和实时数据可视化。让我们仔细看看 Zoomdata。
面向大数据的自助式 BI 和分析
Zoomdata 是一款专为大数据设计的现代 BI 和数据分析工具。Zoomdata 支持广泛的数据可视化和分析功能,从探索性自助数据分析、嵌入式可视化分析到仪表板。在过去几年中,探索性自助数据分析变得越来越流行。它允许用户交互式地导航和探索数据,目的是从数据中发现隐藏的模式和见解。相比之下,传统的商业智能工具严重依赖于使用静态仪表板显示预定义的指标和 KPI。
Zoomdata 也支持仪表板。仪表板仍然是向只想看到结果的用户传达信息的有效方式。另一个不错的特性是能够将 Zoomdata 中创建的图表和仪表板嵌入到其他 web 应用程序中。利用标准的 HTML5、CSS、WebSockets 和 JavaScript,嵌入式分析可以在最流行的 web 浏览器和移动设备上运行,如 iPhones、iPads 和 Android *板电脑。如果您需要额外的灵活性,并且需要控制应用程序的外观和行为,Zoomdata 提供了 JavaScript SDK。REST API 有助于 Zoomdata 环境的自动化管理和操作。
实时数据可视化
也许 Zoomdata 最受欢迎的特性是它对实时数据可视化的支持。图表和 KPI 从实时流源(如 MQTT 和 Kafka)或基于 SQL 的数据源(如 Oracle、SQL Server 或 Impala)实时更新。
体系结构
Zoomdata 的核心是一个流处理引擎,它将所有传入的数据作为数据流进行划分和处理。数据源不一定是实时的,只要数据具有时间属性,Zoomdata 就可以对其进行流式处理。Zoomdata 有一种称为数据 DVR 的技术,它不仅可以让用户流式传输数据,还可以像 DVR 一样倒带、重放和暂停实时流式数据。Zoomdata 将大部分数据处理下推到数据所在的数据源,通过最小化数据移动来利用数据局部性。 iii Zoomdata 在幕后使用 Apache Spark 作为补充的数据处理层。它将流数据缓存为 Spark 数据帧,并将其存储在结果集缓存中。Zoomdata 总是先检查并尝试从结果集缓存中检索数据,然后再转到原始源。 iv 图 9-1 展示了 Zoomdata 的高层架构。Apache Spark 支持 Zoomdata 的大部分功能,如 Zoomdata 融合和结果集缓存。
图 9-1
High-level architecture of Zoomdata
与 Apache Spark 的深度集成
Zoomdata 最令人印象深刻的特性之一是它与 Apache Spark 的深度集成。如前所述,Zoomdata 使用 Spark 来实现其流特性。Zoomdata 还使用 Spark 进行查询优化,方法是将数据缓存为 Spark 数据帧,然后使用 Spark 对缓存的数据执行计算、聚合和过滤。Spark 还支持另一个名为 SparkIt 的 Zoomdata 特性。SparkIt 提高了慢速数据源的性能,如 S3 桶和文本文件。SparkIt 将这些数据集缓存到 Spark 中,Spark 将它们转换成可查询的高性能 Spark 数据帧。 v 最后,Zoomdata 使用 Spark 实现了 Zoomdata Fusion,这是一个高性能的数据虚拟化层,将多个数据源虚拟化为一个。
缩放数据融合
Zoomdata 使用一个名为 Zoomdata Fusion 的高性能数据虚拟化层。Zoomdata 也是由 Apache Spark 提供支持的,它使多个数据源显示为一个数据源,而无需将数据集一起移动到一个公共位置。 vi Zoomdata 在优化跨*台连接时使用 Spark 来缓存数据,但将处理推给数据源以获得最大的可伸缩性。通过 Zoomdata Fusion,用户将能够方便地查询和连接不同格式的不同数据集,显著缩短洞察时间,提高生产率,并减少对 ETL 的依赖(见图 9-2 )。
图 9-2
Diagram of Zoomdata Fusion
数据锐化
Zoomdata 获得了“数据锐化”专利,这是一种用于可视化大型数据集的优化技术。数据锐化的工作原理是将一个大型查询分成一系列微型查询。然后,这些微查询被发送到数据源并并行执行。Zoomdata 会立即返回第一个微查询的结果,并将其可视化为对最终结果的估计。随着新数据的到来,Zoomdata 会更新可视化效果,逐渐显示更好、更准确的估计,直到查询完成并最终显示完整的可视化效果。当所有这些都发生时,用户仍然可以与图表交互并启动其他仪表板,而无需等待长时间运行的查询完成。在底层,Zoomdata 的流架构使所有这些成为可能,将查询分解为微查询并将结果流回给用户。 vii
支持多种数据源
尽管 Zoomdata 是为大数据而设计的,但它也支持市场上最流行的 MPP 和 SQL 数据库,包括 Oracle、SQL Server、Teradata、MySQL、PostgreSQL、Vertica 和 Amazon Aurora。 viii 在几乎所有的企业环境中,您都会发现各种风格的 SQL 数据库和 Hadoop 发行版的组合。通过 Zoomdata Fusion,Zoomdata 允许用户轻松地查询这些不同的数据源,就像它是单个数据源一样。其他支持快速可视化分析的 Zoomdata 特性,如数据锐化、数据 DVR 和微查询,也都适用于这些 SQL 和 MPP 数据库。还支持其他流行的数据源,如 MongoDB、Solr、Elasticsearch 和亚马逊 S3(见图 9-3 )。
图 9-3
Partial list of Zoomdata’s supported data sources Note
有关如何安装 Zoomdata 的最新说明,请参考 Zoomdata 的网站。您还需要安装 Zoomdata 的 Kudu 连接器来连接 Kudu,这也可以在 Zoomdata 的网站上找到。
让我们从一个例子开始。启动 Zoomdata。您将看到一个类似图 9-4 的登录页面。
图 9-4
Zoomdata login page
你需要做的第一件事是创建一个新的数据源(见图 9-5 )。
图 9-5
Zoomdata homepage
注意,Kudu Impala 数据源被添加到数据源列表中(参见图 9-6 )。
图 9-6
Available data sources
您需要输入您的凭证,包括 JDBC URL 和 Kudu Master 的位置(图 9-7 )。在我们的示例中,Kudu 主机位于 192.168.56.101:7051。将 JDBC 网址更新为 JDBC:hive 2://192 . 168 . 56 . 101:21050/;auth=noSasl。注意,即使我们连接到 Impala,我们也需要在我们的 JDBC URL 中指定“hive2”。完成后点击“下一步”(图 9-8 )。
图 9-8
Kudu Impala Connector Input Credentials
图 9-7
Kudu Impala Connector page
选择我们将在本例中使用的表格(图 9-9 )。您也可以选择字段(图 9-10 )。除非内存不足,否则打开缓存总是一个好主意。因为我们的表来自关系数据存储,所以不需要启用 SparkIt。如果您还记得的话,SparkIt 只适用于简单的数据源,比如不提供缓存功能的文本文件。单击“下一步”继续下一步。
图 9-10
Data Source Fields
图 9-9
Data Source Tables
Zoomdata 可以异步维护某些数据源的缓存结果集(图 9-11 )。
图 9-11
Data Source Refresh
您还可以安排一个任务来刷新缓存和元数据(图 9-12 )。
图 9-12
Data Source Scheduler
对于更熟悉 cron Unix 风格调度程序的高级用户,也可以使用类似的界面(图 9-13 )。
图 9-13
Data Source Cron Scheduler
为了支持实时可视化,必须启用实时模式和回放(图 9-14 )。只要数据集有时间属性,在我们的例子中就是 sensortimestamp,Zoomdata 就可以使用时间属性进行可视化。我将刷新率和延迟设置为 1 秒。我还设置了从现在-1 分钟到现在的范围。
图 9-14
Time Bar Global Default Settings
您可以配置可用的图表(图 9-15 )。
图 9-15
Global Default Settings – Charts
根据图表的类型,有不同的配置选项可用(图 9-16 )。
图 9-16
Map: World Countries – Options
单击“完成”后,数据源将被保存您可以立即根据源创建图表(图 9-17 )。在这种情况下,我们将选择“条形图:多个指标”
图 9-17
Add New Chart
将显示一个类似于图 9-18 的条形图。您可以调整不同的条形图选项。
图 9-18
Bar Chart
当图表最大化时,您会得到更多的配置选项,如过滤器、颜色和图表样式等。在图 9-19 所示的例子中,我已经将条形图的颜色从黄色和蓝色改为紫色和绿色。
图 9-19
Bar Chart – Color
要添加更多图表,点击位于应用程序右上角附*的“添加图表”(图 9-20 )。
图 9-20
Add New Chart
饼图是最常见的图表类型之一(图 9-21 )。
图 9-21
Pie Chart
Zoomdata 有多种图表类型,如图 9-22 所示。
图 9-22
Multiple Chart Types
Zoomdata 具有映射功能,如图 9-23 所示。
图 9-23
Zoomdata Map
Zoomdata 可以用标记实时更新地图,如图 9-24 所示。
图 9-24
Zoomdata Map with Markers
具有 StreamSets、Kudu 和 Zoomdata 的实时物联网
最令人兴奋的大数据用例之一是 IoT(物联网)。物联网支持从嵌入硬件的传感器收集数据,例如智能手机、电子设备、电器、制造设备和医疗保健设备等。使用案例包括实时预测医疗保健、管网泄漏检测、水质监控、紧急警报系统、智能家居、智能城市和联网汽车等。物联网数据可以通过规则引擎进行实时决策,或通过机器学习模型进行更高级的实时预测分析。
对于我们的物联网示例,我们将为一家虚构的水务公司检测网络管道泄漏并监控水质。我们将使用一个 shell 脚本来生成随机数据,以模拟来自安装在水管上的传感器的物联网数据。我们将使用 Kudu 进行数据存储,使用 StreamSets 进行实时数据接收和流处理,使用 Zoomdata 进行实时数据可视化。
在真实的物联网项目中,数据源最有可能是通过轻量级消息协议(如 MQTT)提供数据的物联网网关。还强烈建议 Kafka 作为物联网网关和数据存储之间的缓冲区,以提供高可用性和可再生性,以防需要旧数据(图 9-25 )。
图 9-25
A Typical IoT Architecture using StreamSets, Kafka, Kudu, and Zoomdata
创建 Kudu 表
Zoomdata 要求 Kudu 表在时间戳字段上进行分区。要启用实时模式,时间戳字段必须存储为纪元时间中的 BIGINT。Zoomdata 会将时间戳字段识别为“可播放”,并在数据源上启用实时模式(清单 9-1 )。
CREATE TABLE my_sensors (
rowid BIGINT NOT NULL,
sensortimestamp BIGINT NOT NULL,
deviceid INTEGER,
temperature INTEGER,
pressure INTEGER,
humidity INTEGER,
ozone INTEGER,
sensortimestamp_str STRING,
city STRING,
temperature_warning INTEGER,
temperature_critical INTEGER,
pressure_warning INTEGER,
pressure_critical INTEGER,
humidity_warning INTEGER,
humidity_critical INTEGER,
ozone_warning INTEGER,
ozone_critical INTEGER,
lead_level INTEGER,
copper_level INTEGER,
phosphates_level INTEGER,
methane_level INTEGER,
chlorine_level INTEGER,
lead_warning INTEGER,
lead_critical INTEGER,
copper_warning INTEGER,
copper_critical INTEGER,
phosphates_warning INTEGER,
phosphates_critical INTEGER,
methane_warning INTEGER,
methane_critical INTEGER,
chlorine_warning INTEGER,
chlorine_critical INTEGER,
PRIMARY KEY(rowid,sensortimestamp)
)
PARTITION BY HASH (rowid) PARTITIONS 16,
RANGE (sensortimestamp)
(
PARTITION unix_timestamp('2017-01-01') <= VALUES <
unix_timestamp('2018-01-01'),
PARTITION unix_timestamp('2018-01-01') <= VALUES <
unix_timestamp('2019-01-01'),
PARTITION unix_timestamp('2019-01-01') <= VALUES <
unix_timestamp('2020-01-01')
)
STORED AS KUDU;
Listing 9-1Table for the sensor data. Must be run in impala-shell
测试数据源
我们将执行 shell 脚本来生成随机数据:generatetest.sh。我引入了每 50 条记录 1 秒的延迟,这样我就不会淹没我的测试服务器(清单 9-2 )。
#!/bin/bash
x=0
while true
do
echo $RANDOM$RANDOM,$(( $RANDOM % 10 + 20 )),
$(( ( RANDOM % 40 ) + 1 )),$(( ( RANDOM % 80 ) + 1 )),
$(( ( RANDOM % 30 ) + 1 )),$(( ( RANDOM % 20 ) + 1 )),
`date "+%Y-%m-%d %T"`,"Melton", 0,0,0,0,0,0,0,0,
$(( $RANDOM % 10 + 1 )), $(( $RANDOM % 10 + 1 )),
$(( $RANDOM % 10 + 1 )),$(( $RANDOM % 10 + 1 )),
$(( $RANDOM % 10 + 1 )),0,0,0,0,0,0,0,0,0,0
if [ "$x" = 50 ];
then
sleep 1
x=0
fi
((x++))
Done
Listing 9-2generatetest.sh shell script to generate test data
该脚本将生成随机传感器数据。清单 9-3 显示了数据的样子。
891013984,23,8,4,24,11,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,2,8,3,1,4,0,0,0,0,0,0,0,0,0,0
1191723491,29,20,68,14,10,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,7,1,6,4,3,0,0,0,0,0,0,0,0,0,0
919749,24,25,67,12,10,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,4,6,10,9,4,0,0,0,0,0,0,0,0,0,0
2615810801,22,21,59,24,11,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,5,7,10,7,2,0,0,0,0,0,0,0,0,0,0
2409532223,25,6,45,21,3,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,4,1,9,4,4,0,0,0,0,0,0,0,0,0,0
2229524773,29,20,68,12,3,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,6,7,2,4,9,0,0,0,0,0,0,0,0,0,0
295358267,22,15,16,7,1,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,1,4,6,10,8,0,0,0,0,0,0,0,0,0,0
836218647,28,25,59,3,19,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,7,2,6,3,4,0,0,0,0,0,0,0,0,0,0
2379015092,24,23,23,10,14,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,2,1,10,8,7,0,0,0,0,0,0,0,0,0,0
189463852,20,2,10,30,16,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,8,4,7,8,7,0,0,0,0,0,0,0,0,0,0
1250719778,26,15,68,30,4,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,7,6,9,8,10,0,0,0,0,0,0,0,0,0,0
1380822028,27,32,40,11,7,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,2,6,7,6,5,0,0,0,0,0,0,0,0,0,0
2698312711,21,14,5,29,19,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,2,2,8,1,3,0,0,0,0,0,0,0,0,0,0
1300319275,23,33,52,24,4,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,2,7,1,1,3,0,0,0,0,0,0,0,0,0,0
2491313552,27,25,69,24,10,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,8,2,4,3,8,0,0,0,0,0,0,0,0,0,0
149243062,21,24,2,15,8,2017-07-09 17:31:33,Melton,
0,0,0,0,0,0,0,0,7,3,3,5,7,0,0,0,0,0,0,0,0,0,0
Listing 9-3Sample test sensor data
设计管道
我们现在可以设计 StreamSets 管道了。有关流集的更深入讨论,请参考第九章。我们需要做的第一件事是定义一个原点,或者数据源。StreamSets 支持不同类型的源,例如 MQTT、JDBC、S3、卡夫卡和 Flume 等等。对于这个例子,我们将使用“文件尾”原点。将数据源设置为 sensordata.csv。稍后我们将运行一个脚本来填充该文件(图 9-26 )。
图 9-26
StreamSets file tail origin
文件尾数据源需要其元数据的第二个目标。让我们添加一个本地文件系统目的地来存储元数据(图 9-27 )。
图 9-27
StreamSets local file system
我们需要一个字段拆分器处理器将 CSV 数据拆分成单独的列(图 9-28 )。我们将在新的拆分字段配置框中指定我们的 SDC 字段(清单 9-4 )。
图 9-28
StreamSets field splitter
[
"/rowid",
"/deviceid",
"/temperature",
"/pressure",
"/humidity",
"/ozone",
"/sensortimestamp_str",
"/city",
"/temperature_warning",
"/temperature_critical",
"/pressure_warning",
"/pressure_critical",
"/humidity_warning",
"/humidity_critical",
"/ozone_warning",
"/ozone_critical",
"/lead_level",
"/copper_level",
"/phosphates_level",
"/methane_level",
"/chlorine_level",
"/lead_warning",
"/lead_critical",
"/copper_warning",
"/copper_critical",
"/phosphates_warning",
"/phosphates_critical",
"/methane_warning",
"/methane_critical",
"/chlorine_warning",
"/chlorine_critical"
]
Listing 9-4New SDC Fields
我们还需要一个字段类型转换器来转换字段拆分器处理器生成的字段的数据类型(图 9-29 )。
图 9-29
StreamSets field converter
在配置字段类型转换器处理器时,我们必须指定需要转换的字段以及要将数据转换成的数据类型。默认情况下,SDC 将所有字段设置为字符串。您需要将字段转换成正确的数据类型(图 9-30 )。
图 9-30
StreamSets field converter
Kudu 希望时间戳采用 Unix 时间格式。我们将使用 JavaScript 评估器(图 9-31 )并编写代码将时间戳从字符串转换为 Unix 时间格式,如 BIGINT ix (清单 9-5 )。请注意,Kudu 的最新版本已经解决了这个限制。但是,Zoomdata 仍然希望日期采用 Unix 时间格式。查阅第七章,了解更多关于流集赋值器的信息。
图 9-31
StreamSets Javascript Evaluator
Date.prototype.getUnixTime = function() { return this.getTime()/1000|0 };
if(!Date.now) Date.now = function() { return new Date(); }
Date.time = function() { return Date.now().getUnixTime(); }
for(var i = 0; i < records.length; i++) {
try {
var someDate = new Date(records[i].value['sensortimestamp_str']);
var theUnixTime = someDate.getUnixTime();
records[i].value['sensortimestamp'] = theUnixTime;
output.write(records[i]);
} catch (e) {
error.write(records[i], e);
}
Listing 9-5Javascript Evaluator code
最后,我们使用 Kudu 目的地作为数据的最终目的地。确保将 SDC 字段映射到正确的 Kudu 列名(图 9-32 )。
图 9-32
StreamSets Kudu destination
就在 StreamSets 那头。
配置 Zoomdata
让我们为 Zoomdata 配置一个数据源。我们将使用 Kudu Impala 连接器(图 9-33 )。
图 9-33
Zoomdata data source
输入连接信息,如 Kudu 主机的 IP 地址和端口号、JDBC URL 等。确保验证您的连接(图 9-34 )。
图 9-34
Zoomdata data connection input credentials
选择您刚才创建的表。如果您愿意,您可以排除字段(图 9-35 和图 9-36 )。
图 9-36
Zoomdata data connection fields
图 9-35
Zoomdata data connection tables
如果愿意,您可以安排数据缓存。如果您有一个大型数据集,缓存可以帮助提高性能和可伸缩性(图 9-37 )。
图 9-37
Zoomdata data connection refresh
配置时间栏。确保启用实时模式和回放(图 9-38 )。
图 9-38
Zoomdata time bar
您还可以设置图表的默认配置选项(图 9-39 )。
图 9-39
Zoomdata time bar
保存数据源。你现在可以开始设计用户界面了(图 9-40 )。
图 9-40
Zoomdata add new chart
我选择棒线:多重指标作为我的第一个图表。您还看不到任何数据,因为我们还没有启动 StreamSets 管道(图 9-41 )。
图 9-41
An empty chart
让我们开始生成一些数据。在安装 StreamSets 的服务器上,打开一个终端窗口并运行脚本。将输出重定向到文件尾目标:。/generate test . CSV > > sensor data . CSV
启动 StreamSets 管道。接下来,您将通过关于您的管道的不同统计数据(如记录吞吐量、批处理吞吐量、每个步骤的输入和输出记录计数)看到流集中的数据。它还会显示您的管道中是否有任何错误(图 9-42 )。
图 9-42
StreamSets canvas
当您设计 Zoomdata 仪表板时,图表会立即填充(图 9-43 )。
图 9-43
Live Zoomdata dashboard
恭喜你!您刚刚使用 StreamSets、Kudu 和 ZoomData 实现了一个实时物联网管道。
数据整理
在大多数情况下,数据不会以适合数据分析的格式出现。可能需要一些数据转换来转换字段的数据类型;可能需要数据清理来处理缺失或不正确的值;或者数据需要与 CRM、天气数据或 web 日志等其他数据源相结合。一项调查表明,数据科学家所做的 80%是数据准备和清理,剩下的 20%用于实际的数据分析。 x 在传统的数据仓库和商业智能环境中,数据通过提取、转换和加载(ETL)过程适合于分析。在非高峰时段,数据被清理、转换并从在线事务处理(OLTP)源复制到企业数据仓库。各种报告、关键绩效指标(KPI)和数据可视化都用新数据进行了更新,并呈现在企业仪表盘中,随时可供用户使用。OLAP 立方体和数据透视表提供了一定程度的交互性。在某些情况下,超级用户被授予对报告数据库或数据仓库复制副本的 SQL 访问权限,他们可以在其中运行即席 SQL 查询。但是总的来说,传统的数据仓库和商业智能是相当静态的。
大数据的出现使得旧式 ETL 不再适用。数周或数月的前期数据建模和 ETL 设计来适应新的数据集被认为是僵化和过时的。随着越来越多的工作负载被转移到大数据*台,曾经被视为组织的中央数据存储库的企业数据仓库现在与企业“数据中枢”或“数据湖”共享这一称号。在适当的情况下,ETL 已经被 ELT 所取代。提取、加载和转换(ELT)允许用户在很少或没有转换的情况下将数据转储到大数据*台。然后由数据分析师根据他或她的需求使数据适合于分析。ELT 在,ETL 不在。EDW 是僵化和结构化的。大数据快速而敏捷。
与此同时,用户变得更加精明,要求也更高。大量的数据是可用的,等待 ETL 开发人员团队几周或几个月来设计和实现数据摄取管道以使这些数据可供消费不再是一种选择。出现了一系列新的活动,统称为数据整理。
数据整理不仅仅是为分析准备数据。数据整理是数据分析过程本身的一部分。通过执行数据整理、迭代发现、结构化、清理、丰富和验证,用户可以更好地理解他们的数据,并能够提出更好的问题。当用户反复讨论他们的数据时,隐藏的模式就会暴露出来,揭示出分析数据的新方法。数据整理是大数据和 ELT 的完美补充。斯坦福大学和伯克利大学的数据整理先驱们,后来又创办了 Trifacta,提出了数据整理的六个核心活动(表 9-1 )。Xi
表 9-1
Six Core Data Wrangling Activities
| 活动 | 描述 | | :-- | :-- | | 发现 | 这是数据分析和评估阶段。 | | 结构化 | 确定数据结构的一组活动。创建模式、旋转行、添加或删除列等。 | | 清洁 | 一组活动,包括修复无效或缺失的值、标准化特定字段的格式(如日期、电话和州)、删除额外字符等。 | | 浓缩 | 一组活动,涉及将数据与其他数据源连接或混合,以改善数据分析的结果。 | | 验证 | 一组活动,用于检查所执行的充实和数据清理是否真正实现了其目标。 | | 出版 | 一旦你对数据整理的结果感到满意,就该公布结果了。结果可以是 Tableau 等数据可视化工具的输入数据,也可以是营销活动计划的输入数据。 |请注意,数据整理活动本质上是迭代的。您通常会以不同的顺序多次执行这些步骤,以获得想要的结果。
如果您曾经使用 Microsoft Excel 使数值看起来像某种格式,或者使用正则表达式删除某些字符,那么您已经执行了一种简单形式的数据整理。Microsoft Excel 实际上是一个很好的数据辩论工具,尽管它并不是严格为这类任务设计的。然而,当在大型数据集上使用时,或者当数据存储在大数据*台或关系数据库中时,Microsoft Excel 存在不足。一种新型的交互式数据辩论工具被开发出来,以填补市场空白。这些数据整理工具将交互式数据处理提升到了一个新的水*,提供了使通常需要编码专业知识的任务变得容易执行的功能。这些工具中的大多数都可以自动转换,并根据所提供的数据集提供相关的建议。它们与 Cloudera Enterprise 等大数据*台深度集成,可以通过 Impala、Hive、Spark HDFS 或 HBase 进行连接。我将展示如何使用一些最流行的数据整理工具的例子。
三连中
Trifacta 是数据整理的先驱之一。由斯坦福大学博士肖恩·坎德尔,加州大学伯克利分校教授乔·赫勒斯坦,华盛顿大学和前斯坦福大学教授杰弗里·赫尔开发。他们开始了一个名为斯坦福/伯克利牧马人的联合研究项目,最终成为 Trifacta。 xii Trifacta 销售他们产品的两个版本,Trifacta 牧马人和 Trifacta 牧马人企业。Trifacta Wrangler 是一个免费的桌面应用程序,旨在供个人使用小型数据集。Trifacta Wrangler Enterprise 专为集中管理安全和数据治理的团队而设计。 xiii 如前所述,Trifacta 可以连接到 Impala 以提供高性能的查询能力。 xiv 关于如何安装 Trifacta 牧马人,请咨询 tri acta 的网站。
我给你看看 Trifacta 牧马人的一些特点。Trifacta 组织基于“流”的数据整理活动。Trifacta 牧马人会在你第一次启动应用的时候要求你创建一个新的流量。
接下来你需要做的是上传一个数据集。Trifacta 可以连接到不同类型的数据源,但是现在我们将上传一个包含客户信息的示例 CSV 文件。上传 CSV 文件后,您现在可以浏览数据。点击“详细信息”按钮旁边的按钮,并选择“新流程中的争论”(图 9-44 )。
图 9-44
Wrangle in new Flow
您将看到类似图 9-45 的 transformer 页面。Trifacta 立即检测并显示各个列中的数据是如何分布的。
图 9-45
Trifacta’s transformer page
您可以滚动到显示数据分布的图表的左侧或右侧,以快速查看该列的内容。这将帮助您确定需要处理或转换的数据(图 9-46 )。
图 9-46
Histogram showing data distribution
在列名称下,您会看到特定列的数据质量指示器。不匹配的数据类型将显示为红色,空行将显示为灰色。如果存储在该栏中的数据全部有效,指示器应为绿色(图 9-47 )。
图 9-47
Data quality bar
通过单击特定的列,您将看到关于如何转换存储在该列上的数据的各种建议。这项功能由机器学习提供支持。Trifacta 从您过去的数据整理活动中学习,以提出下一步建议(图 9-48 )。
图 9-48
Trifacta’s suggestions
不同的栏目得到不同的建议(图 9-49 )。
图 9-49
Trifacta has different suggestions for different types of data
单击列名旁边的按钮,获取特定列的数据转换选项列表。您可以重命名列名、更改数据类型、过滤、清理或对列执行聚合等等(图 9-50 )。
图 9-50
Additional transformation options
几乎每种类型的数据转换都可用(图 9-51 )。
图 9-51
Additional transformation options
双击该列将显示另一个窗口,其中包含关于该列中存储的信息的各种统计信息和有用信息。信息包括最大值、最频繁值和字符串长度统计等(图 9-52 )。
图 9-52
The final output from the Job Results window
Trifacta 足够聪明,能够知道该列是否包含位置信息,比如邮政编码。它将尝试使用地图显示信息(图 9-53 )。
图 9-53
The final output from the Job Results window
让我们尝试一种建议的数据转换。让我们将姓氏从全部大写转换为正确的姓氏(只有第一个字母是大写的)。选择最后一个建议并点击“修改”按钮以应用更改(图 9-54 )。
图 9-54
Apply the transformation
姓氏已经转换,如图 9-55 所示。
图 9-55
The transformation has been applied
点击页面右上角附*的“生成结果”按钮(图 9-56 )。
图 9-56
Generate Results
您可以用不同的格式保存结果。为我们的数据集选择 CSV。如果需要,您可以选择压缩结果。确保选中“分析结果”选项,以生成结果的分析结果(图 9-57 )。
图 9-57
Result Summary
将出现“结果摘要”窗口。它将包括各种统计数据和关于您的数据的信息。检查一下,确保结果如你所愿。Trifacta 没有内置的数据可视化。它依赖于其他工具,如 Tableau、Qlik、Power BI,甚至 Microsoft Excel 来提供可视化。
Alteryx(变形虫)
Alteryx 是另一家受欢迎的软件开发公司,开发数据整理和分析软件。该公司成立于 2010 年,总部位于加利福尼亚州欧文市。它支持多种数据源,如 Oracle、SQL Server、Hive 和 Impala xv ,以实现快速的即席查询功能。
第一次启动 Alteryx 时会出现一个入门窗口(图 9-58 )。您可以浏览教程、打开最*的工作流程或创建新的工作流程。现在,让我们创建一个新的工作流。
图 9-58
Getting started window
Alteryx 以易用著称。用户与 Alteryx 的大多数交互都涉及到从工具面板中拖放和配置工具(图 9-59 )。
图 9-59
Alteryx main window
工具选项板为用户提供了按工具类别组织的工具。您将这些工具拖到画布中,并连接它们来设计您的工作流。这些工具允许用户执行不同的操作,用户通常会执行这些操作来处理和分析数据。常见的操作包括选择、过滤、排序、连接、联合、汇总和浏览等等。这些是用户通常通过开发 SQL 查询来执行的任务。Alteryx 提供了一个更加简单和高效的方法(图 9-60 )。
图 9-60
Tool Palette – Favorites
Alteryx 还为更复杂的操作提供了无数的工具,例如相关性、换位、人口统计分析、行为分析和空间匹配等等。浏览其他选项卡,熟悉可用的工具(图 9-61 )。
图 9-61
Tool Palette – Preparation
每个工作流都从使用输入数据工具指定数据源开始。将输入数据工具拖放到工作流窗口。您可以指定一个文件或连接到外部数据源,如 Impala、Oracle、SQL Server 或任何提供 ODBC 接口的数据源(图 9-62 )。
图 9-62
Input Data Tool
在本例中,我们将从一个文件中获取数据(图 9-63 )。
图 9-63
Select file
Alteryx 附带了几个示例文件。让我们使用 Customers.csv 文件(图 9-64 )。
图 9-64
Select Customers.csv
现在您已经配置了一个数据源,让我们拖放选择工具。选择工具允许您选择特定的列。默认情况下,所有的列都是选中的,所以在这个例子中,让我们取消选中一些列。确保连接输入数据并选择工具,如图 9-65 所示。
图 9-65
Select tool
在我们选择了几列之后,我们可以使用排序工具对结果进行排序。连接选择工具和排序工具。排序工具允许您指定希望如何对结果进行排序。在本例中,我们将按城市和客户群升序排序。您可以通过配置选项(图 9-66 )来实现。
图 9-66
Sort Tool
我们可以继续添加更多的工具,让工作流变得尽可能复杂,但是我们将在这个例子中停止。几乎每个 Alteryx 工作流都以浏览或保存工作流的结果而结束。
让我们浏览结果以确保数据看起来是正确的。在工作流程窗口上拖动一个浏览工具,并确保它与分类工具连接(图 9-67 )。
图 9-67
Browse data tool
通过单击位于窗口右上角帮助菜单项附*的绿色运行按钮来运行工作流。几秒钟后,工作流的结果将显示在输出窗口中。检查结果,查看是否选择了正确的列,以及排序顺序是否与您之前指定的一致。
像 Trifacta 一样,Alteryx 也可以分析您的数据,并显示有用的统计数据和相关信息,如列中数据的分布、数据质量信息、列的数据类型、空值或空白值的数量、*均长度、最大长度等。在图 9-68 中,显示了 CustomerID 字段的统计数据。
图 9-68
Browse data
点击客户细分字段以显示关于该列的统计数据(图 9-69 )。
图 9-69
Data quality – Customer Segment field
图 9-70 显示了城市字段的统计数据。
图 9-70
Data quality – City field
如前所述,我们还可以将结果保存到文件或外部数据源,如 Kudu、Oracle、SQL Server 或 HDFS。从工作流窗口中删除浏览工具,并将其替换为输出数据工具(图 9-71 )。
图 9-71
Output Data tool
让我们将结果保存到一个文件中(图 9-71 )。屏幕左侧的配置窗口将允许您设置如何保存文件的选项,包括文件格式。如图所示,您可以选择以不同的格式保存文件,例如 CSV、Excel、JSON、Tableau data extract 和 Qlikview data eXchange 等。
图 9-72
Select file format
让我们将数据保存为 CSV 格式(图 9-72 )。保存结果后,让我们用记事本检查文件以确保数据被正确保存(图 9-73 )。
图 9-73
Validate saved data
恭喜你!现在,您已经对 Alteryx 有了一些基本的了解和实践经验。
大数据
Datameer 是另一个流行的数据整理工具,内置了数据可视化和机器学习功能。它有一个类似电子表格的界面,包括 200 多个分析功能。 xvi Datameer 总部位于美国加州三藩市,成立于 2009 年。与 Trifacta 和 Alteryx 不同,Datameer 没有用于 Impala 的连接器,尽管它有用于 Spark、HDFS、Hive 和 HBase 的连接器。XVII
让我们探索一下 Datameer。你需要做的第一件事是上传一个数据文件(图 9-74 )。
图 9-74
Upload data file
指定文件和文件类型并点击下一步(图 9-75 )。
图 9-75
Specify file type
您可以配置数据字段。Datameer 允许你改变数据类型、字段名称等等(图 9-76 )。
图 9-76
Configure the data fields
数据以电子表格的形式呈现(图 9-77 )。
图 9-77
Spreadsheet-like user interface
就像 Trifacta 和 Alteryx 一样,Datameer 对您的数据进行分析,并提供有关您的数据字段的统计数据和信息(图 9-78 )。
图 9-78
Datameer Flipside for visual data profiling
Datameer 使用一种称为智能分析的功能,为常见的机器学习任务提供内置支持,如聚类、决策树分类和建议(图 9-79 )。
图 9-79
Smart Analytics
执行聚类可以显示关于我们数据的一些有趣的模式(图 9-80 )。
图 9-80
Clustering
数据将被分组到簇中。分组将作为另一列添加到您的数据集中。每个集群将由一个集群 id 标识(图 9-81 )。
图 9-81
Column ID showing the different clusters
也可以使用决策树进行分类(图 9-82 )。
图 9-82
Decision trees
数据将通过如图 9-83 所示的附加预测字段进行分类。
图 9-83
Prediction column
如前所述,Datameer 包括内置的数据可视化(图 9-84 )。
图 9-84
Datameer data visualization
摘要
确保您的大数据可视化工具能够处理大数据的三个 V(量、多样性和速度)。如果您正在为您的组织选择工具,请考虑该工具是否能够处理 TB/Pb 大小的数据集。您的数据源是纯关系型的,还是也分析半结构化和非结构化的数据集?您是否要求实时或接*实时地接收和处理数据?尝试利用您现有的 BI 和数据可视化工具,并确定它们是否能够满足您的需求。您可能正在连接到 Hadoop 集群,但是您的数据集可能没有那么大。如今,这是一种常见的情况,组织使用大数据*台作为一种经济高效的方式来整合昂贵的报告服务器和数据集市。
在过去的几年里,数据整理工具变得很流行。用户变得越来越精明,要求也越来越高。谁比最了解数据的人更适合准备数据?如果您的组织中有坚持自己准备和转换数据的高级用户,那么可能值得看看我在本章中介绍的一些数据整理工具。
参考
- 克里斯汀·维特伦,詹姆斯·霍尔曼;“将 SAS 可视化分析添加到现有 SAS 商业智能部署的考虑因素”,SAS,2018,
http://support.sas.com/resources/papers/proceedings14/SAS146-2014.pdf
- 缩放数据;“关于缩放数据”,2018 年,“??””
- Zoomdata《实时&流分析》,Zoomdata,2018,
https://www.zoomdata.com/product/real-time-streaming-analytics/
- Zoomdata《实时&流分析》,Zoomdata,2018,
https://www.zoomdata.com/product/stream-processing-analytics/
https://www.zoomdata.com/product/apache-spark-data-stream-processing/
- Zoomdata《实时&流分析》,Zoomdata,2018,
https://www.zoomdata.com/product/stream-processing-analytics/
- Zoomdata《Zoomdata 中的数据锐化》,Zoomdata,2018,
https://www.zoomdata.com/docs/2.5/data-sharpening-in-zoomdata.html
- Zoomdata《实时&流分析》,Zoomdata,2018,
https://www.zoomdata.com/product/stream-processing-analytics/
- Coderwall《如何在 JavaScript 中从任意日期获取正确的 Unix 时间戳》,Coderwall,2018,
https://coderwall.com/p/rbfl6g/how-to-get-the-correct-unix-timestamp-from-any-date-in-javascript
- 吉尔出版社;“清洗大数据:最耗时、最不愉快的数据科学任务,调查称”,福布斯,2018,
https://www.forbes.com/sites/gilpress/2016/03/23/data-preparation-most-time-consuming-least-enjoyable-data-science-task-survey-says/#3bfc5cdc6f63
- Tye Rattenbury《六大核心数据角力活动》,Datanami,2015,
https://www.datanami.com/2015/09/14/six-core-data-wrangling-activities/
- 斯坦福;"牧马人是一个用于数据清理和转换的交互式工具."斯坦福,2013,
http://vis.stanford.edu/wrangler/
- Trifacta《组织的数据角力》,Trifacta,2018,
https://www.trifacta.com/products/wrangler-enterprise/
- Trifacta《黑斑羚 Hadoop 连接:从 Cloudera 黑斑羚和 Apache Hadoop 及更远》,2018,
https://www.trifacta.com/impala-hadoop/
- Alteryx" Alteryx 支持以多种方式访问 Cloudera . ",Alteryx,2018,
https://www.alteryx.com/partners/cloudera
- Datameer《Cloudera 与 Datameer》,Datameer,2018,
https://www.cloudera.com/partners/solutions/datameer.html
- Datameer《连接器》,Datameer,2018,
https://www.datameer.com/product/connectors/
十、分布式内存大数据计算
Alluxio,原名 Tachyon,是加州大学伯克利分校 AMPLab 的一个开源项目。Alluxio 是一个以内存为中心的分布式存储系统,最初是由李皓原在 2012 年作为一个研究项目开发的,当时他是 AMPLab 的一名博士生和 Apache Spark 创始人。 i 项目是 Berkeley Data Analytics Stack(BDAS)的存储层。2015 年,Alluxio,Inc .由李创立,旨在将 Alluxio 商业化,获得了 Andre esen Horowitz 750 万美元的现金注入。如今,Alluxio 拥有来自英特尔、IBM、雅虎和 Red Hat 等全球 50 个组织的 200 多名贡献者。几家知名公司目前正在生产中使用 Alluxio,如百度、阿里巴巴、Rackspace 和巴克莱。 ii
Spark 的合著者、DataBricks 的联合创始人兼执行主席、加州大学伯克利分校 AMPLab 的联合董事、李皓原大学的博士联合顾问扬·斯托伊察(ion Williams)表示:“作为一个从 Apache Spark 和 Hadoop MapReduce 等集群计算框架中抽象出现有存储系统差异的层,Alluxio 可以实现大数据存储的快速发展,类似于互联网协议(IP)实现互联网发展的方式。” iii
此外,加州大学伯克利分校计算机科学教授兼 AMPLab 主任 Michael Franklin 表示,“AMPLab 在新的大数据堆栈中创建了一些最重要的开源技术,包括 Apache Spark。Alluxio 是 AMPLab 的下一个具有重大影响的项目。我们看到它在存储层的发展中发挥着巨大的颠覆性作用,以处理不断扩大的大数据使用案例。” iv
体系结构
Alluxio 是一个以内存为中心的分布式存储系统,旨在成为大数据事实上的存储统一层。它提供了一个虚拟化层,统一了对不同存储引擎(如本地文件系统、HDFS、S3 和 NFS)和计算框架(如 Spark、MapReduce、Hive 和 Presto)的访问。图 10-1 给你一个 Alluxio 架构的概述。
图 10-1
Alluxio Architecture Overview
Alluxio 是协调数据共享和指导数据访问的中间层,同时为计算框架和大数据应用提供高性能低延迟的内存速度。Alluxio 与 Spark 和 Hadoop 无缝集成,只需要少量的配置更改。通过利用 Alluxio 的统一命名空间功能,应用程序只需连接到 Alluxio 即可访问存储在任何受支持的存储引擎中的数据。Alluxio 有自己的原生 API 以及 Hadoop 兼容的文件系统接口。便利类使用户能够执行最初为 Hadoop 编写的代码,而无需任何代码更改。REST API 提供了对其他语言的访问。我们将在本章的后面探讨 API。
Alluxio 的统一名称空间特性不支持 Kudu 等关系数据存储、Oracle 或 SQL Server 等关系数据库或 MongoDB 等文档数据库。当然,支持对 Alluxio 和提到的存储引擎的读写。开发人员可以使用 Spark 等计算框架从 Kudu 表创建数据帧,并以 Parquet 或 CSV 格式将其存储在 Alluxio 文件系统中,反之亦然(图 10-2 )。
图 10-2
Alluxio Technical Architecture
为什么要用 Alluxio?
典型的 Hadoop 发行版包括 20 多个开源组件。向您的技术堆栈中添加另一个组件可能是您最想不到的事情。尽管如此,Alluxio 提供了大量的好处,这将使你想知道为什么 Alluxio 不是核心 Apache Spark 的一部分。
显著提高大数据处理性能和可扩展性
这些年来,内存越来越便宜,而它的性能却越来越快。与此同时,硬盘驱动器的性能只是略有改善。毫无疑问,在内存中处理数据比在磁盘上处理数据快一个数量级。在几乎所有的编程范例中,我们都被建议在内存中缓存数据以提高性能。Apache Spark 优于 MapReduce 的一个主要优势是它能够缓存数据。Alluxio 将这一点提升到了一个新的水*,不仅作为缓存层提供大数据应用,还提供了一个成熟的分布式高性能以内存为中心的存储系统。
百度正在运营世界上最大的 Alluxio 集群之一,1000 个工作节点处理超过 2PB 的数据。借助 Alluxio,百度在查询和处理时间方面的性能*均提高了 10 倍,最高可达 30 倍,显著提高了百度做出重要业务决策的能力。 v 巴克莱发表文章描述了他们与 Alluxio 的经历。巴克莱数据科学家 Gianmario Spacagna 和高级分析主管 Harry Powell 能够使用 Alluxio 将他们的 Spark 工作从数小时调整到数秒。中国最大的旅游搜索引擎之一 Qunar.com 使用 Alluxio 后,性能提升了 15 到 300 倍。 vii
多个框架和应用程序可以以内存速度共享数据
典型的 Hadoop 集群有多个会话运行不同的计算框架,如 Spark 和 MapReduce。对于 Spark,每个应用程序都有自己的执行器进程,执行器中的每个任务都运行在自己的 JVM 上,将 Spark 应用程序相互隔离。这意味着 Spark(和 MapReduce)应用程序无法共享数据,除了写入 HDFS 或 S3 等存储系统。如图 10-3 所示,Spark 作业和 MapReduce 作业使用存储在 HDFS 或 S3 的相同数据。在图 10-4 中,多个 Spark 作业使用相同的数据,每个作业在自己的堆空间中存储自己版本的数据。 viii 不仅数据会重复,通过 HDFS 或 S3 共享数据也会很慢,尤其是当你共享大量数据的时候。
图 10-4
Different jobs sharing data via HDFS or S3
图 10-3
Different frameworks sharing data via HDFS or S3
通过使用 Alluxio 作为堆外存储(图 10-5 ),多个框架和作业可以以内存速度共享数据,减少数据重复,提高吞吐量,减少延迟。
图 10-5
Different jobs and frameworks sharing data at memory speed
在应用程序终止或出现故障时提供高可用性和持久性
在 Spark 中,执行器进程和执行器内存驻留在同一个 JVM 中,所有缓存的数据都存储在 JVM 堆空间中(图 10-6 )。
图 10-7
Spark job crashes or completes
图 10-6
Spark job with its own heap memory
当作业完成或者由于某种原因 JVM 由于运行时异常而崩溃时,堆空间中缓存的所有数据都将丢失,如图 10-7 和 10-8 所示。
图 10-8
Spark job crashes or completes. Heap space is lost
解决方案是使用 Alluxio 作为堆外存储(图 10-9 )。
图 10-9
Spark using Alluxio as off-heap storage
在这种情况下,即使 Spark JVM 崩溃,数据在 Alluxio 中仍然可用(图 10-10 和 10-11 )。
图 10-11
Spark job crashes or completes. Heap space is lost. Off-heap memory is still available.
图 10-10
Spark job crashes or completes
优化整体内存使用并最大限度地减少垃圾收集
通过使用 Alluxio,内存使用效率大大提高,因为数据在作业和框架之间共享,并且因为数据存储在堆外,所以垃圾收集也被最小化,从而进一步提高了作业和应用程序的性能(图 10-12 )。
图 10-12
Multiple Spark and MapReduce jobs can access the same data stored in Alluxio
降低硬件要求
Alluxio 的大数据处理速度明显快于 HDFS。IBM 的测试显示,在写入 io 方面,Alluxio 比 HDFS 快 110 倍。 ix 有了这样的性能,对额外硬件的需求就会减少,从而节省基础设施和许可成本。
Alluxio 组件
与 Hadoop 和其他 Hadoop 组件类似,Alluxio 有一个主/从架构。
主要主人
第一主节点管理集群的全局元数据。
第二主设备
辅助主节点管理日志并定期执行检查点操作。
工人
工作人员存储数据,并为来自应用程序的读或写数据请求提供服务。工作人员还管理本地资源,如内存和磁盘空间。
客户
Alluxio 客户端提供了一个文件系统 API,供用户与 Alluxio 通信。
装置
有几种方法可以安装 Alluxio。Alluxio 运行在 YARN、Mesos、Docker 和 EC2 上。 x 为了让您快速入门,我将在一台服务器上安装 Alluxio。
从 Alluxio 网站下载 Alluxio 的最新版本。
wget http://alluxio.org/downloads/files/1.6.1/alluxio-1.4.0-bin.tar.gz
tar xvfz alluxio-1.4.0-bin.tar.gz
cd alluxio-1.4.0
让我们格式化 worker 存储目录和 Alluxio 日志,以准备 worker 和 master。
./bin/alluxio format
Waiting for tasks to finish...
All tasks finished, please analyze the log at /opt/alluxio-1.4.0/bin/../logs/task.log.
Formatting Alluxio Master @ server01
开始 Alluxio 吧。
./bin/alluxio-start.sh local
Waiting for tasks to finish...
All tasks finished, please analyze the log at /opt/alluxio-1.4.0/bin/../logs/task.log.
Waiting for tasks to finish...
All tasks finished, please analyze the log at /opt/alluxio-1.4.0/bin/../logs/task.log.
Killed 0 processes on server01
Killed 0 processes on server01
Starting master @ server01\. Logging to /opt/alluxio-1.4.0/logs
Formatting RamFS: /mnt/ramdisk (4000mb)
Starting worker @ server01\. Logging to /opt/alluxio-1.4.0/logs
Starting proxy @ server01\. Logging to /opt/alluxio-1.4.0/logs
我创建了一个 100 MB 的文件,并将其复制到内存中。如果你有更多的内存,你可以创建一个更大的文件。列出目录的内容。
./bin/alluxio fs ls /
[root@server01 alluxio-1.4.0]# ./bin/alluxio fs copyFromLocal /root/test01.csv /
Copied /root/test01.csv to /
./bin/alluxio fs ls /
-rw-r--r-- root root 103.39MB 05-22-2017 22:21:14:925 In Memory /test01.csv
让我们将文件从内存保存到本地文件系统。
./bin/alluxio fs persist /test01.csv
persisted file /test01.csv with size 108416290
阿帕奇 Spark 和 Alluxio
您在 Alluxio 中访问数据的方式类似于从 Spark 中访问存储在 HDFS 和 S3 的数据。
val dataRDD = sc.textFile("alluxio://localhost:19998/test01.csv")
val parsedRDD = dataRDD.map{_.split(",")}
case class CustomerData(userid: Long, city: String, state: String, age: Short)
val dataDF = parsedRDD.map{ a => CustomerData (a(0).toLong, a(1), a(2), a(3).toShort) }.toDF
dataDF.show()
+------+---------------+-----+---+
|userid| city|state|age|
+------+---------------+-----+---+
| 300| Torrance| CA| 23|
| 302|Manhattan Beach| CA| 21|
+------+---------------+-----+---+
您还可以从 MapReduce、Hive、Flink 和 Presto 访问 Alluxio。查看 Alluxio 的在线文档了解更多详细信息。
管理 Alluxio
Alluxio 提供了一个 web 界面来方便系统管理和监控。您可以获得有关空间容量、使用情况、正常运行时间、启动时间和文件列表等高级和详细信息。Alluxio 为您提供了一个主界面和工作界面。Alluxio 还为典型的文件系统操作提供了命令行界面。
掌握
你可以通过访问http://<Master IP Address >:19999
(图 10-13 )进入 Alluxio 的主主页。
图 10-13
Master home page
工人
您可以通过访问http://<Worker IP Address>:30000
(图 10-14 )来访问每个 Alluxio worker 的 web 界面。
图 10-14
Worker home page
Apache Ignite
Ignite 是另一个类似于 Alluxio 的内存*台。GridGain Systems 最初在 2014 年向 Apache Software Foundation 捐赠了 Apache Ignite。2015 年被提升为顶层项目。 xi 它功能极其丰富,可以用作内存中数据网格、内存中数据库、内存中分布式文件系统、流分析引擎以及 Hadoop 和 Spark 的加速器等等。XII
Apache geode(Apache geode)
Geode 是一个分布式内存数据库,专为具有低延迟响应时间和高并发性要求的事务性应用程序而设计。Pivotal 在 2015 年向 Apache 孵化器提交了 Geode。它于 2016 年 11 月从 Apache 孵化器毕业,成为顶级 Apache 项目。Gemfire 是 Geode 的商业版本,是华尔街交易*台中使用的一种流行的低延迟交易系统。XIII
摘要
Spark 是一个快速的内存数据处理框架。Alluxio 提供了一个堆外存储,可用于提高跨作业和框架的数据共享效率,最大限度地减少垃圾收集,并优化整体内存使用,从而显著提高速度。不仅作业运行速度大大加快,而且由于硬件需求减少,您还可以降低成本。Alluxio 不是唯一可用的内存数据库;Apache Ignite 和 Geode 是可行的选择,其他商业替代产品如 Oracle Coherence 和 Times Ten 也是可行的选择。
本章介绍了分布式内存计算,尤其是 Alluxio。Alluxio 是 Spark 的默认堆外存储解决方案。你可以通过访问 Alluxio 在 Alluxio.org 或 Alluxio.com 的网站来了解更多。
参考
- 克里斯·马特曼;“Apache Spark for the Incubator”,Apache Spark,2013,
http://mail-archives.apache.org/mod_mbox/incubator-general/201306.mbox/%3CCDD80F64.D5F9D%25chris.a.mattmann@jpl.nasa.gov%3E
- 李皓原;“Alluxio,原名超光速粒子,随着 1.0 版本进入新时代,”Alluxio,2016,
https://www.alluxio.com/blog/alluxio-formerly-tachyon-is-entering-a-new-era-with-10-release
- 李皓原;“Alluxio,原名超光速粒子,随着 1.0 版本进入新时代,”Alluxio,2016,
https://www.alluxio.com/blog/alluxio-formerly-tachyon-is-entering-a-new-era-with-10-release
- MarketWired“Alluxio 以内存速度虚拟化用于 Pb 级计算的分布式存储,”Alluxio,2016,
http://www.marketwired.com/press-release/alluxio-virtualizes-distributed-storage-petabyte-scale-computing-in-memory-speeds-2099053.htm
- MarketWired“Alluxio 以内存速度虚拟化用于 Pb 级计算的分布式存储,”Alluxio,2016,
http://www.marketwired.com/press-release/alluxio-virtualizes-distributed-storage-petabyte-scale-computing-in-memory-speeds-2099053.htm
- 亨利·鲍威尔、詹马里奥·斯帕卡尼亚;“用超光速粒子让不可能成为可能:将 Spark 工作从数小时加速到数秒,”DZone,2016,
https://dzone.com/articles/Accelerate-In-Memory-Processing-with-Spark-from-Hours-to-Seconds-With-Tachyon
- 李皓原;“Alluxio 在 Strata+Hadoop World Beijing 2016 上的主题演讲”,Alluxio,2016,
https://www.slideshare.net/Alluxio/alluxio-keynote-at-stratahadoop-world-beijing-2016-65172341
- 费明·s。《通过用例快速入门》,英特尔,2016,
https://software.intel.com/en-us/blogs/2016/02/04/getting-started-with-tachyon-by-use-cases
- 吉尔·韦尔尼克;《用于超快速大数据处理的超光速粒子》,IBM,2015,
https://www.ibm.com/blogs/research/2015/08/tachyon-for-ultra-fast-big-data-processing/
- Alluxio《快速入门指南》,Alluxio,2018,
https://www.alluxio.org/docs/1.6/en/Getting-Started.html
- 尼基塔·伊万诺夫;“用 Apache Ignite 点燃大数据处理”,InfoWorld,2016,
https://www.infoworld.com/article/3135070/data-center/fire-up-big-data-processing-with-apache-ignite.html
- GridGain“GridGain 内存计算*台的基础”,GridGain,2018,
https://www.gridgain.com/technology/apache-ignite
- 阿帕奇软件基金会;“Apache Software Foundation 宣布 Apache Geode 为顶级项目,”GlobeNewsWire,2018,
https://globenewswire.com/news-release/2016/11/21/891611/0/en/The-Apache-Software-Foundation-Announces-Apache-Geode-as-a-Top-Level-Project.html
十一、大数据治理和管理
数据治理是用于正式管理组织数据的一组策略和流程。数据治理是一个广泛的话题,包括几个功能,比如数据质量、元数据管理、主数据管理和数据安全性等等。数据治理的主要目标是确保组织的数据安全可靠。
数据治理可能是组织信息管理策略中最重要的部分之一。数据治理问题,如缺乏数据质量或数据安全性受损,可能会导致数据驱动型项目失败或造成巨大的收入损失。对于银行和政府机构等组织来说,数据治理是必须的。有几个数据治理和管理框架可供选择,比如 DAMA 框架。DAMA 框架在一个指南中编纂了一个知识领域和过程的集合,该指南被称为“知识的数据管理主体的 DAMA 指南或 DAMA-DMBoK 指南” ii
本章的目的不是提供数据治理的详尽内容。已经有整本书都是关于数据治理的。相反,我的目的是从大数据的角度向您介绍数据治理。我使用 Cloudera Navigator 来展示大数据*台上的常见数据治理任务。要了解更多关于数据治理的信息,我推荐 John Ladley 的《数据治理:如何设计、部署和维持有效的数据治理计划》(Morgan Kaufmann,2012)。要了解更多关于 Cloudera Navigator 的信息,请访问 Cloudera 的网站。
大数据的数据治理
因为数据被整合到大数据*台(也称为“数据湖”或“数据中心”)中,所以数据治理变得更加重要。在单个*台上存储数百万个文件和表格会成为数据管理的噩梦。想象一下,从不同的数据源获取相同数据的多个版本,或者不得不费力地通过数百万个文件和表来寻找特定的信息。在数据仓库环境中,数据治理至关重要,但在涉及大数据时,这一重要性被放大了。一旦您开始处理以不同类型和格式存储的数百 TB 或数 Pb 的数据,您就会对数据治理有真正的了解。成功的大数据战略依赖于执行良好的数据治理计划。
Cloudera 导航器
Cloudera 包括一个名为 Cloudera Navigator 的数据治理工具,用于帮助管理和组织存储在数据湖中的数据。在一些组织中,根据组织的规模,通常会有一个指定的人或小组负责数据治理。他们被称为数据管家。一些团队将数据管理员的角色与管理员、架构师或数据质量工程师的角色结合起来。无论您的团队是如何构建的,都应该有一个正式的数据治理框架在您的组织中实现和遵循。
数据管家和管理员可以使用 Cloudera Navigator 来主动管理和监控存储在数据湖中的数据。Cloudera Navigator 允许您执行元数据管理、数据分类、数据审计和访问控制等任务,探索数据谱系,执行影响分析,实现策略,以及自动化数据生命周期。
您可以从 Cloudera Manager 访问 Cloudera Navigator。导航到集群,然后点击 Cloudera Navigator,如图 11-1 所示。您也可以通过将浏览器指向在端口 7187 上安装了 Cloudera 元数据服务器的主机名来直接访问 Cloudera Navigator。
图 11-1
How to access Cloudera Navigator from Cloudera Manager
登录后,您会看到一个搜索框,您可以在其中输入实体名称、元数据标签或任何可以帮助您找到想要搜索的实体的关键字。如图 11-2 所示,窗口左侧的过滤器可以帮助您执行更精确的分面搜索。
图 11-2
Cloudera Navigator User Interface
元数据管理
元数据管理涉及管理描述组织数据的数据。 iii 例如,元数据可以是文件的创建日期、表格大小或文件权限等等。Cloudera Navigator 允许用户搜索、定义和更新属性,并标记文件、表和目录等实体。元数据有三种类型 iv :技术元数据,包括文件或表格名称;创建日期和时间;大小权限和所有者等等。不允许用户修改技术元数据。自定义元数据是键值对,可以在创建实体之前和之后添加到实体中。托管元数据包括只能在创建实体后添加或更新的标记、描述和键值对。元数据是大多数数据治理任务的基础,如数据分类、搜索和策略实现。我向存储在 HDFS (annie,berkeley 和 marketing)的客户文件添加了一个描述和三个元数据标记,如图 11-3 所示。
图 11-3
Adding metadata tags and description to entities
搜索“marketing”会返回我之前标记的客户文件,如图 11-4 所示。
图 11-4
Searching using a specific metadata tag
数据分类
元数据标签和描述可能非常强大。标签可用于对数据进行分类。例如,可以将文件和表标记为“marketing ”,以标识属于市场营销部门的实体,也可以将实体标记为“finance ”,以让每个人都知道特定的实体属于财务部等等。您可以使用标签和键值对的组合,以任何方式对数据进行分类。您可以使用标签快速搜索分类在一起的实体,如图 11-5 所示。
图 11-5
Classifying data using metadata
数据沿袭和影响分析
Cloudera Navigator 允许用户查看数据血统。数据沿袭显示了一个实体与其他实体的关系。它显示了针对特定实体执行的所有数据转换(图 11-6 )。它显示了特定实体是如何创建的、原始数据源以及谁执行了数据转换。检查实体的数据沿袭是确定其可信度的有效方法。它在应用程序开发中也很有用,帮助架构师确定模式更改对其他实体的影响。
图 11-6
Data lineage showing the actual SQL statement used to create the table
审计和访问控制
Cloudera Navigator 提供审计功能,允许数据管理员监控 HBase、HDFS、Hive、HUE、Impala、Navigator 元数据服务器、Sentry 和 Solr 等服务上的审计事件。vcloud era Navigator 监测用户成功和不成功登录时的事件;创建或删除列、表、文件、文件夹或集合。并授予或撤消对象权限,以提及一些可用的审计事件。审核安全事件可以确保数据不会被没有必要权限的人更改。这在多用户访问数据湖的多租户环境中尤其有用。在大数据环境中跟踪访问尝试和不正确的对象权限可能会很困难。Cloudera Navigator 记录了用户 ID、对象名称、IP 地址以及执行的确切命令或查询。 vi 图 11-7 显示了一个审计事件,包括时间戳、用户名、IP 地址、资源以及执行的确切命令。
图 11-7
Searching using a specific metadata tag
策略实现和数据生命周期自动化
Cloudera Navigator 允许数据管理员定义策略,这些策略可用于自动化某些操作,如执行命令、添加自定义元数据标记以及向 JMS 消息队列发送消息。 vii 例如,您可以创建策略,在指定的时间段后自动归档数据,或者根据实体的元数据将数据移动到安全的位置。
Cloudera Navigator REST API
Cloudera Navigator 拥有 REST API,开发人员可以使用它来提供额外的功能或自动化任务(图 11-8 )。该 API 使用 HTTP 基本认证,并使用您用于登录 Cloudera Navigator 的相同用户名和密码。
图 11-8
Cloudera Navigator REST API
Cloudera Navigator 加密
Cloudera Navigator 包括 Cloudera Navigator Encrypt、Cloudera Navigator 密钥托管服务器和 Cloudera Navigator 密钥 HSM,以提供企业加密和密钥管理解决方案。Navigator Encrypt 的讨论超出了本章的范围。有关 Cloudera Navigator Encrypt 的更多信息,请咨询 Cloudera 的网站。
其他数据治理工具
这绝不是一个详尽的列表,但它应该让您对当今市场上可用的数据治理工具有所了解。
Apache Atlas 系统
Apache Atlas 是一个数据治理和元数据帧,主要由 Hortonworks 使用。它提供了与 Cloudera Navigator 类似的特性和功能,如数据分类、审计、数据沿袭和策略引擎。 viii 它还提供了一个 REST API,并与第三方商业数据治理工具集成。
信息元数据管理器和企业数据目录
Informatica 元数据管理器是 Informatica 的元数据管理工具,与 Cloudera Navigator 集成。元数据管理器提供端到端的企业数据沿袭。Cloudera Navigator 只管理存储在数据湖中的数据,数据湖之外的任何东西都是 Cloudera Navigator 无法触及的。Metadata Manager 提供了从数据仓库到数据湖的数据流和转换的完整视图,以及跨整个 ETL 管道的商业智能工具,使数据管理员能够更好地了解数据转换链和已应用于数据的业务规则。 ix Informatica 企业数据目录是 Informatica 的下一代数据治理工具,将很快取代元数据管理器。企业数据目录使用机器学习来分类和组织存储在数据湖中的数据。Informatica 还拥有主数据管理和数据质量解决方案。
科里布拉
Collibra 是另一个与 Cloudera Navigator 集成的企业数据治理工具。Collibra 数据治理包括业务术语表、数据字典、参考数据管理、数据管理自动化、摄取控制、分析模型治理和 Hadoop 作业监督。
水线数据
Waterline Data 是另一种数据治理工具,它使用机器学习来自动推荐元数据标签,并将其与业务术语表术语进行匹配。水线数据声称,它可以减少 80%以上的数据人工标记。机器学习算法基于数据管理员对标签的接受或拒绝而自动改进。 x
开始逻辑
Smartlogic 与我讨论的其他数据治理工具略有不同。Smartlogic 提供了一个内容智能*台,它结合了规则引擎和自然语言处理,通过应用元数据标签来自动对数据进行分类。 xi 它集成了 Solr、Sharepoint 和 Marklogic,并与 Oracle、微软、Lucidworks 和 SAP 建立了合作伙伴关系。
摘要
Cloudera Navigator 是一个企业数据治理工具,使组织能够正确管理和组织他们的数据。目标是通过确保您的数据安全可靠,防止数据湖变成“数据沼泽”。Cloudera Navigator 非常有用,应该成为你的工具集的一部分;然而,它不是一个全面的数据治理和管理工具。例如,它不处理主数据管理和数据质量管理。如前所述,如果您需要这样的功能,您可能需要将 Cloudera Navigator 与第三方数据治理工具(如 Informatica 主数据管理或数据质量)集成。其他工具如 Informatica Enterprise Information Catalog、Waterline Data 和 Smartlogic 使用机器学习来构建分类法和本体,以自动组织数据,提供更高级的数据治理功能。
参考
- A.R .猜测;《数据治理与数据管理的区别》,Dataversity,2018,
http://www.dataversity.net/the-difference-between-data-governance-data-management/
- 帕特里夏·库波利、苏珊·厄尔利、黛博拉·亨德森;《DAMA-DMBOK2 框架》,DAMA,2014,
https://dama.org/sites/default/files/download/DAMA-DMBOK2-Framework-V2-20140317-FINAL.pdf
- 玛格丽特·劳斯;《元数据管理》,TechTarget,2018,
http://whatis.techtarget.com/definition/metadata-management
- Cloudera《Cloudera Navigator 元数据架构》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/5-11-x/topics/cn_iu_metadata_arch.html
- Cloudera《Cloudera 领航员审计》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/5-6-x/topics/cn_iu_audits.html#cn_topic_7
- Cloudera“Hadoop 中的数据治理—第二部分”,Cloudera,2018,
https://vision.cloudera.com/data-governance-in-hadoop-part-2/
- Cloudera《元数据政策》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/5-7-x/topics/navigator_policies.html#xd_583c10bfdbd326ba-7dae4aa6-147c30d0933--7c4a
- 阿帕奇地图集;《Hadoop 的数据治理和元数据帧》,Apache Atlas,2018,
http://atlas.apache.org/
- 斯科特·亨德里克斯;“与 Cloudera Navigator 的 Informatica 集成现已提供端到端数据血统,”Cloudera,2018,
https://vision.cloudera.com/end-to-end-data-lineage-now-available-with-informatica-integration-with-cloudera-navigator/
- 水线数据;《像管理资产一样管理你的数据》,水线数据,2018,
https://www.waterlinedata.com/product-overview/
- 彭博;《Smartlogic 信号机有限公司公司概况》,彭博,2018,
https://www.bloomberg.com/research/stocks/private/snapshot.asp?privcapId=37460152
十二、云中的大数据
在过去几年中,云中的大数据部署变得越来越流行。云的灵活性和敏捷性非常适合运行 Hadoop 集群。云显著降低了 IT 成本,同时为应用程序提供了扩展能力。扩展和收缩集群只需几分钟,并且大多数任务不需要系统管理员。虽然一些组织仍然倾向于本地大数据部署,但如今大多数大数据环境都部署在三大公共云提供商之一。
我在这一章的目标是给你一个关于云中的 Cloudera Enterprise 的高层次概述。对于这个主题的更完整的处理,请参考用于 AWS、 i Azure、 ii 和 Google 云*台 iii 部署的 Cloudera 参考架构。
亚马逊网络服务(AWS)
AWS 于 2006 年 3 月推出,是三大主要公共云提供商中的第一家也是最大的一家。 iv AWS 提供最广泛的存储、应用、分析和计算服务。由于其先发优势,AWS 是领先的云提供商,客户范围从最大的公司到创新型初创企业和小型企业。
微软 Azure 服务
微软 Azure 提供类似 AWS 的云服务,例如 SQL 数据库、存储、应用程序和虚拟机等等。Azure 最*开始蚕食亚马逊的市场份额,因为其积极的定价策略和与微软技术栈的紧密集成。根据一项调查,2017 年 Azure 的整体采用率从 20%增长到 34%,而 AWS 在其受访者中保持在 57%。 v 在企业中,Azure 也凭借 Azure 降低了 AWS 的领先优势,实现率从 26%大幅提升至 46%。AWS 在企业中的采用率从 56%略微上升到 59%。VI
谷歌云*台(GCP)
2011 年推出的谷歌云*台是三大主要公共云提供商中最年轻的一家。最初是为了支持自己的服务,如 YouTube 和谷歌搜索,谷歌最终建立了包括 Cloud Spanner 和 Cloud Bigtable 在内的其他云服务,并向公众开放。GCP 最*获得了一些牵引力,但与 AWS 和 Azure 相比,它仍被认为没有准备好迎接黄金时间。
表 12-1 提供了一个粗略的指南 vii 帮助您在三个提供商之间映射等价服务 viii 。
表 12-1
Service Comparison Between AWS, Azure, and GCP
| 亚马逊网络服务 | 谷歌云*台 | 微软 Azure 服务 | | :-- | :-- | :-- | | 亚马逊 EC2 | 谷歌计算引擎 | Azure 虚拟机 | | 亚马逊 EC2 容器服务 | 谷歌容器引擎 | Azure 集装箱服务 | | AWS 弹性豆茎 | 谷歌应用引擎 | Azure 云服务和应用服务 | | 自动气象站λ | 谷歌云功能 | 天青函数 | | 亚马逊冰川和亚马逊 S3 标准——很少进入 | 谷歌云存储*线 | | | 亚马逊 S3 | 谷歌云存储标准 | 天蓝色块状斑点 | | 亚马逊 EC2 容器注册中心 | 谷歌容器注册 | | | 亚马逊 DynamoDB | 谷歌云数据存储或谷歌云大表 | DocumentDB 蓝色 | | 亚马逊 RDS | 谷歌云 SQL | 蓝色数据库 | | 亚马逊 EMR 和 AWS 数据管道 | Google Cloud Dataflow 和 Google Cloud Dataproc | Azure HD Insight | | 亚马逊 Kinesis 和亚马逊简单队列服务(SQS) | 谷歌云发布/订阅 | Azure 事件中心和 Azure 服务总线 | | 亚马逊红移 | Google big query(Google big query) | Azure SQL 数据仓库和 Azure 数据湖分析 | | 亚马逊云观测 | 谷歌云监控和谷歌云日志 | Azure 应用洞察和 Azure 运营洞察 | | 亚马逊弹性负载*衡 | 谷歌云负载*衡 | Azure 负载*衡器 | | 亚马逊 53 号公路 | 谷歌云域名系统和谷歌域名 | DNA 蓝色 | | AWS 直接连接 | 谷歌云互联 | 蓝色快车路线 | | AWS 身份和访问管理(IAM) | 谷歌云身份和访问管理 | 活动目录 | | AWS 组织 | 谷歌云资源管理器 | Azure 资源管理器 | | AWS 密钥管理服务(KMS) | 谷歌云密钥管理服务 | 蓝色钥匙保险库 | | 亚马逊检查员 | 谷歌云安全扫描器 | | | AWS 云阵 | 谷歌云部署管理器 | Azure 资源管理器 |云时代云中的企业
本章的重点是在云中运行 Cloudera Enterprise。在这种特定的设置下,云提供商只负责提供基础设施。将使用 Cloudera 企业组件和功能,而不是云提供商提供的服务。Cloudera Enterprise 可以集成亚马逊 S3 和 Azure Block Blobs 等云提供商提供的一些服务。
混合云和多云
您可能想知道,当三家公共云提供商已经提供大数据服务时,为什么有人会在云中使用 Cloudera Enterprise?两个原因:厂商锁定和数据可移植性。一旦你把数据放在一个云提供商那里,并且完全依赖他们的服务,你就被他们的*台锁定了。将您的数据迁移到本地或其他云提供商非常困难。即使您能够迁移您的数据,您仍然需要使用目标云提供商提供的服务来重构您的应用程序。将此与在云中使用 Cloudera Enterprise 进行比较。Cloudera Enterprise 是一个混合/多云大数据*台,这意味着可以部署 Cloudera Enterprise 集群在 AWS、Azure 和 GCP(或内部)上运行。将 Cloudera Enterprise 迁移到另一家云提供商要容易得多。因为您基本上只是在改变基础设施提供者,所以迁移您的应用程序只是更新连接细节的问题。
在当今不断变化的技术环境中,这种灵活性极其重要。多年来,许多公司一直在将其内部部署的 CDH 集群迁移到 AWS,反之亦然。我知道有几家公司成功地将其 CDH 集群从 AWS 迁移到 Azure。一些公司在内部部署生产集群,而他们的开发和测试/QA 环境在 AWS 或 Azure 中。
根据 RightScale 2017 年云状态报告,混合云是首选的企业战略,85%的企业拥有多云战略,高于 2016 年的 82%。 ix
瞬态集群
借助 Cloudera Enterprise,您可以选择在云中配置临时或永久(持久)集群。瞬态集群可以通过基于使用的定价来降低某些用例的成本。自助服务模式允许用户部署和管理自己的集群,从而提供了灵活性。它适用于间歇执行的数据科学或数据工程作业。您启动一个集群,运行您的作业,将您的数据存储在一个对象存储中,比如 S3(这样您的数据仍然可供将来使用),然后在完成后关闭您的集群。这些步骤有时是脚本化的,所以一切都是自动化的。在这种类型的部署模型中,存储和计算是分离的。 x 瞬态工作负载的示例包括 ETL 作业、训练机器学习模型、临时数据分析以及开发和测试/QA 工作流 xi 等等。
持久集群
对于需要随时可用的集群,持久集群更合适。这些持久集群是云中永久“开启”的集群,与本地集群有相似的要求。这些星系团也被称为“提升移位”星系团。这些集群通常是企业环境的一部分,其中的用例更具战略性。这些集群通常具有高可用性和灾难恢复、资源管理、安全性和数据治理。持久集群的例子有大型多用户集群、BI/分析集群、HBase 和 Kafka 集群等等。 xii 持久集群的定价通常是传统的基于节点的许可。
Cloudera 总监
Cloudera Enterprise 提供了一个强大的云管理工具,名为 Cloudera Director(见图 12-1 )。Cloudera Director 通过提供灵活且易于使用的自助服务功能,在 AWS、Azure 和 GCP 等云环境中部署和管理 Cloudera Enterprise,从而支持 Cloudera 的混合云和多云功能。Cloudera Director 包括一个客户端组件和一个 REST API,可用于自动化和编写云部署脚本。
图 12-1
Cloudera Director
Cloudera Director 架构
Cloudera Director 使用户可以非常轻松地在所有三家公共云提供商上部署和维护 Cloudera 环境。如图 12-2 所示,CDH 集群的实际管理和监控仍由 Cloudera Manager 执行,而数据治理、安全性、审计和元数据管理则由 Cloudera Navigator 执行。一旦您的组织开始在云中部署多个集群,您就会开始体会到 Cloudera Director 的价值。
图 12-2
Cloudera Director high-level architecture
Cloudera Director 客户端
Cloudera Director 包括客户端软件,您可以使用该软件从命令行部署和管理集群。它通常用于快速部署测试和开发环境。客户端使用配置文件来自动化集群部署。
Cloudera Director REST API
Cloudera Director 的所有功能都可以通过 REST API 访问。您可以使用 REST API 来自动化安装和管理 Cloudera 云部署的任务。您可以从用 Python 和 Java 编写的脚本或应用程序中调用 REST API。要了解更多关于 Cloudera Director 的 REST API,请参考 Cloudera Director SDK Github 页面。XIII
AWS 上的 Cloudera
由于 AWS 是最受欢迎的公共云提供商,我们将在本章重点介绍在 AWS 上运行 Cloudera Enterprise。我在本章谈到的大多数概念也适用于 Azure 和 GCP 上的 Cloudera Enterprise。我将对在 AWS 上部署 Cloudera Enterprise 的组件和需求进行概要介绍。提供在 AWS 上部署 Cloudera Enterprise 的全面指南超出了本章的范围。有关更多详细信息,请参考用于 AWS 部署的 Cloudera 企业参考架构。 十四
区域和可用性区域
在 AWS 中,服务部署在自包含的地理位置,称为区域。区域被进一步细分为可用性区域。可用性区域是区域内可以部署集群的独立位置。Cloudera 企业部署只能驻留在单个可用性区域中。XV
虚拟专用云(VPC)
AWS 有一个虚拟私有云或 VPC 的概念。XVIVPC 呈现了一个虚拟网络,其行为类似于传统网络。
安全组
安全组就像 AWS 中的防火墙。安全组使用户能够打开或关闭端口,限制 IP 地址,以及允许或禁止 EC2 实例的特定网络流量。Cloudera 企业集群需要三个安全组用于集群、flume 或摄取节点以及 edge 或 gateway 节点。 xvii 图 12-3 显示了在 AWS 中如何实现安全组的高级视图。
图 12-3
Cloudera Enterprise on AWS Security Groups
EC2 实例
EC2 实例有点像一个虚拟服务器,具有与之相关联的 CPU、RAM、网络和存储。Amazon 提供了几种针对各种用例优化的 EC2 实例类型。实例类型分为几个类别,以帮助您为您的应用程序选择正确的 EC2 实例类型。实例类型类别包括通用、计算优化、内存优化、加速计算和存储优化。Cloudera 为每个 CDH 集群节点类型推荐了合适的 EC2 实例类型。XVIII
主节点
将 EBS 存储用于主节点时,需要 EBS 优化的实例。
- c 4.2x 大,c 4.4x 大,c 4.8x 大
- m4.xlarge,m4.2xlarge,m4.4xlarge,m4.8xlarge,m4.10xlarge,m4.16xlarge
- R4 . XL 大,r 4.2x 大,r 4.4x 大,r 4.8x 大,r 4.16 x 大
如果使用临时磁盘部署主节点,建议使用以下实例类型。
- c 3.8 x 大号
- d 2.8 x 大
- 12.8 x 大
- 3.16 倍大
- r 3.8 x 大
工作节点
将 EBS 存储用于工作节点时,需要 EBS 优化的实例。
- c 4.2x 大,c 4.4x 大,c 4.8x 大
- m4.xlarge,m4.2 倍大,m4.4 倍大,m4.10 倍大
- R4 . XL 大,r 4.2x 大,r 4.4x 大,r 4.8x 大,r 4.16 x 大
- EBS 优化的 D2、I2 或 R3 实例类型
如果使用临时磁盘部署主节点,建议使用以下实例类型。
- c 3.8 x 大号
- d 2.8 x 大
- 2.8 x 大,2.16 x 大
- 3.8 x 大,3.16 x 大
- r 3.8 x 大
Note
Cloudera 建议大量使用 Impala 的 CDH 集群使用 d2.8xlarge、i2.8xlarge 或 i3.8xlarge 实例。
边缘节点
对于边缘节点没有具体的建议。您可以选择任何实例类型,只要它对您的工作负载有足够的资源。
关系数据库
Cloudera Enterprise 需要 MySQL、PostgreSQL 或 Oracle 等关系数据库来存储 Cloudera Manager、Hive 和 Impala metastore、Hue 和 Oozie 使用的数据库。您可以使用 EC2 实例或 RDS 来存放您的关系数据库。
Cloudera 总监
Cloudera 建议对 Cloudera Director 使用 c3.large 或 c4.large 实例。 xix
仓库
我描述了 Cloudera Enterprise 支持的不同类型的存储。
短暂的
如果您停止或终止附加到它的 EC2 实例,临时或实例存储将丢失(它将在实例重启后继续存在)。由于性能原因,临时存储适合于 HDFS 数据目录,因为从临时存储读取不需要通过网络远程读取。
使用临时存储时,您应该有一个备份或灾难恢复计划。使用 distcp 将数据复制到 S3,使用 Cloudera Manager 的备份和数据恢复(BDR)功能备份数据,或者使用 StreamSets Data Collector 等数据传输或复制工具,都有助于防止数据丢失。
弹性块存储
与暂时存储相比,弹性块存储(EBS)为 EC2 实例提供永久的块级存储。即使您停止或终止 EC2 实例,数据也不会丢失。有几种类型的 EBS 存储,如 ST1、SC1 和 GP2 卷。Cloudera 建议所有 EBS 支持的实例都使用 GP2。Cloudera 还建议 GP2 卷的大小至少为 100 GB,以保持足够的 IOPS。xx
S3
S3 是亚马逊作为服务提供的一个对象商店。S3 比传统存储引擎便宜得多,这使得它非常适合存储备份和“冷”数据,如旧的历史数据。对于瞬态工作负载,S3 也是保存 ETL 或 BI/分析工作流结果的理想数据存储。
S3 的一些 IO 特征使它不适合某些用例。首先是延迟。从 S3 读取数据比从 HDFS 或库都读取数据慢几倍。如果性能很重要,最佳实践是将“热”数据存储在 HDFS 或库都,将“冷”数据存储在 S3。其次,S3 是一个最终一致的数据存储,这意味着写入其中的数据可能在一段时间内不可读取,通常是几毫秒或几秒。有记录的案例表明滞后时间为几个小时。 xxi 这可能会给需要读取一致性的应用程序带来问题。
Cloudera Enterprise and S3
一些 Cloudera 企业服务可以与 S3 交互。Cloudera S3 连接器支持顺化的安全 S3 访问。 xxii 蜂巢,蜂巢上的 Spark、Spark 和 Impala 可以读写 S3 的数据。关于如何使用 Impala 访问 S3 数据的更多细节,请参阅第三章。有关如何使用 Spark 访问 S3 的详细信息,请参阅第五章。
S3Guard
如前所述,S3 是一个最终一致的数据存储。然而,大多数 Hadoop 应用程序需要读取一致性才能正常工作。Cloudera Enterprise 附带的大多数存储引擎(HDFS、HBase 和 Kudu)都提供读取一致性(Solr 最终是一致的)。
S3Guard 是由开源社区开发的,用于处理由 S3 最终一致性引起的问题。 xxiii S3Guard 不“修复”S3 的一致性模型,它通过将写入 S3 的所有元数据更改记录到外部“元数据存储”来工作;由于这个元数据存储应该是一致的,应用程序可以使用这些信息来完成丢失的元数据,这些元数据在 S3 可能还不可用。S3 护卫队和 CDH 一起出航。
基于 AWS 架构的 Cloudera Enterprise
图 12-4 展示了 AWS 上典型的 Cloudera 企业环境。XXIV
图 12-4
Cloudera Enterprise on AWS Architecture
使用 Cloudera Director 在 AWS 上部署 Cloudera Enterprise
没有比使用 Cloudera Director 在 AWS 上实际部署集群更好的学习方法了。请注意,此安装仅适用于测试或开发环境。
您需要在 EC2 实例上下载并安装 Cloudera Director。设置 AWS 环境超出了本书的范围。如果您需要有关如何为 Cloudera Director 设置 AWS 环境的指导,例如设置 VPC、安全组和 EC2 实例,请参考 Cloudera Director 用户指南。XXV
我假设您已经启动了 EC2 实例。本例中我们将使用 Red Hat/CentOS 7 . x(Ubuntu 和 Red Hat/CentOS 6.x 也受支持)。
使用 pem 文件登录到 EC2 实例。
ssh –i mypemfile.pem ec2-user@cloudera_director_private_ip
Note
根据您使用的 AMI,您可能需要使用不同的 ssh 登录。在 AWS 控制台中,查看“实例”页面中的使用说明。您可能需要使用 centos 或 root 登录,而不是 ec2-user。
您需要安装受支持版本的 Oracle JDK。目前支持 JDK 7 和 8。您可以从 Oracle 网站下载 rpm。
sudo yum localinstall jdk-version-linux-x64.rpm
添加 Cloudera Director 存储库
cd /etc/yum.repos.d/
sudo wget http://archive.cloudera.com/director/redhat/7/x86_64/director/cloudera-director.repo
一旦更新了存储库,现在就可以安装 Cloudera Director 服务器和客户端软件了。
sudo yum install cloudera-director-server cloudera-director-client
启动 Cloudera Director 服务器。
sudo service cloudera-director-server start
有时会启用 Red Hat/CentoOS 7 中的防火墙。这将阻止您连接到 Cloudera Director。禁用并停止防火墙。
sudo systemctl disable firewalld
sudo systemctl stop firewalld
Note
Cloudera 不建议将您的安全组配置为允许通过互联网访问您的 EC2 实例的公共 IP 地址。Cloudera 建议您使用 SOCKS 代理服务器连接到集群节点和 Cloudera Manager。有关如何配置 SOCKS 代理服务器的更多详细信息,请参考 Cloudera Director 用户指南。出于测试目的,您可以打开安全组以允许访问公共 IP 地址。只是别忘了关上它!
打开 web 浏览器,转到运行 Cloudera Director 的 EC2 实例的端口 7189 上的公共或私有 IP 地址。在用户名和密码字段使用“admin”登录(参见图 12-5 )。
图 12-5
Cloudera Director Login Page
您将看到一个指示板,帮助您添加 Cloudera 管理器或集群。让我们添加一个 Cloudera 管理器(见图 12-6 )。
图 12-6
Cloudera Director Dashboard
填写创建 Cloudera Manager 实例所需的信息,例如实例类型、AMI ID、安全组 ID、VPC 子网 ID 等,如图 12-7 所示。
图 12-7
Edit Instance Template
向下滚动以输入关于您的实例的更多细节(参见图 12-8 )。有关不同选项的更多信息,请参考 AWS 文档。完成后保存更改。
图 12-8
Edit Instance Template Page 2
输入关于 Cloudera Manager 实例的详细信息,例如名称和密码(参见图 12-9 )。
图 12-9
Add Cloudera Manager
在创建 Cloudera Manager 实例的同时,您可以开始创建集群。Cloudera Director 为您提供从头创建集群或从现有集群克隆的选项。您还必须输入集群名称,并选择要在集群上运行的服务(参见图 12-10 )。
图 12-10
Add Cluster
Cloudera Director 让您可以轻松配置集群节点,如图 12-11 所示。请注意,您的员工至少需要三个节点。
图 12-11
Cluster nodes
确认集群中的节点数量。准备好后点击确定(见图 12-12 )。
图 12-12
Confirmation to create compute instance
您将看到 Cloudera Manager 和集群安装的进度指示器(参见图 12-13 )。
图 12-13
Cloudera Manager and Cluster installation
安装完成后点击“下一步”(参见图 12-14 )。
图 12-14
Cloudera Manager and Cluster installation completed
将显示 Cloudera Director 仪表盘(参见图 12-15 )。
图 12-15
Cloudera Director Dashboard
点击 Cloudera Manager 安装的名称,获取更多关于 Cloudera Manager 的信息(见图 12-16 )。
图 12-16
Cloudera Director Dashboard – Cloudera Manager Details
或者,您可以单击 Cloudera Enterprise 集群的名称来获取关于该集群的更多信息,例如已安装的服务和实例组状态(参见图 12-17 )。
图 12-17
Cloudera Director Dashboard – Cluster Details
单击链接“转到集群”打开 Cloudera Manager。你会看到 Cloudera Manager 主页,如图 12-18 所示。
图 12-18
Cloudera Manager main page
导航到“主机”选项卡以获取集群节点列表(参见图 12-19 )。
图 12-19
All Hosts
展开“角色”以获得每个节点上运行的服务列表(参见图 12-20 )。
图 12-20
All Host – Roles
恭喜你!您刚刚使用 Cloudera Director 在 AWS 中安装了一个测试 Cloudera 企业集群。
Azure 和 GCP 上的 Cloudera 企业
有关如何在 Azure 和 GCP 上部署 Cloudera 的详细信息,请参考 Cloudera Director 用户指南。 xxvi 技术可能不同,但在 AWS 上部署 Cloudera Enterprise 中讨论的大部分概念也适用于 Azure 和 GCP。
Note
在 Microsoft Azure 上运行 Kudu 时,您可能会遇到 ntp xxvii 的问题。禁用时间同步应该可以解决这个问题。
云是时候了
根据 Cloudera 的说法,cloud era AltusXXVIII是一种“*台即服务,可以轻松、经济地在云中处理大规模数据集。”它面向数据工程师,支持在 Spark、Hive on Spark、Hive on MapReduce 和 MapReduce 中创建的数据管道的提交和执行。数据工程师不需要担心创建集群,因为集群是按需自动创建的。您按小时、工作节点的数量以及您为集群选择的 AWS EC2 实例类型付费。 xxix 目前,Cloudera Altus 只在 AWS 上运行,但对 Azure 的支持即将推出。
如果您曾经使用 Cloudera Director 创建过临时集群来运行您的 ETL 管道,请将 Cloudera Altus 视为一个更加简化和自动化的版本。Cloudera Altus 在很多方面与亚马逊 EMR 相似。
亚马逊弹性映射减少(EMR)
Amazon EMR 是第一个允许用户使用 EC2 实例在云中动态运行临时集群的服务。EMR 允许用户访问其他亚马逊服务,并将数据存储在亚马逊数据存储上,如 DynamoDB 和 S3。 xxx EMR 享有显著的市场份额,目标是具有瞬时大数据工作负载的公司。
大数据
Databricks 是另一个基于云的大数据*台。由在加州大学伯克利分校 AMPLab 创建 Apache Spark 的同一团队于 2013 年创建。虽然除了 Spark (Hive 和 MapReduce),Amazon 和 Cloudera 还提供了其他数据处理框架,但 Databricks 主要专注于 Spark。Databricks 最*又筹集了 1.4 亿美元的风险资金,使该公司筹集的资金总额达到 2.47 亿美元, xxxi 使其有足够的资金与 Cloudera、亚马逊、MapR 和 Hortonworks 等其他大数据*台竞争。
摘要
云中的大数据部署可以帮助组织更快、更便宜、更有效地从数据中获取价值。然而,并非所有的*台都是*等的。有些比其他的功能更多。有些提供了更多的灵活性。有些比较好用。在为您的组织选择合适的大数据和云*台时,有很多选择。我希望我在本章提供的信息能帮助你做出正确的决定。
参考
- Cloudera《面向 AWS 部署的 Cloudera 企业参考架构》,Cloudera,2017,
http://www.cloudera.com/documentation/other/reference-architecture/PDF/cloudera_ref_arch_aws.pdf
- Cloudera《面向 Azure 部署的 Cloudera 企业参考架构》,Cloudera,2017,
http://www.cloudera.com/documentation/other/reference-architecture/PDF/cloudera_ref_arch_azure.pdf
- Cloudera《Google 云*台部署的 Cloudera 企业参考架构》,Cloudera,2017,
http://www.cloudera.com/documentation/other/reference-architecture/PDF/cloudera_ref_arch_gcp.pdf
- 沃尔夫,达米安;“三方云战谁胜?谷歌 vs. Azure vs. AWS,《ReadWrite,2017,
https://readwrite.com/2017/02/20/wins-three-way-cloud-battle-google-vs-azure-vs-aws-dl1/
- RightScale《RIGHTSCALE 2017 年云状况报告揭示云采用趋势》,RIGHTSCALE,2017,
https://www.rightscale.com/press-releases/rightscale-2017-state-of-the-cloud-report-uncovers-cloud-adoption-trends
- RightScale《RIGHTSCALE 2017 年云状况报告揭示云采用趋势》,RIGHTSCALE,2017,
https://www.rightscale.com/press-releases/rightscale-2017-state-of-the-cloud-report-uncovers-cloud-adoption-trends
- 谷歌;《地图 AWS 服务到谷歌云*台产品》,谷歌,2017,
https://cloud.google.com/free/docs/map-aws-google-cloud-platform
- 谷歌;《谷歌云*台产品的地图 Azure 服务》,谷歌,2017,
https://cloud.google.com/free/docs/map-azure-google-cloud-platform
- RightScale《RIGHTSCALE 2017 年云状况报告揭示云采用趋势》,RIGHTSCALE,2017,
https://www.rightscale.com/press-releases/rightscale-2017-state-of-the-cloud-report-uncovers-cloud-adoption-trends
- Cloudera《Cloudera 企业在云端》,Cloudera,2016,
http://www.clouderaworldtokyo.com/session-download/B5-ClouderaUpdateNov2016.pdf
- Cloudera《Cloudera 企业在云端》,Cloudera,2016,
http://www.clouderaworldtokyo.com/session-download/B5-ClouderaUpdateNov2016.pdf
- Cloudera《Cloudera 企业在云端》,Cloudera,2016,
http://www.clouderaworldtokyo.com/session-download/B5-ClouderaUpdateNov2016.pdf
- Cloudera《Cloudera Director API 客户端》,Cloudera,2018,
https://github.com/cloudera/director-sdk
- Cloudera《面向 AWS 部署的 Cloudera 企业参考架构》,Cloudera,2018,
http://www.cloudera.com/documentation/other/reference-architecture/PDF/cloudera_ref_arch_aws.pdf
- Cloudera《面向 AWS 部署的 Cloudera 企业参考架构》,Cloudera,2018,
http://www.cloudera.com/documentation/other/reference-architecture/PDF/cloudera_ref_arch_aws.pdf
- 亚马逊;《亚马逊虚拟私有云概述》,亚马逊,2018,
http://docs.aws.amazon.com/AmazonVPC/latest/GettingStartedGuide/ExerciseOverview.html
- Cloudera《面向 AWS 部署的 Cloudera 企业参考架构》,Cloudera,2018,
http://www.cloudera.com/documentation/other/reference-architecture/PDF/cloudera_ref_arch_aws.pdf
- Cloudera《面向 AWS 部署的 Cloudera 企业参考架构》,Cloudera,2018,
http://www.cloudera.com/documentation/other/reference-architecture/PDF/cloudera_ref_arch_aws.pdf
- Cloudera《为 Cloudera Director 创建 EC2 实例》,Cloudera,2018,
https://www.cloudera.com/documentation/director/1-5-x/topics/director_deployment_start_launcher.html
- Cloudera《面向 AWS 部署的 Cloudera 企业参考架构》,Cloudera,2018,
http://www.cloudera.com/documentation/other/reference-architecture/PDF/cloudera_ref_arch_aws.pdf
- GitHub“S3BinaryCacheStore 最终是一致的”,GitHub,2018,
https://github.com/NixOS/nix/issues/1420
- Cloudera《如何在顺化启用 S3 云存储》,Cloudera,2018,
https://www.cloudera.com/documentation/enterprise/5-11-x/topics/hue_use_s3_enable.html
- Cloudera《S3Guard 简介:Apache Hadoop 的 S3 一致性》,Cloudera,2018,
https://blog.cloudera.com/blog/2017/08/introducing-s3guard-s3-consistency-for-apache-hadoop/
- Cloudera《面向 AWS 部署的 Cloudera 企业参考架构》,Cloudera,2018,
http://www.cloudera.com/documentation/other/reference-architecture/PDF/cloudera_ref_arch_aws.pdf
- Cloudera《Cloudera Director 用户指南》,Cloudera,2018,
https://www.cloudera.com/documentation/director/latest/PDF/cloudera-director.pdf
- Cloudera《Cloudera Director 用户指南》,Cloudera,2018,
https://www.cloudera.com/documentation/director/latest/PDF/cloudera-director.pdf
- Cloudera“酷度服务频繁宕机”,Cloudera,2018,
https://community.cloudera.com/t5/Interactive-Short-cycle-SQL/kudu-service-are-getting-down-frequently/m-p/56122
- Cloudera《Cloudera 奥特斯云 PaaS》,Cloudera,2018,
https://www.cloudera.com/products/altus.html
- ZDNet“Cloudera 推出 Altus,提供 Hadoop jobs 即服务”,Cloudera,2018,
http://www.zdnet.com/article/cloudera-introduces-altus-offering-hadoop-jobs-as-a-service/
- 亚马逊;《亚马逊电子病历》,Cloudera,2018,
https://aws.amazon.com/emr/
- 商业内幕;“成立四年的初创公司 Databricks 又筹集了 1.4 亿美元来解决 AI 中最难的问题,”BusinessInsider,2017,
https://www.businessinsider.com.au/databricks-ai-funding-valuation-2017-8?r=US&IR=T
十三、大数据案例研究
大数据颠覆了整个行业。金融服务、电信、运输、医疗保健、零售、保险、公用事业、能源和技术(仅举几例)等领域的创新用例彻底改变了组织管理、处理和分析数据的方式。在这一章中,我展示了来自六家创新公司的真实大数据案例研究:Navistar、Cerner、英国电信、Shopzilla、Thomson Reuters 和 Mastercard。关于案例研究的信息和细节参考自 Cloudera 的网站: www.Cloudera.com
。
纳威司达
纳威司达是商用公共汽车、卡车、国防车辆和发动机的领先制造商之一。
用例
Navistar 的使用案例包括预测性维护、远程诊断和路线优化。计划外的车辆维修和故障成本高且效率低。当服务中断发生时,影响可能是巨大的。车主通常每天每辆车损失 1000 美元的收入。基于里程的定期车辆维护是原始的,不能解决纳威司达日益严重的车辆维护问题。需要一种涉及实时数据监控和预测分析的更现代的方法。此外,Navistar 的传统数据仓库无法支持不断增长的实时、高容量远程信息处理和传感器数据。 i
As we collect more and more data, the analysis process of our legacy system becomes almost stagnant. —Ashish Bayas, Chief Technology Officer of Navistar
解决办法
Navistar 在 Cloudera Enterprise 上构建了一个支持物联网的远程诊断*台,该*台可从 30 多万辆联网车辆中获取 70 多种远程信息处理和传感器数据。第三方数据进一步丰富了数据,如气象、地理位置、交通、车辆使用、历史保修和零件库存信息。该*台使用机器学习来主动检测车辆问题并预测车辆维护需求。纳威司达还利用该*台帮助预防事故和促进道路安全。在 2014 年 9 月制造出原型后,纳威司达仅用了 6 个月就将该*台投入生产。
Navistar has completely changed the way we sell, maintain and serve our customer fleet by using the IoT devices, machine learning and predictive analysis supported by Cloudera. —Ashish Bayas, Chief Technology Officer of Navistar
技术和应用
有了 Cloudera,我们可以用以前不可能的方式和速度分析数据。我们可以在几个小时而不是几周内评估联网车辆的数十亿行数据,从而实现预测性维护。—纳威司达首席信息官 Terry Kline
- 数据*台:Cloudera 企业版
- 工作负载:分析数据库、数据科学和工程
- 组件:阿帕奇 Spark,阿帕奇 Impala(孵化中),阿帕奇 Kafka
- BI 和分析工具:信息构建器 WebFOCUS 文档内分析、Microsoft Power BI、Microsoft SQL Server Analytic Services 模型、Microsoft SQL Server Reporting Services、SAS 企业指南、Tableau 桌面、Tableau 服务器
- 数据科学工具:Python、R、Scala
- ETL 工具:IBM InfoSphere DataStage
结果
纳威司达现在能够为其客户提供主动车辆诊断和实时监控服务。Cloudera Enterprise 使 Navistar 能够构建一个高度可扩展的实时物联网*台,从多个数据源中获取有价值的见解。该*台帮助纳威司达客户减少了高达 40%的维护成本,而车辆问题的早期检测也减少了高达 40%的车辆停机时间。结果是非常积极的。使用实时大数据来制定业务决策和部署主动维护已经开辟了新的收入来源,并为客户带来了额外的价值。—纳威司达首席执行官 Troy Clarke
转角(corner)
Cerner 是医疗保健 IT 领域的领导者,为数以千计的机构提供解决方案,如医院、流动办公室和医生办公室。
用例
Cerner 的目标是将全球医疗保健数据整合到一个通用*台中,以降低成本、提高医疗保健服务的效率并改善患者的疗效。该项目需要应对几个挑战:数据必须安全、可审计且易于探索。我们的愿景是将所有这些信息整合到一个公共*台上,然后理解它们——事实证明,这实际上是一个非常具有挑战性的问题。ii—大卫·爱德华兹,Cerner 副总裁兼研究员
解决办法
Cerner 通过实现由 Cloudera Enterprise 支持的人口健康综合视图实现了其目标。大数据*台目前存储 2pb 的数据,从多个来源接收数据,如电子病历(EMR)、HL7 源、医疗信息交换信息、索赔数据以及来自不同的几个专有和客户拥有的数据源的自定义摘要。Cerner 使用 Apache Kafka 将实时数据导入 HBase 或使用 Apache Storm 的 HDFS。Cerner 正在探索用其他实时组件来扩充其*台,如 Apache Flume、Apache Samza 和 Apache Spark。 iii
数据从 Cloudera *台传输到运行 HP Vertica 的 Cerner 数据集市,支持 SAS 和 SAP Business Objects 用户进行报告和分析。Cerner 利用这些数据来帮助他们确定人群中的风险和改进机会。Cerner 还利用 Cloudera Enterprise 上的 SAS 进行数据科学计划,例如构建预测模型以避免医院再入院。Cerner 团队正在评估 Cloudera Search (Solr)和 Impala,以允许整个组织的数百名用户直接访问存储在 Cloudera Enterprise 中的数据。安全性对 Cerner 来说极其重要,选择 Cloudera(市场上最安全的 Hadoop 发行版之一)作为他们的大数据*台,让他们对患者数据的安全性和完全保护充满信心。我们能够取得更好的结果,无论是与患者相关的还是经济上的,这比我们单独研究拼图的碎片要好得多。这一切都归结于将一切整合在一起,并能够为任何需求提取价值。企业数据中心拓扑让我们可以做到这一点。—Ryan Brush,Cerner 高级总监兼杰出工程师
技术和应用
- Hadoop *台:Cloudera 企业版,数据中心版
- 正在使用的组件:Apache Crunch、Apache HBase、Apache Hive、Apache Kafka、Apache Oozie、Apache Storm、Cloudera Manager、MapReduce
- 服务器:惠普
- 数据集市:HP Vertica
- BI 和分析工具:SAP 业务对象、SAS
结果
通过整合来自多个来源的医疗保健数据,Cerner 能够更全面地了解任何患者、趋势或状况,帮助他们实现更好的患者相关和财务成果。例如,大数据*台赋予了他们预测患者再次入院概率的能力。利用相同的*台,Cerner 还能够准确预测患者败血症的早期发作。我们的客户报告说,新的系统能够比以前更有效地预测病人是否有败血症,从而实际上挽救了数百人的生命。—Ryan Brush,Cerner 高级总监兼杰出工程师
英国电信
BT 是英国领先的电信公司之一,在 180 个国家拥有超过 1800 万客户和业务。
用例
像每个组织一样,英国电信需要向其业务部门提供最相关和最新的信息。他们遗留的 ETL 系统建立在传统的关系数据库上,无法扩展,几乎无法及时处理* 10 亿行数据。他们的 ETL 工作需要超过 24 小时来处理 24 小时的数据。其业务部门不得不满足于使用陈旧的数据。 iv
We have a proposal to transplant the system to a new relational database. But when we sat down, our discussion turned to Hadoop. We realize that we basically have a data speed problem. We must process data faster and increase the amount of data we can receive, which Hadoop is good at. —Phillip Radley, Chief Data Architect of BT
解决办法
BT 实现了一个 Cloudera 企业集群,并用 MapReduce 代码替换了他们的批处理 ETL 作业。该*台不仅解决了英国电信的 ETL 问题,还解决了其他数据管理挑战,帮助英国电信加快了新产品的交付。
由于数据被整合到一个经济高效的基础设施中,它使 BT 能够获得其多个业务部门数据的统一 360 度视图。该*台还将使英国电信能够将数据存档 1 年至 10 年以上,并实现关键任务数据管理和分析用例。
很快,该公司计划使用 Apache Spark 来结合批处理、流和交互式分析,Impala 使商业智能(BI)团队能够对数据执行 SQL 查询。
技术和应用
- Hadoop *台:Cloudera 企业版,数据中心版
- Hadoop 组件:Apache Hive、Apache Pig、Apache Sentry、Apache Spark、Cloudera Manager、Cloudera Navigator、Impala
结果
将 ETL 和数据处理转移到 Hadoop 使 BT 能够提高数据速度,在业务用户需要时为他们提供所需的信息。
We can increase the data speed by 15 times. We spend one third of the time processing five times the data. Business sponsors don't know that we moved to Hadoop, and they don't care. What they know is that they are now processing today's data instead of yesterday's data. —Phillip Radley, Chief Data Architect of BT
- 处理 5 倍多的客户数据
- 数据速度提高了 15 倍
- 一年内实现 200–250%的投资回报率
- 此举也为英国电信节省了大量成本
Shopzilla(连接性)
Shopzilla 是一家领先的电子商务公司,总部位于加利福尼亚州洛杉矶,拥有 1 亿独立访客,与数万家零售商的 1 亿种产品相关联。 v
用例
Shopzilla 现有一个 500 TB 的 Oracle 企业数据仓库,该数据仓库每天增长 5tb。由于每天处理 1 亿件产品所需的数据量和处理量,Shopzilla 的传统数据仓库已经超出了其容量,无法进一步扩展,每天需要数小时来处理数据。
解决办法
Shopzilla 通过使用 Cloudera 企业集群补充其 Oracle 企业数据仓库,实现了一个混合环境。低价值的 ETL 和数据处理由 CDH 集群处理。然后,使用 Apache Sqoop 将聚合的数据传输到 Oracle EDW,让它自由地完成设计任务,为业务用户提供分析和报告。Shopzilla 计划在不久的将来使用阿帕奇 Impala 和阿帕奇 Spark。VICDH 集群用于支持在线价格比较服务、SEO、SEM、销售、观众评分和数据科学工作负载。
Scientists usually don't need to consume data warehouse resources now, because all the latest data can be obtained in Cloudera through R or Mahout. We need huge processing power, scalability, complete redundancy and large-capacity storage, all of which require affordable prices. Our Cloudera platform provides all of these and even more. -—Rony Sawdayi, vice president of Connexity Engineering, we can answer complex questions, such as users' behaviors on specific websites and what advertisements are most effective, and perform other complex data mining queries. It improves Connexity's ability to provide relevant results to users, which is the core tenet of our business. Param JIT Singh, Connexity Data Director
技术和应用
- 数据*台:Cloudera 企业版
- Hadoop 组件:Apache HBase、Apache Hive、Apache Mahout、Apache Pig、Apache Spark、Apache Sqoop、Cloudera Impala、Cloudera Manager
- 服务器:戴尔
- EDW: Oracle
- BI 和分析工具:Oracle BI 企业版(OBIEE);稀有
结果
借助 Cloudera Enterprise,Connexity 现在可以在几个小时内处理来自零售商的 15,000 个源和 1 亿个产品的数据,而不是几天。一个新的架构正在测试中,将进一步减少处理时间到分钟。更快的性能还使 Connexity 每天能够对 1000 万个关键词进行评分和竞价, vii 使其搜索引擎营销活动能够扩大规模,达到 1 亿独立访客,并收集数十亿个数据点,可用于高度有针对性的营销和创新数据分析。
Our traditional system provides excellent analysis and reporting performance, but it doesn't have the bandwidth of intensive data conversion that we need, and it takes several hours to process 100 million products every day. We need huge processing power, scalability, complete redundancy and mass storage at an affordable price. Our Cloudera platform provides all these functions and complements our current data warehouse system. We can reduce the delay from days to hours or even minutes. Param JIT Singh, Connexity Data Director
汤森路透
汤森路透是一家领先的大众媒体和信息公司,为专业人士提供值得信赖的信息。
用例
汤森路透的目标是在 40 毫秒内对推文进行分类,并区分假新闻和观点与真实新闻。 viii
解决办法
汤森路透(Thomson Reuters)转向机器学习和高级分析来构建路透 Tracer,这是一个“训练中的机器人记者”,路透 Tracer 每天分析 1300 万条推文,处理事件以确定推文是真实新闻还是观点或假新闻。 ix 汤森路透使用 Cloudera Enterprise 和 Apache Spark 来提供实现路透 Tracer 所需的机器学习能力。Spark 的快速内存功能使 Reuter Tracer 能够在 40 毫秒内处理数百万条推文并从中获取意义。
In order to help assess the authenticity of an event, we rely on hundreds of features, and train the platform to check the history and diversity of sources, languages used in tweets, communication modes, etc., just like investigative journalists. Sameena Shah, research director and chief scientist of Reuters Tracer Cloudera, provided us with the most advanced technology, helping us to analyze data, synthesize text and extract value and meaning from data, so as to provide the insights needed by customers. The whole application is very fast. It takes less than 40 milliseconds to capture and detect events. —— Townsend Reuters
公司研究&发展部主管 Khalid Al-Kofahi
技术和应用
- 数据*台:Cloudera 企业版
- 工作负载:数据科学和工程
- Hadoop 组件:Apache Spark
结果
我们致力于为金融、法律、税务和会计行业的专业客户以及领先的新闻机构之一路透社构建基于信息的解决方案。有了 Reuters Tracer,当市场波动事件发生时,我们可以毫不延迟地向客户发出警报。我们有很多这样的例子,路透社的 Tracer 先于主要新闻机构发现了突破性的事件。此外,由于我们帮助记者发现事件,他们可以专注于更高附加值的工作,而不仅仅是报道事件。—汤森路透&发展部企业研究主管 Khalid Al-kofa hi
- 在主要新闻媒体之前披露有价值的新闻事件
- 在 40 毫秒内将 1300 万条推文中有新闻价值的推文与谣言和假新闻区分开来
万事达信用卡
万事达卡是全球支付领域的领导者,连接着全球数十亿消费者和数百万家组织。
用例
万事达卡建立了一个名为 MATCH (Mastercard Alert,控制高风险商户)的反欺诈系统,允许用户搜索万事达卡的专有数据库,其中包含数以亿计的欺诈业务。随着时间的推移,MATCH 基于语音的查找功能显然无法满足 MATCH 用户日益增长的需求。此外,为 MATCH 提供支持的关系数据库管理系统(RDBMS)跟不上不断增长的数据量。 x
解决办法
万事达卡实现了基于 Cloudera Search(由 Apache Solr 提供支持)的新反欺诈解决方案,cloud era Search 是 CDH 的一个集成部分,提供全文搜索和分面导航。Cloudera Search 提供了更高的可扩展性、更丰富的搜索功能和更好的搜索准确性。新的解决方案可以使用几种搜索算法和新的评分功能,这些功能在以前的传统 RDBMS 上很难实现。新*台还将允许万事达卡在机会出现时增加更多数据集。
技术和应用
- Apache Hadoop *台:Cloudera 企业版,数据中心版
- Apache Hadoop 组件:Apache Solr、Cloudera Search、Hue
结果
基于 Cloudera 的新解决方案正在帮助万事达卡轻松识别欺诈商户,以降低风险。万事达卡用户体验到搜索准确性的显著提高,5X 每年支持的搜索数量增加,每位客户每天的搜索量增加了 25 倍。这使得万事达卡能够扩展到新的市场,从而增加收入。
摘要
我的目标是提供灵感,鼓励您使用有效且成熟的方法开始自己的大数据用例。我希望这一章对你有用。
参考
- Cloudera“纳威司达:将联网汽车的维护成本降低 30%以上”,Cloudera,2018,
https://www.cloudera.com/more/customers/navistar.html
- Cloudera“Cerner:利用预测患者状况的大数据分析拯救生命”,Cloudera,2018,
https://www.cloudera.com/more/customers/cerner.html
- Cloudera“Cloudera Cerner 案例研究:利用预测患者状况的大数据分析拯救生命”,Cloudera,2018,
https://www.cloudera.com/content/dam/www/marketing/resources/case-studies/cloudera-cerner-casestudy.pdf.landing.html
https://www.cloudera.com/more/customers/bt.html
https://www.cloudera.com/more/customers/connexity.html
https://globenewswire.com/news-release/2014/08/05/656022/10092934/en/Shopzilla-Implements-a-Cloudera-Enterprise-Data-Hub-to-Enhance-its-EDW-and-Capture-Unparalleled-Retail-Insights.html
https://www.cloudera.com/content/dam/www/marketing/resources/case-studies/connexity-complements-the-edw-with-cloudera-to-improve-retail-insights.pdf.landing.html
https://www.cloudera.com/content/dam/www/marketing/resources/case-studies/Cloudera_Thomson_Reuters_Case_Study.pdf.landing.html
https://www.cloudera.com/more/customers/thomson-reuters.html
https://www.cloudera.com/more/customers/mastercard.html