Python-数据采集秘籍-全-

Python 数据采集秘籍(全)

原文:zh.annas-archive.org/md5/bb72759661c0389ef8b3f59f451d

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

欢迎来到Python 数据摄取食谱。我希望你像我一样兴奋地进入数据工程的世界。

Python 数据摄取食谱是一本实用的指南,将赋予你设计和实施高效数据摄取管道的能力。通过实际案例和知名开源工具,本书直面你的疑问和障碍。

从设计管道开始,你将探索在有和无数据模式的情况下工作,使用 Airflow 构建监控工作流程,并在遵循最佳实践的同时拥抱数据可观察性原则。面对读取不同数据源和格式的挑战,你将全面理解所有这些内容。

我们的旅程继续,我们将深入了解错误日志记录、识别、解决、数据编排和有效监控。你将发现存储日志的最佳方法,确保未来可以轻松访问和引用它们。

在本书结束时,你将拥有一个完全自动化的设置,用于启动数据摄取和管道监控。这个简化的流程将无缝集成到提取、转换和加载ETL)过程的后续阶段,推动你的数据集成能力达到新的高度。准备好开始一段启发性和变革性的数据摄取之旅吧。

本书面向对象

这本全面的书专门为数据工程师、数据集成专家和对数据摄取过程、数据流以及沿途遇到的典型挑战有深入了解热情的数据爱好者而设计。它提供了宝贵的见解、最佳实践和实用知识,以增强你在有效处理数据摄取任务方面的技能和熟练度。

无论你是数据世界的初学者还是经验丰富的开发者,这本书都适合你。建议了解 Python 编程基础,并具备基本的 Docker 知识,以便阅读和运行本书的代码。

本书涵盖内容

第一章数据摄取简介,介绍了数据摄取最佳实践和与不同数据源一起工作的挑战。它解释了书中涵盖的工具的重要性,展示了它们,并提供了安装说明。

第二章数据访问原则 – 访问你的数据,探讨了与数据治理相关的数据访问概念,涵盖了熟悉的数据源(如 SFTP 服务器、API 和云提供商)的工作流程和管理。它还提供了在数据库、数据仓库和云中创建数据访问策略的示例。

第三章在摄取数据之前理解我们的数据,教授您在数据摄取之前执行数据发现过程的重要性。它涵盖了手动发现、文档记录以及使用开源工具 OpenMetadata 进行本地配置。

第四章读取 CSV 和 JSON 文件并解决问题,向您介绍使用 Python 和 PySpark 摄取 CSV 和 JSON 文件。它展示了在处理不同数据量和基础设施的同时,解决常见挑战并提供解决方案。

第五章从结构化和非结构化数据库中摄取数据,涵盖了关系型和非关系型数据库的基本概念,包括日常用例。您将学习如何读取和处理这些模型中的数据,了解重要考虑事项,并解决潜在的错误。

第六章使用定义和非定义模式下的 PySpark,深入探讨了常见的 PySpark 用例,重点关注处理定义和非定义模式。它还探讨了从 Spark(PySpark 核心)读取和理解复杂日志以及便于调试的格式化技术。

第七章摄取分析数据,向您介绍分析数据以及读取和写入的常见格式。它探讨了为提高性能读取分区数据,并讨论了反向 ETL 理论及其在实际工作流程和图表中的应用。

第八章设计受监控的数据工作流,涵盖了数据摄取的最佳日志记录实践,便于错误识别和调试。例如,监控文件大小、行数和对象数等技术,可以改善仪表板、警报和洞察力的监控。

第九章利用 Airflow 整合一切,总结了之前介绍的信息,并指导您使用 Airflow 构建一个真实的数据摄取应用程序。它涵盖了过程中的基本组件、配置和问题解决。

第十章在 Airflow 中记录和监控您的数据摄取,探讨了使用 Airflow 进行数据摄取的高级日志记录和监控。它涵盖了创建自定义操作符、设置通知以及监控数据异常。还涵盖了为 Slack 等工具配置通知,以保持对数据摄取过程的更新。

第十一章自动化您的数据摄取管道,侧重于使用之前学到的最佳实践自动化数据摄取,实现读者自主权。它解决了与调度器或编排工具相关的常见挑战,并提供了避免生产集群中问题的解决方案。

第十二章**, 使用数据可观察性进行调试、错误处理和预防停机时间,探讨了数据可观察性概念、流行的监控工具如 Grafana,以及日志存储和数据血缘的最佳实践。它还涵盖了使用 Airflow 配置和数据摄取脚本来创建可视化图表以监控数据源问题。

为了充分利用这本书

要执行本书中的代码,您至少需要具备基本的 Python 知识。我们将使用 Python 作为执行代码的核心语言。代码示例已使用 Python 3.8 进行测试。然而,预计它仍然可以使用未来的语言版本运行。

除了 Python 之外,本书还使用 Docker 在我们的本地机器上模拟数据系统和应用程序,例如 PostgreSQL、MongoDB 和 Airflow。因此,建议您具备基本的 Docker 知识,以便编辑容器镜像文件以及运行和停止容器。

请记住,某些命令行命令可能需要根据您的本地设置或操作系统进行调整。代码示例中的命令基于 Linux 命令行语法,可能需要在 Windows PowerShell 上运行时进行一些调整。

本书涵盖的软件/硬件 操作系统要求
Python 3.8 或更高版本 Windows, Mac OS X, 和 Linux(任何)
Docker Engine 24.0 / Docker Desktop 4.19 Windows, Mac OS X, 和 Linux(任何)

对于本书中的几乎所有食谱,您都可以使用 Jupyter Notebook 来执行代码。尽管安装它不是强制性的,但这个工具可以帮助您测试代码并在代码上尝试新事物,因为它具有 友好的界面。

如果您正在使用本书的数字版,我们建议您亲自输入代码或通过 GitHub 仓库(下一节中提供链接)访问代码。这样做将帮助您避免与代码复制和粘贴相关的任何潜在错误。

下载示例代码文件

您可以从 GitHub 下载本书的示例代码文件github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook。如果代码有更新,它将在现有的 GitHub 仓库中更新。

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

下载彩色图像

我们还提供了一份包含本书中使用的截图/图表的彩色图像的 PDF 文件。您可以从这里下载:packt.link/xwl0U

使用的约定

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

文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“然后我们使用with open语句继续操作。”

代码块设置如下:

def gets_csv_first_line (csv_file):
    logging.info(f"Starting function to read first line")
    try:
        with open(csv_file, 'r') as file:
            logging.info(f"Reading file")

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

$ python3 –-version
Python 3.8.10

showString at NativeMethodAccessorImpl.java:0,这使我们转向阶段页面。”

小贴士或重要注意事项

看起来像这样。

部分

在本书中,您将找到几个频繁出现的标题(准备就绪如何操作...它是如何工作的...还有更多...,和另请参阅)。

为了清楚地说明如何完成食谱,请按照以下方式使用这些部分:

准备就绪

本节告诉您在食谱中可以期待什么,并描述如何设置任何软件或任何为食谱所需的初步设置。

如何操作…

本节包含遵循食谱所需的步骤。

它是如何工作的…

本节通常包含对前一个节中发生情况的详细解释。

还有更多…

本节包含有关食谱的附加信息,以便您对食谱有更多的了解。

另请参阅

本节提供了对食谱中其他有用信息的链接。

联系我们

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

一般反馈: 如果您对本书的任何方面有疑问,请在邮件主题中提及书名,并通过 customercare@packtpub.com 与我们联系。

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

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

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

分享您的想法

一旦您阅读了《使用 Python 进行数据摄取烹饪书》,我们很乐意听听您的想法!请点击此处直接访问此书的亚马逊评论页面并分享您的反馈。

您的评论对我们和科技社区都很重要,并将帮助我们确保我们提供高质量的内容。

下载此书的免费 PDF 副本

感谢您购买此书!

您喜欢在路上阅读,但无法携带您的印刷书籍到处走?

您的电子书购买是否与您选择的设备不兼容?

别担心,现在,随着每本 Packt 书籍,您都可以免费获得该书的 DRM 免费 PDF 版本。

在任何地方、任何设备上阅读。直接从您喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。

优惠远不止于此,您还可以获得独家折扣、时事通讯和每日免费内容的每日访问权限

按照以下简单步骤获取福利:

  1. 扫描下面的二维码或访问以下链接

packt.link/free-ebook/9781837632602

  1. 提交您的购买证明

  2. 就这样!我们将直接将您的免费 PDF 和其他福利发送到您的邮箱

第一部分:数据摄取基础

在这部分,您将了解数据摄取和数据工程的基础知识,包括摄取管道的基本定义、常见的数据源类型以及涉及的技术。

本部分包含以下章节:

  • 第一章数据摄取简介

  • 第二章数据访问原则 – 访问您的数据

  • 第三章数据发现 – 在摄取数据之前理解我们的数据

  • 第四章读取 CSV 和 JSON 文件并解决问题

  • 第五章从结构化和非结构化数据库中摄取数据

  • 第六章使用 PySpark 与定义和非定义模式

  • 第七章摄取分析数据

第一章:数据摄取简介

欢迎来到数据世界的奇妙世界!你准备好开始一段令人兴奋的数据摄取之旅了吗?如果是这样,这本书正是你开始的最佳选择!摄取数据是进入大数据世界的第一步。

数据摄取是一个涉及收集和导入数据,并且还要妥善存储数据的过程,以便后续的 提取、转换和加载ETL)管道可以利用这些数据。为了实现这一点,我们必须谨慎选择我们将使用的工具以及如何正确配置它们。

在我们的书籍之旅中,我们将使用 PythonPySpark 从不同的数据源检索数据,并学习如何正确存储它们。为了协调所有这些,我们将实现 Airflow 的基本概念,以及高效的监控,以确保我们的管道得到覆盖。

本章将介绍有关数据摄取的一些基本概念以及如何设置你的环境以开始任务。

在本章中,你将构建并学习以下食谱:

  • 设置 Python 和环境

  • 安装 PySpark

  • 为 MongoDB 配置 Docker

  • 为 Airflow 配置 Docker

  • 日志库

  • 创建模式

  • 在摄取中应用数据治理

  • 实现数据复制

技术要求

本章中的命令使用 Linux 语法。如果你不使用基于 Linux 的系统,你可能需要调整命令:

  • Docker 或 Docker Desktop

  • 选择你喜欢的 SQL 客户端(推荐);我们推荐 DBeaver,因为它有一个社区免费版本

你可以在这个 GitHub 仓库中找到本章的代码:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook

注意

Windows 用户可能会收到类似于 Docker Desktop 需要更新的 WSL 内核版本的错误消息。这可以通过遵循以下步骤来解决:docs.docker.com/desktop/windows/wsl/

设置 Python 和其环境

在数据领域,如 JavaScalaPython 这样的语言被广泛使用。前两种语言因其与大数据工具环境(如 HadoopSpark)的兼容性而被使用,其中核心部分运行在 Java 虚拟机JVM)上。然而,在过去的几年里,由于语言的通用性、易于理解以及社区构建的许多开源库,Python 在数据工程和数据科学领域的使用显著增加。

准备工作

让我们为我们的项目创建一个文件夹:

  1. 首先,打开你的系统命令行。由于我使用的是 Windows Subsystem for LinuxWSL),我将打开 WSL 应用程序。

  2. 进入你的主目录并创建一个文件夹,如下所示:

    $ mkdir my-project
    
  3. 进入这个文件夹:

    $ cd my-project
    
  4. 按照以下方式检查你的操作系统上的 Python 版本:

    $ python -–version
    

根据您的操作系统,您可能在这里看到输出,例如,WSL 20.04 用户可能看到以下输出:

Command 'python' not found, did you mean:
 command 'python3' from deb python3
 command 'python' from deb python-is-python3

如果您的 Python 路径配置为使用 python 命令,您将看到类似以下输出的内容:

Python 3.9.0

有时,您的 Python 路径可能配置为使用 python3 来调用。您可以使用以下命令尝试:

$ python3 --version

输出将与 python 命令类似,如下所示:

Python 3.9.0
  1. 现在,让我们检查我们的 pip 版本。这个检查是必要的,因为一些操作系统安装了多个 Python 版本:

    $ pip --version
    

您应该看到类似的输出:

pip 20.0.2 from /usr/lib/python3/dist-packages/pip (python 3.9)

如果您的系统没有安装 3.8x 或其他版本的语言,请继续执行 如何操作 步骤;否则,您就可以开始以下 安装 PySpark 菜谱了。

如何操作…

我们将使用来自 Python.org 的官方安装程序。您可以在以下链接找到它:www.python.org/downloads/:

注意

对于 Windows 用户,检查您的操作系统版本很重要,因为 Python 3.10 可能还不兼容 Windows 7,或者您的处理器类型(32 位或 64 位)。

  1. 下载其中一个稳定版本。

在撰写本文时,与这里展示的工具和资源兼容的稳定推荐版本是 3.83.93.10。我将使用 3.9 版本,并使用以下链接下载它:www.python.org/downloads/release/python-390/。向下滚动页面,您将找到根据操作系统列出的 Python 安装程序的链接,如下面的截图所示。

图 1.1 – Python.org 3.9 版本下载文件

图 1.1 – Python.org 3.9 版本下载文件

  1. 下载安装文件后,双击它,并按照向导窗口中的说明操作。为了避免复杂性,请选择显示的推荐设置。

以下截图显示了在 Windows 上的样子:

图 1.2 – Windows 的 Python 安装程序

图 1.2 – Windows 的 Python 安装程序

  1. 如果您是 Linux 用户,您可以使用以下命令从源安装它:

    $ wget https://www.python.org/ftp/python/3.9.1/Python-3.9.1.tgz
    $ tar -xf Python-3.9.1.tgz
    $ ./configure –enable-optimizations
    $ make -j 9
    

安装 Python 后,您应该能够执行 pip 命令。如果不能,请参考此处提供的 pip 官方文档页面:pip.pypa.io/en/stable/installation/.

它是如何工作的…

Python 是一种 解释型语言,其解释器扩展了用 CC++ 制成的几个功能。语言包还附带了一些内置库,当然,还有解释器。

解释器的工作方式类似于 Unix shell,可以在 usr/local/bin 目录中找到:docs.python.org/3/tutorial/interpreter.xhtml.

最后,请注意,本书中许多 Python 第三方包需要安装 pip 命令。这是因为 pip(即 Pip Installs Packages 的缩写)是 Python 的默认包管理器;因此,它用于安装、升级和管理来自 Python 包索引PyPI)的 Python 包及其依赖项。

更多内容…

即使你的机器上没有 Python 版本,你仍然可以使用命令行或 HomeBrew(适用于 macOS 用户)来安装它们。Windows 用户也可以从 MS Windows Store 下载它们。

注意

如果你选择从 Windows Store 下载 Python,请确保使用由 Python 软件基金会制作的应用程序。

参见

你可以使用 pip 安装方便的第三方应用程序,例如 Jupyter。这是一个开源的、基于网络的、交互式(且用户友好)的计算平台,常被数据科学家和数据工程师使用。你可以从这里安装它:jupyter.org/install

安装 PySpark

为了处理、清理和转换大量数据,我们需要一个提供弹性和分布式处理的工具,这就是为什么 PySpark 是一个好的选择。它通过 Spark 库提供了一个 API,让你可以使用其应用程序。

准备工作

在开始 PySpark 安装之前,我们需要在我们的操作系统中检查我们的 Java 版本:

  1. 这里,我们检查 Java 版本:

    $ java -version
    

你应该看到类似的输出:

openjdk version "1.8.0_292"
OpenJDK Runtime Environment (build 1.8.0_292-8u292-b10-0ubuntu1~20.04-b10)
OpenJDK 64-Bit Server VM (build 25.292-b10, mixed mode)

如果一切正常,你应该看到命令的输出中显示上述消息,以及 OpenJDK 18 版本或更高。然而,某些系统默认没有安装任何 Java 版本,为了覆盖这种情况,我们需要进行到 步骤 2

  1. 现在,我们下载Java 开发工具包JDK)。

访问 www.oracle.com/java/technologies/downloads/,选择你的 操作系统,下载 JDK 的最新版本。在撰写本文时,是 JDK 19。

JDK 的下载页面看起来如下:

图 1.3 – JDK 19 下载官方网页

图 1.3 – JDK 19 下载官方网页

执行下载的应用程序。点击应用程序以启动安装过程。以下窗口将出现:

注意

根据你的操作系统,安装窗口可能略有不同。

图 1.4 – Java 安装向导窗口

图 1.4 – Java 安装向导窗口

点击 下一步回答以下两个问题,应用程序将开始安装。你不需要担心 JDK 将安装在哪里。默认情况下,应用程序配置为与标准兼容,以便与其他工具的安装兼容。

  1. 接下来,我们再次检查我们的 Java 版本。再次执行命令时,你应该看到以下版本:

    $ java -version
    openjdk version "1.8.0_292"
    OpenJDK Runtime Environment (build 1.8.0_292-8u292-b10-0ubuntu1~20.04-b10)
    OpenJDK 64-Bit Server VM (build 25.292-b10, mixed mode)
    

如何操作…

这里是执行此菜谱的步骤:

  1. 从 PyPi 安装 PySpark:

    $ pip install pyspark
    

如果命令运行成功,安装输出的最后一行将如下所示:

Successfully built pyspark
Installing collected packages: py4j, pyspark
Successfully installed py4j-0.10.9.5 pyspark-3.3.2
  1. 执行pyspark命令以打开交互式 shell。当你在命令行中执行pyspark命令时,你应该看到以下消息:

    $ pyspark
    Python 3.8.10 (default, Jun 22 2022, 20:18:18)
    [GCC 9.4.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    22/10/08 15:06:11 WARN Utils: Your hostname, DESKTOP-DVUDB98 resolves to a loopback address: 127.0.1.1; using 172.29.214.162 instead (on interface eth0)
    22/10/08 15:06:11 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address
    22/10/08 15:06:13 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
    Using Spark's default log4j profile: org/apache/spark/log4j-defaults.properties
    Setting default log level to "WARN".
    To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).
    Welcome to
          ____              __
         / __/__  ___ _____/ /__
        _\ \/ _ \/ _ `/ __/  '_/
       /__ / .__/\_,_/_/ /_/\_\   version 3.1.2
          /_/
    Using Python version 3.8.10 (default, Jun 22 2022 20:18:18)
    Spark context Web UI available at http://172.29.214.162:4040
    Spark context available as 'sc' (master = local[*], app id = local-1665237974112).
    SparkSession available as 'spark'.
    >>>
    

你可以在这里看到一些有趣的消息,例如 Spark 的版本和 PySpark 使用的 Python 版本。

  1. 最后,我们按照以下方式退出交互式 shell:

    >>> exit()
    $
    

它是如何工作的...

如本食谱开头所见,Spark 是一个运行在 JVM 之上的强大框架。它也是一个开源工具,可以从大量数据中创建健壮和分布式处理输出。随着 Python 语言在过去几年中的普及,需要一个解决方案来适配 Spark 以便与 Python 一起运行。

PySpark 是一个通过 Py4J 与 Spark API 交互的接口,它动态地允许 Python 代码与 JVM 交互。我们首先需要在我们的操作系统上安装 Java 才能使用 Spark。当我们安装 PySpark 时,它已经包含了 Spark 和 Py4J 组件,这使得启动应用程序和构建代码变得容易。

还有更多...

Anaconda 是安装 PySpark 和其他数据科学工具的便捷方式。这个工具封装了所有手动过程,并为与 Python 组件(如NumPypandasJupyter)交互和安装提供了一个友好的界面:

  1. 要安装 Anaconda,请访问官方网站并选择产品 | Anaconda 发行版www.anaconda.com/products/distribution

  2. 根据你的操作系统下载发行版。

关于如何安装 Anaconda 和其他强大命令的更详细信息,请参阅docs.anaconda.com/

使用 virtualenv 与 PySpark

可以配置并使用virtualenv与 PySpark,如果你选择这种安装类型,Anaconda 会自动完成。然而,对于其他安装方法,我们需要采取一些额外步骤来确保我们的 Spark 集群(本地或服务器上)能够运行它,这包括指定virtualenv /bin/文件夹和你的 PySpark 路径。

参见

关于这个主题有一篇不错的文章,使用 VirtualEnv 与 PySpark,作者是 jzhang,可以在这里找到:community.cloudera.com/t5/Community-Articles/Using-VirtualEnv-with-PySpark/ta-p/245932

配置 Docker 用于 MongoDB

MongoDB是一个非 SQLNoSQL)文档型数据库,广泛用于存储物联网IoT)数据、应用程序日志等。NoSQL 数据库是一个非关系型数据库,它以不同于 MySQL 或 PostgreSQL 等关系型数据库的方式存储非结构化数据。现在不必过于担心这个问题;我们将在第五章中更详细地介绍它。

你的集群生产环境可以处理大量数据并创建具有弹性的数据存储。

准备工作

遵循代码组织的良好实践,让我们开始在项目内创建一个文件夹来存储 Docker 镜像:

在我们的项目目录内创建一个文件夹以存储 MongoDB Docker 镜像和数据,如下所示:

my-project$ mkdir mongo-local
my-project$ cd mongo-local

如何操作…

下面是尝试这个菜谱的步骤:

  1. 首先,我们按照以下方式从 Docker Hub 拉取 Docker 镜像:

    my-project/mongo-local$ docker pull mongo
    

你应该在命令行中看到以下消息:

Using default tag: latest
latest: Pulling from library/mongo
(...)
bc8341d9c8d5: Pull complete
(...)
Status: Downloaded newer image for mongo:latest
docker.io/library/mongo:latest

注意

如果你使用的是 WSL 1 版本而不是版本 2,可能会出现错误。你可以通过遵循以下步骤轻松修复此问题:learn.microsoft.com/en-us/windows/wsl/install

  1. 然后,我们按照以下方式运行 MongoDB 服务器:

    my-project/mongo-local$ docker run \
    --name mongodb-local \
    -p 27017:27017 \
    -e MONGO_INITDB_ROOT_USERNAME="your_username" \
    -e MONGO_INITDB_ROOT_PASSWORD="your_password"\
    -d mongo:latest
    

然后,我们检查我们的服务器。为此,我们可以使用命令行来查看哪些 Docker 镜像正在运行:

my-project/mongo-local$ docker ps

然后,我们在屏幕上看到以下内容:

图 1.5 – MongoDB 和 Docker 运行容器

图 1.5 – MongoDB 和 Docker 运行容器

我们甚至可以在 Docker Desktop 应用程序中检查我们的容器是否正在运行:

图 1.6 – 运行中的 MongoDB 容器的 Docker Desktop 视图

图 1.6 – 运行中的 MongoDB 容器的 Docker Desktop 视图

  1. 最后,我们需要停止我们的容器。我们需要使用 Container ID 来停止容器,这是我们之前在检查正在运行的 Docker 镜像时看到的。我们将在 第五章 中重新运行它:

    my-project/mongo-local$ docker stop 427cc2e5d40e
    

它是如何工作的…

MongoDB 的架构使用 main 节点与客户端请求交互的概念,例如查询和文档操作。它自动在其分片之间分配请求,分片是这里更大数据集合的子集。

图 1.7 – MongoDB 架构

图 1.7 – MongoDB 架构

由于我们可能在机器内还有其他运行的项目或软件应用程序,因此隔离开发中使用的任何数据库或应用程序服务器是一种良好的做法。这样,我们确保没有任何东西干扰我们的本地服务器,调试过程可以更容易管理。

此 Docker 镜像设置在本地创建了一个 MongoDB 服务器,甚至允许我们进行额外的更改,以便模拟任何其他测试或开发场景。

我们使用的命令如下:

  • --name 命令定义了我们给容器起的名字。

  • -p 命令指定了容器将打开的端口,以便我们可以通过 localhost:27017 访问它。

  • -e 命令定义了环境变量。在这种情况下,我们为 MongoDB 容器设置了 root 用户名和密码。

  • -d 是分离模式——也就是说,Docker 进程将在后台运行,我们不会看到输入或输出。然而,我们仍然可以使用 docker ps 来检查容器状态。

  • mongo:latest表示 Docker 正在拉取此镜像的最新版本。

更多内容...

对于频繁用户,手动配置 MongoDB 容器的其他参数,例如版本、镜像端口、数据库名称和数据库凭证,也是可能的。

此镜像的一个带有示例值的版本也作为docker-compose文件在官方文档中提供:hub.docker.com/_/mongo

MongoDB 的docker-compose文件看起来类似于以下内容:

# Use your own values for username and password
version: '3.1'
services:
  mongo:
    image: mongo
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
  mongo-express:
    image: mongo-express
    restart: always
    ports:
      - 8081:8081
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: example
      ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/

参见

您可以在完整的 Docker Hub 文档中查看 MongoDB:hub.docker.com/_/mongo

配置 Docker 用于 Airflow

在这本书中,我们将使用Airflow来编排数据摄取并提供日志以监控我们的管道。

Airflow 可以直接安装在您的本地机器和任何服务器上,使用 PyPi(pypi.org/project/apache-airflow/)或 Docker 容器(hub.docker.com/r/apache/airflow). Docker Hub 上可以找到 Airflow 的官方和受支持的版本,并且由Apache 基金会社区维护。

然而,配置我们的 Airflow 还有一些额外的步骤。幸运的是,Apache 基金会也有一个包含使 Airflow 工作所需的所有其他要求的docker-compose文件。我们只需要完成几个额外的步骤。

准备工作

让我们从在我们的机器上初始化 Docker 应用程序开始。您可以使用桌面版本或 CLI 命令。

确保您处于项目文件夹内。创建一个文件夹来存储 Airflow 内部组件和docker-compose.yaml文件:

my-project$ mkdir airflow-local
my-project$ cd airflow-local

如何操作...

  1. 首先,我们从 Airflow 官方文档中直接获取docker-compose.yaml文件:

    my-project/airflow-local$ curl -LfO 'https://airflow.apache.org/docs/apache-airflow/2.3.0/docker-compose.yaml'
    

您应该看到类似以下输出:

图 1.8 – Airflow 容器镜像下载进度

图 1.8 – Airflow 容器镜像下载进度

注意

在您下载时检查此docker-compose文件的最稳定版本,因为在此书出版后可能会有更合适的新版本。

  1. 接下来,我们创建dagslogsplugins文件夹,如下所示:

    my-project/airflow-local$ mkdir ./dags ./logs ./plugins
    
  2. 然后,我们创建并设置 Airflow 用户,如下所示:

    my-project/airflow-local$ echo -e "AIRFLOW_UID=$(id -u)\nAIRFLOW_GID=0" > .env
    

注意

如果您有任何与AIRFLOW_UID变量相关的错误消息,您可以在与您的docker-compose.yaml文件相同的文件夹中创建一个.env文件,并将变量定义为AIRFLOW_UID=50000

  1. 然后,我们初始化数据库:

    my-project/airflow-local$ docker-compose up airflow-init
    

执行命令后,您应该看到类似以下输出:

Creating network "airflow-local_default" with the default driver
Creating volume "airflow-local_postgres-db-volume" with default driver
Pulling postgres (postgres:13)...
13: Pulling from library/postgres
(...)
Status: Downloaded newer image for postgres:13
Pulling redis (redis:latest)...
latest: Pulling from library/redis
bd159e379b3b: Already exists
(...)
Status: Downloaded newer image for redis:latest
Pulling airflow-init (apache/airflow:2.3.0)...
2.3.0: Pulling from apache/airflow
42c077c10790: Pull complete
(...)
Status: Downloaded newer image for apache/airflow:2.3.0
Creating airflow-local_postgres_1 ... done
Creating airflow-local_redis_1    ... done
Creating airflow-local_airflow-init_1 ... done
Attaching to airflow-local_airflow-init_1
(...)
airflow-init_1       | [2022-10-09 09:49:26,250] {manager.py:213} INFO - Added user airflow
airflow-init_1       | User "airflow" created with role "Admin"
(...)
airflow-local_airflow-init_1 exited with code 0
  1. 然后,我们启动 Airflow 服务:

    my-project/airflow-local$ docker-compose up
    
  2. 然后,我们需要检查 Docker 进程。使用以下 CLI 命令,您将看到正在运行的 Docker 镜像:

    my-project/airflow-local$ docker ps
    

这些是我们看到的镜像:

图 1.9 – docker ps 命令输出

图 1.9 – docker ps 命令输出

在 Docker 桌面应用程序中,您也可以看到相同的容器正在运行,但界面更加友好:

图 1.10 – 运行中的 Airflow 容器在 Docker 桌面上的视图

图 1.10 – 运行中的 Airflow 容器在 Docker 桌面上的视图

  1. 然后,我们通过网页浏览器访问 Airflow:

在您首选的浏览器中,输入 http://localhost:8080/home。将出现以下屏幕:

图 1.11 – Airflow UI 登录页面

图 1.11 – Airflow UI 登录页面

  1. 然后,我们登录到 Airflow 平台。由于它是一个用于测试和学习的本地应用程序,Airflow 中用于管理访问的默认凭据(用户名和密码)是 airflow

登录后,将出现以下屏幕:

图 1.12 – Airflow UI 主页面

图 1.12 – Airflow UI 主页面

  1. 然后,我们停止我们的容器。我们可以停止我们的容器,直到我们达到第九章,届时我们将探索 Airflow 中的数据摄取:

    my-project/airflow-local$ docker-compose stop
    

它是如何工作的…

Airflow 是一个开源平台,允许批量数据处理管道的开发、监控和调度。然而,它需要其他组件,如内部数据库,以存储元数据才能正确工作。在这个例子中,我们使用 PostgreSQL 存储元数据,并使用 Redis 缓存信息。

所有这些都可以直接在我们的机器环境中逐一安装。尽管看起来很简单,但由于与操作系统、其他软件版本等问题可能存在兼容性问题,可能并不总是如此。

Docker 可以创建一个隔离的环境,并提供所有使其工作的要求。使用 docker-compose,它变得更加简单,因为我们可以在其他组件健康的情况下创建只能创建的组件之间的依赖关系。

您还可以打开我们为这个配方下载的 docker-compose.yaml 文件,并查看它以更好地探索它。我们还会在第 第九章 中详细讨论它。

参见

如果您想了解更多关于此 docker-compose 文件的工作方式,您可以在 Apache Airflow 官方 Docker 文档的 Apache Airflow 文档页面上查看:airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.xhtml

创建模式

模式被认为是数据库或表的蓝图。虽然一些数据库严格要求模式定义,但其他数据库可以在没有它的情况下工作。然而,在某些情况下,与数据模式一起工作可能是有益的,以确保应用程序数据架构得到维护,并且可以接收所需的数据输入。

准备工作

让我们想象我们需要为学校创建一个数据库来存储有关学生、课程和讲师的信息。有了这些信息,我们知道我们至少有到目前为止的三个表。

图 1.13 – 三个实体的表图

图 1.13 – 三个实体的表图

在这个菜谱中,我们将通过使用实体关系图ERD),即数据库中实体之间关系的视觉表示,来展示模式是如何连接的。

如何做…

下面是尝试这一步骤的步骤:

  1. 我们定义了模式类型。以下图示帮助我们了解如何进行这一步骤:

图 1.14 – 帮助你决定使用哪个模式的图示

图 1.14 – 帮助你决定使用哪个模式的图示

  1. 然后,我们定义每个表列的字段和数据类型:

图 1.15 – 每个表的列的定义

图 1.15 – 每个表的列的定义

  1. 接下来,我们定义哪些字段可以是空的或NULL

图 1.16 – 定义哪些列可以是 NULL 的图示

图 1.16 – 定义哪些列可以是 NULL 的图示

  1. 然后,我们创建表之间的关系:

图 1.17 – 表的关系图

图 1.17 – 表的关系图

它是如何工作的…

在设计数据模式时,我们首先需要做的是定义它们的类型。正如我们在步骤 1中的图示中可以看到,应用模式架构取决于数据的目的。

之后,设计表。决定如何定义数据类型可能因项目或目的而异,但决定列可以接收哪些值是很重要的。例如,如果我们知道房间的识别号总是数字,Teacher表上的officeRoom可以是Integer类型,或者如果对识别方式不确定(例如,Room 3-D),则可以是String类型。

步骤 3中涵盖的另一个重要主题是如何定义哪些列可以接受NULL字段。学生的姓名字段可以空着吗?如果不能,我们需要创建一个约束来禁止这种类型的插入。

最后,根据模式类型,制定表之间的关系定义。

相关内容

如果你想了解更多关于数据库模式设计和它们的应用的文章,请阅读 Mark Smallcombe 的这篇文章:www.integrate.io/blog/database-schema-examples/

在摄取中应用数据治理

数据治理是一套确保数据安全、可用、妥善存储、文档化、隐私和准确的方法论。

准备工作

数据摄取是数据管道过程的开始,但这并不意味着数据治理没有得到广泛应用。最终数据管道输出中的治理状态取决于它在摄取过程中的实施情况。

下面的图示展示了数据摄取通常是如何进行的:

图 1.18 – 数据摄取过程

图 1.18 – 数据摄取过程

让我们分析图中的步骤:

  1. 从源获取数据:第一步是定义数据的类型、其周期性、我们将从哪里收集它以及为什么我们需要它。

  2. 编写数据摄取的脚本:基于上一步的答案,我们可以开始规划我们的代码将如何表现以及一些基本步骤。

  3. 在临时数据库或其他类型的存储中存储数据:在摄取和转换阶段之间,数据通常存储在临时数据库或存储库中。

图 1.19 – 数据治理支柱

图 1.19 – 数据治理支柱

如何做…

逐步,让我们将图 1.19 中的支柱归因于摄取阶段:

  1. 在数据源级别应用对可访问性的关注,定义允许查看或检索数据的人员。

  2. 接下来,有必要对数据进行编目以更好地理解它。由于数据摄取在这里仅作介绍,因此更相关的是涵盖数据源。

  3. 质量支柱将应用于摄取和暂存区域,在那里我们控制数据并保持其质量与源保持一致。

  4. 然后,让我们定义所有权。我们知道数据源 属于 一个业务领域或公司。然而,当我们摄取数据并将其放入临时或暂存存储时,它就变成了我们的责任来维护它。

  5. 最后一个支柱涉及在整个管道中保持数据的安全。在所有步骤中,安全性都是至关重要的,因为我们可能正在处理私人或敏感信息。

图 1.20 – 增加数据摄取

图 1.20 – 增加数据摄取

它是如何工作的…

虽然一些文章定义了“支柱”来创建良好的治理实践,但了解如何应用它们最好的方式是理解它们的组成。正如你在上一节“如何做…*”中看到的,我们将一些项目分配给了我们的管道,现在我们可以理解它们是如何与以下主题相连的:

  • 数据可访问性:数据可访问性是指一个群体、组织或项目中的成员如何查看和使用数据。信息需要易于使用。同时,它需要对参与过程的人员可用。例如,敏感数据可访问性应限制在某些人或程序。在我们构建的图中,我们将其应用于我们的数据源,因为我们需要理解和检索数据。同样地,它也可以应用于临时存储需求。

  • 数据编目:编目和记录数据对于业务和工程团队至关重要。当我们知道哪些类型的信息依赖于我们的数据库或数据湖,并且可以快速访问这些文档时,解决问题的行动时间就会缩短。

再次强调,记录我们的数据源可以使摄取过程更快,因为我们每次需要摄取数据时都需要进行发现。

  • 数据质量:质量始终与数据摄入、处理和加载相关。通过其周期性跟踪和监控数据的预期收入和结果至关重要。例如,如果我们预计每天摄入 300 GB 的数据,突然降至 1 GB,那么肯定出了大问题,这将影响我们最终输出的质量。其他质量参数可以是列数、分区等,我们将在本书的后面部分探讨。

  • 所有权:谁负责数据?这个定义对于在出现问题时与所有者取得联系或分配责任以保持和维护数据至关重要。

  • 安全性:如今,数据安全是一个令人担忧的话题。随着关于数据隐私的许多法规的出现,数据工程师和科学家至少需要了解加密、敏感数据以及如何避免数据泄露的基础知识。甚至用于工作的语言和库也需要评估。这就是为什么这个条目被归入图 1的三个步骤中。19。

除了我们探讨的主题外,全球数据治理项目有一个至关重要的角色,称为数据管理员,负责管理组织的资产数据,并确保数据准确、一致和安全。总之,数据管理是管理和监督组织的资产数据。

参考内容

你可以在这里了解更多关于最近发现的数据工程中最常用工具之一的一个漏洞:www.ncsc.gov.uk/information/log4j-vulnerability-what-everyone-needs-to-know

实施数据复制

数据复制是在数据环境中应用的一个过程,用于创建数据的多个副本并将它们存储在不同的位置、服务器或站点。这种技术通常用于创建更好的可用性,以避免在停机或自然灾害影响数据中心时数据丢失。

准备工作

你会在论文和文章中找到关于数据复制决策的不同类型(甚至名称)。在这个菜谱中,你将学习如何决定哪种复制更适合你的应用程序或软件。

如何做…

让我们开始构建实施数据复制的基本支柱:

  1. 首先,我们需要决定复制的规模,这可以通过存储数据的一部分或全部来完成。

  2. 下一步是考虑复制何时进行。它可以在新数据到达存储时同步进行,或在特定时间段内进行。

  3. 最后一个基本支柱是数据是增量形式还是批量形式。

最后,我们将得到一个如下所示的图表:

图 1.21 – 数据复制模型决策图

图 1.21 – 数据复制模型决策图

它是如何工作的…

分析前面的图示,我们有三个主要问题需要回答,关于扩展、频率以及我们的复制将是增量还是批量。

对于第一个问题,我们决定复制将是完全的还是有选择的。换句话说,数据将始终被复制,无论进行了何种类型的交易或更改,或者只是部分数据将被复制。一个真实的例子是跟踪所有商店销售或仅跟踪最昂贵的销售。

第二个问题,与频率相关,是决定何时进行复制。这个问题也需要考虑相关成本。实时复制通常更昂贵,但同步性保证了几乎没有任何数据不一致性。

最后,考虑数据将如何传输到复制站点也是相关的。在大多数情况下,一个带有脚本的调度器可以复制小批量数据并降低运输成本。然而,在数据摄取过程中,可以使用大量复制,例如将当前批次的原始数据从源复制到冷存储。

还有更多...

在过去几年中,数据复制的一种使用增加的方法是冷存储,用于保留不常使用或甚至不活跃的数据。与此类复制相关的成本微乎其微,并保证了数据的长期性。你可以在所有云服务提供商中找到冷存储解决方案,例如Amazon GlacierAzure Cool BlobGoogle Cloud Storage Nearline

除了复制之外,像通用数据保护条例GDPR)这样的监管合规法律也得益于这种存储方式,因为对于某些案例场景,用户数据需要保留数年。

在本章中,我们探讨了基本概念,并为本书后续章节和食谱奠定了基础。我们从 Python 安装开始,准备我们的 Docker 容器,并了解了数据治理和复制概念。在接下来的章节中,你将观察到几乎所有主题都是相互关联的,你将理解在 ETL 过程开始时理解它们的相关性。

进一步阅读

第二章:数据访问原则 – 访问您的数据

数据访问是一个术语,指的是将数据从一个系统或应用程序存储、检索、传输和复制到另一个系统或应用程序的能力。它涉及安全性、法律,在某些情况下还涉及国家事务。除了最后两点,我们还将在本章中涵盖一些安全主题。

作为数据工程师或科学家,知道如何正确检索数据是必要的。其中一些可能需要加密身份验证,为此,我们需要了解一些解密库的工作原理以及如何在不泄露敏感数据的情况下使用它们。数据访问还涉及系统或数据库的授权级别,从管理到只读角色。

在本章中,我们将介绍数据访问级别是如何定义的,以及在数据摄取过程中最常用的库和身份验证方法。

在本章中,你将完成以下食谱:

  • 在数据访问工作流程中实施治理

  • 访问数据库和数据仓库

  • 访问SSH 文件传输协议SFTP)文件

  • 使用 API 身份验证检索数据

  • 管理加密文件

  • 从 AWS 访问数据

  • 从 GCP 访问数据

技术要求

如果你已经有 Gmail 账户,可以轻松创建 Google Cloud 账户,并且大部分资源都可以使用免费层访问。它还提供了 300 美元的信用额度,用于非免费资源。如果你想在 GCP 中使用本书中的其他食谱进行其他测试,这是一个很好的激励措施。

要访问和启用 Google Cloud 账户,请访问cloud.google.com/页面,并按照屏幕上提供的步骤操作。

注意

本章涵盖的所有食谱都适用于免费层。

你也可以在这个 GitHub 仓库中找到本章的代码:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook

在数据访问工作流程中实施治理

正如我们之前看到的,数据访问可访问性治理支柱,与安全性密切相关。数据安全不仅是管理员或经理的担忧,也是所有与数据相关的人的担忧。话虽如此,了解如何设计一个基础工作流程以实施数据的安全层,只允许授权人员读取或操作数据,这是至关重要的。

此食谱将创建一个包含实施数据访问管理必要主题的工作流程。

准备工作

在设计我们的工作流程之前,我们需要确定干扰我们数据访问的向量。

那么,什么是数据向量呢?

向量是某人可以用来未经授权访问服务器、网络或数据库的路径。在这种情况下,我们将识别与数据泄露相关的向量。

让我们以以下图表所示的形式来探索它们:

图 2.1 – 数据治理向量

图 2.1 – 数据治理向量

让我们在这里理解路径中的每个阶段:

  1. 数据创建:在这个步骤中,我们确定数据在哪里创建以及由谁创建。有了这个定义,我们可以确保只有负责的人可以访问创建或更新数据。

  2. 数据存储:在创建后,了解我们的数据在哪里或将被存储非常重要。根据这个问题的答案,检索数据的方法将不同,可能需要额外的步骤。

  3. 用户和服务管理:数据必须被使用,人们需要访问。在这里,我们定义了参与者或我们可能拥有的角色,常见的类型是管理员写入只读角色。

  4. 数据传输:我们的数据将如何传输?决定它是实时、近实时还是批量传输至关重要。你可以在你的工作流程中添加更多问题,例如数据将通过 API 或其他方法进行传输时将如何可用。

如何操作…

在确定我们的向量后,我们可以定义数据访问管理的实施工作流程。

为了让大家更容易理解如何实现它,让我们设想一个假设的场景,在这个场景中,我们想要从患者那里检索医疗记录。

这是我们的做法:

  1. 第一步是记录所有我们的数据并将其分类。如果有机密数据,我们需要制定如何识别它的方法。

  2. 然后,我们将开始定义谁可以访问数据,并相应地确定必要的使用权限。例如,我们确定数据管理员并在此处设置读写权限。

  3. 一旦实现了数据访问级别,我们就开始观察用户的操作。将日志记录到数据库、数据仓库或任何其他具有用户活动记录的系统至关重要。

  4. 最后,我们检查整个流程,以确定是否需要任何更改。

最后,我们将得到一个类似于以下流程图的流程:

图 2.2 – 开始实施数据治理的流程图

图 2.2 – 开始实施数据治理的流程图

它是如何工作的…

数据访问管理是一个持续的过程。每天,我们都会摄取并创建新的管道,供不同团队中的多个人使用。以下是具体步骤:

  1. 发现、分类和记录所有数据:首先要组织的是我们的数据。我们将检索哪位患者的数据?它是否包含个人身份信息PII)或受保护/个人健康信息PHI)?由于这是我们第一次摄取这些数据,我们需要用有关 PII 和谁负责源数据的标志对其进行编目。

  2. 创建访问控制:在这里,我们根据角色定义访问权限,因为并非每个人都需要访问患者病史。我们根据角色、职责和分类分配数据权限。

  3. 检查用户行为:在这个步骤中,我们观察用户在其角色中的行为。创建、更新和删除操作被记录下来以便监控和必要时审查。如果医疗部门不再使用某个报告,我们可以限制他们的访问,甚至阻止他们获取信息。

  4. 分析和审查合规性要求:我们必须确保我们的访问管理遵循合规性和当地法规。不同类型的数据有不同的法律规范,这需要考虑。

相关内容

访问数据库和数据仓库

数据库是任何系统或应用的基石,无论你的架构如何。有时需要数据库来存储日志、用户活动或信息,以及系统相关内容。

从更大的角度来看,数据仓库有相同的用途,但与分析数据相关。在摄取和转换数据后,我们需要将其加载到更容易检索用于仪表板、报告等的地方。

目前,可以找到多种类型的数据库(SQL 和 NoSQL 类型)和数据仓库架构。然而,这个配方旨在介绍通常如何对关系结构进行访问控制。目标是了解访问级别是如何定义的,即使是在一个通用场景中。

准备工作

对于这个配方,我们将使用 MySQL。你可以按照 MySQL 官方页面上的说明进行安装:dev.mysql.com/downloads/installer/

你可以使用你选择的任何 SQL 客户端来执行这里的查询。在我的情况下,我将使用 MySQL Workbench:

  1. 首先,我们将使用我们的root用户创建一个数据库。你可以给它取任何名字。我的建议是设置字符集为UTF-8

    CREATE SCHEMA `your_schema_name` DEFAULT CHARACTER SET utf8 ;
    

我的模式将被称为cookbook-data

  1. 下一步是创建表。仍然使用 root 账户,我们将使用数据定义语言DDL)语法创建一个people_city表:

    CREATE TABLE `cookbook-data`.`people_city` (
      `id` INT NOT NULL,
      `name` VARCHAR(45) NULL,
      `country` VARCHAR(45) NULL,
      `occupation` VARCHAR(45) NULL,
      PRIMARY KEY (`id`));
    

如何操作...

注意

自从 MySQL 8.0 的最后一个更新以来,我们无法直接使用GRANT命令创建用户。将出现这样的错误:

ERROR 1410 (42000): 您不允许使用 GRANT 创建用户

为了解决这个问题,我们将采取一些额外的步骤。我们还需要至少打开两次 MySQL,所以如果你选择直接在命令行中执行命令,请记住这一点。

我还想感谢 Lefred 的博客为此解决方案和对社区的贡献。你可以在他们的博客中找到更多详细信息和其他有用的信息:lefred.be/content/how-to-grant-privileges-to-users-in-mysql-8-0/

让我们看看执行这个菜谱的步骤:

  1. 首先,让我们创建admin用户。在这里,如果我们没有正确遵循以下步骤,我们将会遇到问题。我们需要创建一个用户作为我们的超级用户或管理员,使用我们的root 用户

    CREATE user 'admin'@'localhost' identified by 'password';
    > Query OK, 0 rows affected
    
  2. 然后,我们使用admin用户登录。使用你在步骤 1中定义的密码,使用admin用户登录 MySQL 控制台。在 SQL 软件客户端上,我们目前还看不到任何数据库,但在接下来的步骤中我们会解决这个问题。登录到控制台后,你可以看到准备使用的 SQL 命令:

    $ mysql -u admin -p password -h localhost
    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
    mysql>
    

保持此会话开启。

  1. 然后,我们通过角色授予admin用户权限。在 root 会话中,让我们创建一个名为administration的角色,并将数据库的完全访问权限授予admin用户:

    create ROLE administration;
    

然后,我们授予角色权限:

grant alter,create,delete,drop,index,insert,select,update,trigger,alter routine,create routine, execute, create temporary tables on `cookbook_data`.* to 'administration';

然后,我们将角色授予我们的admin用户:

grant 'administration' to 'admin';

然后,我们将此角色设置为默认:

set default role 'administration' to 'admin';
  1. 接下来,就像在步骤 2中一样,我们创建另外两个用户,writeread-only角色。

重复步骤 3,给出新的角色名称,并且对于这两个角色,我的建议是授予以下权限:

  • altercreateindexinsertselectupdatetriggeralter routinecreate routineexecutecreate temporary tables

  • selectexecute

  1. 接下来,我们执行操作。如果我们尝试使用我们的adminwrite角色执行INSERT,我们可以看到它是可行的:

    INSERT INTO `cookbook_data`.`people_city` (id, `name`, country, occupation)
    VALUES (1, 'Lin', 'PT', 'developer');
    > 1 row(s) affected
    

然而,read-only用户却不能这样做:

Error Code: 1142\. INSERT command denied to user 'reader'@'localhost' for table 'people_city'

它是如何工作的…

在实际项目中,大多数情况下,会有一个专门的人员(或数据库管理员)来处理和照顾数据库或数据仓库的访问。尽管如此,任何需要访问关系型数据源的人员都需要了解访问级别的基本概念,以便请求其权限(并证明其合理性)。

在这个菜谱中,我们使用了show privileges命令。

altercreatedeletedropindexinsertselectupdatetriggeralter routinecreate routineexecutecreate temporary tables是日常使用中常用的命令,了解它们有助于更容易地识别错误。以先前的错误为例:

Error Code: 1142\. INSERT command denied to user 'reader'@'localhost' for table 'people_city'

第一行精确地显示了我们需要权限。第二行显示了哪个用户(reader)缺少权限,他们使用的连接(@localhost),以及他们想要访问的表(people_city)。

此外,如果你是系统管理员,你也可以识别出不被允许的行为并帮助解决它。

还有更多…

如果你感兴趣了解更多,也可以找到数据库和数据仓库的另外三种访问控制类型:

  • 自主访问控制DAC

  • 强制访问控制MAC

  • 基于属性的访问控制ABAC

下图以简化的形式说明了访问控制,如下所示:

图 2.3 – 数据库访问控制比较 – 来源:

图 2.3 – 数据库访问控制比较 – 来源:www.cloudradius.com/access-control-paradigms-compared-rbac-vs-pbac-vs-abac/

即使前一个图中没有描述,我们也可以找到使用基于行和列的访问控制的数据库。更多关于它的信息可以在这里找到:documentation.datavirtuality.com/23/reference-guide/authentication-access-control-and-security/access-control/data-roles/permissions/row-and-column-based-security.

参考信息

访问 SSH 文件传输协议(SFTP)文件

文件传输协议FTP)于 20 世纪 70 年代在麻省理工学院MIT)推出,基于传输控制协议/互联网协议TCP/IP)的应用层。自 20 世纪 80 年代以来,它被广泛用于在计算机之间传输文件。

随着计算机和互联网使用的增加,多年来,引入一种更安全的解决方案使用方式变得必要。为了提高FTP 事务的安全性,实现了SSH 层,创建了SSH 文件传输协议SFTP)协议。

现在,从 SFTP 服务器中获取数据是很常见的,在这个菜谱中,我们将努力从公共 SFTP 服务器检索数据。

准备工作

在这个菜谱中,我们将使用 Python 和pysftp库编写代码,以连接并从公共 SFTP 服务器检索样本数据。

如果你拥有 SFTP 服务器,可以自由地测试这里提供的 Python 代码来练习更多:

  1. 首先,我们将获取 SFTP 凭证。访问 SFTP.NET 地址 https://www.sftp.net/public-online-sftp-servers,并在记事本上保存主机名登录(用户名/密码)信息。

图 2.4 – SFTP.NET 主页

图 2.4 – SFTP.NET 主页

注意

此 SFTP 服务器仅用于测试和研究目的。因此,凭证是不安全的,并且是公开可用的;对于生产目的,该信息需要在密码保险库中安全存储,并且永远不要通过代码共享。

  1. 然后,我们使用命令行安装 Python 的pysftp包:

    $ pip install pysftp
    

如何操作...

执行此菜谱的步骤如下:

  1. 首先,让我们创建一个名为accessing_sftp_files.py的 Python 文件。然后,我们插入以下代码以使用 Python 创建我们的 SFTP 连接:

    import pysftp
    host = " test.rebex.net"
    username = "demo"
    password = "password"
    with pysftp.Connection(host=host, username=username, password=password) as sftp:
        print("Connection successfully established ... ")
    

您可以使用以下命令调用该文件:

$ python accessing_sftp_files.py

这是它的输出:

Connection successfully established ...

在这里,可能会出现一个已知错误——SSHException: No hostkey for host test.rebex.net found.

这是因为 pysftp 在您的 KnowHosts 中找不到 hostkey。

如果出现此错误,请按照以下步骤操作:

  1. 打开您的命令行并执行ssh demo@test.rebex.net

图 2.5 – 将主机添加到 known_hosts 列表

图 2.5 – 将主机添加到 known_hosts 列表

  1. 输入demo用户的密码并退出 Rebex 虚拟外壳:

图 2.6 – 来自 Rebex SFTP 服务器的欢迎信息

图 2.6 – 来自 Rebex SFTP 服务器的欢迎信息

  1. 然后,我们列出 SFTP 服务器上的文件:

    import pysftp
    host = "test.rebex.net"
    username = "demo"
    password = "password"
    with pysftp.Connection(host=host, username=username, password=password) as sftp:
        print("Connection successfully established ... ")
        # Switch to a remote directory
        sftp.cwd('pub/example/')
        # Obtain structure of the remote directory '/pub/example'
        directory_structure = sftp.listdir_attr()
        # Print data
        for attr in directory_structure:
            print(attr.filename, attr)
    

现在,让我们下载readme.txt文件:

让我们将代码的最后几行修改一下,以便能够下载readme.txt

import pysftp
host = "test.rebex.net"
username = "demo"
password = "password"
with pysftp.Connection(host=host, username=username, password=password) as sftp:
    print("Connection successfully established ... ")
    # Switch to a remote directory
    sftp.cwd('pub/example/')
    print("Changing to pub/example directory... ")
    sftp.get('readme.txt', 'readme.txt')
    print("File downloaded ... ")
    sftp.close()

其输出如下:

Connection successfully established ...
Changing to pub/example directory...
File downloaded ...

它是如何工作的...

pysftp是一个 Python 库,允许开发者连接、上传和从 SFTP 服务器下载数据。它的使用很简单,并且该库具有众多功能。

注意,我们的大部分代码都位于pysftp.Connection内部缩进。这是因为我们为特定凭证创建了一个连接会话。with语句负责资源的获取和释放,如您所见,它在文件流、锁、套接字等中广泛使用。

我们还使用了sftp.cwd()方法,允许我们更改目录,并在需要列出或检索文件时避免指定路径。

最后,使用sftp.get()完成了下载,其中第一个参数是我们想要下载的文件的路径和名称,第二个参数是我们将放置它的位置。由于我们已经在文件的目录中,我们可以将其保存在我们的本地HOME目录中。

最后但同样重要的是,sftp.close()关闭了连接。这是在脚本中避免与网络并发或其他管道或 SFTP 服务器的好做法。

更多内容...

如果您想深入了解并进行其他测试,您还可以创建一个本地 SFTP 服务器。

对于 Linux 用户来说,可以使用ssh命令行来完成这项操作。更多内容请查看这里:linuxhint.com/setup-sftp-server-ubuntu/.

对于 Windows 用户,请转到此处的SFTP 服务器部分:www.sftp.net/servers.

图 2.7 – SFTP.NET 页面,包含创建小型 SFTP 服务器的教程链接

图 2.7 – SFTP.NET 页面,包含创建小型 SFTP 服务器的教程链接

Minimalist SFTP servers 下选择 Rebex Tiny SFTP Server,下载并启动程序。

参见

使用 API 身份验证获取数据

应用程序编程接口(API)是一组配置,允许两个系统或应用程序相互通信或传输数据。近年来,其概念得到了改进,允许使用 OAuth 方法实现更快的传输和更高的安全性,防止 拒绝服务(DoS)或 分布式拒绝服务(DDoS)攻击等。

它在数据摄取中得到了广泛应用,无论是从应用程序中检索数据以获取分析的最新日志,还是从 BigQuery 使用云服务提供商(如 Google)检索数据。如今,大多数应用程序都通过 API 服务提供其数据,这使得数据世界从中获得了许多好处。关键在于知道如何使用最接受的认证形式从 API 服务中检索数据。

在这个菜谱中,我们将使用 API 密钥身份验证从公共 API 获取数据,这是一种标准的数据收集方法。

准备工作

由于我们将使用两种不同的方法,本节将分为两部分,以便更容易理解如何处理它们。

对于本节,我们将使用 HolidayAPI,这是一个公开且免费的 API,提供有关全球假期的信息:

  1. 安装 Python 的 requests 库:

    $ pip3 install requests
    
  2. 然后,访问 Holiday API 网站。访问 holidayapi.com/ 并点击 获取您的免费 API 密钥。你应该会看到以下页面:

图 2.8 – Holidays API 主网页

图 2.8 – Holidays API 主网页

  1. 然后,我们创建一个账户并获取 API 密钥。要创建账户,您可以使用电子邮件和密码,或者使用 GitHub 账户注册:

图 2.9 – Holiday API 用户身份验证页面

图 2.9 – Holiday API 用户身份验证页面

身份验证后,你可以看到并复制你的 API 密钥。请注意,你可以在任何时候生成一个新的密钥。

图 2.10 – Holiday API 页面上的用户仪表板页面

图 2.10 – Holiday API 页面上的用户仪表板页面

注意

我们将使用此 API 的免费层,每月请求量有限。它也禁止用于商业用途。

如何操作…

下面是执行菜谱的步骤:

  1. 我们使用 requests 库创建一个 Python 脚本:

    import requests
    import json
    params = { 'key': 'YOUR-API-KEY',
              'country': 'BR',
              'year': 2022
    }
    url = "https://holidayapi.com/v1/holidays?"
    req = requests.get(url, params=params)
    print(req.json())
    

确保您使用与上一年相同的 year 值,因为我们正在使用 API 的免费版本,该版本仅限于去年的历史数据。

这里是代码的输出:

{'status': 200, 'warning': 'These results do not include state and province holidays. For more information, please visit https://holidayapi.com/docs', 'requests': {'used': 7, 'available': 9993, 'resets': '2022-11-01 00:00:00'}, 'holidays': {'name': "New Year's Day", 'date': '2021-01-01', 'observed': '2021-01-01', 'public': True, 'country': 'BR', 'uuid': 'b58254f9-b38b-42c1-8b30-95a095798b0c',{...}

注意

作为最佳实践,API 密钥绝不应该在脚本中硬编码。这里的定义仅用于教育目的。

  1. 然后,我们将我们的 API 请求保存为 JSON 文件:

    import requests
    import json
    params = { 'key': 'YOUR-API-KEY',
              'country': 'BR',
              'year': 2022
    }
    url = "https://holidayapi.com/v1/holidays?"
    req = requests.get(url, params=params)
    with open("holiday_brazil.json", "w") as f:
        json.dump(req.json(), f)
    

它是如何工作的…

Python 的 requests 库是 PyPi 服务器上下载量最大的库之一。这种流行度并不令人惊讶,因为我们将在使用库时看到它的强大和多功能性。

步骤 1 中,我们在 Python 脚本的开始处导入了 requestsjson 模块。params 字典是发送到 API 的有效载荷发送者,因此我们插入了 API 密钥和另外两个必填字段。

注意

此 API 授权密钥是通过有效载荷请求发送的;然而,这取决于 API 的构建方式。一些请求要求通过 Header 定义发送认证,例如。始终检查 API 文档或开发者以了解如何正确认证。

步骤 1 中的 print() 函数用作测试,以查看我们的调用是否已认证。

当 API 调用返回 200 状态码时,我们继续保存 JSON 文件,您应该有如下输出:

![图 2.11 – 下载的 JSON 文件数据

图 2.11 – 下载的 JSON 文件数据

更多内容…

API 密钥通常用于认证客户端,但根据数据敏感性级别,还应考虑其他安全方法,如 OAuth。

注意

如果与 HTTPS/SSL 等其他安全机制相关联,API 密钥认证才能被认为是安全的。

使用 OAuth 方法进行认证

开放授权OAuth)是一个行业标准协议,用于授权网站或应用程序进行通信和访问信息。您可以在官方文档页面这里了解更多信息:oauth.net/2/

您还可以使用 Google 日历 API 测试此类型的认证。要启用 OAuth 方法,请按照以下步骤操作:(https://edps.europa.eu/data-protection/data-protection/reference-library/health-data-workplace_en)

  1. 通过访问页面 developers.google.com/calendar/api/quickstart/python启用 Google 日历 API,然后转到 启用 API 部分。

将打开一个新标签页,Google Cloud 将要求您选择或创建一个新项目。选择您想要使用的项目。

注意

如果您选择创建一个新项目,请在 项目名称 字段中输入项目名称,并将 组织 字段保留为默认值(无组织)。

选择 下一步 以确认您的项目,然后点击 激活;您应该会看到这个页面:

图 2.12 – 激活资源 API 的 GCP 页面

图 2.12 – 激活资源 API 的 GCP 页面

现在,我们几乎准备好获取我们的凭证了。

  1. 通过返回到developers.google.com/calendar/api/quickstart/python页面,点击转到凭证,并遵循为桌面应用程序授权凭证下的说明来启用 OAuth 认证。

图 2.13 – 创建 credentials.json 文件的 GCP 教程页面

图 2.13 – 创建 credentials.json 文件的 GCP 教程页面

最后,您应该为这个配方有一个credentials.json文件。请妥善保管此文件,因为所有对 Google API 的调用都需要它来验证您的真实性。

您可以使用 GCP 脚本示例之一来测试此认证方法。Google 提供了一个用于从Google 日历 API检索数据的 Python 脚本示例,可以在此处访问:github.com/googleworkspace/python-samples/blob/main/calendar/quickstart/quickstart.py

其他认证方法

尽管我们已经介绍了两种最常用的 API 认证方法,但数据摄取管道并不局限于这些。在您的日常工作中,您可能会遇到需要其他形式认证的遗留系统或应用程序。

HTTP 基本认证BearerOpenID ConnectOpenAPI 安全方案等方法也广泛使用。您可以在由Guy Levin撰写的这篇文章中找到更多关于它们的信息:blog.restcase.com/4-most-used-rest-api-authentication-methods/

SFTP 与 API 的比较

你可能想知道,从 SFTP 服务器和 API 中摄取数据有什么区别?很明显,它们的认证方式不同,代码的行为也各不相同。但我们应该在何时实现 SFTP 或 API 来使数据可用于摄取?

FTP 或 SFTP 事务旨在使用平面文件,例如CSVXMLJSON文件。这两种类型的事务在需要传输大量数据时也表现良好,并且是旧系统唯一可用的方法。API 提供实时数据交付和更安全的基于互联网的连接,并且由于其与多个云应用程序的集成,在数据摄取领域变得流行。然而,API 调用有时是根据请求数量付费的。

本文件事务架构讨论主要针对金融和人力资源系统,这些系统可能使用一些旧的编程语言版本。系统架构讨论基于更现代和基于云的应用程序中的平面文件或实时数据。

参见

管理加密文件

当处理敏感数据是常见的情况时,一些字段甚至整个文件都会被加密。当实施这种文件安全措施时,这是全面的,因为敏感数据可能会暴露用户的生命。毕竟,加密是将信息转换为隐藏原始内容的代码的过程。

尽管如此,我们仍然需要在我们的数据管道中摄取和处理这些加密文件。为了能够做到这一点,我们需要了解更多关于加密是如何工作以及如何进行的。

在这个菜谱中,我们将使用 Python 库和最佳实践解密 GnuPG 加密的文件(其中 GnuPG 代表 GNU Privacy Guard)。

准备工作

在进入有趣的部分之前,我们必须在本地机器上安装 GnuPG 库并下载加密的数据集。

对于 GnuPG 文件,您需要安装两个版本 – 一个用于 操作系统OS),另一个用于 Python 包。这是因为 Python 包需要从已安装的 OS 包中获取内部资源:

  1. 要使用 Python 封装库,我们首先需要在本地机器上安装 GnuPG:

    $ sudo apt-get install gnupg
    

对于 Windows 用户,建议在此处下载可执行文件:gnupg.org/download/index.xhtml

Mac 用户可以使用 Homebrew 安装它:formulae.brew.sh/formula/gnupg

  1. 然后,我们按照以下方式安装 Python GnuPG 封装库:

    $ pip3 install python-gnupg
    Collecting python-gnupg
      Downloading python_gnupg-0.5.0-py2.py3-none-any.whl (18 kB)
    Installing collected packages: python-gnupg
    Successfully installed python-gnupg-0.5.0
    
  2. 接下来,我们下载 spotify tracks chart encrypted 加密数据集。您可以使用此链接下载文件:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook/tree/main/Chapter_2/managing_encrypted_%EF%AC%81les

如何操作…

我们将需要一个密钥来使用 GnuPG 解密文件。您可以在 | 管理加密文件 文件夹中找到它。访问链接在本书开头的 技术要求 部分中:

  1. 首先,我们导入我们的密钥:

    import gnupg
    # Create gnupg directory
    gpg = gnupg.GPG(gnupghome='gpghome')
    # Open and import the key
    key_data = open('mykeyfile.asc').read()
    import_result = gpg.import_keys(key_data)
    # Show the fingerprint of our key
    print(import_result.results)
    
  2. 然后,我们解密导入文件:

    with open('spotify_data.csv.gpg', 'rb') as f:
        status = gpg.decrypt_file(f, passphrase='mypassphrase', output='spotify_data.csv')
    print(status.ok)
    print(status.status)
    print('error: ', status.stderr)
    

它是如何工作的…

关于加密的最佳实践,GnuPG 是一个安全参考,广泛使用,并在 RFC 4880 中进行了记录。您可以在以下链接中了解更多信息:www.rfc-editor.org/rfc/rfc4880

注意

请求评论RFC)是由 互联网工程任务组IETF)开发和维护的技术文档。该机构规定了互联网上协议、服务和模式的最佳实践。

在这里,我们看到了一个 GnuPG 应用程序的实际例子,尽管它看起来很简单。让我们通过代码中的一些重要行来了解一下:

gpg = gnupg.GPG(gnupghome='gpghome') 这一行中,我们实例化了我们的 GPG 类,并传递了它存储临时文件的位置,你可以设置任何你想要的路径。在我的情况下,我在我的主目录中创建了一个名为 gpghome 的文件夹。

在接下来的几行中,我们导入了密钥,并仅为了演示目的打印了它的指纹。

对于 步骤 2,我们使用 with open 语句打开我们想要解密的文件,并对其进行解密。你可以看到为 passphrase 设置了一个参数。这是因为 GnuPG 的较新版本要求加密的文件设置密码。由于这个食谱仅用于教育目的,这里的密码很简单且是硬编码的。

之后,你应该能够无问题地打开 .csv 文件。

图 2.14 – 解密后的 Spotify CSV 文件

图 2.14 – 解密后的 Spotify CSV 文件

还有更多...

通常,GnuPG 是加密文件的首选工具,但还有其他市场解决方案,例如 Python 的 cryptography 库,它有一个 Fernet 类,是一种对称加密方法。正如你在以下代码中所看到的,它的使用与我们在这个食谱中所做的是非常相似的:

from cryptography.fernet import Fernet
# Retrieving key
fernet_key = Fernet(key)
# Getting and opening the encrypted file
with open('spotify_data.csv', 'rb') as enc_file:
    encrypted = enc_file.read()
# Decrypting the file
decrypted = fernet_key.decrypt(encrypted)
# Creating a decrypted file
with open('spotify_data.csv', 'wb') as dec_file:
    dec_file.write(decrypted)

尽管如此,Fernet 方法在数据世界中并不广泛使用。这是因为带有敏感数据的加密文件通常来自使用 GnuPG 混合加密的应用程序或软件,正如我们在 如何工作… 部分所看到的,它符合 RFC 4880。

你可以在 cryptography 库的文档中找到更多详细信息:cryptography.io/en/latest/fernet/

另请参阅

使用 S3 从 AWS 访问数据

AWS 是最受欢迎的云服务提供商之一,它混合了不同的服务架构,并允许轻松快速的实施。

尽管它为关系型和非关系型数据库提供了各种解决方案,但在本食谱中,我们将介绍如何管理从S3 存储桶的数据访问,这是一个允许上传文本文件、媒体以及物联网和大数据领域使用的其他几种类型文件的对象存储服务。

对于 S3 存储桶,有两种常用的数据访问管理类型,都在数据摄取管道中使用 – 用户控制存储桶策略。在本食谱中,我们将学习如何通过用户控制来管理访问,鉴于它是数据摄取管道中最常用的方法。

准备工作

要完成这个食谱,拥有或创建 AWS 账户不是强制性的。目标是构建一个逐步的身份访问管理IAM)策略,使用您理解的良好数据访问实践从 S3 存储桶中检索数据。

然而,如果您想创建一个免费的 AWS 账户来测试,您可以按照以下提供的AWS 官方文档中的步骤进行:docs.aws.amazon.com/accounts/latest/reference/manage-acct-creating.xhtml。创建您的 AWS 账户后,按照以下步骤操作:

  1. 让我们创建一个用户来测试我们的 S3 策略。要创建用户,请查看以下 AWS 链接:docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.xhtml。您无需担心将其附加到策略上,因此请跳过本教程的这一部分。目的是探索没有任何策略附加的用户在 AWS 控制台中能做什么。

  2. 接下来,让我们使用我们的管理员用户创建一个 S3 存储桶。在搜索栏中输入S3并点击第一个链接:

图 2.15 – AWS 搜索栏

图 2.15 – AWS 搜索栏

  1. 然后,点击创建存储桶按钮,将加载一个新页面如下:

图 2.16 – AWS S3 主页面

图 2.16 – AWS S3 主页面

将加载一个新页面如下:

图 2.17 – 创建新存储桶的 AWS S3 页面

图 2.17 – 创建新存储桶的 AWS S3 页面

在本食谱中,我们选择在斯德哥尔摩进行操作,因为它是我居住地最近的一个区域。现在请跳过其他字段,向下滚动并点击创建存储桶按钮:

在 S3 页面上,您应该能够看到并选择您创建的存储桶:

图 2.18 – S3 存储桶对象页面

图 2.18 – S3 存储桶对象页面

如果您想测试,可以在这里上传任何文件。

完成步骤后,在您的浏览器中打开另一个窗口并切换到您为测试创建的用户。如果可能的话,尽量在不同的浏览器中保持管理员和测试用户登录,这样您就可以实时看到变化。

我们已准备好开始创建和应用访问策略。

如何操作...

如果我们尝试列出S3,我们可以看到存储桶,但当点击任何存储桶时,将会发生以下错误:

图 2.19 – 使用权限不足的消息测试 S3 桶的用户视图

图 2.19 – 使用权限不足的消息测试 S3 桶的用户视图

让我们通过以下步骤创建并附加一个策略来解决这个问题:

  1. 首先,我们将为用户定义一个访问策略。用户将能够列出、检索和删除我们创建的 S3 存储桶中的任何对象。让我们首先创建一个包含 AWS 策略要求的 JSON 文件,使其成为可能。请参阅以下文件:

    {
       "Version":"2012-10-17",
       "Statement":[
          {
             "Effect":"Allow",
             "Action": "s3:ListAllMyBuckets",
             "Resource":"*"
          },
          {
             "Effect":"Allow",
             "Action":["s3:ListBucket","s3:GetBucketLocation"],
             "Resource":"arn:aws:s3:::cookbook-s3-accesspolicies"
          },
          {
             "Effect":"Allow",
             "Action":[
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:GetObject",
                "s3:GetObjectAcl",
                "s3:DeleteObject"
             ],
             "Resource":"arn:aws:s3:::cookbook-s3-accesspolicies /*"
          }
       ]
    }
    
  2. 接下来,我们允许用户通过 IAM 策略访问存储桶。在用户 IAM 页面上,按照以下步骤点击添加内联策略

图 2.20 – AWS 用户权限策略部分

图 2.20 – AWS 用户权限策略部分

将上一段代码插入到JSON选项卡中,然后点击审查策略。在审查策略页面,输入策略的名称并点击创建策略以确认,如下所示:

图 2.21 – AWS IAM 审查策略页面

图 2.21 – AWS IAM 审查策略页面

如果我们现在检查,我们可以看到添加文件是可能的,但删除文件则不行。

它是如何工作的...

在本食谱的开始,我们的测试用户没有权限访问 AWS 上的任何资源。例如,当我们访问我们创建的存储桶时,页面上会出现警告。然后我们允许用户访问并上传文件或对象到存储桶。

步骤 1中,我们按照以下步骤构建了一个内联 IAM 策略:

  1. 首先,我们允许测试用户列出相应 AWS 账户内的所有存储桶:

    "Statement":[
          {
             "Effect":"Allow",
             "Action": "s3:ListAllMyBuckets",
             "Resource":"*"
          },
    
  2. 第二个语句允许用户列出对象并获取存储桶的位置。请注意,在资源键中,我们只指定了一个目标 S3 存储桶AWS 资源 名称ARN):

    {
             "Effect":"Allow",
             "Action":["s3:ListBucket","s3:GetBucketLocation"],
             "Resource":"arn:aws:s3:::cookbook-s3-accesspolicies"
          },
    
  3. 最后,我们创建另一个语句以允许对象的插入和检索。在这种情况下,资源现在在 ARN 的末尾也有一个/*字符。这代表将影响相应存储桶对象的策略:

    {
             "Effect":"Allow",
             "Action":[
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:GetObject",
                "s3:GetObjectAcl",
             ],
             "Resource":"arn:aws:s3:::cookbook-s3-accesspolicies /*"
          }
    

根据您想要管理访问的 AWS 资源,操作键可以非常不同,并且可以有不同的应用。关于 S3 存储桶和对象,您可以在 AWS 文档中找到所有可能的操作:docs.aws.amazon.com/AmazonS3/latest/userguide/using-with-s3-actions.xhtml

还有更多...

当导入数据时,用户控制方法是使用最频繁的。这是因为像AirflowElastic MapReduceEMR)这样的应用程序通常可以连接到存储桶。从管理控制的角度来看,它也更容易处理,只需要少量程序性访问,而不是公司中每个用户的单独访问。

当然,会有一些场景,每个数据工程师都有一个具有权限设置的账户。但通常(并且应该是)这样的场景是一个开发环境,其中包含数据样本。

存储桶策略

存储桶策略可以为控制外部资源对内部对象的访问添加一个安全层。使用这些策略,可以限制对特定 IP 地址、特定资源(如CloudFront)或HTTP方法请求类型的访问。在 AWS 官方文档中,您可以查看一系列实用示例:docs.aws.amazon.com/AmazonS3/latest/userguide/example-bucket-policies.xhtml

参见

在 AWS 官方文档中,您还可以看到其他类型的访问控制,如访问控制列表ACLs)和跨源资源共享CORS):docs.aws.amazon.com/AmazonS3/latest/userguide/s3-access-control.xhtml

使用云存储从 GCP 访问数据

Google Cloud PlatformGCP)是一个提供多种服务的云提供商,从云计算到人工智能AI),只需几个步骤即可实现。它还提供广泛范围的存储服务,称为云存储

在本配方中,我们将逐步构建策略来控制对云存储桶内数据的访问。

准备工作

此配方将使用由 Google Cloud 团队定义的统一方法:

  1. 首先,我们将创建一个测试用户。转到IAM页面(console.cloud.google.com/iam-admin/iam)并选择授予权限。在新主体字段中添加一个有效的 Gmail 地址。目前,此用户将只有浏览器角色:

图 2.22 – 将策略附加到用户的 GCP IAM 页面

图 2.22 – 将策略附加到用户的 GCP IAM 页面

  1. 然后,我们将创建一个云存储桶。转到云存储页面并选择创建一个****存储桶console.cloud.google.com/storage/create-bucket

图 2.23 – 已选择云存储的 GCP 搜索栏

图 2.23 – 已选择云存储的 GCP 搜索栏

为您的存储桶添加一个唯一的名称,并保留其他选项不变:

图 2.24 – 创建新存储桶的 GCP 页面

图 2.24 – 创建新存储桶的 GCP 页面

如何做到这一点...

执行此配方的步骤如下:

  1. 我们将尝试访问云存储对象。首先,让我们尝试使用我们刚刚创建的用户访问存储桶。应该会显示一个错误消息:

图 2.25 – GCP 控制台中测试用户的权限不足消息

图 2.25 – GCP 控制台中测试用户的权限不足信息

  1. 然后,我们在云存储中授予 编辑者 权限。转到 IAM 页面并选择你创建的测试用户。在编辑用户页面上,选择 编辑者

图 2.26 – GCP IAM 页面 – 将编辑者角色分配给测试用户

图 2.26 – GCP IAM 页面 – 将编辑者角色分配给测试用户

注意

如果你对角色有疑问,请使用 Google Cloud 中的策略模拟器:console.cloud.google.com/iam-admin/simulator.

  1. 接下来,我们使用适当的角色访问云存储。用户应该能够查看并将对象上传到存储桶:

图 2.27 – 测试用户对 GCP 存储桶的视图

图 2.27 – 测试用户对 GCP 存储桶的视图

它是如何工作的...

步骤 1 中,我们的测试用户只有浏览权限,当尝试查看存储桶列表时出现了错误信息。然而,云存储的 编辑者 角色通过授予对存储桶(以及大多数其他基本 Google Cloud 资源)的访问权限解决了这个问题。此时,也可以创建一个条件,仅允许访问这个存储桶。

Google Cloud 的访问层次结构基于其组织和项目。为了提供对相应存储桶的访问权限,我们需要确保我们也有对项目资源的访问权限。请参考以下截图:

图 2.28 – GCP 控制访问层次结构 – 来源:https://cloud.google.com/resource-manager/docs/cloud-platform-resource-hierarchy#inheritance

图 2.28 – GCP 控制访问层次结构 – 来源:https://cloud.google.com/resource-manager/docs/cloud-platform-resource-hierarchy#inheritance

一旦定义了访问层次结构,我们就可以在 IAM 页面上选择几个内置的用户权限组,并在需要时添加条件。与 AWS 不同,Google Cloud 策略通常以“角色”的形式创建,并分组以服务于特定的区域或部门。可以为特定情况创建额外的权限或条件,但它们不会共享。

尽管统一的方法看起来很简单,但通过适当分组、修订并统一授予权限,它可以成为一种管理 Google Cloud 访问的强大方式。在我们的案例中,编辑者 角色解决了我们的问题,但在与大型团队和不同类型的访问策略一起工作时,建议咨询该领域的专家。

还有更多...

与 S3 类似,云存储还有另一种名为细粒度的访问控制。它由 IAM 策略和 ACLs 的混合组成,在存储连接到 S3 存储桶等情况下推荐使用。正如其名所示,权限被细化到存储桶和单个对象级别。由于数据暴露可能会因[ACL 策略设置不正确而加剧],因此需要由具有高度安全知识的人(或团队)进行配置。

你可以在这里了解更多关于 ACLs 的信息:云存储 ACLs.

进一步阅读

第三章:数据发现 – 在摄取之前理解我们的数据

正如你可能已经注意到的,数据摄取不仅仅是将数据从源处检索出来并插入到另一个地方。它涉及到理解一些业务概念,确保对数据的安全访问,以及如何存储它,而现在发现我们的数据变得至关重要。

数据发现是理解我们数据模式和行为的流程,确保整个数据管道将成功。在这个过程中,我们将了解我们的数据是如何建模和使用的,这样我们就可以根据最佳匹配来设置和计划我们的摄取。

在本章中,你将了解以下内容:

  • 记录数据发现过程

  • 配置 OpenMetadata

  • 将 OpenMetadata 连接到我们的数据库

技术要求

你也可以在这里找到本章的代码,GitHub 仓库:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook

记录数据发现过程

近年来,手动数据发现迅速被淘汰,机器学习和其他自动化解决方案兴起,为存储或在线电子表格(如 Google Sheets)中的数据带来了快速洞察。

然而,许多小型公司刚开始他们的业务或数据领域,因此立即实施付费或成本相关的解决方案可能不是一个好主意。作为数据专业人士,我们在将第一个解决方案应用于问题时也需要具有可塑性 – 总是会有空间来改进它。

准备工作

这个配方将涵盖有效启动数据发现过程的步骤。尽管在这里,这个过程更多地与手动发现步骤相关,但你也会看到它也适用于自动化步骤。

让我们从下载数据集开始。

对于这个配方,我们将使用病毒和细菌中基因的演变数据集(www.kaggle.com/datasets/thedevastator/the-evolution-of-genes-in-viruses-and-bacteria),以及另一个包含医院管理信息(www.kaggle.com/datasets/girishvutukuri/hospital-administration)的数据集。

注意

这个配方不需要使用提到的确切数据集 – 它普遍地说明了如何将方法论应用于数据集或任何数据源。请随意使用你想要的数据。

下一个阶段是创建文档。你可以使用任何适合你的软件或在线应用程序 – 重要的是要有一个地方来详细和分类信息。

我将使用 Notion (www.notion.so/)。其主页如 图 3.1 所示。它提供免费计划,并允许您为不同类型的文档创建单独的位置。然而,一些公司使用 Atlassian 的 Confluence 来记录他们的数据。这始终取决于您所处的场景。

图 3.1 – Notion 主页

图 3.1 – Notion 主页

这是一个可选阶段,我们正在创建 Notion 账户。在主页上,点击 获取 Notion 免费版

另一个页面将出现,您可以使用 Google 或 Apple 邮箱创建账户,如下所示:

图 3.2 – Notion 注册页面

图 3.2 – Notion 注册页面

之后,您应该会看到一个带有 Notion 欢迎信息的空白页面。如果需要采取其他操作,只需遵循页面说明。

如何操作…

让我们想象一个场景,我们在一家医院工作,需要应用数据发现过程。以下是我们的操作步骤:

  1. 识别我们的数据来源:两个主要部门需要他们的数据被摄取——行政部门和研究部门。我们知道他们通常将 CSV 文件保存在本地数据中心,因此我们可以通过内网访问它们。不要在意文件名;通常,在实际应用中,它们不受支持。

以下为研究部门的文件:

图 3.3 – 关于大肠杆菌基因演化的研究文件

图 3.3 – 关于大肠杆菌基因演化的研究文件

以下为行政部门文件:

图 3.4 – 医院行政部门文件

图 3.4 – 医院行政部门文件

  1. 按部门或项目分类数据:在这里,我们创建与部门和数据类型(关于患者或特定疾病)相关的文件夹和子文件夹。

图 3.5 – 研究部门页面

图 3.5 – 研究部门页面

  1. 识别数据集或数据库:在查看文件时,我们可以找到四种模式。有专属数据集:大肠杆菌基因组蛋白质注释一般大肠杆菌病毒患者

图 3.6 – 根据研究类型和医院行政部门主题创建的子部分

图 3.6 – 根据研究类型和医院行政部门主题创建的子部分

  1. 描述我们的数据:现在,在数据集级别,我们需要有关它的有用信息,例如该数据集表的总体描述、更新时间、其他团队可以找到它的位置、表中每一列的描述,以及最后但同样重要的是,所有元数据。

图 3.7 – 使用 Notion 记录患者数据

图 3.7 – 使用 Notion 记录患者数据

注意

文件存储位置的描述可能并不适用于所有情况。你可以找到数据库名称的引用,例如 'admin_database.patients'

它是如何工作的…

在开始数据发现时,第一个目标是识别模式并将它们分类以创建逻辑流程。通常,最初的分类是按部门或项目,然后是数据库和数据集的识别,最后是描述数据本身。

有一些方法可以手动记录数据发现。那些更习惯于老式风格BI(即商业智能)的人倾向于创建更美观的视觉模型来应用发现。然而,这个菜谱的目标是使用像 Notion 这样的简单工具创建一个目录:

  1. 按部门或项目对数据进行分类:我们首先做的是确定每份数据负责的部门。在摄入问题或数据集损坏的情况下,谁是联系人?在正式术语中,他们也被称为数据管理员。在一些公司,按项目分类也可以应用,因为一些公司可能有他们特定的需求和数据。

  2. 识别数据集或数据库:在这里,我们只使用了数据集。在项目或部门下,我们插入每个表的名字和其他有用的信息。如果表是定期更新的,记录这一点也是一个好的做法。

  3. 描述我们的数据:最后,我们详细记录了预期的列及其数据类型。这有助于数据工程师在导入原始数据时规划脚本;如果自动化后出现问题,他们可以轻松地检测到问题。

你可能会注意到一些数据表现得很奇怪。例如,图 3.7中的medical_speciality列有描述和数字来参考其他内容。在实际项目中,有必要在我们的摄入过程中创建辅助数据来创建模式,并随后便于报告或仪表板。

配置 OpenMetadata

OpenMetadata是一个开源工具,用于元数据管理,允许进行数据发现治理。你可以在这里了解更多信息:open-metadata.org/

通过执行几个步骤,可以使用DockerKubernetes创建本地或生产实例。OpenMetadata 可以连接到多个资源,如MySQLRedisRedshiftBigQuery等,以获取构建数据目录所需的信息。

准备工作

在开始我们的配置之前,我们必须安装OpenMetadata并确保 Docker 容器正在正确运行。让我们看看它是如何完成的:

注意

在本书编写时,应用程序处于 0.12 版本,并有一些文档和安装改进。这意味着安装的最佳方法可能会随时间而改变。请参阅官方文档,链接如下:docs.open-metadata.org/quick-start/local-deployment

  1. 让我们创建一个文件夹和 virtualenv(可选):

    $ mkdir openmetadata-docker
    $ cd openmetadata-docker
    

由于我们正在使用 Docker 环境在本地部署应用程序,你可以使用 virtualenv 创建它,也可以不使用:

$ python3 -m venv openmetadata
$ source openmetadata /bin/activate
  1. 接下来,我们按照以下方式安装 OpenMetadata:

    $ pip3 install --upgrade "openmetadata-ingestion[docker]"
    
  2. 然后我们检查安装,如下所示:

    $ metadata
    Usage: metadata [OPTIONS] COMMAND [ARGS]...
      Method to set logger information
    Options:
      --version                       Show the version and exit.
      --debug / --no-debug
      -l, --log-level [INFO|DEBUG|WARNING|ERROR|CRITICAL]
                                      Log level
      --help                          Show this message and exit.
    Commands:
      backup                          Run a backup for the metadata DB.
      check
      docker                          Checks Docker Memory Allocation Run...
      ingest                          Main command for ingesting metadata...
      openmetadata-imports-migration  Update DAG files generated after...
      profile                         Main command for profiling Table...
      restore                         Run a restore for the metadata DB.
      test                            Main command for running test suites
      webhook                         Simple Webserver to test webhook...
    

如何操作…

在下载 Python 包和 Docker 之后,我们将继续进行如下配置:

  1. 运行容器:首次执行时可能需要一些时间才能完成:

    $ metadata docker –start
    

注意

这种类型的错误很常见:

错误响应来自守护进程:在端点 openmetadata_ingestion (3670b9566add98a3e79cd9a252d2d0d377dac627b4be94b669482f6ccce350e0) 上编程外部连接失败:绑定 0.0.0.0:8080 失败:端口已被 占用

这意味着其他容器或应用程序已经使用了端口 8080。为了解决这个问题,指定另一个端口(例如 8081)或停止其他应用程序。

第一次运行此命令时,由于与之相关的其他容器,结果可能需要一段时间。

最后,你应该看到以下输出:

图 3.8 – 命令行显示成功运行 OpenMetadata 容器

图 3.8 – 命令行显示成功运行 OpenMetadata 容器

  1. http://localhost:8585 地址:

图 3.9 – 浏览器中的 OpenMetadata 登录页面

图 3.9 – 浏览器中的 OpenMetadata 登录页面

  1. 创建用户账户和登录:要访问 UI 面板,我们需要按照以下方式创建用户账户:

图 3.10 – 在 OpenMetadata 创建账户部分创建用户账户

图 3.10 – 在 OpenMetadata 创建账户部分创建用户账户

之后,我们将被重定向到主页,并能够访问面板,如下所示:

图 3.11 – OpenMetadata 主页

图 3.11 – OpenMetadata 主页

注意

也可以通过输入用户名 admin@openmetadata.org 和密码 admin 使用默认管理员用户登录。

对于生产问题,请参阅此处启用安全指南:docs.open-metadata.org/deployment/docker/security

  1. 创建团队:在 设置 部分中,你应该看到几种可能的配置,从创建用户以访问控制台到与 SlackMS Teams 等消息传递程序的集成。

一些摄取和集成需要用户被分配到团队中。要创建团队,我们首先需要以admin身份登录。然后,转到设置 | 团队 | 创建 新团队

图 3.12 – 在 OpenMetadata 设置中创建团队

图 3.12 – 在 OpenMetadata 设置中创建团队

  1. 向我们的团队添加用户:选择您刚刚创建的团队并转到用户选项卡。然后选择您想要添加的用户。

图 3.13 – 向团队添加用户

图 3.13 – 向团队添加用户

创建团队非常方便,可以跟踪用户的活动并定义一组角色和政策。在以下情况下,添加到该团队的所有用户都将能够导航并创建他们的数据发现管道。

图 3.14 – 团队页面和默认关联的数据消费者角色

图 3.14 – 团队页面和默认关联的数据消费者角色

我们必须为本章和以下食谱中的活动设置一个数据管理员或管理员角色。数据管理员角色几乎与管理员角色具有相同的权限,因为它是一个负责定义和实施数据策略、标准和程序以管理数据使用并确保一致性的职位。

您可以在此处了解更多关于 OpenMetadata 的角色和策略github.com/open-metadata/OpenMetadata/issues/4199

它是如何工作的…

现在,让我们更深入地了解 OpenMetadata 是如何工作的。

OpenMetadata 是一个开源元数据管理工具,旨在帮助组织跨不同系统或平台管理其数据和元数据。由于它将数据信息集中在一个地方,因此使发现和理解数据变得更加容易。

它也是一个灵活且可扩展的工具,因为它使用诸如Python(主要核心代码)和 Java 等编程语言,因此可以与 Apache Kafka、Apache Hive 等工具集成。

要协调和从源中摄取元数据,OpenMetadata 使用 Airflow 代码来计数源。如果你查看其核心,所有 Airflow 代码都可以在openmetadata-ingestion中找到。对于更多希望调试此框架中与摄取过程相关的任何问题的重用用户,当元数据 Docker 容器启动并运行时,可以轻松访问http://localhost:8080/

它还使用MySQL DB来存储用户信息和关系,并使用Elasticsearch容器创建高效的索引。请参阅以下图示(docs.open-metadata.org/developers/architecture):

图 3.15 – OpenMetadata 架构图 字体来源:OpenMetadata 文档

图 3.15 – OpenMetadata 架构图 字体来源:OpenMetadata 文档

关于设计决策的更详细信息,您可以访问 主概念 页面并详细了解其背后的理念:docs.open-metadata.org/main-concepts/high-level-design

更多...

我们看到了如何轻松地在我们的机器上本地配置和安装 OpenMetadata,以及其架构的简要概述。然而,市场上还有其他优秀的选项可以用来记录数据,甚至可以使用基于 Google CloudOpenMetadataSaaS 解决方案。

OpenMetadata SaaS 沙盒

最近,OpenMetadata 实现了一个使用 Google 的 软件即服务 (SaaS) 沙盒 (sandbox.open-metadata.org/signin),这使得部署和启动发现和目录过程变得更加容易。然而,它可能会产生费用,所以请记住这一点。

参见

将 OpenMetadata 连接到我们的数据库

现在我们已经配置了我们的 数据发现 工具,让我们创建一个到本地数据库实例的示例连接。让我们尝试使用 PostgreSQL 进行简单的集成并练习另一种数据库的使用。

准备工作

首先,确保我们的应用程序通过访问 http://localhost:8585/my-data 地址运行得当。

注意

在 OpenMetadata 内部,用户必须使用我们之前看到的凭据以 admin 用户身份。

您可以在此处检查 Docker 状态:

图 3.16 – Docker 桌面应用程序中显示活动容器

图 3.16 – 在 Docker 桌面应用程序中显示活动容器

使用 PostgreSQL 进行测试。由于我们已经有了一个准备好的 Google 项目,让我们使用 PostgreSQL 引擎创建一个 SQL 实例。

由于我们在 第二章 中将创建数据库和表的查询保持不变,我们可以在 Postgres 中再次构建它。这些查询也可以在本章的 GitHub 仓库中找到。但是,请随意创建您自己的数据。

图 3.17 – SQL 实例的 Google Cloud 控制台标题

图 3.17 – SQL 实例的 Google Cloud 控制台标题

请记住让此实例允许公共访问;否则,我们的本地 OpenMetadata 实例将无法访问它。

如何操作...

在浏览器标题栏中输入 http://localhost:8585/my-data 以访问 OpenMetadata 主页:

  1. 向 OpenMetadata 添加新的数据库:转到 设置 | 服务 | 数据库,然后点击 添加新的数据库服务。将出现一些选项。点击 Postgres

图 3.18 – 添加数据库作为源的 OpenMetadata 页面

图 3.18 – 打开 OpenMetadata 页面以添加数据库作为源

点击 CookBookData

  1. 添加我们的连接设置:再次点击 下一步 后,将出现一个包含一些输入 MySQL 连接设置的页面的字段:

图 3.19 – 添加新的数据库连接信息

图 3.19 – 添加新的数据库连接信息

  1. 测试我们的连接:在所有凭据就绪后,我们需要测试到数据库的连接。

图 3.20 – 数据库连接测试成功消息

图 3.20 – 数据库连接测试成功消息

  1. 创建摄取管道:您可以将所有字段保持原样,无需担心 数据库工具DBT)。对于 调度间隔,您可以设置最适合您的选项。我将将其设置为 每日

图 3.21 – 添加数据库元数据摄取

图 3.21 – 添加数据库元数据摄取

  1. 摄取元数据:前往 摄取,我们的数据库元数据已成功摄取。

图 3.22 – 成功摄取的 Postgres 元数据

图 3.22 – 成功摄取的 Postgres 元数据

  1. 探索我们的元数据:要探索元数据,请转到 探索 |

图 3.23 – 显示已摄取的表元数据的探索页面

图 3.23 – 显示已摄取的表元数据的探索页面

您可以看到 people 表与其他内部表一起存在:

图 3.24 – 人员表元数据

图 3.24 – 人员表元数据

在这里,您可以探索应用程序的一些功能,例如定义对组织及其所有者的重要性级别,查询表,以及其他功能。

它是如何工作的…

正如我们之前看到的,OpenMetadata 使用 Python 来构建和连接到不同的来源。

连接方案 中使用 psycopg2,这是一个在 Python 中广泛使用的库。所有其他参数都传递给背后的 Python 代码以创建连接字符串。

对于每次元数据摄取,OpenMetadata 都会创建一个新的 Airflow 有向无环图DAG)来处理它,基于一个通用的 DAG。为每次元数据摄取创建一个单独的 DAG,在出现错误时使调试更加容易管理。

图 3.25 – OpenMetadata 创建的 Airflow DAG

图 3.25 – OpenMetadata 创建的 Airflow DAG

如果您打开 OpenMetadata 使用的 Airflow 实例,您可以清楚地看到它,并获得有关元数据摄取的其他信息。这是一个在发生错误时进行调试的好地方。了解我们的解决方案是如何工作的,以及遇到问题时应该查看哪些地方,有助于更有效地识别和解决问题。

进一步阅读

其他工具

如果你对市场上可用的其他数据发现工具感兴趣,以下是一些:

  • Tableau:Tableau (Tableau 官网) 更广泛地用于数据可视化和仪表板,但也提供了一些发现和编目数据的特性。你可以在他们的资源页面上了解更多关于如何使用 Tableau 进行数据发现的信息:数据驱动的组织 - 7 个数据发现关键

  • OpenDataDiscovery(免费和开源):OpenDataDiscovery 最近进入市场,可以提供一个非常好的起点。查看这里:OpenDataDiscovery 官网

  • Atlan:Atlan (Atlan 官网) 是一个完整的解决方案,同时也带来了数据治理结构;然而,成本可能很高,并且需要与他们的销售团队联系以启动 MVP(即 最小可行产品)。

  • Alation:Alation 是一款企业级工具,提供包括数据治理所有支柱在内的多种数据解决方案。了解更多信息请访问:Alation 官网

第四章:读取 CSV 和 JSON 文件并解决问题

在处理数据时,我们会遇到多种不同的数据类型,例如结构化、半结构化和非结构化数据,以及来自其他系统输出的某些具体信息。然而,两种广泛使用的文件类型是逗号分隔值CSV)和JavaScript 对象表示法JSON)。这两种文件有许多应用,由于它们的通用性,它们被广泛用于数据导入。

在本章中,你将了解这些文件格式以及如何使用 Python 和 PySpark 导入它们,应用最佳实践,并解决导入和转换相关的问题。

在本章中,我们将涵盖以下食谱:

  • 读取 CSV 文件

  • 读取 JSON 文件

  • 创建 PySpark 的 SparkSession

  • 使用 PySpark 读取 CSV 文件

  • 使用 PySpark 读取 JSON 文件

技术要求

你可以在这个 GitHub 仓库中找到本章的代码:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook

使用 Jupyter Notebook 不是强制性的,但它可以帮助你交互式地查看代码的工作方式。由于我们将执行 Python 和 PySpark 代码,它可以帮助我们更好地理解脚本。一旦安装,你可以使用以下命令执行 Jupyter:

$ jupyter notebook

建议创建一个单独的文件夹来存储本章中我们将创建的 Python 文件或笔记本;然而,请随意按照最适合你的方式组织它们。

读取 CSV 文件

CSV 文件是一种纯文本文件,其中逗号分隔每个数据点,每行代表一条新记录。它在许多领域得到广泛应用,如金融、营销和销售,用于存储数据。例如 Microsoft ExcelLibreOffice 以及在线解决方案 Google Spreadsheets 等软件都提供了对此文件的读写操作。从视觉上看,它类似于结构化表格,这大大增强了文件的可用性。

准备工作

你可以从 Kaggle 下载这个 CSV 数据集。使用此链接下载文件:www.kaggle.com/datasets/jfreyberg/spotify-chart-data。我们将使用与 第二章 中相同的 Spotify 数据集。

注意

由于 Kaggle 是一个动态平台,文件名可能会偶尔更改。下载后,我将文件命名为 spotify_data.csv

对于这个食谱,我们将仅使用 Python 和 Jupyter Notebook 来执行代码并创建一个更友好的可视化。

如何做到这一点...

按照以下步骤尝试这个食谱:

  1. 我们首先读取 CSV 文件:

    import csv
    filename = "     path/to/spotify_data.csv"
    columns = []
    rows = []
    with open (filename, 'r', encoding="utf8") as f:
        csvreader = csv.reader(f)
        fields = next(csvreader)
        for row in csvreader:
            rows.append(row)
    
  2. 然后,我们按以下方式打印列名:

    print(column for column in columns)
    
  3. 然后,我们打印前十个列:

    print('First 10 rows:')
    for row in rows[:5]:
        for col in row:
            print(col)
        print('\n')
    

这就是输出看起来像什么:

图 4.1 – spotify_data.csv 文件的前五行

图 4.1 – spotify_data.csv 文件的前五行

它是如何工作的...

看看下面的代码:

import csv
filename = "spotify_data.csv"
columns = []
rows = []

在“如何做...”部分的第一个步骤中,我们导入了内置库并指定了我们的文件名。由于它与我们的 Python 脚本位于同一目录级别,因此不需要包含完整路径。然后我们声明了两个列表:一个用于存储我们的列名(或 CSV 的第一行),另一个用于存储我们的行。

然后,我们使用了with open语句。在幕后,with语句创建了一个上下文管理器,它简化了文件处理器的打开和关闭。

(filename, 'r')表示我们想要使用filename并且只读取它('r')。之后,我们读取文件,并使用next()方法将第一行存储在我们的columns列表中,该方法从迭代器返回下一个项目。对于其余的记录(或行),我们使用了for迭代来将它们存储在一个列表中。

由于声明的变量都是列表,我们可以很容易地使用for迭代器读取它们。

更多内容...

对于这个菜谱,我们使用了 Python 内置的 CSV 库,并为处理 CSV 文件的列和行创建了一个简单的结构;然而,使用 pandas 有一个更直接的方法来做这件事。

pandas 是一个 Python 库,它被构建用来通过将其转换为称为DataFrame的结构来分析和操作数据。如果您对此概念感到陌生,请不要担心;我们将在接下来的菜谱和章节中介绍它。

让我们看看使用 pandas 读取 CSV 文件的一个例子:

  1. 我们需要安装 pandas。为此,请使用以下命令:

    $ pip install pandas
    

注意

请记住使用与您的 Python 版本关联的pip命令。对于一些读者来说,最好使用pip3。您可以使用 CLI 中的pip –version命令验证pip的版本和关联的 Python 版本。

  1. 然后,我们读取 CSV 文件:

    import pandas as pd
    spotify_df = pd.read_csv('spotify_data.csv')
    spotify_df.head()
    

您应该得到以下输出:

图 4.2 – spotify_df DataFrame 前五行

图 4.2 – spotify_df DataFrame 前五行

注意

由于其特定的渲染能力,这种友好的可视化只能在 Jupyter Notebook 中使用时看到。如果您在命令行或代码中执行.head()方法,输出将完全不同。

参见

安装 pandas 还有其他方法,您可以在以下链接中探索一种方法:pandas.pydata.org/docs/getting_started/install.xhtml

读取 JSON 文件

JavaScript 对象表示法JSON)是一种半结构化数据格式。一些文章也将 JSON 定义为非结构化数据格式,但事实是这种格式可以用于多种目的。

JSON 结构使用嵌套的对象和数组,由于其灵活性,许多应用程序和 API 都使用它来导出或共享数据。这就是为什么在本章中描述这种文件格式是至关重要的。

本菜谱将探讨如何使用内置的 Python 库读取 JSON 文件,并解释这个过程是如何工作的。

注意

JSON 是 XML 文件的替代品,XML 文件非常冗长,并且需要更多的代码来操作其数据。

准备工作

这个食谱将要使用 GitHub 事件 JSON 数据,这些数据可以在本书的 GitHub 仓库中找到,网址为 github.com/jdorfman/awesome-json-datasets,以及其他免费 JSON 数据。

要检索数据,点击 .json 文件。

如何做到这一点...

按照以下步骤完成这个食谱:

  1. 让我们先从读取文件开始:

    import json
    filename_json = 'github_events.json'
    with open (filename_json, 'r') as f:
        github_events = json.loads(f.read())
    
  2. 现在,让我们通过让这些行与我们的 JSON 文件交互来获取数据:

    id_list = [item['id'] for item in github_events]
    print(id_list)
    

输出如下所示:

['25208138097',
 '25208138110',
 (...)
 '25208138008',
 '25208137998']

它是如何工作的...

看看以下代码:

import json
filename_json = 'github_events.json'

对于 CSV 文件,Python 也有一个内置的 JSON 文件库,我们通过导入它来开始我们的脚本。然后,我们定义一个变量来引用我们的文件名。

看看以下代码:

with open (filename_json, 'r') as f:
    github_events = json.loads(f.read())

使用 open() 函数,我们打开 JSON 文件。json.loads() 语句不能打开 JSON 文件。为了做到这一点,我们使用了 f.read(),它将返回文件的内容,然后将其作为参数传递给第一个语句。

然而,这里有一个技巧。与 CSV 文件不同,我们并没有一行单独的列名。相反,每个数据记录都有自己的键来表示数据。由于 JSON 文件与 Python 字典非常相似,我们需要遍历每个记录来获取文件中的所有 id 值。

为了简化这个过程,我们在列表中创建了一个以下单行 for 循环:

id_list = [item['id'] for item in github_events]

还有更多...

尽管使用 Python 的内置 JSON 库看起来非常简单,但更好地操作数据或通过一行进行过滤,在这种情况下,可能会创建不必要的复杂性。我们可以再次使用 pandas 来简化这个过程。

让我们用 pandas 读取 JSON。看看以下代码:

import pandas as pd
github_events = pd.read_json('github_events.json')
github_events.head(3)

输出如下所示:

图 4.3 – github_events DataFrame 前五行

图 4.3 – github_events DataFrame 前五行

然后,让我们获取 id 列表:

github_events['id']

输出如下所示:

0     25208138097
1     25208138110
2     25208138076
(...)
27    25208138012
28    25208138008
29    25208137998
Name: id, dtype: int64

就像我们之前读取 CSV 文件一样,使用 pandas 读取 JSON 文件非常简单,节省了大量时间和代码来获取我们所需的信息。在第一个例子中,我们需要遍历 JSON 对象列表,而 pandas 本地将其理解为一个 DataFrame。由于每个列都表现得像一个一维数组,我们可以通过传递列名(或键)到 DataFrame 名称来快速获取值。

就像任何其他库一样,pandas 有其局限性,例如同时读取多个文件、并行处理或读取大型数据集。考虑到这一点可以防止问题发生,并帮助我们最优地使用这个库。

为什么使用 DataFrame?

DataFrame 是二维和大小可变的表格结构,在视觉上类似于结构化表格。由于它们的通用性,它们在如 pandas(如我们之前所见)等库中被广泛使用。

PySpark 与此不同。Spark 使用 DataFrame 作为分布式数据集合,并且可以 并行化 其任务,通过其他核心或节点来处理。我们将在 第七章 中更深入地介绍这一点。

参见

要了解更多关于 pandas 库支持的文件的信息,请点击以下链接:pandas.pydata.org/pandas-docs/stable/user_guide/io.xhtml

为 PySpark 创建 SparkSession

第一章 中之前介绍过的,PySpark 是一个为与 Python 一起工作而设计的 Spark 库。PySpark 使用 Python API 来编写 Spark 功能,如数据处理、处理(批量或实时)和机器学习。

然而,在用 PySpark 导入或处理数据之前,我们必须初始化一个 SparkSession。本食谱将教会我们如何使用 PySpark 创建 SparkSession,并解释其重要性。

准备工作

我们首先需要确保我们拥有正确的 PySpark 版本。我们在 第一章 中安装了 PySpark;然而,检查我们是否使用正确的版本总是好的。运行以下命令:

$ pyspark –version

你应该看到以下输出:

Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  '_/
   /___/ .__/\_,_/_/ /_/\_\   version 3.1.2
      /_/
Using Scala version 2.12.10, OpenJDK 64-Bit Server VM, 1.8.0_342
Branch HEAD
Compiled by user centos on 2021-05-24T04:27:48Z
Revision de351e30a90dd988b133b3d00fa6218bfcaba8b8
Url https://github.com/apache/spark
Type --help for more information.

接下来,我们选择一个代码编辑器,可以是任何你想要的代码编辑器。我将使用 Jupyter,因为它具有交互式界面。

如何操作...

让我们看看如何创建 SparkSession:

  1. 我们首先创建 SparkSession 如下:

    from pyspark.sql import SparkSession
    spark = SparkSession.builder \
          .master("local[1]") \
          .appName("DataIngestion") \
          .config("spark.executor.memory", '1g') \
          .config("spark.executor.cores", '3') \
          .config("spark.cores.max", '3') \
          .enableHiveSupport() \
          .getOrCreate()
    

这是收到的警告:

22/11/14 11:09:55 WARN Utils: Your hostname, DESKTOP-DVUDB98 resolves to a loopback address: 127.0.1.1; using 172.27.100.10 instead (on interface eth0)
22/11/14 11:09:55 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address
22/11/14 11:09:56 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Using Spark's default log4j profile: org/apache/spark/log4j-defaults.properties
Setting default log level to "WARN".
To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).

执行代码时,我们不会得到任何输出。另外,不要担心 WARN 消息;它们不会影响我们的工作。

  1. 然后我们获取 Spark UI。为此,在你的 Jupyter 单元中,按照以下方式输入并执行实例名称:

    spark
    

你应该看到以下输出:

图 4.4 – 实例执行输出

图 4.4 – 实例执行输出

  1. 接下来,我们在浏览器中访问 SparkUI。为此,我们点击 Spark UI 超链接。它将在你的浏览器中打开一个新标签页,显示一个包含 ExecutorsJobs 以及其他有用信息的图表:

图 4.5 – 带有事件时间线图的 Spark UI 主页

图 4.5 – 带有事件时间线图的 Spark UI 主页

由于我们还没有执行任何过程,图表是空的。不要担心;我们将在接下来的食谱中看到它的实际应用。

工作原理...

正如我们在本章开头所看到的,SparkSession 是启动我们的 Spark 作业的基本部分。它为 Spark 的 YARN(即 Yet Another Resource Manager)设置所有必需的配置,以便分配内存、核心、写入临时和最终输出的路径等。

在此基础上,让我们查看代码的每个步骤:

 spark = .builder \

要执行创建 DataFrame 等操作,我们需要使用一个实例。spark 变量将用于访问 DataFrame 和其他程序。现在,看看这个:

       .master("local[1]") \
      .appName("DataIngestion") \

.master()方法指示我们使用了哪种类型的分布式处理。由于这是我们仅用于教育目的的本地机器,我们将其定义为"local[1]",其中整数值需要大于0,因为它表示分区数。.appName()方法定义了我们的应用程序会话名称。

在声明我们的应用程序名称后,我们可以设置.config()方法:

      .config("spark.executor.memory", '1g') \
      .config("spark.executor.cores", '3') \
      .config("spark.cores.max", '3') \
      .enableHiveSupport() \

在这里,我们定义了两种类型的配置:内存分配和核心分配。spark.executor.memory告诉 YARN 每个执行器可以分配多少内存来处理数据;g代表其大小单位。

spark.executor.cores定义了 YARN 使用的执行器数量。默认情况下,使用1。接下来,spark.cores.max设置 YARN 可以扩展的核心数量。

最后,.enableHiveSupport()启用了对 Hive 查询的支持。

最后,让我们看看这个:

      .getOrCreate()

.getOrCreate()就像它的名字一样简单。如果有这个名称的会话,它将检索其配置。如果没有,它将创建一个新的。

还有更多...

查看 Spark 文档页面,你会看到大多数配置不是启动我们的作业或数据摄取所必需的。然而,我们必须记住,我们正在处理一个可扩展的框架,该框架旨在在单个机器或集群中分配资源以处理大量数据。如果没有为 SparkSession 设置限制,YARN 将分配它处理或摄取数据所需的所有资源,这可能导致服务器停机,甚至冻结整个本地机器。

在现实世界的场景中,Kubernetes 集群通常用于与其他应用程序或用户摄取或处理共享数据,这些用户或团队与你或你的团队做同样的事情。物理内存和计算资源往往有限,因此始终设置配置是一个非常好的做法,即使在小型项目中使用无服务器云解决方案也是如此。

获取所有配置

使用以下代码也可以检索此应用程序会话当前使用的配置:

spark.sparkContext.getConf().getAll()

这是我们的结果:

图 4.6 – 为配方设置的配置

图 4.6 – 为配方设置的配置

参见

使用 PySpark 读取 CSV 文件

如预期,PySpark 为读取和写入 CSV 文件提供了原生支持。它还允许数据工程师在 CSV 具有不同类型的分隔符、特殊编码等情况时传递各种设置。

在这个菜谱中,我们将介绍如何使用 PySpark 读取 CSV 文件,使用最常用的配置,并解释为什么需要它们。

准备工作

您可以从 Kaggle 下载此菜谱的 CSV 数据集:www.kaggle.com/datasets/jfreyberg/spotify-chart-data。我们将使用与 第二章 中相同的 Spotify 数据集。

就像在 为 PySpark 创建 SparkSession 菜谱中一样,请确保 PySpark 已安装并运行最新稳定版本。此外,使用 Jupyter Notebook 是可选的。

如何做到这一点…

让我们开始吧:

  1. 我们首先导入并创建一个 SparkSession:

    from pyspark.sql import
    spark = .builder \
          .master("local[1]") \
          .appName("DataIngestion_CSV") \
          .config("spark.executor.memory", '3g') \
          .config("spark.executor.cores", '1') \
          .config("spark.cores.max", '1') \
          .getOrCreate()
    
  2. 然后,我们读取 CSV 文件:

    df = spark.read.option('header',True).csv('spotify_data.csv')
    
  3. 然后,我们展示数据

    df.show()
    

这是我们的结果:

图 4.7 – 使用 Spark 查看 spotify_data.csv DataFrame

图 4.7 – 使用 Spark 查看 spotify_data.csv DataFrame

它是如何工作的…

虽然很简单,但我们需要了解 Spark 读取文件的方式,以确保它始终正确执行:

df = spark.read.option('header',True).csv('spotify_data.csv')

我们将一个变量分配给我们的代码语句。在初始化任何文件读取时,这是一个好的做法,因为它允许我们控制文件的版本,如果需要做出更改,我们可以将其分配给另一个变量。变量的名称也是故意的,因为我们正在创建我们的第一个 DataFrame。

使用 .option() 方法允许我们告诉 PySpark 我们想要传递哪种类型的配置。在这种情况下,我们将 header 设置为 True,这使得 PySpark 将 CSV 文件的第一个行设置为列名。如果我们没有传递所需的配置,DataFrame 将看起来像这样:

图 4.8 – 没有列名的 spotify_data.csv DataFrame

图 4.8 – 没有列名的 spotify_data.csv DataFrame

更多内容…

文件的内容可能不同,但在 PySpark 处理 CSV 文件时,一些设置是受欢迎的。在这里,请注意,我们将更改方法为 .options()

df = spark.read.options(header= 'True',
                       sep=',',
                       inferSchema='True') \
                .csv('spotify_data.csv')

headersepinferSchema 是读取 CSV 文件时最常用的设置。尽管 CSV 代表 sep(代表分隔符)声明。

让我们看看读取使用管道分隔字符串并传递错误分隔符的 CSV 文件时出现的错误示例:

df = spark.read.options(header= 'True',
                       sep=',',
                       inferSchema='True') \
                .csv('spotify_data_pipe.csv')

这就是输出看起来像这样:

图 4.9 – 在没有正确设置 sep 定义的情况下读取 CSV DataFrame

图 4.9 – 在没有正确设置 sep 定义的情况下读取 CSV DataFrame

如您所见,它只创建了一个包含所有信息的列。但如果我们传递 sep='|',它将正确返回:

图 4.10 – 将 sep 定义设置为管道的 CSV DataFrame

图 4.10 – 将 sep 定义设置为管道的 CSV DataFrame

其他常见的 .options() 配置

在数据导入过程中,还有其他复杂情况,如果不进行纠正,可能会导致其他 ETL 步骤出现一些问题。这里,我使用的是 listing.csv 数据集,可以在以下链接找到:data.insideairbnb.com/the-netherlands/north-holland/amsterdam/2022-03-08/visualisations/listings.csv

  1. 我们首先读取我们的通用配置:

    df_broken = spark.read.options(header= 'True', sep=',',
                           inferSchema='True') \
                    .csv('listings.csv')
    df_broken.show()
    

图 4.11 – listing.csv DataFrame

图 4.11 – listing.csv DataFrame

初看一切正常。然而,如果我尝试使用 room_type 执行一个简单的分组操作会发生什么呢?

group = df_1.groupBy("room_type").count()
group.show()

这是我们的输出:

图 4.12 – 按房间类型分组 df_broken

图 4.12 – 按房间类型分组 df_broken

目前忽略 group by,这是因为文件中有大量的转义引号和换行符。

  1. 现在,让我们设置正确的 .options()

    df_1 = spark.read.options(header=True, sep=',',
                              multiLine=True, escape='"') \
                    .csv('listings.csv')
    group = df_1.groupBy("room_type").count()
    group.show()
    

这是结果:

图 4.13 – 使用正确的 options() 设置按房间类型分组

图 4.13 – 使用正确的 options() 设置按房间类型分组

参见

你可以在官方文档中查看更多 PySpark .options()spark.apache.org/docs/latest/sql-data-sources-csv.xhtml

使用 PySpark 读取 JSON 文件

在“读取 JSON 文件”的菜谱中,我们了解到 JSON 文件广泛用于在应用程序之间传输和共享数据,我们也看到了如何使用简单的 Python 代码读取 JSON 文件。

然而,随着数据量和共享的增加,仅使用 Python 处理大量数据可能会导致性能或弹性问题。这就是为什么,对于这种场景,强烈建议使用 PySpark 读取和处理 JSON 文件。正如你所预期的,PySpark 提供了一个直接的读取解决方案。

在这个菜谱中,我们将介绍如何使用 PySpark 读取 JSON 文件,以及常见的相关问题和解决方法。

准备工作

与之前的菜谱“读取 JSON 文件”一样,我们将使用 GitHub Events JSON 文件。此外,使用 Jupyter Notebook 是可选的。

如何做...

下面是这个菜谱的步骤:

  1. 我们首先创建 SparkSession:

    spark = .builder \
          .master("local[1]") \
          .appName("DataIngestion_JSON") \
          .config("spark.executor.memory", '3g') \
          .config("spark.executor.cores", '1') \
          .config("spark.cores.max", '1') \
          .getOrCreate()
    
  2. 然后,我们读取 JSON 文件:

    df_json = spark.read.option("multiline", "true") \
                        .json('github_events.json')
    
  3. 然后,我们展示数据:

    df_json.show()
    

这是输出结果:

图 4.14 – df_json DataFrame

图 4.14 – df_json DataFrame

它是如何工作的...

与 CSV 文件类似,使用 PySpark 读取 JSON 文件非常简单,只需要一行代码。就像 pandas 一样,它忽略了文件中的括号,并创建了一个表格结构的 DataFrame,尽管我们正在处理一个半结构化数据文件。然而,真正的魔法在于 .option("multiline", "true")

如果你记得这个文件的 JSON 结构,它可能像这样:


  {
    "id": "25208138097",
    "type": "WatchEvent",
    "actor": {...},
    "repo": {...},
    "payload": {
      "action": "started"
    },
    "public": true,
    "created_at": "2022-11-13T22:52:04Z",
    "org": {...}
  },
  {
    "id": "25208138110",
    "type": "IssueCommentEvent",
    "actor": {...},
    "repo": {...},
  },...

由于它包含对象中的对象,这是一个多行 JSON。在读取文件时传递的 .option() 设置确保 PySpark 会按预期读取它,如果我们不传递此参数,将出现如下错误:

![图 4.15 – 当多行 JSON 文件中的 .options() 未正确定义时显示此错误

图 4.15 – 当多行 JSON 文件中的 .options() 未正确定义时显示此错误

PySpark 会将其视为损坏的文件。

还有更多…

读取 JSON 文件的一个非常广泛使用的配置是 dropFieldIfAllNull。当设置为 true 时,如果存在空数组,它将从模式中删除。

非结构化和半结构化数据因其弹性而具有价值。有时,应用程序可以更改其输出,某些字段可能已过时。为了避免更改摄入脚本(尤其是如果这些更改可能很频繁),dropFieldIfAllNull 会将其从 DataFrame 中删除。

参见

进一步阅读

第五章:从结构化和非结构化数据库中摄取数据

现在,我们可以从多个来源存储和检索数据,最佳存储方法取决于正在处理的信息类型。例如,大多数 API 以非结构化格式提供数据,因为这允许共享多种格式的数据(例如,音频、视频和图像),并且通过使用数据湖具有低存储成本。然而,如果我们想使定量数据可用于与多个工具一起支持分析,那么最可靠的选择可能是结构化数据。

最终,无论你是数据分析师、科学家还是工程师,了解如何管理结构化和非结构化数据都是至关重要的。

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

  • 配置 JDBC 连接

  • 使用 SQL 从 JDBC 数据库中摄取数据

  • 连接到 NoSQL 数据库(MongoDB)

  • 在 MongoDB 中创建我们的 NoSQL 表

  • 使用 PySpark 从 MongoDB 中摄取数据

技术要求

你可以从本章的 GitHub 仓库 github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook 中找到代码。

使用 Jupyter Notebook 不是强制性的,但它允许我们以交互式的方式探索代码。由于我们将执行 Python 和 PySpark 代码,Jupyter 可以帮助我们更好地理解脚本。一旦你安装了 Jupyter,你可以使用以下行来执行它:

$ jupyter notebook

建议创建一个单独的文件夹来存储本章中将要介绍的 Python 文件或笔记本;然而,请随意以最适合你的方式组织它们。

配置 JDBC 连接

与不同的系统合作带来了找到一种有效方式连接系统的挑战。适配器或驱动程序是解决这种通信问题的解决方案,它创建了一个桥梁来翻译一个系统到另一个系统的信息。

JDBC,或 Java 数据库连接,用于促进基于 Java 的系统与数据库之间的通信。本食谱涵盖了在 SparkSession 中配置 JDBC 以连接到 PostgreSQL 数据库,一如既往地使用最佳实践。

准备工作

在配置 SparkSession 之前,我们需要下载 .jars 文件(Java 存档)。你可以在 PostgreSQL 官方网站 jdbc.postgresql.org/ 上完成此操作。

选择 下载,你将被重定向到另一个页面:

图 5.1 – PostgreSQL JDBC 主页

图 5.1 – PostgreSQL JDBC 主页

然后,选择 Java 8 下载 按钮。

请将此 .jar 文件保存在安全的地方,因为你稍后需要它。我建议将其保存在你的代码所在的文件夹中。

对于 PostgreSQL 数据库,你可以使用 Docker 镜像或我们在第四章中创建的实例。如果你选择 Docker 镜像,请确保它已启动并运行。

此菜谱的最终准备步骤是导入一个要使用的数据集。我们将使用 world_population.csv 文件(您可以在本书的 GitHub 仓库中找到,位于 github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook/tree/main/Chapter_5/datasets)。使用 DBeaver 或您选择的任何其他 SQL IDE 导入它。我们将在本章后面的 使用 SQL 从 JDBC 数据库中获取数据 菜谱中使用此数据集。

要将数据导入 DBeaver,在 Postgres 数据库下创建一个您选择的表名。我选择给我的表取与 CSV 文件完全相同的名字。目前您不需要插入任何列。

然后,右键单击表并选择 导入数据,如下面的截图所示:

图 5.2 – 使用 DBeaver 在表格中导入数据

图 5.2 – 使用 DBeaver 在表格中导入数据

将打开一个新窗口,显示使用 CSV 文件或数据库表选项。选择 CSV 然后选择 下一步,如下所示:

图 5.3 – 使用 DBeaver 将 CSV 文件导入到表格中

图 5.3 – 使用 DBeaver 将 CSV 文件导入到表格中

将打开一个新窗口,您可以在其中选择文件。选择 world_population.csv 文件并选择 下一步 按钮,保留如下所示的默认设置:

图 5.4 – CSV 文件成功导入到 world_population 表中

图 5.4 – CSV 文件成功导入到 world_population 表中

如果一切顺利,您应该能够看到 world_population 表已填充了列和数据:

图 5.5 – 已用 CSV 数据填充的 world_population 表

图 5.5 – 已用 CSV 数据填充的 world_population 表

如何操作…

我将使用 Jupyter notebook 来插入和执行代码,使这个练习更加动态。以下是我们的操作方法:

  1. SparkSession,我们需要一个额外的类 SparkConf 来设置我们的新配置:

    from pyspark.conf import SparkConf
    from pyspark.sql import SparkSession
    
  2. SparkConf(),我们实例化后,我们可以使用 spark.jars 设置 .jar 路径:

    conf = SparkConf()
    conf.set('spark.jars', /path/to/your/postgresql-42.5.1.jar')
    

您将看到创建了一个 SparkConf 对象,如下面的输出所示:

图 5.6 – SparkConf 对象

图 5.6 – SparkConf 对象

  1. 创建 SparkSession 并初始化它:

    spark = SparkSession.builder \
            .config(conf=conf) \
            .master("local") \
            .appName("Postgres Connection Test") \
            .getOrCreate()
    

如果出现如下截图所示的警告信息,您可以忽略它:

图 5.7 – SparkSession 初始化警告信息

图 5.7 – SparkSession 初始化警告信息

  1. 连接到我们的数据库:最后,我们可以通过传递所需的凭据(包括主机、数据库名称、用户名和密码)来连接到 PostgreSQL 数据库,如下所示:

    df= spark.read.format("jdbc") \
        .options(url="jdbc:postgresql://localhost:5432/postgres",
                 dbtable="world_population",
                 user="root",
                 password="root",
                 driver="org.postgresql.Driver") \
        .load()
    

如果凭据正确,我们应在此处不期望有任何输出。

  1. .printSchema(),现在可以查看表列:

    df.printSchema()
    

执行代码将显示以下输出:

图 5.8 – world_population 架构的 DataFrame

图 5.8 – world_population 架构的 DataFrame

它是如何工作的…

我们可以观察到 PySpark(以及 Spark)需要额外的配置来与数据库建立连接。在这个配方中,使用 PostgreSQL 的.jars文件是使其工作所必需的。

让我们通过查看我们的代码来了解 Spark 需要什么样的配置:

conf = SparkConf()
conf.set('spark.jars', '/path/to/your/postgresql-42.5.1.jar')

我们首先实例化了SparkConf()方法,该方法负责定义在 SparkSession 中使用的配置。实例化类后,我们使用set()方法传递一个键值对参数:spark.jars。如果使用了多个.jars文件,路径可以通过逗号分隔的值参数传递。也可以定义多个conf.set()方法;它们只需要依次包含即可。

如您在以下代码中看到的,配置的集合是在 SparkSession 的第二行传递的:

spark = SparkSession.builder \
        .config(conf=conf) \
        .master("local") \
    (...)

然后,随着我们的 SparkSession 实例化,我们可以使用它来读取我们的数据库,如下面的代码所示:

df= spark.read.format("jdbc") \
    .options(url="jdbc:postgresql://localhost:5432/postgres",
             dbtable="world_population",
             user="root",
             password="root",
             driver="org.postgresql.Driver") \
    .load()

由于我们正在处理第三方应用程序,我们必须使用.format()方法设置读取输出的格式。.options()方法将携带认证值和驱动程序。

注意

随着时间的推移,您将观察到声明.options()键值对有几种不同的方式。例如,另一种常用的格式是.options("driver", "org.postgresql.Driver")。这两种方式都是正确的,取决于开发者的口味

还有更多…

这个配方涵盖了如何使用 JDBC 驱动程序,同样的逻辑也适用于开放数据库连接ODBC)。然而,确定使用 JDBC 或 ODBC 的标准需要了解我们从哪个数据源摄取数据。

Spark 中的 ODBC 连接通常与 Spark Thrift Server 相关联,这是 Apache HiveServer2 的一个 Spark SQL 扩展,允许用户在商业智能BI)工具(如 MS PowerBI 或 Tableau)中执行 SQL 查询。以下图表概述了这种关系:

图 5.9 – Spark Thrift 架构,由 Cloudera 文档提供(https://docs.cloudera.com/HDPDocuments/HDP3/HDP-3.1.5/developing-spark-applications/content/using_spark_sql.xhtml)

图 5.9 – Spark Thrift 架构,由 Cloudera 文档提供(docs.cloudera.com/HDPDocuments/HDP3/HDP-3.1.5/developing-spark-applications/content/using_spark_sql.xhtml

与 JDBC 相比,ODBC 在实际项目中使用较多,这些项目规模较小,且更具体于某些系统集成。它还需要使用另一个名为 pyodbc 的 Python 库。您可以在 kontext.tech/article/290/connect-to-sql-server-in-spark-pyspark 上了解更多信息。

调试连接错误

PySpark 错误可能非常令人困惑,并可能导致误解。这是因为错误通常与 JVM 上的问题有关,而 Py4J(一个与 JVM 动态通信的 Python 解释器)将消息与其他可能发生的 Python 错误合并。

一些错误信息在管理数据库连接时很常见,并且很容易识别。让我们看看以下代码使用时发生的错误:

df= spark.read.format("jdbc") \
    .options(url="jdbc:postgresql://localhost:5432/postgres",
             dbtable="world_population",
             user="root",
             password="root") \
    .load()

这里是产生的错误信息:

图 5.10 – Py4JJavaError 信息

图 5.10 – Py4JJavaError 信息

在第一行,我们看到 Py4JJavaError 通知我们在调用加载函数时出现错误。继续到第二行,我们可以看到消息:java.sql.SQLException: No suitable driver。它通知我们,尽管 .jars 文件已配置并设置,但 PySpark 仍然不知道使用哪个驱动程序来从 PostgreSQL 加载数据。这可以通过在 .options() 下添加 driver 参数来轻松解决。请参考以下代码:

df= spark.read.format("jdbc") \
    .options(url="jdbc:postgresql://localhost:5432/postgres",
             dbtable="world_population",
             user="root",
             password="root",
             driver="org.postgresql.Driver") \
    .load()

参见

了解更多关于 Spark Thrift 服务器的信息,请访问 jaceklaskowski.gitbooks.io/mastering-spark-sql/content/spark-sql-thrift-server.xhtml

使用 SQL 从 JDBC 数据库中摄取数据

在测试连接并配置 SparkSession 后,下一步是从 PostgreSQL 中摄取数据,过滤它,并将其保存为称为 Parquet 文件的分析格式。现在不用担心 Parquet 文件是如何工作的;我们将在接下来的章节中介绍。

这个菜谱旨在使用我们与 JDBC 数据库创建的连接,并从 world_population 表中摄取数据。

准备工作

这个菜谱将使用与 配置 JDBC 连接 菜谱相同的数据集和代码来连接到 PostgreSQL 数据库。请确保您的 Docker 容器正在运行或您的 PostgreSQL 服务器已启动。

这个菜谱从 配置 JDBC 连接 中继续。我们现在将学习如何摄取 Postgres 数据库内部的数据。

如何做到这一点...

在我们之前的代码基础上,让我们按照以下方式读取数据库中的数据:

  1. world_population 表:

    df= spark.read.format("jdbc") \
        .options(url="jdbc:postgresql://localhost:5432/postgres",
                 dbtable="world_population",
                 user="root",
                 password="root",
                 driver="org.postgresql.Driver") \
        .load()
    
  2. 创建临时视图:为了组织目的,我们使用表的准确名称,从 DataFrame 中在 Spark 默认数据库中创建一个临时视图:

    df.createOrReplaceTempView("world_population")
    

这里不应该有输出。

  1. spark 变量:

    spark.sql("select * from world_population").show(3)
    

根据您的显示器大小,输出可能看起来很混乱,如下所示:

图 5.11 – 使用 Spark SQL 的 world_population 视图

图 5.11 – 使用 Spark SQL 的 world_population 视图

  1. 过滤数据:使用 SQL 语句,让我们仅过滤 DataFrame 中的南美国家:

    south_america = spark.sql("select * from world_population where continent = 'South America' ")
    

由于我们将结果分配给一个变量,因此没有输出。

  1. .toPandas() 函数以更用户友好的方式查看:

    south_america.toPandas()
    

这就是结果呈现的样子:

图 5.12 – 使用 toPandas() 可视化的 south_america 国家

图 5.12 – 使用 toPandas() 可视化的 south_america 国家

  1. 保存我们的工作:现在,我们可以如下保存我们的过滤数据:

    south_america.write.parquet('south_america_population')
    

查看您的脚本文件夹,您应该看到一个名为 south_america_population 的文件夹。在里面,您应该看到以下输出:

图 5.13 – Parquet 文件中的 south_america 数据

图 5.13 – Parquet 文件中的 south_america 数据

这是我们的过滤和导入的 DataFrame,以分析格式呈现。

工作原理...

与 Spark 一起工作的一个显著优势是使用 SQL 语句从 DataFrame 中过滤和查询数据。这允许数据分析和 BI 团队通过处理查询来帮助数据工程师。这有助于构建分析数据并将其插入数据仓库。

然而,我们需要注意一些事项来正确执行 SQL 语句。其中之一是使用 .createOrReplaceTempView(),如代码中的这一行所示:

df.createOrReplaceTempView("world_population")

在幕后,这个临时视图将作为一个 SQL 表来工作,并从 DataFrame 中组织数据,而不需要物理文件。

然后,我们使用实例化的 SparkSession 变量来执行 SQL 语句。请注意,表名与临时视图的名称相同:

spark.sql("select * from world_population").show(3)

在执行所需的 SQL 查询后,我们继续使用 .write() 方法保存我们的文件,如下所示:

south_america.write.parquet('south_america_population')

parquet() 方法内部的参数定义了文件的路径和名称。在写入 Parquet 文件时,还有其他一些配置可用,我们将在后面的 第七章 中介绍。

更多内容...

虽然我们使用了临时视图来编写我们的 SQL 语句,但也可以使用 DataFrame 中的过滤和聚合函数。让我们通过仅过滤南美国家来使用本食谱中的示例:

df.filter(df['continent'] == 'South America').show(10)

您应该看到以下输出:

图 5.14 – 使用 DataFrame 操作过滤的南美国家

图 5.14 – 使用 DataFrame 操作过滤的南美国家

重要的是要理解并非所有 SQL 函数都可以用作 DataFrame 操作。您可以在 spark.apache.org/docs/2.2.0/sql-programming-guide.xhtml 找到更多使用 DataFrame 操作进行过滤和聚合函数的实用示例。

参见

TowardsDataScience 有关于使用 PySpark 的 SQL 函数的精彩博客文章,请参阅 towardsdatascience.com/pyspark-and-sparksql-basics-6cb4bf967e53

连接到 NoSQL 数据库(MongoDB)

MongoDB 是一个用 C++ 编写的开源、非结构化、面向文档的数据库。它在数据界因其可扩展性、灵活性和速度而闻名。

对于将处理数据(或者可能已经正在处理数据)的人来说,了解如何探索 MongoDB(或任何其他非结构化)数据库是至关重要的。MongoDB 有一些独特的特性,我们将在实践中进行探索。

在这个菜谱中,您将学习如何创建连接以通过 Studio 3T Free 访问 MongoDB 文档,这是一个 MongoDB 图形用户界面。

准备工作

为了开始使用这个强大的数据库,我们首先需要在本地机器上安装并创建一个 MongoDB 服务器。我们已经在 第一章 中配置了一个 MongoDB Docker 容器,所以让我们启动它。您可以使用 Docker Desktop 或通过以下命令在命令行中完成此操作:

my-project/mongo-local$ docker run \
--name mongodb-local \
-p 27017:27017 \
-e MONGO_INITDB_ROOT_USERNAME=<your_username> \
-e MONGO_INITDB_ROOT_PASSWORD=<your_password>\
-d mongo:latest

不要忘记使用您选择的用户名和密码更改变量。

在 Docker Desktop 上,您应该看到以下内容:

图 5.15 – 运行中的 MongoDB Docker 容器

图 5.15 – 运行中的 MongoDB Docker 容器

下一步是下载并配置 Studio 3T Free,这是开发社区用来连接到 MongoDB 服务器的免费软件。您可以从 studio3t.com/download-studio3t-free 下载此软件,并按照安装程序为您的操作系统提供的步骤进行操作。

在安装过程中,可能会出现类似于以下图所示的提示信息。如果是这样,您可以留空字段。我们不需要为本地或测试目的进行密码加密。

图 5.16 – Studio 3T Free 密码加密消息

图 5.16 – Studio 3T Free 密码加密消息

安装过程完成后,您将看到以下窗口:

图 5.17 – Studio 3T Free 连接窗口

图 5.17 – Studio 3T Free 连接窗口

现在我们已准备好将我们的 MongoDB 实例连接到 IDE。

如何做到这一点…

现在我们已经安装了 Studio 3T,让我们连接到我们的本地 MongoDB 实例:

  1. 创建连接:在您打开 Studio 3T 后,将出现一个窗口,要求您输入连接字符串或手动配置它。选择第二个选项,然后点击 下一步

您将看到类似以下内容:

图 5.18 – Studio 3T 新连接初始选项

图 5.18 – Studio 3T 新连接初始选项

  1. localhost27017

目前您的屏幕应该看起来如下:

图 5.19 – 新连接服务器信息

图 5.19 – 新连接服务器信息

现在在连接组字段下选择身份验证选项卡,然后从身份验证模式下拉菜单中选择基本

将出现三个字段——在身份验证 数据库字段中为admin

图 5.20 – 新连接身份验证信息

图 5.20 – 新连接身份验证信息

  1. 测试我们的连接:使用此配置,我们应该能够测试我们的数据库连接。在左下角,选择测试 连接按钮。

如果您提供的凭据正确,您将看到以下输出:

图 5.21 – 测试连接成功

图 5.21 – 测试连接成功

点击保存按钮,窗口将关闭。

  1. 连接到我们的数据库:在保存我们的配置后,将出现一个包含可用连接的窗口,包括我们新创建的连接:

图 5.22 – 创建连接后的连接管理器

图 5.22 – 创建连接后的连接管理器

选择连接按钮,将出现三个默认数据库:adminconfiglocal,如下截图所示:

图 5.23 – 服务器上默认数据库的本地 MongoDB 主页

图 5.23 – 服务器上默认数据库的本地 MongoDB 主页

我们现在已经完成了 MongoDB 的配置,并准备好进行本章以及其他章节的后续食谱,包括第六章第十一章第十二章

工作原理…

就像可用的数据库一样,通过 Docker 容器创建和运行 MongoDB 是直接了当的。检查以下命令:

my-project/mongo-local$ docker run \
--name mongodb-local \
-p 27017:27017 \
-e MONGO_INITDB_ROOT_USERNAME=<your_username> \
-e MONGO_INITDB_ROOT_PASSWORD=<your_password>\
-d mongo:latest

正如我们在准备就绪部分所看到的,需要传递的最关键信息是用户名和密码(使用 -e 参数),连接的端口(使用 -p 参数),以及容器镜像版本,这是最新可用的。

连接到 Studio 3T Free 的 MongoDB 容器的架构甚至更加简单。一旦连接端口可用,我们就可以轻松访问数据库。您可以在以下架构表示中看到:

图 5.24 – 使用 Docker 镜像连接到 Studio 3T Free 的 MongoDB

图 5.24 – 使用 Docker 镜像连接到 Studio 3T Free 的 MongoDB

如本食谱开头所述,MongoDB 是一个面向文档的数据库。其结构类似于 JSON 文件,除了每一行都被解释为一个文档,并且每个文档都有自己的 ObjectId,如下所示:

图 5.25 – MongoDB 文档格式

图 5.25 – MongoDB 文档格式

文档的集合被称为集合,这可以更好地理解为结构化数据库中的表表示。您可以在以下架构中看到它是如何按层次结构组织的:

图 5.26 – MongoDB 数据结构

图 5.26 – MongoDB 数据结构

正如我们在使用 Studio 3T Free 登录 MongoDB 时观察到的,有三个默认数据库:adminconfiglocal。目前,让我们忽略最后两个,因为它们与数据的操作工作有关。admin数据库是由root用户创建的主要数据库。这就是为什么我们在步骤 3中提供了这个数据库作为认证数据库选项的原因。

创建一个用户以导入数据并访问特定的数据库或集合通常是被推荐的。然而,为了演示目的,我们在这里以及本书接下来的食谱中将继续使用 root 访问权限。

更多内容…

连接字符串将根据你的 MongoDB 服务器配置而有所不同。例如,当存在副本集分片集群时,我们需要指定我们想要连接到哪些实例。

注意

分片集群是一个复杂且有趣的话题。你可以在 MongoDB 的官方文档中了解更多关于这个话题的内容,并深入探讨www.mongodb.com/docs/manual/core/sharded-cluster-components/

让我们看看一个使用基本认证模式的独立服务器字符串连接的例子:

mongodb://mongo-server-user:some_password@mongo-host01.example.com:27017/?authSource=admin

如你所见,它与其他数据库连接类似。如果我们想要连接到本地服务器,我们将主机更改为localhost

现在,对于副本集或分片集群,字符串连接看起来是这样的:

mongodb://mongo-server-user:some_password@mongo-host01.example.com:27017, mongo-host02.example.com:27017, mongo-hosta03.example.com:27017/?authSource=admin

在这个 URI 中的authSource=admin参数对于通知 MongoDB 我们想要使用数据库的管理员用户进行认证是必不可少的。没有它,将会引发错误或认证问题,如下面的输出所示:

MongoError: Authentication failed

避免这种错误的另一种方式是创建一个特定的用户来访问数据库和集合。

SRV URI 连接

MongoDB 引入了域名系统DNS)种子列表连接,它通过 DNS 中的服务记录SRV)规范构建数据,以尝试解决这种冗长的字符串。我们在本食谱的第一步中看到了使用 SRV URI 配置 MongoDB 连接的可能性。

下面是一个例子:

mongodb+srv://my-server.example.com/

它与我们在之前看到的标准连接字符串格式相似。然而,我们需要在开头指示使用 SRV,然后提供 DNS 条目。

当处理副本或节点时,这种类型的连接具有优势,因为 SRV 为集群创建了一个单一的身份。你可以在 MongoDB 官方文档中找到更详细的解释,以及如何配置它的概述,请参阅www.mongodb.com/docs/manual/reference/connection-string/#dns-seed-list-connection-format

参见

如果您感兴趣,市场上还有其他 MongoDB 图形用户界面工具可供选择:www.guru99.com/top-20-mongodb-tools.xhtml

在 MongoDB 中创建我们的 NoSQL 表

在成功连接并了解 Studio 3T 的工作原理后,我们现在将导入一些 MongoDB 集合。我们在连接到 NoSQL 数据库(MongoDB)的配方中看到了如何开始使用 MongoDB,在这个配方中,我们将导入一个 MongoDB 数据库并了解其结构。尽管 MongoDB 有特定的格式来组织内部数据,但在处理数据摄取时了解 NoSQL 数据库的行为至关重要。

我们将在本章的以下配方中通过摄取导入的集合进行练习。

准备工作

对于这个配方,我们将使用一个名为listingsAndReviews.json的 Airbnb 评论样本数据集。您可以在本书的 GitHub 存储库中找到此数据集:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook/tree/main/Chapter_5/datasets/sample_airbnb。下载后,将文件放入我们创建的mongo-local目录中,如第一章所示。

我将我的数据库放在sample_airbnb文件夹中,只是为了组织目的,如以下截图所示:

图 5.27 – 带有 listingsAndReviews.json 的命令行

图 5.27 – 带有 listingsAndReviews.json 的命令行

下载数据集后,我们需要安装pymongo,这是一个用于连接和管理 MongoDB 操作的 Python 库。要安装它,请使用以下命令:

$ pip3 install pymongo

您可以自由地为这个安装创建virtualenv

现在我们已经准备好开始向 MongoDB 中插入数据。在我们开始之前,别忘了检查您的 Docker 镜像是否正在运行。

如何操作...

执行此配方的步骤如下:

  1. 使用pymongo,我们可以轻松地与 MongoDB 数据库建立连接。请参考以下代码:

    import json
    import os
    from pymongo import MongoClient, InsertOne
    mongo_client = pymongo.MongoClient("mongodb://root:root@localhost:27017/")
    
  2. 定义我们的数据库和集合:我们将使用实例化的客户端连接创建数据库和集合实例。

对于json_collection变量,插入您放置 Airbnb 样本数据集的路径:

db_cookbook = mongo_client.db_airbnb
collection = db_cookbook.reviews
json_collection = "sample_airbnb/listingsAndReviews.json"
  1. 使用bulk_write函数,我们将把 JSON 文件中的所有文档插入到我们创建的销售集合中,并关闭连接:

    requesting_collection = []
    with open(json_collection) as f:
        for object in f:
            my_dict = json.loads(object)
            requesting.append(InsertOne(my_dict))
    result = collection.bulk_write(requesting_collection)
    mongo_client.close()
    

此操作不应有任何输出,但我们可以检查数据库以查看操作是否成功。

  1. 检查 MongoDB 数据库结果:让我们检查我们的数据库,看看数据是否已正确插入。

打开 Studio 3T Free 并刷新连接(右键单击连接名称并选择刷新所有)。您应该会看到一个名为db_airbnb的新数据库已创建,其中包含一个reviews集合,如以下截图所示:

图 5.28 – 在 MongoDB 上成功创建数据库和集合

图 5.28 – 在 MongoDB 上成功创建数据库和集合

现在集合已经创建并包含了一些数据,让我们更深入地了解代码是如何工作的。

它是如何工作的...

如您所见,我们实现的代码非常简单,只用几行代码就能在我们的数据库中创建和插入数据。然而,由于 MongoDB 的特殊性,有一些重要的点需要注意。

现在我们逐行检查代码:

mongo_client = pymongo.MongoClient("mongodb://root:root@localhost:27017/")

这行代码定义了与我们的 MongoDB 数据库的连接,并且从这个实例,我们可以创建一个新的数据库及其集合。

注意

注意 URI 连接包含用户名和密码的硬编码值。在实际应用中,甚至在开发服务器上都必须避免这样做。建议将这些值存储为环境变量或使用秘密管理器保险库。

接下来,我们定义数据库和集合的名称;您可能已经注意到我们之前没有在数据库中创建它们。在代码执行时,MongoDB 会检查数据库是否存在;如果不存在,MongoDB 将创建它。同样的规则适用于 reviews 集合。

注意集合是从 db_cookbook 实例派生的,这使得它明确地与 db_airbnb 数据库相关联:

db_cookbook = mongo_client.db_airbnb
collection = db_cookbook.reviews

按照代码,下一步是打开 JSON 文件并解析每一行。这里我们开始看到 MongoDB 的一些棘手的特殊性:

requesting_collection = []
with open(json_collection) as f:
    for object in f:
        my_dict = json.loads(object)
        requesting_collection.append(InsertOne(my_dict))

人们常常想知道为什么我们实际上需要解析 JSON 行,因为 MongoDB 接受这种格式。让我们检查下面的截图中的 listingsAndReviews.json 文件:

图 5.29 – 包含 MongoDB 文档行的 JSON 文件

图 5.29 – 包含 MongoDB 文档行的 JSON 文件

如果我们使用任何工具来验证这是有效的 JSON,它肯定会说这不是有效的格式。这是因为这个文件的每一行代表 MongoDB 集合中的一个文档。仅使用传统的 open()json.loads() 方法尝试打开该文件将产生如下错误:

json.decoder.JSONDecodeError: Extra data: line 2 column 1 (char 190)

为了使 Python 解释器能够接受,我们需要逐行打开和读取并追加到 requesting_collection 列表中。此外,InsertOne() 方法将确保每行单独插入。在插入特定行时出现的问题将更容易识别。

最后,bulk_write() 将文档列表插入 MongoDB 数据库:

result = collection.bulk_write(requesting_collection)

如果一切正常,这个操作将完成而不会返回任何输出或错误信息。

还有更多...

我们已经看到创建一个 Python 脚本来将数据插入我们的 MongoDB 服务器是多么简单。尽管如此,MongoDB 提供了数据库工具来提供相同的结果,并且可以通过命令行执行。mongoimport 命令用于将数据插入我们的数据库,如下面的代码所示:

mongoimport --host localhost --port 27017-d db_name -c collection_name --file path/to/file.json

如果你想了解更多关于其他数据库工具和命令的信息,请查看官方 MongoDB 文档,链接为www.mongodb.com/docs/database-tools/installation/installation/.

字段名称的限制

当将数据加载到 MongoDB 时,一个主要问题是对字段名称中使用的字符的限制。由于 MongoDB 服务器版本或编程语言特定性,有时字段的键名会带有$前缀,并且默认情况下,MongoDB 与它不兼容,会创建如下错误输出:

localhost:27017: $oid is not valid for storage.

在这种情况下,从 MongoDB 服务器导出了一个 JSON 文件,ObjectID的引用带有$前缀。尽管 MongoDB 的最新版本已经开始接受这些字符(见此处线程:jira.mongodb.org/browse/SERVER-41628?fbclid=IwAR1t5Ld58LwCi69SrMCcDbhPGf2EfBWe_AEurxGkEWHpZTHaEIde0_AZ-uM%5D),但在可能的情况下避免使用它们是一个好的做法。在这种情况下,我们有两个主要选项:使用脚本删除所有受限字符,或者将 JSON 文件编码成二进制 JavaScript 对象表示法BSON)文件。你可以在kb.objectrocket.com/mongo-db/how-to-use-python-to-encode-a-json-file-into-mongodb-bson-documents-545了解更多关于将文件编码成 BSON 格式的信息。

参见

你可以在www.mongodb.com/docs/manual/reference/limits/#mongodb-limit-Restrictions-on-Field-Names了解更多关于 MongoDB 对字段名称的限制。

使用 PySpark 从 MongoDB 导入数据

虽然自己创建和导入数据似乎不太实际,但这个练习可以应用于实际项目。与数据打交道的人通常参与定义数据库类型的架构过程,帮助其他工程师将应用程序中的数据插入到数据库服务器中,然后仅导入仪表板或其他分析工具的相关信息。

到目前为止,我们已经创建并评估了我们的服务器,然后在 MongoDB 实例内部创建了集合。有了所有这些准备工作,我们现在可以使用 PySpark 导入我们的数据。

准备工作

这个菜谱需要执行在 MongoDB 中创建我们的 NoSQL 表菜谱,因为需要数据插入。然而,你可以创建并插入其他文档到 MongoDB 数据库中,并在此处使用它们。如果你这样做,确保设置合适的配置以使其正常运行。

同样,正如在在 MongoDB 中创建我们的 NoSQL 表食谱中一样,检查 Docker 容器是否正在运行,因为这是我们 MongoDB 实例的主要数据源。让我们继续进行数据摄取!

图 5.30 – 运行中的 MongoDB Docker 容器

图 5.30 – 运行中的 MongoDB Docker 容器

如何做…

要尝试这个食谱,你需要执行以下步骤:

  1. SparkSession,但这次传递读取我们的 MongoDB 数据库db_airbnb的特定配置,例如 URI 和.jars

    from pyspark.sql import SparkSession
    spark = SparkSession.builder \
          .master("local[1]") \
          .appName("MongoDB Ingest") \
          .config("spark.executor.memory", '3g') \
          .config("spark.executor.cores", '1') \
          .config("spark.cores.max", '1') \
          .config("spark.mongodb.input.uri", "mongodb://root:root@127.0.0.1/db_airbnb?authSource=admin&readPreference=primaryPreferred") \
          .config("spark.mongodb.input.collection", "reviews") \
          .config("spark.jars.packages","org.mongodb.spark:mongo-spark-connector_2.12:3.0.1") \
          .getOrCreate()
    

我们应该期望这里有一个显著的结果,因为 Spark 下载了包并设置了我们传递的其余配置:

图 5.31 – 使用 MongoDB 配置初始化 SparkSession

图 5.31 – 使用 MongoDB 配置初始化 SparkSession

  1. 我们实例化的SparkSession。在这里,我们不应该期望有任何输出,因为SparkSession仅设置为在WARN级别发送日志:

    df = spark.read.format("mongo").load()
    
  2. 获取我们的 DataFrame 模式:我们可以通过在 DataFrame 上执行打印操作来查看集合的模式:

    df.printSchema()
    

你应该观察到以下输出:

图 5.32 – Reviews DataFrame 集合模式打印

图 5.32 – Reviews DataFrame 集合模式打印

如你所见,结构类似于一个嵌套对象的 JSON 文件。非结构化数据通常以这种形式呈现,并且可以包含大量信息,以便创建一个 Python 脚本来将数据插入我们的数据中。现在,让我们更深入地了解我们的代码。

它是如何工作的…

MongoDB 在SparkSession中需要一些额外的配置来执行.read函数。理解为什么我们使用配置而不是仅仅使用文档中的代码是至关重要的。让我们来探索相关的代码:

config("spark.mongodb.input.uri", "mongodb://root:root@127.0.0.1/db_airbnb?authSource=admin) \

注意使用spark.mongodb.input.uri,它告诉我们的SparkSession需要进行一个使用 MongoDB URI 的读取操作。如果我们想进行写入操作(或两者都进行),我们只需要添加spark.mongodb.output.uri配置。

接下来,我们传递包含用户和密码信息、数据库名称和认证源的 URI。由于我们使用 root 用户检索数据,因此最后一个参数设置为admin

接下来,我们定义在读取操作中使用的集合名称:

.config("spark.mongodb.input.collection", "reviews")\

注意

即使在 SparkSession 中定义这些参数看起来可能有些奇怪,并且可以设置数据库和集合,但这是一种社区在操作 MongoDB 连接时采用的良好实践。

.config("spark.jars.packages","org.mongodb.spark:mongo-spark-connector_2.12:3.0.1")

在这里还有一个新的配置是spark.jars.packages。当使用这个键与.config()方法一起时,Spark 将搜索其可用的在线包,下载它们,并将它们放置在.jar文件夹中以供使用。虽然这是一种设置.jar连接器的有利方式,但这并不是所有数据库都支持的。

一旦建立连接,读取过程与 JDBC 非常相似:我们传递数据库的 .format()(这里为 mongo),由于数据库和集合名称已经设置,我们不需要配置 .option()

df = spark.read.format("mongo").load()

当执行 .load() 时,Spark 将验证连接是否有效,如果无效则抛出错误。在下面的截图中,你可以看到一个错误信息的示例,当凭证不正确时:

图 5.33 – Py4JJavaError:MongoDB 连接认证错误

图 5.33 – 当 MongoDB 包未在配置中设置时引发的 Py4JJavaError:MongoDB 连接认证错误

尽管我们正在处理非结构化数据格式,但一旦 PySpark 将我们的集合转换为 DataFrame,所有过滤、清理和操作数据的过程基本上与 PySpark 数据相同。

更多内容...

正如我们之前看到的,PySpark 错误信息可能会让人困惑,并且一开始看起来会让人不舒服。让我们探索在没有适当配置的情况下从 MongoDB 数据库中摄取数据时出现的其他常见错误。

在这个例子中,让我们不要在 SparkSession 配置中设置 spark.jars.packages

spark = SparkSession.builder \
      (...)
      .config("spark.mongodb.input.uri", "mongodb://root:root@127.0.0.1/db_aibnb?authSource=admin") \
      .config("spark.mongodb.input.collection", "reviews")
     .getOrCreate()
df = spark.read.format("mongo").load()

如果你尝试执行前面的代码(传递剩余的内存设置),你将得到以下输出:

图 5.34 – 当 MongoDB 包未在配置中设置时引发的 java.lang.ClassNotFoundException 错误

图 5.34 – 当 MongoDB 包未在配置中设置时引发的 java.lang.ClassNotFoundException 错误

仔细查看以 java.lang.ClassNotFoundException 开头的第二行,JVM 强调了一个需要搜索第三方存储库的缺失包或类。该包包含连接到我们的 JVM 的连接器代码,并建立与数据库服务器的通信。

另一个常见的错误信息是 IllegalArgumentException。此类错误向开发者表明,方法或类中传递了一个错误的参数。通常,当与数据库连接相关时,它指的是无效的字符串连接,如下面的截图所示:

图 5.35 – 当 URI 无效时引发的 IllegalArgumentException 错误

图 5.35 – 当 URI 无效时引发的 IllegalArgumentException 错误

虽然看起来不太清楚,但 URI 中存在一个拼写错误,其中 db_aibnb/? 包含一个额外的正斜杠。移除它并再次运行 SparkSession 将会使此错误消失。

注意

建议在重新定义 SparkSession 配置时关闭并重新启动内核进程,因为 SparkSession 倾向于向进程追加而不是替换它们。

参见

进一步阅读

第六章:使用 PySpark 与定义和非定义模式

通常,模式是用来创建或应用结构到数据的形式。作为一个处理或将要处理大量数据的人,理解如何操作 DataFrame 并在需要为涉及的信息提供更多上下文时应用结构是至关重要的。

然而,如前几章所见,数据可能来自不同的来源或以未定义的结构存在,应用模式可能具有挑战性。在这里,我们将看到如何使用 PySpark 创建模式和标准格式,无论是结构化数据还是非结构化数据。

在本章中,我们将涵盖以下食谱:

  • 应用模式到数据摄入

  • 使用已定义模式导入结构化数据

  • 使用未定义模式的非结构化数据导入

  • 使用已定义和非定义模式导入结构化数据

  • 插入格式化的 SparkSession 日志以方便你的工作

技术要求

你也可以在这个 GitHub 仓库中找到本章的代码:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook

使用 Jupyter Notebook 不是强制性的,但它可以帮助你交互式地查看代码的工作方式。由于我们将执行 Python 和 PySpark 代码,这可以帮助我们更好地理解脚本。一旦安装完成,你可以使用以下命令来运行 Jupyter:

$ jupyter Notebook

建议创建一个单独的文件夹来存储本章中将要涵盖的 Python 文件或 Notebooks;然而,你可以自由地以最适合你的方式组织文件。

在本章中,所有食谱都需要初始化一个 SparkSession 实例,并且你可以为它们使用相同的会话。你可以使用以下代码来创建你的会话:

from pyspark.sql import SparkSession
spark = SparkSession.builder \
      .master("local[1]") \
      .appName("chapter6_schemas") \
      .config("spark.executor.memory", '3g') \
      .config("spark.executor.cores", '1') \
      .config("spark.cores.max", '1') \
      .getOrCreate()

注意

在某些情况下,预期输出会有 WARN 消息,特别是如果你在 Windows 上使用 WSL,所以你不需要担心。

应用模式到数据摄入

在处理数据时,应用模式是一种常见的做法,PySpark 本地支持将这些模式应用到 DataFrame 中。为了定义并应用模式到我们的 DataFrame,我们需要了解一些 Spark 的概念。

本食谱介绍了使用 PySpark 处理模式的基本概念及其最佳实践,以便我们可以在以后将它们应用到结构化和非结构化数据中。

准备工作

确保你的机器上安装并运行了 PySpark,你可以通过在命令行中运行以下代码来检查此要求:

$ pyspark --version

你应该看到以下输出:

图 6.1 – PySpark 版本控制台输出

图 6.1 – PySpark 版本控制台输出

如果你没有在本地机器上安装 PySpark,请参考 第一章 中的 安装 PySpark 食谱。

我将使用 Jupyter Notebook 来执行代码,使其更具交互性。你可以使用此链接并遵循屏幕上的说明来安装它:jupyter.org/install

如果你已经安装了它,请使用以下命令行代码检查版本:

$ jupyter --version

以下截图显示了预期的输出:

图 6.2 – Jupyter 包版本

图 6.2 – Jupyter 包版本

如你所见,本书编写时 Notebook 版本是 6.4.4。请确保始终使用最新版本。

如何做到这一点…

这里是执行食谱的步骤:

  1. 创建模拟数据:在将模式应用到我们的 DataFrame 之前,我们需要创建一个包含模拟数据的简单集合,这些数据以以下格式表示人的信息——ID、姓名、姓氏、年龄和性别:

    my_data = [("3456","Cristian","Rayner",30,"M"),
                ("3567","Guto","Flower",35,"M"),
                ("9867","Yasmin","Novak",23,"F"),
                ("3342","Tayla","Mejia",45,"F"),
                ("8890","Barbara","Kumar",20,"F")
                ]
    
  2. 导入和构建模式:下一步是导入类型并创建我们模式的结构:

    from pyspark.sql.types import StructType, StructField, StringType, IntegerType
    schema = StructType([ \
        StructField("id",StringType(),True), \
        StructField("name",StringType(),True), \
        StructField("lastname",StringType(),True), \
        StructField("age", IntegerType(), True), \
        StructField("gender", StringType(), True), \
      ])
    
  3. 创建 DataFrame:然后,我们创建 DataFrame,应用我们创建的模式:

    df = spark.createDataFrame(data=my_data,schema=schema)
    

当使用 .printSchema() 方法打印我们的 DataFrame 模式时,这是预期的输出:

图 6.3 – DataFrame 模式

图 6.3 – DataFrame 模式

它是如何工作的…

在理解 步骤 2 中的方法之前,让我们稍微回顾一下 DataFrame 的概念。

DataFrame 类似于一个存储和组织在二维数组中的数据表,它类似于 MySQL 或 Postgres 等关系型数据库中的表。每一行对应一个记录,pandas 和 PySpark 等库默认情况下会为每一行(或索引)分配一个内部记录号。

图 6.4 – GeeksforGeeks DataFrame 解释

图 6.4 – GeeksforGeeks DataFrame 解释

注意

谈到 Pandas 库,通常将 Pandas DataFrame 中的列称为序列,并期望它像 Python 列表一样表现。这使得分析数据和处理可视化变得更加容易。

使用 DataFrame 的目的是利用 Spark 在底层提供的多个数据处理的优化,这些优化与并行处理直接相关。

回到 schema 变量中的模式定义,让我们看看我们创建的代码:

schema = StructType([ \
    StructField("id",StringType(),True), \
    StructField("name",StringType(),True), \
    StructField("lastname",StringType(),True), \
    StructField("age", IntegerType(), True), \
    StructField("gender", StringType(), True), \
  ])

我们声明的第一个对象是 StructType 类。这个类将创建一个集合或我们行对象的实例。接下来,我们声明一个 StructField 实例,它代表我们的列,包括其名称、数据类型、是否可空以及适用时的元数据。StructField 必须按照 DataFrame 中列的顺序排列;否则,由于数据类型不兼容(例如,列具有字符串值,而我们将其设置为整数)或存在空值,可能会产生错误。定义 StructField 是标准化 DataFrame 名称以及因此分析数据的绝佳机会。

最后,StringTypeIntegerType是将数据类型转换为相应列的方法。它们在菜谱开始时导入,并来自 PySpark 内部的 SQL 类型。在我们的模拟数据示例中,我们将idage列定义为IntegerType,因为我们预计其中不会有其他类型的数据。然而,在许多情况下,id列被称为字符串类型,通常当数据来自不同的系统时。

在这里,我们使用了StringTypeIntegerType,但还可以使用许多其他类型来创建上下文并标准化我们的数据。请参考以下图表:

图 6.5 – SparkbyExample 中 Spark 的数据类型表

图 6.5 – SparkbyExample 中 Spark 的数据类型表

您可以在 Spark 官方文档中了解如何应用Spark 数据类型spark.apache.org/docs/latest/sql-ref-datatypes.xhtml

还有更多…

在处理术语时,需要对使用数据集或 DataFrame 有一个共同的理解,尤其是如果您是数据世界的新手。

数据集是一组包含行和列(例如,关系型数据)或文档和文件(例如,非关系型数据)的数据集合。它来自一个源,并以不同的文件格式提供。

另一方面,DataFrame 是从数据集派生出来的,即使不是主要格式,也会以表格形式呈现数据。DataFrame 可以根据创建时设置的配置将 MongoDB 文档集合转换为表格组织。

参见

更多关于SparkbyExample网站的示例可以在以下链接找到:sparkbyexamples.com/pyspark/pyspark-sql-types-datatype-with-examples/

使用定义良好的模式导入结构化数据

如前一章所述,在“从结构化和非结构化数据库中摄取数据”中,结构化数据具有标准的行和列格式,通常存储在数据库中。

由于其格式,DataFrame 模式的适用性通常较为简单,并且具有几个优点,例如确保摄取的信息与数据源相同或遵循规则。

在这个菜谱中,我们将从结构化文件(如 CSV 文件)中摄取数据,并应用 DataFrame 模式以更好地理解它在现实世界场景中的应用。

准备工作

此练习需要 GitHub 存储库中这本书的listings.csv文件。同时,请确保您的SparkSession已初始化。

此菜谱中的所有代码都可以在 Jupyter Notebook 单元格或 PySpark shell 中执行。

如何做…

执行此菜谱的步骤如下:

  1. StringTypeIntegerType,我们将在导入中包含两种更多数据类型,即FloatTypeDateType,如下所示:

    from pyspark.sql.types import StructType, StructField, StringType, IntegerType, FloatType, DateType, DoubleType
    
  2. StructField分配给相应的数据类型:

    schema = StructType([ \
        StructField("id",IntegerType(),True), \
        StructField("name",StringType(),True), \
        StructField("host_id",IntegerType(),True), \
        StructField("host_name",StringType(),True), \
        StructField("neighbourhood_group",StringType(),True), \
        StructField("neighbourhood",StringType(),True), \
        StructField("latitude",DoubleType(),True), \
        StructField("longitude",DoubleType(),True), \
        StructField("room_type",StringType(),True), \
        StructField("price",FloatType(),True), \
        StructField("minimum_nights",IntegerType(),True), \
        StructField("number_of_reviews",IntegerType(),True), \
        StructField("last_review",DateType(),True), \
        StructField("reviews_per_month",FloatType(),True), \
          StructField("calculated_host_listings_count",IntegerType(),True), \
        StructField("availability_365",IntegerType(),True), \
        StructField("number_of_reviews_ltm",IntegerType(),True), \
        StructField("license",StringType(),True)
      ])
    
  3. .options()配置和.schema()方法与步骤 2中看到的schema变量一起添加到listings.csv文件中。

    df = spark.read.options(header=True, sep=',',
                              multiLine=True, escape='"')\
                    .schema(schema) \
                    .csv('listings.csv')
    

如果一切设置正确,你应该不会看到此执行的任何输出。

  1. 检查读取的 DataFrame:我们可以通过执行以下代码来检查我们 DataFrame 的模式:

    df.printSchema()
    

你应该看到以下输出:

图 6.6 – listings.csv DataFrame 模式

图 6.6 – listings.csv DataFrame 模式

它是如何工作的……

在这个练习中,我们做了一些与上一个食谱应用模式到数据摄取不同的添加。

如同往常,我们首先导入使脚本工作的必需方法。我们添加了三种更多数据类型:float、double 和 date。选择是基于 CSV 文件的内容。让我们看看我们文件的第一个行,如下面的截图所示:

图 6.7 – 从 Microsoft Excel 查看 listings.csv

图 6.7 – 从 Microsoft Excel 查看 listings.csv

我们可以观察到不同类型的数值字段;一些需要更多的小数位数,而last_review是日期格式。因此,我们添加了额外的库导入,如下面的代码片段所示:

from pyspark.sql.types import StructType, StructField, StringType, IntegerType, FloatType, DoubleType, DateType

步骤 2与之前我们所做的是相似的,其中我们分配了列名及其相应的数据类型。在步骤 3中,我们使用SparkSession类的schema()方法进行了模式分配。如果模式包含与文件相同的列数,我们应该在这里看不到任何输出;否则,将出现此消息:

图 6.8 – 当模式不匹配时输出警告信息

图 6.8 – 当模式不匹配时输出警告信息

即使是WARN日志消息,内容也很重要。仔细观察,它说模式与文件中的列数不匹配。这可能在 ETL 管道的后期加载数据到数据仓库或任何其他分析数据库时成为问题。

还有更多……

如果你回到第四章,你会注意到我们 CSV 读取中的一个inferSchema参数被插入到options()方法中。参考以下代码:

df_2 = spark.read.options(header=True, sep=',',
                          multiLine=True, escape='"',
                         inferSchema=True) \
                .csv('listings.csv')

此参数告诉 Spark 根据行的特征推断数据类型。例如,如果一行没有引号且是数字,那么这很可能是一个整数。然而,如果它包含引号,Spark 可以将其解释为字符串,任何数值操作都会失败。

在食谱中,如果我们使用inferSchema,我们将看到与我们所定义的模式非常相似的printSchema输出,除了某些被解释为DoubleType的字段,而我们将其声明为FloatTypeDateType。这如下面的截图所示:

图 6.9 – 使用带有 inferSchema 模式的模式比较输出

图 6.9 – 使用带有 inferSchema 模式的模式比较输出

即使这看起来像是一个微不足道的细节,但在处理流数据或大数据集以及有限的计算资源时,它可能会产生影响。浮点类型具有较小的范围,并带来较高的处理能力。双精度数据类型提供了更多的精度,并用于减少数学误差或避免十进制数据类型四舍五入的值。

参见

Hackr IO网站上了解更多关于浮点型和双精度类型的信息:hackr.io/blog/float-vs-double

在没有模式的情况下导入非结构化数据

如前所述,非结构化数据或NoSQL是一组不遵循格式(如关系型或表格数据)的信息。它可以表示为图像、视频、元数据、转录等。数据摄取过程通常涉及 JSON 文件或文档集合,正如我们之前在从MongoDB摄取数据时所见。

在这个菜谱中,我们将读取一个 JSON 文件并将其转换为没有模式的 DataFrame。尽管非结构化数据应该具有更灵活的设计,但我们将看到在没有模式或结构的情况下 DataFrame 的一些影响。

准备工作...

在这里,我们将使用holiday_brazil.json文件来创建 DataFrame。你可以在 GitHub 仓库中找到它:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook

我们将使用SparkSession来读取 JSON 文件并创建一个 DataFrame,以确保会话处于运行状态。

所有代码都可以在 Jupyter Notebook 或 PySpark shell 中执行。

如何操作...

让我们现在读取我们的holiday_brazil.json文件,观察 Spark 是如何处理它的:

  1. multiline作为options()方法的参数。我们还将让 PySpark 推断其中的数据类型:

    df_json = spark.read.option("multiline","true") \
                        .json('holiday_brazil.json')
    

如果一切顺利,你应该看不到任何输出。

  1. 使用printSchema()方法,我们可以看到 PySpark 如何解释每个键的数据类型:

    df_json.printSchema()
    

以下截图是预期的输出:

图 6.10 – holiday_brazil.json DataFrame 模式

图 6.10 – holiday_brazil.json DataFrame 模式

  1. 使用toPandas()函数来更好地可视化我们的 DataFrame:

    df_json.toPandas()
    

你应该看到以下输出:

图 6.11 – 使用带有 inferSchema 模式的模式比较输出

图 6.11 – 从 DataFrame 中 toPandas()视图的输出

它是如何工作的...

让我们看一下步骤 2的输出:

图 6.12 – Holiday_brasil.json DataFrame 模式

图 6.12 – Holiday_brasil.json DataFrame 模式

如我们所观察到的,Spark 只带来了四个列,JSON 文件中的前四个键,并且通过将它们保留在主要的四个键中忽略了其他嵌套键。这是因为 Spark 需要通过展平嵌套字段中的值来更好地处理参数,尽管我们在options()配置中传递了multiline作为参数。

另一个重要点是,对weekday数组内numeric键推断的数据类型是字符串值,应该是整数。这是因为这些值有引号,正如你在以下图中可以看到的:

图 6.13 – 星期对象

图 6.13 – 星期对象

将此应用于一个需要定性评估的现实世界场景,这些未格式化和无模式的 DataFrame 在上传到数据仓库时可能会引起问题。如果一个字段突然更改了名称或在源中不可用,它可能导致数据不一致。有一些方法可以解决这个问题,我们将在下一个食谱使用定义良好的模式和格式导入非结构化数据中进一步介绍。

然而,还有许多其他场景中不需要定义模式或非结构化数据不需要标准化。一个很好的例子是应用程序日志或元数据,其中数据通常与应用程序或系统的可用性相关联,以发送信息。在这种情况下,像ElasticSearchDynamoDB和许多其他提供查询支持的存储选项都是好的选择。换句话说,这里的大部分问题将更倾向于生成定量输出。

使用定义良好的模式和格式导入非结构化数据

在上一个食谱中,无模式导入非结构化数据,我们读取了一个没有任何模式或格式化应用的 JSON 文件。这导致了一个奇怪的结果,可能会引起混淆,并在数据管道的后续阶段需要额外的工作。虽然这个例子具体涉及到一个 JSON 文件,但它也适用于所有其他需要转换为分析数据的 NoSQL 或非结构化数据。

目标是继续上一个食谱,并应用一个模式和标准到我们的数据中,使其在后续的ETL阶段更易于阅读和处理。

准备工作

这个食谱与无模式导入无 模式食谱有完全相同的要求。

如何做到这一点…

我们将执行以下步骤来完成这个食谱:

  1. 导入数据类型:像往常一样,让我们从 PySpark 库中导入我们的数据类型:

    from pyspark.sql.types import StructType, ArrayType, StructField, StringType, IntegerType, MapType
    
  2. 结构化 JSON 模式:接下来,我们根据 JSON 的结构设置模式:

    schema = StructType([ \
            StructField('status', StringType(), True),
            StructField('holidays', ArrayType(
                StructType([
                    StructField('name', StringType(), True),
                    StructField('date', DateType(), True),
                    StructField('observed', StringType(), True),
                    StructField('public', StringType(), True),
                    StructField('country', StringType(), True),
                    StructField('uuid', StringType(), True),
                    StructField('weekday', MapType(StringType(), MapType(StringType(),StringType(),True),True))
                ])
            ))
        ])
    
  3. 使用上一个步骤中创建的模式应用schema()方法:

    df_json = spark.read.option("multiline","true") \
                        .schema(schema) \
                        .json('holiday_brazil.json')
    
  4. 使用explode()方法,让我们扩展holidays列内的字段:

    from pyspark.sql.functions import explode
    exploded_json = df_json.select('status', explode("holidays").alias("holidaysExplode"))\
            .select("status", "holidaysExplode.*")
    
  5. 扩展更多列:这是一个可选步骤,但如果需要,我们可以继续增长并展平包含嵌套内容的其他列:

    exploded_json2 = exploded_json.select("*", explode('weekday').alias('weekday_type', 'weekday_objects'))
    
  6. 使用 toPandas() 函数,我们可以更好地查看我们的 DataFrame 现在的样子:

    exploded_json2.toPandas()
    

你应该看到以下输出:

图 6.14 – 扩展列的 DataFrame

图 6.14 – 扩展列的 DataFrame

最终的 DataFrame 可以保存为 Parquet 文件,这将使数据管道中的下一步更容易处理。

它是如何工作的…

如你所见,这个 JSON 文件由于嵌套对象的数量而具有一定的复杂性。当处理这样的文件时,我们可以使用多种方法。在编码时,有许多达到解决方案的方法。让我们了解这个食谱是如何工作的。

步骤 1 中,导入了两种新的数据类型—ArrayTypeMapType。尽管在使用每种类型时有些困惑,但当我们查看 JSON 结构时,它相对简单易懂。

我们使用 ArrayTypeholiday 键,因为它的结构看起来像这样:

holiday : [{},{}...]

它是一个对象数组(如果我们使用 Python 则为列表),每个对象代表巴西的一个节日。当 ArrayType 包含其他对象时,我们需要重新使用 StructType 来告知 Spark 内部对象的架构。这就是为什么在 步骤 2 中,我们的模式开始看起来是这样的:

StructField('holidays', ArrayType(
            StructType([
  ...
])
))

以下新的数据类型是 MapType。这种类型指的是包含其他对象的对象。如果我们只使用 Python,它可以被称为字典。PySpark 从 Spark 的 超类 中扩展了这种数据类型,你可以在这里了解更多信息:spark.apache.org/docs/2.0.1/api/java/index.xhtml?org/apache/spark/sql/types/MapType.xhtml

MapType 的语法需要键值类型、值类型以及它是否接受空值。我们在 weekday 字段中使用了这种类型,正如你所见:

StructField('weekday', MapType(
StringType(),MapType(
StringType(),StringType(),True)
,True))

这可能是我们迄今为止见过的最复杂的结构,这归因于 JSON 通过它的结构:

weekday : {
 day : {{}, {}},
 observed : {{}, {}}
}

我们模式中的结构为 weekday 以及随后的键 dayobserved 创建了 MapType

一旦我们的模式被定义并应用到我们的 DataFrame 上,我们可以使用 df_json.show() 来预览它。如果模式与 JSON 结构不匹配,你应该看到以下输出:

图 6.15 – df_json 打印

图 6.15 – df_json 打印

这表明 Spark 无法正确创建 DataFrame。在这种情况下,最好的行动是逐步应用模式,直到问题得到解决。

为了展平 holidays 列内的字段,我们需要使用 PySpark 中的一个函数 explode,正如你所见:

from pyspark.sql.functions import explode

要应用这些更改,我们需要将结果分配给一个变量,这将创建一个新的 DataFrame:

exploded_json = df_json.select('status', explode("holidays").alias("holidaysExplode"))\
        .select("holidaysExplode.*")

注意

虽然看起来有些多余,但防止原始 DataFrame 被修改是一个好习惯。如果出了问题,我们不需要重新读取文件,因为我们有原始 DataFrame 的完整状态。

使用 select() 方法,我们选择将保持不变的列并展开所需的列。我们这样做是因为 Spark 至少需要一个展开的列作为参考。正如你所观察到的,关于 API 数据摄取状态的其它列已从该模式中移除。

select() 方法的第二个参数是 explode() 方法,其中我们传递 holiday 列并赋予一个别名。第二个链式调用 select() 将仅检索 holidaysExplode 列。步骤 5 遵循相同的流程,但针对的是 weekdays 列。

更多内容…

正如我们之前讨论的,有许多方法可以展平 JSON 并应用模式。你可以在托马斯的 Medium 博客 上看到一个例子:medium.com/@thomaspt748/how-to-flatten-json-files-dynamically-using-apache-pyspark-c6b1b5fd4777.

他使用 Python 函数来解耦嵌套字段,然后应用 PySpark 代码。

数据科学之路 也提供了一个使用 Python 的 lambda 函数的解决方案。你可以在这里看到它:towardsdatascience.com/flattening-json-records-using-pyspark-b83137669def.

展平 JSON 文件可以是一个极好的方法,但需要更多关于复杂 Python 函数的知识。同样,没有正确的方法来做这件事;重要的是要提供一个你和你团队都能支持的解决方案。

参见

你可以在这里了解更多关于 PySpark 数据结构的信息:sparkbyexamples.com/pyspark/pyspark-structtype-and-structfield/.

插入格式化的 SparkSession 日志以方便你的工作

一个常被低估的最佳实践是如何创建有价值的日志。记录信息和小型代码文件的应用程序可以节省大量的调试时间。这在摄取或处理数据时也是如此。

这个配方探讨了在 PySpark 脚本中记录事件的最佳实践。这里提供的示例将给出一个更通用的概述,它可以应用于任何其他代码片段,甚至会在本书的后续部分使用。

准备工作

我们将使用 listings.csv 文件来执行 Spark 的 read 方法。你可以在本书的 GitHub 仓库中找到这个数据集。确保你的 SparkSession 正在运行。

如何做到这一点…

执行此配方的步骤如下:

  1. sparkContext,我们将分配日志级别:

    spark.sparkContext.setLogLevel("INFO")
    
  2. getLogger() 方法:

    Logger= spark._jvm.org.apache.log4j.Logger
    syslogger = Logger.getLogger(__name__)
    
  3. getLogger() 实例化后,我们现在可以调用表示日志级别的内部方法,例如 ERRORINFO

    syslogger.error("Error message sample")
    syslogger.info("Info message sample")
    

这将给我们以下输出:

图 6.16 – Spark 日志消息输出示例

图 6.16 – Spark 日志消息输出示例

  1. 创建 DataFrame:现在,让我们使用本章中已经看到的一个文件来创建一个 DataFrame,并观察输出:

    try:
        df = spark.read.options(header=True, sep=',',
                                  multiLine=True, escape='"',
                                 inferSchema=True) \
                        .csv('listings.csv')
    except Exception as e:
        syslogger.error(f"Error message: {e}")
    

你应该看到以下输出:

图 6.17 – PySpark 日志输出

图 6.17 – PySpark 日志输出

如果你的控制台显示了更多行,不要担心;图片被裁剪以使其更易于阅读。

它是如何工作的…

让我们探索一下这个配方中做了些什么。Spark 有一个名为log4jrootLogger的本地库。log4j是 Spark 使用的默认日志机制,用于抛出所有日志消息,如TRACEDEBUGINFOWARNERRORFATAL。消息的严重性随着每个级别的增加而增加。默认情况下,Spark 的日志级别为WARN,因此当我们将其设置为INFO时,开始出现新的消息,例如内存存储信息。

当我们执行spark-submit命令来运行 PySpark 时,我们将观察到更多的INFO日志(我们将在第十一章中稍后介绍)。

根据我们的脚本大小和所属的环境,只将其设置为显示ERROR消息是一个好习惯。我们可以使用以下代码更改日志级别:

spark.sparkContext.setLogLevel("ERROR")

如你所见,我们使用sparkContext来设置日志级别。sparkContext是 Spark 应用程序中的一个关键组件。它管理集群资源,协调任务执行,并为与分布式数据集交互以及以分布式和并行方式执行计算提供接口。定义我们代码的日志级别将防止ERROR级别以下的级别出现在控制台上,使其更干净:

Logger= spark._jvm.org.apache.log4j.Logger
syslogger = Logger.getLogger(__name__)

接下来,我们从实例化的会话中检索了log4j模块及其Logger类。这个类有一个方法可以显示像 Spark 那样格式化的日志。__name__参数将从 Python 内部检索当前模块的名称;在我们的例子中,它是main

通过这种方式,我们可以创建如下自定义日志:

syslogger.error("Error message sample")
syslogger.info("Info message sample")

当然,你也可以将 PySpark 日志与 Python 输出结合起来,使用try...except异常处理闭包来获得完整的解决方案。让我们通过将错误的文件名传递给我们的读取函数来模拟一个错误,如下所示:

try:
    df = spark.read.options(header=True, sep=',',
                              multiLine=True, escape='"',
                             inferSchema=True) \
                    .csv('listings.cs') # Error here
except Exception as e:
    syslogger.error(f"Error message: {e}")

你应该看到以下输出消息:

图 6.18 – 由 log4j 格式化的错误消息

图 6.18 – 由 log4j 格式化的错误消息

更多内容…

log4j中还有许多可用的自定义选项,但这可能需要一点更多的工作。例如,你可以将一些WARN消息更改为ERROR消息,以防止脚本继续被处理。本章的一个实际例子是使用预定义的架构导入结构化数据时,架构中的列数与文件不匹配。

你可以在 Ivan Trusov 的博客页面上了解更多相关信息:polarpersonal.medium.com/writing-pyspark-logs-in-apache-spark-and-databricks-8590c28d1d51.

另请参阅

进一步阅读

第七章:摄入分析数据

分析数据是一组数据,它为公司、大学或任何其他机构中的多个领域(如财务、营销和销售)提供服务,以促进决策,尤其是在战略问题上。当将分析数据转换为数据管道或常规的 提取、转换和加载ETL)过程时,它对应于最终步骤,其中数据已经摄取、清理、聚合,并根据业务规则进行了其他转换。

在许多场景中,数据工程师必须从包含分析数据的数据仓库或其他存储中检索数据。本章的目标是学习如何读取分析数据和其标准格式,并涵盖与反向 ETL 概念相关的实际用例。

在本章中,我们将学习以下主题:

  • 摄入 Parquet 文件

  • 摄入 Avro 文件

  • 将模式应用于分析数据

  • 过滤数据和处理常见问题

  • 摄入分区数据

  • 应用反向 ETL

  • 选择用于反向 ETL 的分析数据

技术要求

如同 第六章,在本章中,一些菜谱也需要初始化 SparkSession,并且您可以使用相同的会话来执行所有这些。您可以使用以下代码来创建您的会话:

from pyspark.sql import SparkSession
spark = SparkSession.builder \
      .master("local[1]") \
      .appName("chapter7_analytical_data") \
      .config("spark.executor.memory", '3g') \
      .config("spark.executor.cores", '2') \
      .config("spark.cores.max", '2') \
      .getOrCreate()

注意

在某些情况下,您可能会看到 WARN 消息作为输出,尤其是如果您在 Windows 上使用 WSL,所以如果您收到一个,您不需要担心。

您也可以在此章节的 GitHub 仓库中找到代码:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook.

摄入 Parquet 文件

Apache Parquet 是一种开源的列式存储格式,旨在支持快速处理。它在 Hadoop 生态系统 中的任何项目中都可用,并且可以用不同的编程语言读取。

由于其压缩和快速处理能力,这是在需要大量分析数据时最常用的格式之一。本菜谱的目标是了解如何在现实场景中使用 PySpark 读取一组 Parquet 文件。

准备工作

对于这个菜谱,我们需要初始化 SparkSession。您可以使用本章开头提供的代码来完成此操作。

本菜谱的数据集将是 纽约黄色出租车行程记录。您可以通过访问 纽约市政府网站 并选择 2022 | 一月 | 黄色出租车行程记录 或使用此链接下载:

d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2022-01.parquet

您可以自由地使用 Jupyter 笔记本或 PySpark 壳会话执行代码。

如何操作…

执行此菜谱的步骤如下:

  1. 设置 Parquet 文件路径:为了根据 Parquet 文件创建 DataFrame,我们有两种选择:传递文件名或 Parquet 文件的路径。在下面的代码块中,你可以看到一个只传递文件名的示例:

    df = spark.read.parquet('chapter7_parquet_files/yellow_tripdata_2022-01.parquet')
    

对于第二种情况,我们移除了 Parquet 文件名,Spark 处理其余部分。你可以在下面的代码块中看到代码的样式:

df = spark.read.parquet('chapter7_parquet_files/')
  1. .printSchema() 函数用于查看 DataFrame 是否成功创建:

    df.printSchema()
    

你应该看到以下输出:

图 7.1 – 黄色出租车行程 DataFrame 模式

图 7.1 – 黄色出租车行程 DataFrame 模式

  1. 使用 pandas 可视化:这是一个可选步骤,因为它要求你的本地机器有足够的处理能力,并且可能会在尝试处理时冻结内核。

为了更好地查看 DataFrame,让我们使用 .toPandas(),如下所示:

df.toPandas().head(10)

你应该看到以下输出:

图 7.2 – 黄色出租车行程 DataFrame 与 pandas 可视化

图 7.2 – 黄色出租车行程 DataFrame 与 pandas 可视化

它是如何工作的…

如前述代码所示,读取 Parquet 文件很简单。像许多 Hadoop 工具一样,PySpark 本地支持读取和写入 Parquet。

与 JSON 和 CSV 文件类似,我们使用一个从 .read 方法派生的函数来通知 PySpark 将读取 Parquet 文件,如下所示:

spark.read.parquet()

我们还看到了两种读取方式,只传递 Parquet 文件所在的文件夹或传递带有 Parquet 文件名的路径。最佳实践是使用第一种情况,因为通常有多个 Parquet 文件,只读取一个可能会导致多个错误。这是因为每个 Parquet 文件都对应于相应 Parquet 文件夹中的数据的一部分。

在读取并将数据集转换为 DataFrame 后,我们使用 .printSchema() 方法打印了其模式。正如其名所示,它将打印并显示 DataFrame 的模式。由于我们没有指定我们想要的 DataFrame 模式,Spark 将根据列内的数据模式推断它。现在不用担心这个问题;我们将在 将模式应用于分析数据 菜谱中进一步介绍。

在 DataFrame 中进行任何操作之前使用 .printSchema() 方法是理解处理 DataFrame 内部数据的最佳方式的优秀实践。

最后,作为菜谱的最后一步,我们使用了 .toPandas() 方法来更好地可视化我们的数据,因为 .show() Spark 方法并不旨在提供像 pandas 那样的友好可视化。然而,在使用 .toPandas() 方法时必须谨慎,因为它需要计算能力和内存来将 Spark DataFrame 转换为 pandas DataFrame。

更多内容…

现在,让我们了解为什么 parquet 是大数据优化的文件格式。Parquet 文件是列式存储的,以数据块和小块(数据片段)的形式存储,允许优化的读写操作。在下面的图中,你可以从高层次上观察 parquet 如何组织数据:

图 7.3 – 由 Darius Kharazi 制作的 Parquet 文件结构图(https://dkharazi.github.io/blog/parquet)

图 7.3 – 由 Darius Kharazi 制作的 Parquet 文件结构图(https://dkharazi.github.io/blog/parquet)

Parquet 文件通常以压缩形式存在。这为提高数据存储和传输效率增加了另一层。压缩的 Parquet 文件看起来像这样:

yellow_tripdata_2022-01.snappy.parquet

snappy 名称告诉我们压缩类型,在创建 gzip 格式的表时至关重要,但更适用于大量数据。

参考内容

导入 Avro 文件

与 Parquet 类似,Apache Avro 是一种广泛使用的格式,用于存储分析数据。Apache Avro 是记录数据的主要序列化方法,依赖于模式。它还提供了 远程过程调用RPCs),使得数据传输更加容易,并解决诸如字段缺失、额外字段和字段命名等问题。

在这个菜谱中,我们将了解如何正确读取 Avro 文件,然后理解它是如何工作的。

准备工作

此菜谱将需要具有与之前 导入 Parquet 文件 菜谱中不同的配置的 SparkSession。如果你已经运行了 SparkSession,请使用以下命令停止它:

spark.stop()

我们将在 如何操作 部分创建另一个会话。

在这里使用的数据集可以在以下链接找到:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook/tree/main/Chapter_7/ingesting_avro_files.

随意执行 Jupyter notebook 或 PySpark shell 会话中的代码。

如何操作…

这里是执行此菜谱的步骤:

  1. 要处理 avro 文件,我们需要在我们的 SparkSession 配置中导入一个 .jars 文件,如下所示:

    from pyspark.sql import SparkSession
    spark = SparkSession.builder \
          .master("local[1]") \
          .appName("chapter7_analytical_data_avro") \
          .config("spark.executor.memory", '3g') \
          .config("spark.executor.cores", '2') \
          .config("spark.cores.max", '2') \
          .config("spark.jars.packages", 'org.apache.spark:spark-avro_2.12:2.4.4') \
          .getOrCreate()
    

执行时,此代码将提供类似以下输出:

图 7.4 – 下载 Avro 文件包时 SparkSession 的日志

图 7.4 – 下载 Avro 文件包时 SparkSession 的日志

这意味着 avro 包已成功下载并准备好使用。我们将在后面介绍它是如何工作的。

  1. 配置了.jars文件后,我们将传递文件格式到.read并添加文件的路径:

    df = spark.read.format('avro').load('chapter7_avro_files/file.avro')
    
  2. .printSchema(),让我们检索这个 DataFrame 的架构:

    df.printSchema()
    

你将观察到以下输出:

图 7.5 – 从 Avro 文件中获取的 DataFrame 架构

图 7.5 – 从 Avro 文件中获取的 DataFrame 架构

正如我们所观察到的,这个 DataFrame 包含与上一道菜中提到的 Parquet 文件相同的数据,即摄取 Parquet 文件

它是如何工作的...

正如你所观察到的,我们通过创建具有自定义配置的SparkSession来开始这道菜,这与 Spark 自 2.4 版本以来没有提供读取或写入 Avro 文件的内部 API 有关。

如果你尝试读取这里使用的文件而不下载.jars文件,你将得到以下错误信息:

图 7.6 – 当 Spark 找不到 Avro 文件包时的错误信息

图 7.6 – 当 Spark 找不到 Avro 文件包时的错误信息

从错误信息中,我们可以注意到建议搜索一个名为avro的第三方(或外部)源。查看 Spark 第三方文档,可以在以下位置找到:spark.apache.org/docs/latest/sql-data-sources-avro.xhtml#data-source-option

尽管文档中提供了一些关于如何为不同语言设置它的有用信息,例如org.apache.spark:spark-avro_2.12:3.3.1.jars文件与某些 PySpark 版本不兼容,因此建议使用org.apache.spark:spark-avro_2.12:2.4.4

需要下载.jars文件,但它也与某些版本的 PySpark 不兼容:com.databricks:spark-avro_2.11:4.0.0

由于没有内部 API 来处理此文件,我们需要通知 Spark 文件的格式,如下所示:

spark.read.format('avro')

在读取MongoDB集合时,我们做了同样的事情,如第五章中所述,在使用 PySpark 从 MongoDB 中摄取数据的配方中。

一旦我们的文件被转换为 DataFrame,所有其他功能和操作都是相同的,没有偏见。正如我们所见,Spark 将推断架构并将其转换为列格式。

还有更多...

现在我们已经了解了 Apache Parquet 和 Apache Avro,你可能想知道何时使用哪一个。尽管两者都用于存储分析数据,但存在一些关键差异。

主要区别在于它们存储数据的方式。Parquet 存储在列格式中,而 Avro 以行格式存储数据,如果你想要检索整个行或数据集,这可以非常高效。然而,列格式在聚合或更大的数据集方面进行了更多优化,parquet还支持使用大规模数据和压缩进行更有效的查询。

图 7.7 – 列格式与行格式

图 7.7 – 列格式与行格式

另一方面,Avro 文件通常用于数据流。一个很好的例子是使用 KafkaSchema Registry,它将允许 Kafka 在实时验证文件的预期架构。您可以在 Databricks 文档中看到一些示例代码:docs.databricks.com/structured-streaming/avro-dataframe.xhtml

参见

在官方文档页面上了解更多关于 Apache Avro 的工作原理及其功能:avro.apache.org/docs/

将架构应用于分析数据

在上一章中,我们看到了如何将架构应用于结构化和非结构化数据,但架构的应用不仅限于原始文件。

即使在处理已处理的数据时,也可能会遇到需要将列的值转换为其他部门使用的格式或更改列名的情况。在本菜谱中,我们将学习如何将架构应用于 Parquet 文件以及它是如何工作的。

准备工作

我们需要 SparkSession 来完成此菜谱。请确保您有一个正在运行和启动的会话。我们将使用与 Ingesting Parquet 文件 菜谱中相同的数据集。

您可以自由地使用 Jupyter notebook 或您的 PySpark shell 会话执行代码。

如何做…

执行此菜谱的步骤如下:

  1. 查看我们的列:如 Ingesting Parquet 文件 菜谱中所示,我们可以列出列及其推断的数据类型。您可以看到以下列表:

     VendorID: long
     tpep_pickup_datetime: timestamp
     tpep_dropoff_datetime: timestamp
     passenger_count: double
     trip_distance: double
     RatecodeID: double
     store_and_fwd_flag: string
     PULocationID: long
     DOLocationID: long
     payment_type: long
     fare_amount: double
     extra: double
     mta_tax: double
     tip_amount: double
     tolls_amount: double
     improvement_surcharge: double
     total_amount: double
     congestion_surcharge: double
    
  2. VendorID 转换为更易读的形式。请参考以下代码:

    from pyspark.sql.types import StructType, StructField, StringType, LongType, DoubleType, TimestampType
    schema = StructType([ \
        StructField("vendorId", LongType() ,True), \
        StructField("tpep_pickup_datetime", TimestampType() ,True), \
        StructField("tpep_dropoff_datetime", TimestampType() ,True), \
        StructField("passenger_count", DoubleType() ,True), \
        StructField("trip_distance", DoubleType() ,True), \
        StructField("ratecodeId", DoubleType() ,True), \
        StructField("store_and_fwd_flag", StringType() ,True), \
        StructField("puLocationId", LongType() ,True), \
        StructField("doLocationId", LongType() ,True), \
        StructField("payment_type", LongType() ,True), \
        StructField("fare_amount", DoubleType() ,True), \
        StructField("extra", DoubleType() ,True), \
        StructField("mta_tax", DoubleType() ,True), \
        StructField("tip_amount", DoubleType() ,True), \
        StructField("tolls_amount", DoubleType() ,True), \
        StructField("improvement_surcharge", DoubleType() ,True), \
        StructField("total_amount", DoubleType() ,True), \
        StructField("congestion_surcharge", DoubleType() ,True), \
        StructField("airport_fee", DoubleType() ,True), \
    ])
    
  3. parquet 文件:

    df_new_schema = spark.read.schema(schema).parquet('chapter7_parquet_files/yellow_tripdata_2022-01.parquet')
    
  4. 打印新的 DataFrame 架构:当打印架构时,我们可以看到一些列的名称已更改,正如我们在 步骤 1 中在架构对象上设置的那样:

    df_new_schema.printSchema()
    

执行前面的代码将提供以下输出:

图 7.8 – 应用新架构的 DataFrame

图 7.8 – 应用新架构的 DataFrame

  1. .toPandas() 函数,如下所示:

    df_new_schema.toPandas().head(10)
    

输出看起来像这样:

图 7.9 – 使用 pandas 可视化的黄色出租车行程 DataFrame

图 7.9 – 使用 pandas 可视化的黄色出租车行程 DataFrame

如您所见,没有数值数据发生变化,因此数据完整性保持不变。

它是如何工作的…

如我们所观察到的,为 DataFrame 定义和设置架构并不复杂。然而,当我们考虑了解数据类型或声明 DataFrame 的每一列时,它可能会有些费时。

开始架构定义的第一步是了解我们需要处理的数据集。这可以通过咨询数据目录或甚至与数据有更多联系的人来完成。作为最后的选项,您可以创建 DataFrame,让 Spark 推断架构,并在重新创建 DataFrame 时进行调整。

当在 Spark 中创建架构结构时,有一些事项我们需要注意,正如您在这里所看到的:

  • StructType 对象,它表示 StructField 列表的模式。

  • StructField 将定义名称、数据类型以及列是否允许空或空字段。

  • 数据类型:最后要注意的是,我们将在哪里定义列的数据类型;正如你所想象的那样,有几种数据类型可用。你总是可以查阅文档以查看这里支持的数据类型:spark.apache.org/docs/latest/sql-ref-datatypes.xhtml

一旦我们定义了模式对象,就可以很容易地使用 .schema() 方法将其分配给创建 DataFrame 的函数,就像我们在 步骤 3 中看到的那样。

创建 DataFrame 后,所有以下命令保持不变。

更多内容…

让我们做一个实验,在这个实验中,我们不是使用 TimestampType(),而是使用 DateType()。请看以下更改后的代码片段:

    StructField("tpep_pickup_datetime", DateType() ,True), \
    StructField("tpep_dropoff_datetime", DateType() ,True), \

如果我们重复使用前面的代码更改步骤,当我们尝试可视化数据时将出现错误信息:

图 7.10 – 在分配不兼容的数据类型时读取 DataFrame 出错

图 7.10 – 在分配不兼容的数据类型时读取 DataFrame 出错

这背后的原因是这两种数据类型在格式化列内数据时的不兼容性。DateType() 使用 yyyy-MM-dd 格式,而 TimestampType() 使用 yyy-MM-dd HH:mm:ss.SSSS

仔细观察这两个列,我们看到小时、分钟和秒的信息。如果我们尝试将其强制转换为另一种格式,可能会损坏数据。

参见

在这里了解更多关于 Spark 数据类型的信息:spark.apache.org/docs/3.0.0-preview/sql-ref-datatypes.xhtml

过滤数据和处理常见问题

过滤数据是一个排除或仅选择必要信息用于或存储的过程。即使是分析数据也必须重新过滤以满足特定需求。一个很好的例子是 数据集市(我们将在本食谱的后面部分介绍)。

本食谱旨在通过一个实际例子了解如何创建和应用过滤数据。

准备工作

此食谱需要 SparkSession,请确保你的已经启动并运行。你可以使用章节开头提供的代码或创建自己的代码。

这里使用的数据集将与 Ingesting Parquet 文件 食谱中使用的相同。

为了使这个练习更加实用,让我们假设我们想要分析两个场景:每个供应商完成了多少次行程以及一天中的哪个小时段有更多的接单。我们将创建一些聚合并过滤我们的数据集以执行这些分析。

如何做到这一点…

执行此食谱的步骤如下:

  1. 读取 Parquet 文件:让我们从读取我们的 Parquet 文件开始,如下面的代码所示:

    df = spark.read.parquet('chapter7_parquet_files/')
    
  2. vendorId 实例以及每个供应商在 1 月(我们的数据集的时间范围)中执行了多少次行程。我们可以使用 .groupBy() 函数和 .count(),如下所示:

    df.groupBy("vendorId").count().orderBy("vendorId").show()
    

这就是供应商行程计数的样子:

图 7.11 – vendorId 行程计数

图 7.11 – vendorId 行程计数

  1. tpep_pickup_datetime 列,如下面的代码所示。然后,我们进行计数并按升序排序:

    from pyspark.sql.functions import year, month, dayofmonth
    df.groupBy(hour("tpep_pickup_datetime")).count().orderBy("count").show()
    

这就是输出看起来像这样:

图 7.12 – 每小时的行程计数

图 7.12 – 每小时的行程计数

如您所观察到的,在 14 小时和 19 小时,接货数量有所增加。我们可以考虑一些可能的原因,例如午餐时间和高峰时段。

它是如何工作的…

由于我们已经非常熟悉用于创建 DataFrame 的读取操作,让我们直接进入 步骤 2

df.groupBy("vendorId").count().orderBy("vendorId").show()

如您所观察到的,这里的函数链非常类似于 SQL 操作。这是因为,在幕后,我们正在使用 DataFrame 支持的本地 SQL 方法。

注意

与 SQL 操作类似,链中方法的顺序会影响结果,甚至可能影响操作的成功与否。

步骤 3 中,我们通过从 tpep_pickup_datetime 列中提取小时值增加了一些复杂性。这仅因为此列是时间戳数据类型。此外,我们这次按计数列排序,该列是在调用 .count() 函数后创建的,类似于 SQL。您可以在下面的代码中看到这一点:

df.groupBy(hour("tpep_pickup_datetime")).count().orderBy("count").show()

还有许多其他函数,例如 .filter().select()。您可以在以下位置找到更多 PySpark 函数:spark.apache.org/docs/latest/api/python/reference/pyspark.sql/functions.xhtml

还有更多…

在这个配方中进行的分析使用了 PySpark 本地支持的 SQL 函数。然而,这些函数并不适合更复杂的查询。

在这些情况下,最佳实践是使用 Spark 的 SQL API。让我们看看接下来的代码是如何实现的:

  1. .createOrReplaceTempView() 方法并传递一个临时视图的名称,如下所示:

    df.createOrReplaceTempView("ny_yellow_taxi_data")
    
  2. SparkSession 变量 (spark),我们将调用 .sql() 并传递一个包含所需 SQL 代码的多行字符串。为了更容易可视化结果,我们还将它分配给一个名为 vendor_groupby 的变量。

观察到我们使用临时视图名称来指示查询将在哪里进行:

vendor_groupby = spark.sql(
"""
SELECT vendorId, COUNT(*) FROM ny_yellow_taxi_data
GROUP BY vendorId
ORDER BY COUNT(*)
"""
)

执行此代码不会生成输出。

  1. .show() 方法将用于显示结果,如下面的代码所示:

    vendor_groupby.show()
    

这是输出:

图 7.13 – 使用 SQL 代码的 vendorId 行程计数

图 7.13 – 使用 SQL 代码的 vendorId 行程计数

使用 SQL API 的缺点是错误日志有时可能不清楚。请参见以下截图:

图 7.14 – 当 SQL 语法错误时的 Spark 错误

图 7.14 – 当 SQL 语法错误时的 Spark 错误

此截图显示了当按 tpep_pickup_datetime 列分组时语法错误的查询结果。在这种情况下,最佳方法是分步调试,逐个执行查询操作和条件。如果您的 DataFrame 来自数据库中的表,请尝试直接在数据库中重现查询,并查看是否有更直观的错误消息。

数据集市

如本配方开头所述,导入和重新过滤分析数据的一个常见用例是将其用于数据集市。

数据集市是数据仓库的一个较小版本,数据集中在单一主题上,例如来自财务或销售部门。以下图示显示了它们的组织方式:

图 7.15 – 数据集市图

图 7.15 – 数据集市图

实施数据集市概念有许多好处,例如获取特定信息或确保在项目中对严格的数据片段进行临时访问,而无需管理多个用户对数据仓库的安全访问。

参考以下内容

在 Panoply.io 博客上了解更多关于数据集市和数据仓库概念:panoply.io/data-warehouse-guide/data-mart-vs-data-warehouse/.

导入分区数据

数据分区的实践并非最近才出现。它在数据库中实现,用于在多个磁盘或表中分配数据。实际上,数据仓库可以根据数据内部的目的和使用情况来分区数据。您可以在此处了解更多信息:www.tutorialspoint.com/dwh/dwh_partitioning_strategy.htm.

在我们的案例中,数据分区与我们的数据将被分割成小块并处理的方式有关。

在本配方中,我们将学习如何导入已分区数据以及它如何影响我们代码的性能。

准备工作

此配方需要初始化的 SparkSession。您可以创建自己的或使用本章开头提供的代码。

完成步骤所需的数据可以在此处找到:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook/tree/main/Chapter_7/ingesting_partitioned_data.

您可以使用 Jupyter 笔记本或 PySpark 壳会话来执行代码。

如何做…

使用以下步骤执行此配方:

  1. 创建 2 月数据的 DataFrame:让我们使用从 Parquet 文件创建 DataFrame 的常规方法,但这次只传递我们想要读取的月份:

    df_partitioned = spark.read.parquet("chapter7_partitioned_files/month=2/")
    

您应该看不到此执行的任何输出。

  1. 使用 pandas 显示结果:一旦创建 DataFrame,我们可以使用 pandas 更好地可视化结果:

    df_partitioned.toPandas()
    

你应该观察以下输出:

图 7.16 – 使用分区数据可视化黄色出租车行程 DataFrame

图 7.16 – 使用分区数据可视化黄色出租车行程 DataFrame

观察到在tpep_pickup_datetime列中,只有 2 月份的数据,现在我们不需要过分关注我们本地机器的处理能力。

它是如何工作的…

这是一个非常简单的配方,但有一些重要的概念我们需要稍微了解一下。

正如你所观察到的,所有的魔法都在 DataFrame 的创建过程中发生,我们不仅传递了存储 Parquet 文件的路径,还传递了包含月份参考的另一个子文件夹的名称。让我们看看以下截图中的文件夹是如何组织的:

图 7.17 – 按月份分区的文件夹

图 7.17 – 按月份分区的文件夹

注意

_SUCCESS文件表示分区写入过程已成功完成。

chapter7_partitioned_files文件夹内,存在其他带有多个参考的子文件夹。这些子文件夹中的每一个代表一个分区,在这种情况下,按月份进行分类。

如果我们查看子文件夹内部,我们可以观察到一个或多个 Parquet 文件。参考以下截图:

图 7.18 – 2 月份的 Parquet 文件

图 7.18 – 2 月份的 Parquet 文件

分区是从数据集中读取或写入特定数量数据的优化形式。这就是为什么这次使用 pandas 可视化 DataFrame 更快。

分区还使得转换的执行更快,因为数据将通过 Spark 内部工作节点之间的并行处理来处理。你可以在以下图中更好地可视化它:

图 7.19 – 分区并行性图

图 7.19 – 分区并行性图

更多内容…

正如我们所看到的,使用分区在大规模上保存数据带来了许多好处。然而,知道如何分区你的数据是高效读取和写入数据的关键。让我们列出编写分区时最重要的三个最佳实践:

  • month,但也可以对任何列进行分区,甚至可以使用包含年、月或日的列来提供更多粒度。通常,分区反映了检索数据最佳的方式。

图 7.20 – 按月份分区文件夹

图 7.20 – 按月份分区文件夹

  • SparkSession配置或它将写入最终文件的位置,Spark 可以创建小的parquet/avro文件。从大数据规模的角度来看,读取这些小文件可能会影响性能。

一个好的做法是在调用write()函数时使用coalesce()来聚合文件成少量。以下是一个示例:

df.coalesce(1).write.parquet('myfile/path')

你可以在这里找到一篇关于它的好文章:www.educba.com/pyspark-coalesce/

  • 避免过度分区:这与前面的逻辑相同。过度分区将创建小文件,因为我们使用粒度规则将它们分割,然后 Spark 的并行性会减慢。

参考以下内容

你可以在这里找到更多最佳实践:climbtheladder.com/10-spark-partitioning-best-practices/

与分区主题相关,我们还有数据库分片的概念。这是一个非常有趣的话题,MongoDB 官方文档中有一篇非常好的文章介绍它:www.mongodb.com/features/database-sharding-explained

应用反向 ETL

正如其名所示,反向 ETL从数据仓库中提取数据并将其插入到如HubSpotSalesforce这样的业务应用程序中。这样做的原因是为了使数据更具操作性,并使用业务工具为已经以分析或分析格式准备好的数据带来更多见解。

本食谱将教会我们如何构建反向 ETL 管道,以及常用的工具。

准备工作

本食谱没有技术要求。然而,鼓励使用白板或记事本来记录笔记。

在这里,我们将处理一个场景,即我们从在线学习平台中摄取数据。想象一下,我们收到了市场部门的请求,希望使用 Salesforce 系统更好地了解平台上的用户行为。

此处的目标将是创建一个从数据源到 Salesforce 平台的数据流过程图。

如何做…

为了使这个练习更直接,我们假设我们已经在数据库中存储了在线学习平台的数据:

  1. 从网站摄取用户行为数据:让我们假设我们有一个前端 API,它将有关用户在在线学习平台上的行为和操作的有用信息发送到我们的后端数据库。参考以下图表,看看它是什么样子:

图 7.21 – 前端到后端 API 的数据流

图 7.21 – 前端到后端 API 的数据流

  1. 使用 ETL 进行处理:有了可用的数据,我们可以挑选出市场部门需要的信息,并将其放入 ETL 流程中。我们将从后端数据库中摄取它,应用所需的任何清洗或转换,然后将它加载到我们的数据仓库中。

图 7.22 – 显示后端存储到数据仓库的图

图 7.22 – 显示后端存储到数据仓库的图

  1. 在数据仓库中存储数据:数据准备好并转换为分析格式后,它将被存储在数据仓库中。我们在这里不需要担心数据是如何建模的。让我们假设将创建一个新的分析表,专门用于此处理目的。

  2. ETL 到 Salesforce:一旦数据在数据仓库中填充,我们需要将其插入 Salesforce 系统中。如下图所示,让我们使用 PySpark 来完成这个操作:

图 7.23 – 数据仓库数据流向 Salesforce

图 7.23 – 数据仓库数据流向 Salesforce

在 Salesforce 内部有数据的情况下,我们可以向营销团队提供建议,并自动化此过程,使其在必要的时间表上触发。

它是如何工作的……

虽然看起来很复杂,但反向 ETL 过程与数据摄取作业类似。在某些情况下,可能需要添加一些额外的转换以适应最终的应用模型,但这并不复杂。现在,让我们更详细地看看我们在配方中做了什么。

图 7.24 – 反向 ETL 概述图

图 7.24 – 反向 ETL 概述

首先,我们需要了解我们是否已经在我们内部数据库中存储了所需的原始数据,以满足营销部门的需求。如果没有,数据团队负责联系负责的开发人员以验证如何完成这项工作。

一旦检查无误,我们就继续进行常规的 ETL 管道。通常,会有 SQL 转换来根据分析需求过滤或分组信息。然后,我们将它存储在事实来源中,例如主数据仓库。

步骤 4中发生反向 ETL。这个名称的来源是因为,通常,ETL 过程涉及从应用程序(如 Salesforce)检索数据并将其存储在数据仓库中。然而,近年来,这些工具已成为更好地理解用户如何行为或与我们的应用程序交互的另一种形式。

通过以用户为中心的分析数据反馈解决方案,我们可以更好地了解并访问具体结果。除了 Salesforce 之外,另一个例子可以是机器学习解决方案,用于预测电子学习平台的一些变化是否会导致用户保留率的提高。

更多内容……

要执行反向 ETL,我们可以创建自己的解决方案或使用商业解决方案。市场上有很多解决方案可以从数据仓库中检索数据,并动态地与业务解决方案连接。一些还可以生成报告,向数据仓库提供反馈,从而提高发送信息的质量,甚至创建其他分析。这些工具的缺点是大多数都是付费解决方案,免费层通常包括一个或几个连接。

最常用的反向 ETL 工具之一是Hightouch;您可以在此处了解更多信息:hightouch.com/.

另请参阅

你可以在Astasia Myers的 Medium 博客上了解更多关于反向 ETL 的信息:medium.com/memory-leak/reverse-etl-a-primer-4e6694dcc7fb

选择用于反向 ETL 的分析数据

既然我们已经了解了反向 ETL 是什么,下一步就是了解哪些类型的分析数据适合加载到 Salesforce 应用程序中,例如。

这个菜谱从上一个菜谱“应用反向 ETL”继续,目的是展示一个决定将哪些数据传输到 Salesforce 应用程序的真实场景。

准备工作

这个菜谱没有技术要求,但你可以用白板或便签纸进行注释。

仍然以市场营销部门请求将数据加载到他们的 Salesforce 账户的情景为例,我们现在将进一步深入,看看哪些信息对他们的分析是相关的。

我们收到了市场营销团队的一个请求,希望了解电子学习平台中的用户旅程。他们想了解哪些课程被观看最多,以及是否有一些需要改进。目前,他们不知道我们后端数据库中有什么信息。

如何操作...

让我们分步骤来处理这个情景。这里的目的是了解我们需要哪些数据来完成这项任务:

  1. 咨询数据目录:为了简化我们的工作,让我们假设我们的数据工程师已经创建了一个包含收集并存储在后端数据库中的用户信息的数据目录。在下面的图中,我们可以更好地看到信息是如何存储的:

图 7.25 – 高亮显示的用于反向 ETL 的感兴趣表

图 7.25 – 高亮显示的用于反向 ETL 的感兴趣表

如我们所见,有三个表可能包含需要检索的相关信息:user_datacourse_informationvideos

  1. 选择原始数据:在下面的图中,我们可以看到高亮显示的列可以提供分析所需的信息:

图 7.26 – 高亮显示的表及其相关列,这些列对反向 ETL 相关

图 7.26 – 高亮显示的表及其相关列,这些列对反向 ETL 相关

  1. 转换和过滤数据:由于我们需要一个单独的表来加载 Salesforce 中的数据,我们可以创建一个 SQL 过滤器并将信息连接起来。

它是如何工作的...

如同菜谱开头所述,市场营销团队希望了解电子学习应用中的用户旅程。首先,让我们了解一下什么是用户旅程。

用户旅程是指用户在系统或应用上执行的所有动作和交互,从他们选择使用或购买一项服务,直到他们登出或离开为止。在这种情况下,诸如他们观看的内容类型以及观看时长等信息非常重要。

让我们看看我们收集到的字段以及它们为什么重要。前六列将给我们一个关于用户和他们居住地的概念。我们可以在以后使用这些信息来查看内容偏好的任何模式。

图 7.27 – 反向 ETL 的相关用户数据列

图 7.27 – 反向 ETL 的相关用户数据列

然后,最后几列提供了关于用户正在观看的内容以及内容类型之间是否存在任何关系的详细信息。例如,如果他们购买了一门 Python 课程和一门 SQL 课程,我们可以使用内容元数据中的一个标签(例如,编程课程)来创建一个相关性过滤器。

图 7.28 – 高亮显示列的课程信息和视频

图 7.28 – 高亮显示列的课程信息和视频

将所有这些信息反馈到 Salesforce 可以帮助回答关于用户旅程的以下问题:

  • 用户是否有在完成一个课程之前开始另一个课程的倾向?

  • 用户是否倾向于同时观看多个课程?

  • 教育团队是否需要因为高流失率而重新制定课程?

参见

在这里可以找到更多用例:www.datachannel.co/blogs/reverse-etl-use-cases-common-usage-patterns

进一步阅读

这里有一份您可以参考的网站列表,以进一步扩展您的知识:

第二部分:结构化摄取管道

在本书的第二部分,您将了解监控实践,并了解如何使用市场上推荐的工具创建您自己的第一个数据摄取管道,同时应用最佳数据工程实践。

本部分包含以下章节:

  • 第八章设计监控数据工作流

  • 第九章使用 Airflow 整合所有内容

  • 第十章在 Airflow 中记录和监控您的数据摄取

  • 第十一章自动化您的数据摄取管道

  • 第十二章使用数据可观察性进行调试、错误处理和预防停机

第八章:设计受监控的数据工作流程

日志代码是一种良好的实践,它允许开发者更快地调试并提供更有效的应用程序或系统维护。插入日志时没有严格的规则,但了解在使用时何时不要过度使用您的监控或警报工具是非常好的。不必要地创建多个日志消息会模糊重要事件发生的实例。这就是为什么理解在代码中插入日志的最佳实践至关重要。

本章将展示如何使用 Python 和 PySpark 为数据管道创建高效且格式良好的日志,并通过实际示例说明,这些示例可以应用于现实世界项目。

在本章中,我们有以下食谱:

  • 插入日志

  • 使用日志级别类型

  • 创建标准化的日志

  • 监控我们的数据摄入文件大小

  • 基于数据的日志记录

  • 获取 SparkSession 指标

技术要求

您可以从本章节的 GitHub 仓库中找到代码,网址为 github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook

插入日志

如本章引言中所述,将日志功能添加到您的应用程序对于调试或以后进行改进至关重要。然而,不必要地创建多个日志消息可能会引起混淆,甚至导致我们错过关键警报。无论如何,了解显示哪种类型的信息是必不可少的。

本食谱将介绍如何使用 Python 创建有用的日志消息,以及何时插入它们。

准备工作

我们将仅使用 Python 代码。请确保您有 Python 3.7 或更高版本。您可以使用以下命令来检查您的 命令行界面CLI):

$ python3 –-version
Python 3.8.10

以下代码执行可以在 Python 壳或 Jupyter 笔记本中完成。

如何操作…

为了进行这个练习,我们将创建一个函数,该函数使用最佳日志记录实践读取并返回 CSV 文件的第一行。以下是我们的操作方法:

  1. 首先,让我们导入我们将使用的库,并设置我们的 logging 库的主要配置:

    import csv
    import logging
    logging.basicConfig(filename='our_application.log', level=logging.INFO)
    

注意,我们向 basicConfig 方法传递了一个文件名参数。我们的日志将存储在那里。

  1. 接下来,我们将创建一个简单的函数来读取并返回 CSV 文件的第一行。请注意,logging.info() 调用带有消息插入到函数内部,如下所示:

    def get_csv_first_line (csv_file):
        logging.info(f"Starting function to read first line")
        try:
            with open(csv_file, 'r') as file:
                logging.info(f" Opening and reading the CSV file")
                reader = csv.reader(file)
                first_row = next(reader)
            return first_row
        except Exception as e:
            logging.error(f"Error when reading the CSV file: {e}")
            raise
    
  2. 然后,让我们调用我们的函数,传递一个 CSV 文件作为示例。在这里,我将使用 listings.csv 文件,您可以在 GitHub 仓库中找到它,如下所示:

    get_csv_first_line("listings.csv")
    

您应该看到以下输出:

图 8.1 – gets_csv_first_line 函数输出

图 8.1 – gets_csv_first_line 函数输出

  1. 让我们检查我们执行 Python 脚本或 Jupyter 笔记本所在的目录。您应该看到一个名为 our_application.log 的文件。如果您点击它,结果应该如下所示:

图 8.2 – our_application.log 内容

图 8.2 – our_application.log 内容

如你所见,我们有两个不同的输出:一个包含函数结果(步骤 3)的输出,另一个创建包含日志消息的文件(步骤 4)。

它是如何工作的...

让我们通过查看前几行来开始理解代码是如何工作的:

import logging
logging.basicConfig(filename='our_application.log', level=logging.INFO)

在导入内置的日志库后,我们调用了一个名为basicConfig()的方法,它为我们的函数中的后续调用设置了主要配置。filename参数表示我们想要将日志保存到文件中,而level参数设置了我们想要开始看到消息的日志级别。这将在本章后面的“使用日志级别类型”菜谱中更详细地介绍。

然后,我们继续创建我们的函数并插入日志调用。仔细观察,我们插入了以下内容:

logging.info(f"Starting function to read first line")
logging.info(f"Opening and reading the CSV file")

这两个日志是信息性的,跟踪一个动作或在我们通过代码或模块的一部分时通知我们。最佳实践是尽可能保持其清洁和客观,这样下一个人(甚至你自己)就可以确定从哪里开始解决问题。

下一个日志通知我们一个错误,正如你在这里可以看到的:

logging.error(f"Error when reading the CSV file: {e}")

调用此错误方法的方式与.info()类似。在这种情况下,最佳实践是只使用异常子句并将错误作为字符串函数传递,就像我们通过在大括号中传递e变量所做的那样。这样,即使我们看不到 Python 跟踪信息,我们也会将其存储在文件或监控应用程序中。

注意

将异常输出封装在变量中是一种常见的做法,例如except Exception as e。这允许我们控制如何显示或获取错误消息的一部分。

由于我们的函数执行成功,我们预计在our_application.log文件中不会看到任何错误消息,正如你在这里可以看到的:

INFO:root:Starting function to read first line
INFO:root:Reading file

如果我们仔细观察保存的日志结构,我们会注意到一个模式。每行的第一个单词INFO表示日志级别;之后我们看到root这个词,表示日志层次结构;最后,我们得到我们插入到代码中的消息。

我们可以通过多种方式优化和格式化我们的日志,但我们现在不会担心这个问题。我们将在“格式化日志”菜谱中更详细地介绍日志层次结构。

另请参阅

在这里了解更多关于在 Python 中初始化日志的官方文档:docs.python.org/3/howto/logging.xhtml#logging-to-a-file

使用日志级别类型

现在我们已经介绍了如何以及在哪里插入日志,让我们了解日志类型或级别。每个日志级别在系统内部都有其自己的相关性程度。例如,默认情况下控制台输出不会显示调试信息。

我们已经在第六章将格式化的 SparkSession 日志插入以方便您的工作流程中介绍了如何使用 PySpark 记录日志级别。现在我们将仅使用 Python 来完成同样的操作。这个菜谱旨在展示如何在脚本开始时设置日志级别,并在代码中插入不同级别的日志以创建日志的优先级层次结构。有了这个,您可以创建一个结构化的脚本,允许您或您的团队能够监控和识别错误。

准备工作

我们将仅使用 Python 代码。请确保您有 Python 3.7 或更高版本。您可以在 CLI 上使用以下命令来检查您的版本:

$ python3 –-version
Python 3.8.10

以下代码执行可以在 Python shell 或 Jupyter 笔记本上完成。

如何操作…

让我们使用之前在插入日志菜谱中使用的相同示例,并进行一些改进:

  1. 让我们从导入库和定义basicConfig开始。这次,我们将日志级别设置为DEBUG

    import csv
    import logging
    logging.basicConfig(filename='our_application.log', level=logging.DEBUG)
    
  2. 然后,在声明函数之前,我们将插入一个DEBUG日志,告知我们即将测试此脚本:

    logging.debug(f"Start testing function")
    
  3. 接下来,正如我们在插入日志菜谱中所见,我们将构建一个函数,该函数读取 CSV 文件并返回第一行,但有一些细微的变化。让我们在 CSV 的第一行执行成功后插入一个DEBUG消息,如果遇到异常则插入一个CRITICAL消息:

    def gets_csv_first_line (csv_file):
        logging.info(f"Starting function to read first line")
        try:
            with open(csv_file, 'r') as file:
                logging.info(f"Reading file")
                reader = csv.reader(file)
                first_row = next(reader)
                logging.debug(f"Finished without problems")
            return first_row
        except Exception as e:
            logging.debug(f"Entered into a exception")
            logging.error(f"Error when reading the CSV file: {e}")
            logging.critical(f"This is a critical error, and the application needs to stop!")
            raise
    
  4. 最后,在我们调用函数之前,让我们插入一个警告消息,告知我们即将开始执行:

    logging.warning(f"Starting the function to get the first line of a CSV")
    gets_csv_first_line("listings.csv")
    
  5. 在调用函数后,您应该在our_application.log文件中看到以下输出:

图 8.3 – our_application.log 更新了不同的日志级别

图 8.3 – our_application.log 更新了不同的日志级别

它告诉我们函数已正确执行且没有发生错误。

  1. 现在我们模拟一个错误。您现在应该在our_application.log文件中看到以下消息:

图 8.4 – our_application.log 显示的错误消息

图 8.4 – our_application.log 显示的错误消息

如您所见,我们遇到了异常,并且我们可以看到ERRORCRITICAL消息。

如何工作…

虽然这可能看起来无关紧要,但我们已经对我们的函数进行了有益的改进。每个日志级别都对应着不同程度的关键性,与正在发生的事情相关。让我们看看以下图表,它显示了每个级别的权重:

图 8.5 – 根据关键性日志级别的权重图

图 8.5 – 根据关键性日志级别的权重图

根据日志级别插入的位置,它可以防止脚本继续执行并创建错误链,因为我们可以根据级别添加不同的错误处理器。

默认情况下,Python 日志库配置为仅显示DEBUG级别的消息,正如您在这里所看到的:

logging.basicConfig(filename='our_application.log', level=logging.DEBUG)

仅显示警告消息及以上的目的是为了避免在控制台输出或日志文件中充斥着不必要的系统信息。在以下图中,您可以查看 Python 内部如何组织其日志级别:

图 8.6 – 日志级别详细描述及其最佳使用时机(来源:https://docs.python.org/3/howto/logging.xhtml)

图 8.6 – 日志级别详细描述及其最佳使用时机(来源:docs.python.org/3/howto/logging.xhtml

您可以在设置代码中的日志消息时使用此表作为参考。它也可以在官方 Python 文档中找到:docs.python.org/3/howto/logging.xhtml#when-to-use-logging

在这个菜谱中,我们试图涵盖所有日志严重级别,以展示推荐的插入位置。尽管这看起来可能很简单,但知道何时使用每个级别可以带来很大的不同,并使您的应用程序更加成熟。

更多内容…

通常,每种语言都有其结构化的日志级别形式。然而,在软件工程领域,关于如何使用这些级别存在一种共识。以下图显示了由Taco Jan Osinga创建的关于操作系统(OS)级别日志级别行为的出色决策图。

图 8.7 – Taco Jan Osinga 的日志级别决策图(来源:https://stackoverflow.com/users/3476764/taco-jan-osinga?tab=profile)

图 8.7 – Taco Jan Osinga 的日志级别决策图(来源:https://stackoverflow.com/users/3476764/taco-jan-osinga?tab=profile)

参见

更多关于 Python 日志基础的详细信息,请参阅官方文档:docs.python.org/3/howto/logging.xhtml

创建标准化的日志

现在我们已经了解了插入日志和使用日志级别的最佳实践,我们可以向日志中添加更多相关信息,以帮助我们监控代码。例如,日期和时间或执行过的模块或函数等信息,有助于我们确定问题发生的位置或需要改进的地方。

创建应用程序日志或(在我们的情况下)数据管道日志的标准格式,可以使调试过程更加易于管理,并且有各种方法可以实现这一点。其中一种方法就是创建.ini.conf文件,这些文件包含日志的格式化和应用配置,例如。

在这个菜谱中,我们将学习如何创建一个配置文件,该文件将决定日志在代码中的格式以及执行输出中的显示方式。

准备工作

让我们使用与之前使用日志级别类型菜谱相同的代码,但进行更多改进!

你可以使用以下代码在新的文件或笔记本中遵循此配方的步骤,或者重用 使用日志级别类型 配方中的函数。我更喜欢复制,这样第一段代码就保持不变:

Def gets_csv_first_line(csv_file):
    logger.debug(f"Start testing function")
    logger.info(f"Starting function to read first line")
    try:
        with open(csv_file, 'r') as file:
            logger.info(f"Reading file")
            reader = csv.reader(file)
            first_row = next(reader)
            logger.debug(f"Finished without problems")
        return first_row
    except Exception as e:
        logger.debug(f"Entered into a exception")
        logger.error(f"Error when reading the CSV file: {e}")
        logger.critical(f"This is a critical error, and the application needs to stop!")
        raise

如何操作...

执行此配方的步骤如下:

  1. 为了开始我们的练习,让我们创建一个名为 logging.conf 的文件。我的建议是将它存储在与你的 Python 脚本相同的目录中。然而,你也可以自由地将它放在其他地方,但请记住我们稍后需要文件的路径。

  2. 接下来,将以下代码粘贴到 logging.conf 文件中并保存:

    [loggers]
    keys=root,data_ingest
    [handlers]
    keys=fileHandler, consoleHandler
    [formatters]
    keys=logFormatter
    [logger_root]
    level=DEBUG
    handlers=fileHandler
    [logger_data_ingest]
    level=DEBUG
    handlers=fileHandler, consoleHandler
    qualname=data_ingest
    propagate=0
    [handler_consoleHandler]
    class=StreamHandler
    level=DEBUG
    formatter=logFormatter
    args=(sys.stdout,)
    [handler_fileHandler]
    class=FileHandler
    level=DEBUG
    formatter=logFormatter
    args=('data_ingest.log', 'a')
    [formatter_logFormatter]
    format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
    
  3. 然后,插入以下 import 语句、config.fileConfig() 方法以及 logger 变量,在 gets_csv_first_line() 函数之前,如你所见:

    import csv
    import logging
    from logging import config
    # Loading configuration file
    config.fileConfig("logging.conf")
    # Creates a log configuration
    logger = logging.getLogger("data_ingest")
    def gets_csv_first_line(csv_file):
        …
    

注意,我们正在将 logging.conf 作为参数传递给 config.fileConfig() 方法。如果你将其存储在 Python 脚本的不同目录级别,请传递整个路径。

  1. 现在,让我们通过传递一个 CSV 文件来调用我们的函数。像往常一样,我将使用 listings.csv 文件进行此练习:

    gets_csv_first_line("listings."sv")
    

你应该在笔记本单元格或 Python 命令行界面中看到以下输出:

图 8.8 – 带有格式化日志的控制台输出

图 8.8 – 带有格式化日志的控制台输出

  1. 然后,检查你的目录。你应该看到一个名为 data_ingest.log 的文件。打开它,你应该看到以下截图类似的内容:

图 8.9 – 带有格式化日志的 data_ingest.log 文件

图 8.9 – 带有格式化日志的 data_ingest.log 文件

如你所观察到的,我们为控制台和文件输出创建了一个标准化的日志格式。现在,让我们在下一节中了解我们是如何做到这一点的。

工作原理...

在跳入代码之前,让我们首先了解配置文件是什么。.conf.ini 文件扩展名提供了一种创建自定义应用程序以与其他应用程序交互的有用方式。你可以在操作系统的 /etc/var 目录中找到一些。

我们的情况并无不同。在我们的配方开始时,我们创建了一个名为 logging.conf 的配置文件,该文件包含了我们将应用于整个应用程序的 Python 日志模式。

现在,让我们看看 logging.conf 文件内部。仔细观察,你可以在方括号内看到一些值。让我们从这里看到的第一个三个开始:

[loggers]
[handlers]
[formatters]

这些参数是 Python 日志库的模块化组件,由于它们彼此分离,因此易于定制。简而言之,它们代表以下内容:

  • 日志记录器被代码使用,并暴露了自身的接口。默认情况下,Python 使用一个 root 日志记录器。对于新的日志记录器,我们使用 key 参数。

  • 处理器将日志发送到配置的目标位置。在我们的例子中,我们创建了两个:fileHandlerconsoleHandler

  • 格式化器为日志记录创建布局。

在声明基本参数后,我们插入了两个自定义的日志记录器和处理器,如下所示:

[logger_root]
[logger_data_ingest]
[handler_consoleHandler]
[handler_fileHandler]

root Logger 创建自定义不是强制性的,但在这里我们想要将默认日志级别更改为 DEBUG 并始终将其发送到 fileHandler。对于 logger_data_ingest,我们还传递了 consoleHandler

说到处理器,它们在这里起着基本的作用。尽管它们共享相同的日志级别和 Formatter,但它们继承不同的类。StreamHandler 类捕获日志记录,通过 args=(sys.stdout,) 获取所有系统输出以在控制台输出中显示。FileHandler 的工作方式类似,将所有结果保存到 DEBUG 级别及以上。

最后,Formatter 决定了日志的显示方式。有许多方法可以设置格式,甚至可以传递执行日志的代码行。你可以在 https://docs.python.org/3/library/logging.xhtml#logrecord-attributes 查看所有可能的属性。

官方 Python 文档有一个优秀的图表,如下所示,概述了这些修饰符与我们未在此处介绍的其他修饰符之间的关系,称为 Filter

图 8.10 – 日志流程图(来源:https://docs.python.org/3/howto/logging.xhtml#logging-flow)

图 8.10 – 日志流程图(来源:https://docs.python.org/3/howto/logging.xhtml#logging-flow)

在我们的练习中,我们创建了一个简单的日志处理器,名为 data_ingest,以及 gets_csv_first_line() 函数。现在,想象一下它如何在整个应用程序或系统中扩展。使用单个配置文件,我们可以为不同的脚本或 ETL 阶段创建不同的模式。让我们看看我们代码的第一行:

(...)
config.fileConfig("logging.conf")
logger = logging.getLogger("data_ingest")
(...)

config.fileConfig() 加载配置文件,logging.getLogger() 加载要使用的 Logger 实例。如果没有找到合适的 Logger,它将使用 root 作为默认值。

软件工程师通常在现实世界的应用程序中使用这种最佳实践来避免代码冗余并创建集中式解决方案。

还有更多…

有一些其他可接受的文件格式可以用来创建日志配置。例如,我们可以使用 YAML Ain’t Markup LanguageYAML)文件或 Python 字典。

图 8.11 – 使用 YAML 格式配置文件(来源:https://docs.python.org/3/howto/logging.xhtml#configuring-logging)

图 8.11 – 使用 YAML 格式配置文件(来源:https://docs.python.org/3/howto/logging.xhtml#configuring-logging)

如果你想了解更多关于使用 logging.config 包来创建改进的 YAML 或字典配置的信息,请参阅以下官方文档:docs.python.org/3/library/logging.config.xhtml#logging-config-api

参见

要阅读和理解更多关于处理器如何工作的信息,请参阅以下官方文档:docs.python.org/3/library/logging.handlers.xhtml

监控我们的数据摄入文件大小

在摄入数据时,我们可以跟踪一些项目以确保传入的信息是我们预期的。其中最重要的项目之一是我们摄入的数据大小,这可能意味着文件大小或流数据的块大小。

记录摄入数据的尺寸允许创建智能和高效的监控。如果在某个时刻,摄入数据的尺寸与预期不符,我们可以采取行动进行调查和解决问题。

在本菜谱中,我们将创建简单的 Python 代码来记录摄入文件的尺寸,这在数据监控中非常有价值。

准备工作

我们将只使用 Python 代码。请确保您拥有 Python 3.7 或更高版本。您可以在您的命令行界面使用以下命令来检查您的版本:

$ python3 –-version
Python 3.8.10

以下代码执行可以使用 Python shell 或 Jupyter notebook 完成。

如何做到这一点…

本练习将创建一个简单的 Python 函数,用于读取文件路径并默认返回其字节数。如果我们想以兆字节为单位返回值,我们只需要将输入参数作为 True 传递:

  1. 让我们从导入 os 库开始:

    import os
    
  2. 然后,我们声明我们的函数,该函数需要一个文件路径,以及一个可选参数来将大小转换为兆字节:

    def get_file_size(file_name, s_megabytes=False):
    
  3. 让我们使用 os.stat() 从文件中检索信息:

        file_stats = os.stat(file_name)
    
  4. 由于它是可选的,我们可以创建一个 if 条件来将 bytes 值转换为 megabytes。如果没有标记为 True,我们将以 bytes 的形式返回值,如下面的代码所示:

        if s_megabytes:
            return f"The file size in megabytes is: {file_stats.st_size / (1024 * 1024)}"
        return f"The file size in bytes is: {file_stats.st_size}"
    
  5. 最后,让我们调用我们的函数,传递我们已使用的数据集:

    file_name = "listings.csv"
    get_file_size(file_name)
    

对于 listings.csv 文件,您应该看到以下输出:

图 8.12 – 字节单位的文件大小

图 8.12 – 字节单位的文件大小

如果我们通过传递 s_megabytes 作为 True 来执行它,我们将看到以下输出:

图 8.13 – 兆字节单位的文件大小

图 8.13 – 兆字节单位的文件大小

您可以自由地使用您机器上的任何文件路径进行测试,并检查大小是否与控制台输出中指示的一致。

它是如何工作的…

当处理数据时,文件大小的估计很方便。让我们了解我们用来实现这种估计的代码片段。

我们首先使用的是 os.stat() 方法来检索文件信息,正如您在这里看到的:

file_stats = os.stat(file_name)

此方法直接与您的操作系统交互。如果我们单独执行它,对于 listings.csv 文件,我们将得到以下输出:

图 8.14 – 使用 os.stat_result 时 listings.csv 文件的特征

图 8.14 – 使用 os.stat_result 时 listings.csv 文件的特征

在我们的案例中,我们只需要st_size来估算bytes,所以我们后来在return子句中这样调用它:

file_stats.st_size

如果您想了解更多关于其他显示结果的信息,您可以参考以下官方文档页面:docs.python.org/3/library/stat.xhtml#stat.filemode

最后,为了以兆字节为单位提供结果,我们只需要使用st_size值进行简单的转换,其中 1 KB 等于 1,024 字节,1 MB 等于 1,024 KB。您可以在以下链接中查看转换公式:

file_stats.st_size / (1024 * 1024)

更多内容...

这个配方展示了创建一个 Python 函数来检索文件大小是多么容易。不幸的是,在撰写本文时,没有直接的方法来使用 PySpark 执行相同的事情。

软件工程师 Glenn Franxman 在他的 GitHub 上提出了一种使用 Spark 内部功能来估算 DataFrame 大小的解决方案。您可以在以下链接中查看他的代码 – 如果您确实使用了它,请确保给予适当的致谢:gist.github.com/gfranxman/4fd0719ff2618039182dd7ea1a702f8e

让我们用一个例子来使用 Glenn 的代码估算 DataFrame 的大小,看看它是如何工作的:

from pyspark.serializers import PickleSerializer, AutoBatchedSerializer
def _to_java_object_rdd(rdd):
    """ Return a JavaRDD of Object by unpickling
    It will convert each Python object into Java object by Pyrolite, whenever the
    RDD is serialized in batch or not.
    """
    rdd = rdd._reserialize(AutoBatchedSerializer(PickleSerializer()))
    return rdd.ctx._jvm.org.apache.spark.mllib.api.python.SerDe.pythonToJava(rdd._jrdd, True)
def estimate_df_size(df):
    JavaObj = _to_java_object_rdd(df.rdd)
    nbytes = spark._jvm.org.apache.spark.util.SizeEstimator.estimate(JavaObj)
return nbytes

要执行前面的代码,您必须有一个已启动的 SparkSession。一旦您有了这个和 DataFrame,执行代码并调用estimate_df_size()函数,如下所示:

estimate_df_size(df)

您应该看到以下以字节为单位的输出,具体取决于您使用的是哪个 DataFrame:

图 8.15 – DataFrame 的字节大小

图 8.15 – DataFrame 的字节大小

请记住,这个解决方案只有在您将 DataFrame 作为参数传递时才会工作。我们的 Python 代码对其他文件估算工作良好,并且在估算大文件时没有性能问题。

参见

与 PySpark 不同,Scala 有一个SizeEstimator函数可以返回 DataFrame 的大小。您可以在以下链接中找到更多信息:spark.apache.org/docs/latest/api/java/index.xhtml?org/apache/spark/util/SizeEstimator.xhtml

基于数据的日志记录

如同在监控我们的数据摄取文件大小配方中提到的,在数据领域记录摄取是一个好的实践。有几种方法可以探索我们的摄取日志,以增加流程的可靠性和我们对它的信心。在这个配方中,我们将开始进入数据操作领域(或DataOps),其目标是跟踪数据从源头到最终目的地的行为。

这个配方将探索我们可以跟踪的其他指标,以创建一个可靠的数据管道。

准备工作

对于这个练习,让我们假设我们有两个简单的数据摄取,一个来自数据库,另一个来自 API。由于这是一个直接的管道,让我们用以下图表来可视化它:

图 8.16 – 数据摄取阶段

图 8.16 – 数据摄取阶段

在这个前提下,让我们探索我们可以记录以使监控高效的实例。

如何操作…

让我们根据前图中我们看到的每一层(或步骤)定义基本指标:

  1. 数据源:让我们从摄取的第一层——源开始。我们知道我们正在处理两个不同的数据源,因此我们必须为它们创建额外的指标。参见以下图示以供参考:

图 8.17 – 要监控的数据库指标

图 8.17 – 要监控的数据库指标

  1. 摄取:现在,既然源已经进行了日志记录和监控,让我们继续到摄取层。正如我们在本章前面所看到的,我们可以记录诸如错误、代码执行的信息部分、文件大小等信息。让我们在这里插入更多内容,例如模式以及检索或处理数据所需的时间。我们将得到一个类似于以下图表的图表:

图 8.18 – 要监控的数据摄取指标

图 8.18 – 要监控的数据摄取指标

  1. 暂存层:最后,让我们讨论摄取后的最后一层。目标是确保我们保持数据的完整性,因此验证模式是否仍然与数据匹配是至关重要的。我们还可以添加日志来监控 Parquet 文件的数量和大小。参见以下图示以供参考:

图 8.19 – 暂存层指标要监控

图 8.19 – 暂存层指标要监控

现在我们已经涵盖了需要监控的基本主题,让我们了解为什么选择了它们。

它是如何工作的…

自从本章的第一个配方以来,我们一直在强调日志对于使系统正确工作的重要性。在这里,我们将所有这些内容综合起来,尽管是从高层次来看,但我们可以看到存储特定信息如何帮助我们进行监控。

从数据源层开始,选择的指标基于数据的响应和检索数据的可用性。了解我们是否可以开始摄取过程是基本的,更重要的是知道数据大小是否符合我们的预期。

想象以下场景:我们每天从 API 中摄取 50 MB 的数据。然而,有一天我们只收到了 10 KB。通过适当的日志记录和监控功能,我们可以快速回顾历史事件中的问题。我们可以将数据大小检查扩展到我们在配方中覆盖的后续层。

注意

我们故意交替使用“步骤”和“层”这两个词来指代摄取过程的各个阶段,因为在不同的文献和不同公司的内部流程中可能会有所不同。

另一种记录和监控我们的数据的方式是使用模式验证。模式验证(当适用时)保证源数据没有发生变化。因此,转换或聚合的结果往往呈线性。我们还可以实现一个辅助函数或作业来检查包含敏感个人身份信息PII)的字段是否得到了充分的匿名化。

监控parquet 文件大小或数量对于验证质量是否得到保持至关重要。如第七章中所示,parquet 文件的数量可能会影响其他应用程序的读取质量,甚至影响 ETL 的后续阶段。

最后,必须指出,我们在这里讨论日志是为了确保数据摄取的质量和可靠性。请记住,最佳实践是将我们从代码中获取的记录与这里看到的示例对齐。

更多内容...

此配方的内容是更广泛主题的一部分,称为数据可观察性。数据可观察性是数据操作、质量和治理的结合。目标是集中一切,使数据过程的管理和监控高效且可靠,为数据带来健康。

我们将在第十二章中进一步讨论这个问题。然而,如果你对这个主题感兴趣,Databand(一家 IBM 公司)有一个很好的介绍,链接如下:databand.ai/data-observability/

另请参阅

在 DataGaps 博客页面上了解更多关于监控 ETL 管道的信息,链接如下:www.datagaps.com/blog/monitoring-your-etl-test-data-pipelines-in-production-dataops-suite/

获取 SparkSession 指标

到目前为止,我们创建日志是为了提供更多信息,使其在监控方面更有用。日志使我们能够根据管道和代码的需求构建自定义指标。然而,我们也可以利用框架和编程语言内置的指标。

当我们创建一个SparkSession时,它提供了一个带有有用指标的 web UI,这些指标可以用来监控我们的管道。使用这个 UI,以下配方展示了如何访问和检索 SparkSession 的指标信息,并将其用作摄取或处理 DataFrame 的工具。

准备工作

您可以使用 PySpark 命令行或 Jupyter Notebook 执行此配方。

在探索 Spark UI 指标之前,让我们使用以下代码创建一个简单的SparkSession

from pyspark.sql import SparkSession
spark = SparkSession.builder \
      .master("local[1]") \
      .appName("chapter8_monitoring") \
      .config("spark.executor.memory", '3g') \
      .config("spark.executor.cores", '3') \
      .config("spark.cores.max", '3') \
      .getOrCreate()

然后,让我们读取一个 JSON 文件,并按如下方式调用.show()方法:

df_json = spark.read.option("multiline","true") \
                    .json('github_events.json')
df_json.show()

我正在使用一个名为github_events.json的数据集,我们之前在第四章中处理过。然而,请随意使用你喜欢的任何数据集,因为这里的目的是观察数据集的模式,而不是从 Spark UI 中找出我们能了解什么。

如何做到这一点…

  1. 如同在准备就绪部分概述的那样,我们使用spark命令检索 Spark UI 的链接,如下所示:

    spark
    

你应该会看到以下输出:

图 8.20 – spark 命令输出

图 8.20 – spark 命令输出

  1. 点击Spark UI,你的浏览器将打开一个新标签页。你应该会看到一个像这样的页面:

图 8.21 – Spark UI:作业页面视图

图 8.21 – Spark UI:作业页面视图

如果你没有使用 Jupyter Notebook,你可以通过将浏览器指向localhost:4040/来访问这个界面。

我的页面看起来更拥挤,因为我展开了事件时间线完成的作业 – 你可以通过点击它们来做到同样的效果。

  1. 接下来,让我们进一步探索第一个完成的作业。点击showString at NativeMethodAccessorImpl.java:0,你应该会看到一个以下页面:

图 8.22 – Spark UI:特定作业的阶段页面视图

图 8.22 – Spark UI:特定作业的阶段页面视图

在这里,我们可以更详细地看到这个作业的任务状态,包括它使用了多少内存、执行它所需的时间等等。

注意,它切换到了顶部菜单的阶段标签。

  1. 现在,点击页面顶部的Executors按钮。你应该会看到一个类似于这样的页面:

图 8.23 – Spark UI:执行器页面视图

图 8.23 – Spark UI:执行器页面视图

这里所有的指标都与 Spark 驱动器和节点相关。

  1. 然后,点击顶部菜单中的SQL按钮。你应该会看到以下页面:

图 8.24 – Spark UI:SQL 页面视图

图 8.24 – Spark UI:SQL 页面视图

在这个页面上,你可以看到 Spark 内部执行的查询。如果我们代码中使用了显式查询,我们就会在这里看到它是如何内部执行的。

你不需要担心.show()方法。

它是如何工作的…

现在我们已经探索了 Spark UI,让我们了解每个标签是如何组织的,以及我们使用它们的步骤。

步骤 2中,我们首次浏览了界面。这个界面使我们能够看到包含驱动器创建和执行时间信息的事件时间线。我们还可以观察到时间线上标记的作业,如下所示:

图 8.25 – 事件时间线展开菜单的详细视图

图 8.25 – 事件时间线展开菜单的详细视图

我们可以观察到它们在处理更大作业和更复杂的并行任务时的交互。不幸的是,我们需要一个专门的项目和几个数据集来模拟这种情况,但现在你知道了未来参考的地方。

然后,我们选择了 NativeMethodAccessorImpl.java:0 中的 showString,这引导我们进入 阶段页面。这个页面提供了关于 Spark 任务的更详细信息,包括任务是否成功。

一个优秀的指标和可视化工具是 DAG 可视化(指有向无环图),它可以扩展并显示如下内容:

图 8.26 – 作业的 DAG 可视化

图 8.26 – 作业的 DAG 可视化

这提供了每个阶段每个步骤执行的优秀概览。我们还可以参考这些信息来了解在出现错误时,基于跟踪消息的哪个部分存在问题。

由于我们选择了特定的任务(或作业),它显示了其阶段和详细信息。然而,如果我们直接进入 阶段,我们可以显示所有执行的步骤。这样做,你应该会看到如下内容:

图 8.27 – Spark UI:所有执行作业的阶段概览

图 8.27 – Spark UI:所有执行作业的阶段概览

尽管描述信息并不直接,但我们仍能把握每个描述所表达的核心内容。Stage id 0 指的是读取 JSON 的函数,而带有 showString 消息的 Stage id 1 则指的是 .show() 方法的调用。

执行器页面显示了与 Spark 核心相关的指标以及其性能表现。你可以使用这些信息来了解你的集群行为以及是否需要调整。有关每个字段的更详细信息,请参阅 Spark 官方文档:https://spark.apache.org/docs/latest/monitoring.xhtml#executor-metrics。

最后但同样重要的是,我们看到了 SQL 页面,在这里可以看到 Spark 在幕后如何内部洗牌和聚合数据,就像 阶段一样,利用更直观的执行形式,如下面的截图所示:

图 8.28 – 内部执行的 SQL 查询流程图

图 8.28 – 内部执行的 SQL 查询流程图

在这里,我们可以看到查询与 .show() 方法相关。其中包含有用的信息,包括输出行数、读取的文件及其大小。

还有更多...

尽管 Spark 指标很方便,但你可能会想知道如何在云提供商(如 AWS 或 Google Cloud)上托管 PySpark 作业时使用它们。

AWS 为在使用 AWS Glue 时启用 Spark UI 提供了一个简单的解决方案。你可以在此了解更多信息:docs.aws.amazon.com/glue/latest/dg/monitor-spark-ui-jobs.xhtml

Google Data Proc 提供了其集群的 Web 界面,您还可以在此查看 HadoopYARN 的指标。由于 Spark 运行在 YARN 之上,因此您不会找到直接指向 Spark UI 的链接,但您可以使用 YARN 界面来访问它。您可以在此处了解更多信息:cloud.google.com/dataproc/docs/concepts/accessing/cluster-web-interfaces

参考以下内容

《走向数据科学》 上有一篇关于 Spark 指标非常棒的文章:https://towardsdatascience.com/monitoring-of-spark-applications-3ca0c271c4e0

进一步阅读

第九章:使用 Airflow 整合一切

到目前为止,我们已经涵盖了数据采集的不同方面和步骤。我们看到了如何配置和采集结构化和非结构化数据,什么是分析数据,以及如何改进日志以进行更有洞察力的监控和错误处理。现在是时候将这些信息组合起来,创建一个类似于真实世界项目的结构。

从现在开始,在接下来的章节中,我们将使用 Apache Airflow,这是一个开源平台,允许我们创建、调度和监控工作流程。让我们通过配置和理解 Apache Airflow 的基本概念以及这个工具的强大功能开始我们的旅程。

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

  • 配置 Airflow

  • 创建 DAG

  • 创建自定义操作符

  • 配置传感器

  • 在 Airflow 中创建连接器

  • 创建并行数据采集任务

  • 定义依赖数据采集的 DAG

到本章结束时,您将了解 Airflow 的最重要的组件以及如何配置它们,包括在此过程中解决相关问题的方法。

技术要求

您可以在此 GitHub 仓库中找到本章的代码:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook

安装 Airflow

本章要求您在本地机器上安装 Airflow。您可以直接在您的操作系统OS)上安装它,或者使用 Docker 镜像。有关更多信息,请参阅第一章中的配置 Docker 用于 Airflow配方。

配置 Airflow

Apache Airflow 具有许多功能和快速设置,这有助于我们以代码的形式开始设计我们的工作流程。随着我们工作流程和数据处理的进展,可能需要一些额外的配置。幸运的是,Airflow 有一个专门的文件,用于插入其他安排,而无需更改其核心中的任何内容。

在这个配方中,我们将了解更多关于 airflow.conf 文件的信息,如何使用它,以及执行本章中其他配方所需的其他有价值配置。我们还将介绍在哪里可以找到此文件,以及其他环境变量如何与此工具一起工作。在实践中理解这些概念有助于我们识别潜在的改进或解决问题。

准备工作

在继续代码之前,请确保您的 Airflow 运行正确。您可以通过检查此链接的 Airflow UI 来做到这一点:http://localhost:8080

如果您像我一样使用 Docker 容器来托管您的 Airflow 应用程序,您可以使用以下命令在终端检查其状态:

$ docker ps

这是输出:

图 9.1 – 运行的 Airflow Docker 容器

图 9.1 – 运行的 Airflow Docker 容器

或者,您可以在 Docker Desktop 上检查容器状态,如下面的截图所示:

图 9.2 – Docker Desktop 中运行 Airflow 容器的视图

图 9.2 – Docker Desktop 中运行 Airflow 容器的视图

如何操作...

执行此食谱的步骤如下:

  1. 让我们先安装 Airflow 的 MongoDB 附加提供程序。如果你使用的是docker-compose.yaml文件,打开它,并在_PIP_ADDITIONAL_REQUIREMENTS内添加apache-airflow-providers-mongo。你的代码将看起来像这样:

图 9.3 – 环境变量部分中的 docker-compose.yaml 文件

图 9.3 – 环境变量部分中的 docker-compose.yaml 文件

如果你直接在你的机器上托管 Airflow,你可以使用PyPi进行相同的安装:pypi.org/project/apache-airflow-providers-mongo/

  1. 接下来,我们将创建一个名为files_to_test的文件夹,并在其中创建两个额外的文件夹:output_filessensors_files。你目前不需要担心它的用途,因为它将在本章的后面使用。你的 Airflow 文件夹结构应该看起来像这样:

图 9.4 – Airflow 本地目录文件夹结构

图 9.4 – Airflow 本地目录文件夹结构

  1. 现在,让我们挂载 Docker 镜像的卷。如果你不是使用 Docker 托管 Airflow,可以跳过这部分。

在你的docker-compose.yaml文件中,在volume参数下,添加以下内容:

- ./config/airflow.cfg:/usr/local/airflow/airflow.cfg
- ./files_to_test:/opt/airflow/files_to_test

你的最终volumes部分将看起来像这样:

图 9.5 – docker-compose.yaml 的 volumes

图 9.5 – docker-compose.yaml 的 volumes 部分

停止并重新启动你的容器,以便这些更改可以传播。

  1. 最后,我们将修复docker-compose.yaml文件中的错误。这个错误的官方修复在 Airflow 官方文档中,因此没有包含在 Docker 镜像中。你可以在这里看到完整的问题和解决方案:github.com/apache/airflow/discussions/24809

要修复错误,请转到docker-compose文件的airflow-init部分,并在environment参数内插入_PIP_ADDITIONAL_REQUIREMENTS: ''。你的代码将看起来像这样:

图 9.6 – docker-compose.yaml 中带有 PIP_ADDITIONAL_REQUIREMENTS 的环境变量部分

图 9.6 – docker-compose.yaml 中带有 PIP_ADDITIONAL_REQUIREMENTS 的环境变量部分

此操作将修复 GitHub 上注册的以下问题:github.com/apache/airflow/pull/23517

它是如何工作的...

这里展示的配置很简单。然而,它保证了应用程序将保持通过本章食谱的正常运行。

让我们从 步骤 1 中安装的软件包开始。与其他框架或平台一样,Airflow 也包含其 电池,这意味着它已经包含了各种软件包。但随着其知名度的提高,它开始需要其他类型的连接或操作符,这是开源社区负责的。

您可以在此处找到可以安装在 Airflow 上的已发布软件包列表:airflow.apache.org/docs/apache-airflow-providers/packages-ref.xhtml

在跳转到其他代码解释之前,让我们先了解 docker-compose.yaml 文件中的 volume 部分。这个配置允许 Airflow 看到哪些文件夹反映了 Docker 容器内部相应的文件夹,而无需每次都使用 Docker 命令上传代码。换句话说,我们可以同步添加我们的 有向无环图 (DAG) 文件和新的操作符,并查看一些日志等,这些都会在容器内部反映出来。

接下来,我们声明了 Docker 挂载卷配置的两个部分:我们创建的新文件夹(files_to_test)和 airflow.cfg 文件。第一个将允许 Airflow 在容器内部复制 files_to_test 本地文件夹,这样我们就可以更简单地使用文件。否则,如果我们尝试在不挂载卷的情况下使用它,当尝试检索任何文件时,将会出现以下错误:

图 9.7 – 当文件夹未在容器卷中引用时 Airflow 中的错误

图 9.7 – 当文件夹未在容器卷中引用时 Airflow 中的错误

虽然我们现在不会使用 airflow.cfg 文件,但了解如何访问此文件以及它的用途是一个好的实践。此文件包含 Airflow 的配置,并且可以编辑以包含更多内容。通常,敏感数据存储在其中,以防止其他人不当访问,因为默认情况下,airflow.cfg 文件的内容在 UI 中无法访问。

注意

在更改或处理 airflow.cfg 文件时要非常小心。此文件包含所有必需的配置以及其他相关设置,以使 Airflow 运作。我们将在 第十章* 中进一步探讨这个问题。

参见

有关 Docker 镜像的更多信息,请参阅以下文档页面:airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.xhtml

创建 DAG

Airflow 的核心概念基于 DAG,它收集、分组和组织以特定顺序执行的任务。DAG 还负责管理其任务之间的依赖关系。简单来说,它不关心任务在做什么,而只是关心如何执行它。通常,DAG 从预定时间开始,但我们也可以定义其他 DAG 之间的依赖关系,以便它们根据其执行状态启动。

我们将在本食谱中创建我们的第一个 DAG,并设置它根据特定的计划运行。通过这一步,我们实际上进入了设计第一个工作流程。

准备就绪

请参考“准备就绪”部分中的“配置 Airflow”食谱,因为我们将使用相同的技术来处理它。

此外,让我们在 dags 文件夹内创建一个名为 ids_ingest 的目录。在 ids_ingest 文件夹内,我们将创建两个文件:__init__.pyids_ingest_dag.py。最终的目录结构将如下所示:

图 9.8 – 包含 ids_ingest DAG 的 Airflow 本地目录结构

图 9.8 – 包含 ids_ingest DAG 的 Airflow 本地目录结构

如何做到这一点…

在这个练习中,我们将编写一个 DAG,用于检索 github_events.json 文件的 IDs。打开 ids_ingest_dag.py,让我们添加内容来编写我们的第一个 DAG:

  1. 让我们先导入在这个脚本中将使用的库。我喜欢将 Airflow 库和 Python 库的导入分开,作为一种良好的实践:

    from airflow import DAG
    from airflow.settings import AIRFLOW_HOME
    from airflow.operators.bash import BashOperator
    from airflow.operators.python_operator import PythonOperator
    import json
    from datetime import datetime, timedelta
    
  2. 然后,我们将为我们的 DAG 定义 default_args,正如您在这里可以看到的:

    # Define default arguments
    default_args = {
        'owner': 'airflow',
        'depends_on_past': False,
        'start_date': datetime(2023, 3, 22),
        'retries': 1,
        'retry_delay': timedelta(minutes=5)
    }
    
  3. 现在,我们将创建一个 Python 函数,该函数接收 JSON 文件并返回其中的 IDs。由于这是一个小型函数,我们可以在 DAG 的文件中创建它:

    def get_ids_from_json(filename_json):
        with open (filename_json, 'r') as f:
            git = json.loads(f.read())
        print([item['id'] for item in git])
    
  4. 接下来,我们将实例化我们的 DAG 对象,并在其中定义两个操作符:一个 BashOperator 实例用于显示控制台消息,以及 PythonOperator 来执行我们刚刚创建的函数,如下所示:

    # Instantiate a DAG object
    with DAG(
        dag_id='simple_ids_ingest',
        default_args=default_args,
        schedule_interval=timedelta(days=1),
    ) as dag:
        first_task = BashOperator(
                task_id="first_task",
                bash_command="echo $AIRFLOW_HOME",
            )
        filename_json = f"{AIRFLOW_HOME}/files_to_test/github_events.json"
        get_id_from_json = PythonOperator(
            task_id="get_id_from_json",
            python_callable=get_ids_from_json,
            op_args=[filename_json]
        )
    

确保在跳到下一步之前保存文件。

  1. 现在,前往 Airflow UI。尽管 Airflow 团队提供了大量的 DAG 示例,但您应该寻找一个名为 simple_ids_ingest 的 DAG。您会注意到 DAG 未启用。点击切换按钮以启用它,您应该看到如下内容:

图 9.9 – 在 UI 上启用的 Airflow DAG

图 9.9 – 在 UI 上启用的 Airflow DAG

  1. 一旦启用,DAG 将开始运行。点击 DAG 名称,将重定向到 DAG 的页面,如下面的截图所示:

图 9.10 – DAG 网格页面视图

图 9.10 – DAG 网格页面视图

如果一切配置正确,您的页面应该看起来像这样:

图 9.11 – 图形页面视图中的 DAG 成功运行

图 9.11 – 图形页面视图中的 DAG 成功运行

  1. 然后,点击 get_id_from_json 任务。会出现一个小窗口,如下所示:

图 9.12 – 任务选项

图 9.12 – 任务选项

  1. 然后,点击日志按钮。您将被重定向到一个新页面,其中包含此任务的日志,如下所示:

图 9.13 – Airflow UI 中的任务日志

图 9.13 – Airflow UI 中的任务日志

如前一个屏幕截图所示,我们的任务成功完成并返回了我们预期的 ID。您可以在AIRFLOW_CTX_DAG_RUN消息下的INFO日志中看到结果。

它是如何工作的...

我们用几行代码创建了我们的第一个 DAG,用于检索并显示从 JSON 文件中获取的 ID 列表。现在,让我们了解它是如何工作的。

首先,我们在dags目录下创建了我们的文件。这是因为,默认情况下,Airflow 会将它里面的所有内容都理解为一个 DAG 文件。我们在其中创建的文件夹只是为了组织目的,Airflow 会忽略它。除了ids_ingest_dag.py文件外,我们还创建了一个__init__.py文件。此文件内部告诉 Airflow 查看这个文件夹。因此,您将看到以下结构:

图 9.14 – 带有 ids_ingest DAG 的 Airflow 本地目录结构

图 9.14 – 带有 ids_ingest DAG 的 Airflow 本地目录结构

注意

正如你可能想知道的,可以更改此配置,但我根本不推荐这样做,因为其他内部包可能依赖于它。只有在极端必要的情况下才这样做。

现在,让我们看看我们的实例化 DAG:

with DAG(
    dag_id='simple_ids_ingest',
    default_args=default_args,
    schedule_interval=timedelta(days=1),
) as dag:

如您所观察到的,创建一个 DAG 很简单,其参数是自发的。dag_id至关重要,必须是唯一的;否则,它可能会造成混淆并与其他 DAG 合并。我们在步骤 2中声明的default_args将指导 DAG,告诉它何时需要执行,它的用户所有者,在失败情况下的重试次数以及其他有价值的参数。在as dag声明之后,我们插入了 bash 和 Python 运算符,并且它们必须缩进来被理解为 DAG 的任务。

最后,为了设置我们的工作流程,我们声明了以下行:

first_task >> get_id_from_json

正如我们可能猜测的那样,它设置了任务执行的顺序。

更多内容...

我们看到了创建一个任务来执行 Python 函数和 bash 命令是多么容易。默认情况下,Airflow 附带了一些方便的运算符,可以在数据摄取管道中每天使用。更多信息,您可以参考官方文档页面:airflow.apache.org/docs/apache-airflow/stable/_api/airflow/operators/index.xhtml

任务、运算符、XCom 和其他

Airflow DAG 是一种强大的方式来分组和执行操作。除了我们在这里看到的任务和操作符之外,DAG 还支持其他类型的工作负载和跨其他任务或 DAG 的通信。不幸的是,由于这并不是本书的主要内容,我们不会详细涵盖这些概念,但我强烈建议您阅读官方文档:airflow.apache.org/docs/apache-airflow/stable/core-concepts/index.xhtml

错误处理

如果在构建此 DAG 时遇到任何错误,您可以使用 步骤 7步骤 8 中的说明来调试它。您可以看到错误发生时任务的外观预览:

图 9.15 – DAG 图页面视图显示运行错误

图 9.15 – DAG 图页面视图显示运行错误

参见

您可以在他们的官方 GitHub 页面找到 Airflow 示例 DAG 的代码:github.com/apache/airflow/tree/main/airflow/example_dags

创建自定义操作符

如前一个菜谱 创建 DAG 所见,创建一个没有实例化任务或换句话说,定义操作符的 DAG 几乎是不可能的。操作符负责在管道中处理数据所需的逻辑。

我们还知道 Airflow 已经有预定义的操作符,允许以数十种方式摄取和处理数据。现在,是时候将创建自定义操作符的实践付诸实施了。自定义操作符允许我们将特定的逻辑应用于相关项目或数据管道。

在本菜谱中,您将学习如何创建一个简单的自定义操作符。虽然它非常基础,但您将能够将此技术的基石应用于不同的场景。

在本菜谱中,我们将创建一个自定义操作符来连接并从 HolidayAPI 检索数据,就像我们在 第二章 中看到的那样。

准备工作

请参阅 准备工作 部分,该部分在 配置 Airflow 菜单中,因为我们将使用相同的技术来处理它。

我们还需要添加一个环境变量来存储我们的 API 密钥。为此,在 Airflow UI 的 Admin 菜单下选择 Variable 项,然后您将被重定向到所需页面。现在,点击 + 按钮添加一个新变量,如下所示:

图 9.16 – Airflow UI 中的变量页面

图 9.16 – Airflow UI 中的变量页面

Key 字段下的 SECRET_HOLIDAY_APIValue 字段下的您的 API 密钥。使用您在 第二章 中执行 使用 API 认证检索数据 菜单时使用的相同值。保存它,然后您将被重定向到 Variables 页面,如下面的截图所示:

图 9.17 – 带有存储 HolidayAPI 密钥的新变量的 Airflow UI

图 9.17 – 带有存储 HolidayAPI 密钥的新变量的 Airflow UI

现在,我们已准备好创建我们的自定义操作符。

如何做到这一点…

我们将用于创建自定义操作符的代码与我们在 第二章使用 API 认证检索数据 菜单中看到的代码相同,但进行了一些修改以适应 Airflow 的要求。以下是它的步骤:

  1. 让我们从在 plugins 文件夹内创建结构开始。由于我们想要创建一个自定义操作符,我们需要创建一个名为 operators 的文件夹,我们将在这里放置一个名为 holiday_api_plugin.py 的 Python 文件。您的文件树应该看起来像这样:

图 9.18 – Airflow 插件文件夹的本地目录结构

图 9.18 – Airflow 插件文件夹的本地目录结构

  1. 我们将在 holiday_api_plugin.py 中创建一些代码,从库导入和声明一个全局变量开始,该变量用于指定我们的文件输出需要放置的位置:

    from airflow.settings import AIRFLOW_HOME
    from airflow.models.baseoperator import BaseOperator
    import requests
    import json
    file_output_path = f"{AIRFLOW_HOME}/files_to_test/output_files/"
    
  2. 然后,我们需要创建一个 Python 类,声明其构造函数,并最终将 第二章 中的确切代码插入到名为 execute 的函数中:

    class HolidayAPIIngestOperator(BaseOperator):
        def __init__(self, filename, secret_key, country, year, **kwargs):
            super().__init__(**kwargs)
            self.filename = filename
            self.secret_key = secret_key
            self.country = country
            self.year = year
        def execute(self, context):
            params = { 'key': self.secret_key,
                    'country': self.country,
                    'year': self.year
            }
            url = "https://holidayapi.com/v1/holidays?"
            output_file = file_output_path + self.filename
            try:
                req = requests.get(url, params=params)
                print(req.json())
                with open(output_file, "w") as f:
                    json.dump(req.json(), f)
                return "Holidays downloaded successfully"
            except Exception as e:
                raise e
    

保存文件,我们的操作符就准备好了。现在,我们需要创建 DAG 来执行它。

  1. 使用与 创建 DAG 菜单相同的逻辑,我们将创建一个名为 holiday_ingest_dag.py 的文件。您的新 DAG 目录树应该看起来像这样:

图 9.19 – holiday_ingest DAG 文件夹的 Airflow 目录结构

图 9.19 – holiday_ingest DAG 文件夹的 Airflow 目录结构

  1. 现在,让我们将我们的 DAG 代码插入到 holiday_ingest_dag.py 文件中并保存:

    from airflow import DAG
    # Other imports
    from operators.holiday_api_plugin import HolidayAPIIngestOperator
    # Define default arguments
    # Instantiate a DAG object
    with DAG(
        dag_id='holiday_ingest',
        default_args=default_args,
        schedule_interval=timedelta(days=1),
    ) as dag:
        filename_json = f"holiday_brazil.json"
        task = HolidayAPIIngestOperator(
            task_id="holiday_api_ingestion",
            filename=filename_json,
            secret_key=Variable.get("SECRET_HOLIDAY_API"),
            country="BR",
            year=2022
        )
    task
    

对于完整代码,请参阅以下 GitHub 仓库:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook/tree/main/Chapter_9/creating_custom_operators

  1. 然后,转到 Airflow UI,查找 holiday_ingest DAG,并启用它。它将看起来像以下图所示:

图 9.20 – 在 Airflow UI 中启用的 holiday_ingest DAG

图 9.20 – 在 Airflow UI 中启用的 holiday_ingest DAG

您的工作将立即开始。

  1. 现在,让我们按照 创建 DAG 菜单中的相同步骤查找任务日志,但现在点击 holiday_api_ingestion 任务。您的日志页面应该看起来像以下图所示:

图 9.21 – holiday_api_ingestion 任务日志

图 9.21 – holiday_api_ingestion 任务日志

  1. 最后,让我们看看输出文件是否成功创建。前往 files_to_test 文件夹,点击 output_files 文件夹,如果一切配置成功,里面将包含一个名为 holiday_brazil.json 的文件。参见以下图示以供参考:

图 9.22 – output_files 截图中的 holiday_brazil.json

图 9.22 – output_files 截图中的 holiday_brazil.json

输出文件的开始部分应如下所示:

图 9.23 – holiday_brazil.json 的第一行

图 9.23 – holiday_brazil.json 的第一行

它是如何工作的…

如您所见,自定义 Airflow 操作符是一个具有独特目的的独立类。通常,自定义操作符是为了被其他团队或 DAG 使用而创建的,这避免了代码冗余或重复。现在,让我们了解它是如何工作的。

我们通过在 plugin 文件夹内创建用于托管新操作符的文件来开始配方。我们这样做是因为,从内部来看,Airflow 理解它内部的一切都是自定义代码。由于我们只想创建一个操作符,所以我们将其放在一个具有相同名称的文件夹中。然而,也可以创建另一个名为 Hooks 的资源。您可以在以下位置了解更多关于在 Airflow 中创建钩子的信息:airflow.apache.org/docs/apache-airflow/stable/howto/custom-operator.xhtml

现在,让我们转向操作符代码,我们在这里声明我们的代码,在类中摄入 HolidayAPI,正如您在这里可以看到的:

class HolidayAPIIngestOperator(BaseOperator):
    def __init__(self, filename, secret_key, country, year, **kwargs):
        super().__init__(**kwargs)
        self.filename = filename
        self.secret_key = secret_key
        self.country = country
        self.year = year

我们这样做是为了扩展 Airflow 的 BaseOperator,以便我们可以自定义它并插入新的构造函数。filenamesecret_keycountryyear 是执行 API 摄入所需的参数。

然后,我们声明了 execute 函数来从 API 摄入数据。上下文是一个 Airflow 参数,允许函数读取配置值:

def execute(self, context):

然后,我们的最后一步是创建一个 DAG 来执行我们制作的操作符。代码类似于我们在“创建 DAGs”配方中创建的之前的 DAG,只是增加了一些新项目。第一个项目是新的 import 实例,正如您在这里可以看到的:

from airflow.models import Variable
from operators.holiday_api_plugin import HolidayAPIIngestOperator

第一个 import 语句允许我们使用通过 UI 插入的 SECRET_HOLIDAY_API 的值,第二个导入了我们的自定义操作符。注意,我们只使用了 operators.holiday_api_plugin 路径。由于 Airflow 的内部配置,它理解 operators 文件夹(位于 plugins 文件夹内)中的代码是一个操作符。

现在,我们可以通过传递所需的参数,像在 Airflow 中使用任何内置操作符一样实例化自定义操作符,正如代码所示:

task = HolidayAPIIngestOperator(
        task_id="holiday_api_ingestion",
        filename=filename_json,
        secret_key=Variable.get("SECRET_HOLIDAY_API"),
        country="BR",
        year=2022)

还有更多…

如果整个项目在从特定 API 或数据库检索数据时具有相同的身份验证形式,创建自定义操作符或钩子是避免代码重复的有价值方式。

然而,在兴奋地开始创建你的插件之前,请记住,Airflow 社区已经提供了许多操作符。例如,如果你在日常工作中使用 AWS,你不需要担心创建一个新的操作符来连接 AWS Glue,因为那已经由 Apache 社区完成并批准。请参阅以下文档:airflow.apache.org/docs/apache-airflow-providers-amazon/stable/operators/glue.xhtml

你可以在这里看到 AWS 操作符的完整列表:airflow.apache.org/docs/apache-airflow-providers-amazon/stable/operators/index.xhtml

参见

对于更多自定义操作符示例,请参阅以下 Virajdatt Kohir 的博客:kvirajdatt.medium.com/airflow-writing-custom-operators-and-publishing-them-as-a-package-part-2-3f4603899ec2

配置传感器

在操作符的伞下,我们有传感器。传感器被设计成等待直到某个事件发生才执行任务。例如,当文件落在HDFS文件夹中时,传感器会触发管道(或任务),如下所示:airflow.apache.org/docs/apache-airflow-providers-apache-hdfs/stable/_api/airflow/providers/apache/hdfs/sensors/hdfs/index.xhtml。你可能想知道,也有针对特定时间表或时间差的传感器。

传感器是创建自动化和事件驱动管道的基本部分。在这个菜谱中,我们将配置一个weekday传感器,它会在一周中的特定一天执行我们的数据管道。

准备工作

由于我们将使用相同的技术来处理它,因此请参考“准备工作”部分中的“配置 Airflow”菜谱。

此外,让我们将一个 JSON 文件放入 Airflow 文件夹内的以下路径:files_to_test/sensors_files/

在我的情况下,我将使用github_events.json文件,但你也可以使用你喜欢的任何文件。

如何操作…

这里是执行此菜谱的步骤:

  1. 让我们从导入所需的库、定义default_args和实例化我们的 DAG 开始编写我们的 DAG 脚本,如下所示:

    from airflow import DAG
    from airflow.settings import AIRFLOW_HOME
    from airflow.operators.bash import BashOperator
    from airflow.sensors.weekday import DayOfWeekSensor
    from airflow.utils.weekday import WeekDay
    from datetime import datetime, timedelta
    default_args = {
        'owner': 'airflow',
        'start_date': datetime(2023, 3, 22),
        'retry_delay': timedelta(minutes=5)
    }
    # Instantiate a DAG object
    with DAG(
        dag_id='sensors_move_file',
        default_args=default_args,
        schedule_interval="@once",
    ) as dag:
    
  2. 现在,让我们使用DayOfWeekSensor定义我们的第一个任务。代码如下:

        move_file_on_saturdays = DayOfWeekSensor(
            task_id="move_file_on_saturdays",
            timeout=120,
            soft_fail=True,
            week_day=WeekDay.SATURDAY
        )
    

我建议在执行此练习时设置一周中的某一天作为参数,以确保没有混淆。例如,如果你想它在星期一执行,将week_day设置为WeekDay.MONDAY,依此类推。

  1. 然后,我们将使用BashOperator定义另一个任务。这个任务将执行将 JSON 文件从files_to_test/sensors_files/移动到files_to_test/output_files/的命令。你的代码应该看起来像这样:

        move_file_task = BashOperator(
                task_id="move_file_task",
                bash_command="mv $AIRFLOW_HOME/files_to_test/sensors_files/*.json $AIRFLOW_HOME/files_to_test/output_files/",
            )
    
  2. 然后,我们将定义我们的 DAG 的执行工作流程,正如你所看到的:

    move_file_on_saturdays.set_downstream(move_file_task)
    

.set_downstream() 函数的工作方式将与我们已经用来定义工作流的两个箭头(>>)类似。你可以在这里了解更多信息:airflow.apache.org/docs/apache-airflow/1.10.3/concepts.xhtml?highlight=trigger#bitshift-composition.

  1. 如同本章前两个食谱所示,现在我们将启用我们的 sensors_move_file DAG,它将立即启动。如果你将工作日设置为执行此练习的同一日,你的 DAG 图形 视图将看起来像这样,表示成功:

图 9.24 – sensors_move_file 任务显示成功状态

图 9.24 – sensors_move_file 任务显示成功状态

  1. 现在,让我们看看我们的文件是否已移动到目录中。正如 准备就绪 部分所述,我在 sensor_files 文件夹中放置了一个名为 github_events.json 的 JSON 文件。现在,它将位于 output_files 中,正如你所看到的:

图 9.25 – output_files 文件夹内的 github_events.json

图 9.25 – output_files 文件夹内的 github_events.json

这表明我们的传感器按预期执行了!

它是如何工作的……

传感器是有价值的操作符,根据状态执行动作。它们可以在文件进入目录、白天、外部任务完成等情况下触发。在这里,我们使用数据团队常用的一天作为示例,将文件从摄取文件夹移动到冷存储文件夹。

传感器依赖于一个名为 poke 的内部方法,它将检查资源的状态,直到满足条件。如果你查看 move_file_on_saturday 日志,你会看到类似以下内容:

[2023-03-25, 00:19:03 UTC] {weekday.py:83} INFO - Poking until weekday is in WeekDay.SATURDAY, Today is SATURDAY
[2023-03-25, 00:19:03 UTC] {base.py:301} INFO - Success criteria met. Exiting.
[2023-03-25, 00:19:03 UTC] {taskinstance.py:1400} INFO - Marking task as SUCCESS. dag_id=sensors_move_file, task_id=move_file_on_saturdays, execution_date=20230324T234623, start_date=20230325T001903, end_date=20230325T001903
[2023-03-25, 00:19:03 UTC] {local_task_job.py:156} INFO - Task exited with return code 0

观察以下代码,我们没有定义 reschedule 参数,因此作业将停止,直到我们手动再次触发它:

    move_file_on_saturdays = DayOfWeekSensor(
        task_id="move_file_on_saturdays",
        timeout=120,
        soft_fail=True,
        week_day=WeekDay.SATURDAY
    )

我们定义的其他参数包括 timeout,它表示在失败或停止重试之前的时间(以秒为单位),以及 soft_fail,它将任务标记为 SKIPPED 以表示失败。

你可以在这里看到其他允许的参数:airflow.apache.org/docs/apache-airflow/stable/_api/airflow/sensors/base/index.xhtml.

当然,就像其他操作符一样,我们可以通过扩展 Airflow 中的 BaseSensorOperator 类来创建我们的自定义传感器。这里的挑战主要是,为了被视为传感器,它需要覆盖 poke 参数,而不创建递归或无限循环的功能。

参见

您可以在官方文档页面这里看到默认的 Airflow 传感器的列表:airflow.apache.org/docs/apache-airflow/stable/_api/airflow/sensors/index.xhtml

在 Airflow 中创建连接器

没有连接到任何外部源就拥有 DAGs 和操作符是没有用的。当然,有很多方法可以摄取文件,甚至来自其他 DAGs 或任务结果。然而,数据摄取通常涉及使用外部源,如 API 或数据库作为数据管道的第一步。

为了实现这一点,在这个菜谱中,我们将了解如何在 Airflow 中创建连接器以连接到示例数据库。

准备工作

由于我们将使用相同的技术来处理它,因此请参考 Configuring Airflow 菜谱中的 Getting ready 部分。

这个练习还需要 MongoDB 本地数据库处于运行状态。请确保您已按照 第一章 中的说明进行配置,并且至少有一个数据库和集合。您可以使用 第五章连接到 NoSQL 数据库 (MongoDB) 菜单中的说明。

如何做到这一点...

执行此菜谱的步骤如下:

  1. 让我们从打开 Airflow UI 开始。在顶部菜单中,选择 Admin 按钮,然后选择 Connections,您将被重定向到 Connections 页面。由于我们还没有进行任何配置,这个页面将是空的,正如您在下面的屏幕截图中所看到的:

图 9.26 – Airflow UI 中的连接页面

图 9.26 – Airflow UI 中的连接页面

  1. 然后,点击 + 按钮将被重定向到 Add Connection 页面。在 Connection Type 字段下,搜索并选择 MongoDB。在相应的字段下插入您的连接值,如以下屏幕截图所示:

图 9.27 – 在 Airflow UI 中创建新的连接器

图 9.27 – 在 Airflow UI 中创建新的连接器

点击 Save 按钮,您应该在 Connection 页面上看到类似的内容:

图 9.28 – 在 Airflow UI 中创建的 MongoDB 连接器

图 9.28 – 在 Airflow UI 中创建的 MongoDB 连接器

  1. 让我们使用与我们在 创建 DAGs 菜谱中看到的相同的文件夹和文件树结构创建我们的新 DAG。我将称这个 mongodb_check_conn_dag.py DAG 文件。

  2. 在 DAG 文件内部,让我们首先导入和声明所需的库和变量,正如您在这里所看到的:

    from airflow import DAG
    from airflow.settings import AIRFLOW_HOME
    from airflow.providers.mongo.hooks.mongo import MongoHook
    from airflow.operators.python import PythonOperator
    import os
    import json
    from datetime import datetime, timedelta
    default_args = {
        'owner': 'airflow',
        'depends_on_past': False,
        'start_date': datetime(2023, 3, 22),
        'retries': 1,
        'retry_delay': timedelta(minutes=5)
    }
    
  3. 现在,我们将创建一个函数来连接本地 MongoDB 并从 db_airbnb 数据库中打印 collection reviews,正如您在这里所看到的:

    def get_mongo_collection():
        hook = MongoHook(conn_id ='mongodb')
        client = hook.get_conn()
        print(client)
        print( hook.get_collection(mongo_collection="reviews", mongo_db="db_airbnb"))
    
  4. 然后,让我们继续处理 DAG 对象:

    # Instantiate a DAG object
    with DAG(
        dag_id='mongodb_check_conn',
        default_args=default_args,
        schedule_interval=timedelta(days=1),
    ) as dag:
    
  5. 最后,让我们使用 PythonOperator 来调用我们在 步骤 5 中定义的 get_mongo_collection 函数:

        mongo_task = PythonOperator(
            task_id='mongo_task',
            python_callable=get_mongo_collection
        )
    

不要忘记将您的任务名称放在 DAG 的缩进中,如下所示:

mongo_task
  1. 转到 Airflow UI,让我们启用 DAG 并等待其执行。成功完成后,您的 mongodb_task 日志应如下所示:

图 9.29 – mongodb_task 日志

图 9.29 – mongodb_task 日志

如您所见,我们已经连接并从 MongoDB 中检索了 Collection 对象。

它是如何工作的...

在 Airflow 中创建连接非常简单,就像这里使用 UI 展示的那样。也可以使用 Connection 类编程创建连接。

在我们设置了 MongoDB 连接参数后,我们需要创建一个表单来访问它,我们使用钩子做到了这一点。钩子是一个高级接口,允许连接到外部源,而无需担心低级代码或特殊库。

记住,我们在 配置 Airflow 的配方中配置了一个外部包?它是一个提供程序,允许轻松连接到 MongoDB:

from airflow.providers.mongo.hooks.mongo import MongoHook

get_mongo_collection 函数中,我们实例化了 MongoHook 并传递了在 Connection 页面上设置的相同连接 ID 名称,正如您在这里所看到的:

hook = MongoHook(conn_id ='mongodb')

使用该实例,我们可以调用 MongoHook 类的方法,甚至可以传递其他参数来配置连接。有关此类文档,请参阅此处:airflow.apache.org/docs/apache-airflow-providers-mongo/stable/_api/airflow/providers/mongo/hooks/mongo/index.xhtml

还有更多...

您还可以使用 airflow.cfg 文件来设置连接字符串或任何其他环境变量。将敏感信息(如凭证)存储在此处是一个好习惯,因为它们不会在 UI 中显示。还可能通过额外的配置对这些值进行加密。

更多信息,请参阅此处文档:airflow.apache.org/docs/apache-airflow/stable/howto/set-config.xhtml

参见

创建并行摄取任务

当处理数据时,我们很少一次只执行一个摄取操作,现实世界的项目通常涉及许多同时发生的摄取操作,通常是并行的。我们知道可以安排两个或更多 DAG 同时运行,但一个 DAG 内部的任务呢?

本食谱将说明如何在 Airflow 中创建并行任务执行。

准备工作

请参考 准备工作 部分,该部分在 配置 Airflow 食谱中,因为我们将使用相同的技术来处理它。

为了避免在这个练习中产生冗余,我们不会明确包含导入和主 DAG 配置。相反,重点是组织操作符的工作流程。你可以使用与 创建 DAG 食谱中相同的逻辑来创建你的 DAG 结构。

对于这里使用的完整 Python 文件,请访问 GitHub 页面:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook/tree/main/Chapter_9/creating_parallel_ingest_tasks

如何做…

对于这个练习,我的 DAG 将被称为 parallel_tasks_dag。现在,让我们试试看:

  1. 让我们从创建五个 BashOperator 实例开始,如下所示:

    # (DAG configuration above)
        t_0 = BashOperator(
                task_id="t_0",
                bash_command="echo 'This tasks will be executed first'",
            )
        t_1 = BashOperator(
                task_id="t_1",
                bash_command="echo 'This tasks no1 will be executed in parallel with t_2 and t_3'",
            )
        t_2 = BashOperator(
                task_id="t_2",
                bash_command="echo 'This tasks no2 will be executed in parallel with t_1 and t_3'",
            )
        t_3 = BashOperator(
                task_id="t_3",
                bash_command="echo 'This tasks no3 will be executed in parallel with t_1 and t_2'",
            )
        t_final = BashOperator(
            task_id="t_final",
            bash_command="echo 'Finished all tasks in parallel'",
        )
    
  2. 策略是让其中三个并行执行,因此它们将位于方括号内。第一个和最后一个任务将具有与我们在 创建 DAG 章节中看到的相同的流程声明,使用 >> 字符。最终的流程结构将如下所示:

    t_0 >> [t_1, t_2, t_3] >> t_final
    
  3. 最后,启用你的 DAG,让我们看看它在 DAG 图形 页面上看起来像什么。它应该类似于以下图:

图 9.30 – Airflow 中的 parallel_tasks_dag 任务

图 9.30 – Airflow 中的 parallel_tasks_dag 任务

如你所见,方括号内的任务以并行方式显示,将在 t_0 完成其工作后开始。

它是如何工作的…

虽然在 DAG 内创建并行任务很简单,但这种工作流程安排在处理数据时具有优势。

考虑一个数据摄取的例子:我们需要保证在移动到下一个管道阶段之前,我们已经摄取了所有期望的端点。以下图作为参考:

图 9.31 – 并行执行的示例

图 9.31 – 并行执行的示例

并行执行只有在所有并行任务成功完成后才会移动到最后一个任务。通过这种方式,我们保证数据管道不会只摄取一小部分数据,而是所有所需的数据。

回到我们的练习,我们可以通过删除一个简单引号来模拟这种行为,创建一个 t_2。在下面的图中,你可以看到 DAG 图将如何看起来:

图 9.32 – Airflow 的 parallel_tasks_dag 中有错误 t_2 任务

图 9.32 – Airflow 的 parallel_tasks_dag 中有错误 t_2 任务

t_final 任务将重试执行,直到我们修复 t_2 或重试次数达到其限制。

然而,避免执行许多并行任务是一种良好的实践,尤其是在你只有有限的资源来处理这些任务时。有许多方法可以创建对外部任务或 DAG 的依赖,我们可以利用它们来创建更高效的管道。

更多内容…

除了任务并行性的概念,我们还有BranchOperatorBranchOperator根据匹配的准则同时执行一个或多个任务。让我们用以下图例来说明这一点:

图 9.33 – 分支任务图示例

图 9.33 – 分支任务图示例

根据一周中的日期标准,day_of_the_week_branch任务将触发特定于该天的任务。

如果你想了解更多,Analytics Vidhya有一个关于它的很好的博客文章,你可以在这里阅读:www.analyticsvidhya.com/blog/2023/01/data-engineering-101-branchpythonoperator-in-apache-airflow/

参考以下内容

定义依赖导入的 DAGs

在数据世界中,关于如何组织 Airflow DAGs 的讨论相当多。我通常使用的方法是创建一个基于业务逻辑或最终目的地的特定管道的 DAG。然而,有时,为了在 DAG 内部执行任务,我们依赖于另一个 DAG 来完成流程并获取输出。

在这个配方中,我们将创建两个 DAG,其中第一个依赖于第二个的成功结果。否则,它将不会完成。为了帮助我们,我们将使用ExternalTaskSensor操作员。

准备

请参考配置 Airflow配方中的准备部分,因为我们将使用相同的技术来处理它。

这个配方依赖于在创建自定义操作员配方中创建的holiday_ingest DAG,所以请确保你有它。

在这个练习中,我们将不会明确引用导入和主 DAG 配置,以避免冗余和重复。这里的目的是如何组织操作员的作业流程。你可以使用与创建 DAGs配方中相同的逻辑来创建你的 DAG 结构。

对于这里使用的完整 Python 文件,请访问以下 GitHub 页面:

github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook/tree/main/Chapter_9/de%EF%AC%81ning_dependent_ingests_DAGs

如何做到这一点…

对于这个练习,让我们创建一个在 holiday_ingest 成功完成后触发的 DAG,并在控制台输出中返回所有假日日期。我的 DAG 将被称为 external_sensor_dag,但请随意提供任何其他 ID 名称。只需确保它是唯一的,因此不会影响其他 DAG:

  1. 除了我们已有的默认导入外,我们还可以添加以下内容:

    from airflow.sensors.external_task import ExternalTaskSensor
    
  2. 现在,我们将在 holiday_brazil.json 文件中插入一个 Python 函数来返回假日日期,这是 holiday_ingest DAG 的输出:

    def get_holiday_dates(filename_json):
        with open (filename_json, 'r') as f:
            json_hol = json.load(f)
            holidays = json_hol["holidays"]
        print([item['date'] for item in holidays])
    
  3. 然后,我们将定义 DAG 的两个操作符和流程:

        wait_holiday_api_ingest = ExternalTaskSensor(
            task_id='wait_holiday_api_ingest',
            external_dag_id='holiday_ingest',
            external_task_id='holiday_api_ingestion',
            allowed_states=["success"],
            execution_delta = timedelta(minutes=1),
            timeout=600,
        )
        filename_json = f"{AIRFLOW_HOME}/files_to_test/output_files/holiday_brazil.json"
        date_tasks = PythonOperator(
            task_id='date_tasks',
            python_callable=get_holiday_dates,
            op_args=[filename_json]
        )
    wait_holiday_api_ingest >> date_tasks
    

保存它并在 Airflow UI 中启用此 DAG。一旦启用,你会注意到 wait_holiday_api_ingest 任务将处于 RUNNING 状态,并且不会继续到其他任务,如下所示:

图 9.34 – 运行状态下的 wait_holiday_api_ingest 任务

图 9.34 – 运行状态下的 wait_holiday_api_ingest 任务

你还会注意到这个任务的日志看起来像以下这样:

[2023-03-26, 20:50:23 UTC] {external_task.py:166} INFO - Poking for tasks ['holiday_api_ingestion'] in dag holiday_ingest on 2023-03-24T23:50:00+00:00 ...
[2023-03-26, 20:51:23 UTC] {external_task.py:166} INFO - Poking for tasks ['holiday_api_ingestion'] in dag holiday_ingest on 2023-03-24T23:50:00+00:00 ...
  1. 现在,我们将启用并运行 holiday_ingest(如果尚未启用)。

  2. 然后,回到 external_sensor_dag,你会看到它已成功完成,如下所示:

图 9.35 – 显示成功的 external_sensor_dag

图 9.35 – 显示成功的 external_sensor_dag

如果我们检查 date_tasks 的日志,你将在控制台看到以下输出:

[2023-03-25, 16:39:31 UTC] {logging_mixin.py:115} INFO - ['2022-01-01', '2022-02-28', '2022-03-01', '2022-03-02', '2022-03-20', '2022-04-15', '2022-04-17', '2022-04-21', '2022-05-01', '2022-05-08', '2022-06-16', '2022-06-21', '2022-08-14', '2022-09-07', '2022-09-23', '2022-10-12', '2022-11-02', '2022-11-15', '2022-12-21', '2022-12-24', '2022-12-25', '2022-12-31']

这里是完整的日志供参考:

图 9.36 – Airflow UI 中的 date_tasks 日志

图 9.36 – Airflow UI 中的 date_tasks 日志

现在,让我们在下一节中了解它是如何工作的。

工作原理…

让我们先看看我们的 wait_holiday_api_ingest 任务:

wait_holiday_api_ingest = ExternalTaskSensor(
        task_id='wait_holiday_api_ingest',
        external_dag_id='holiday_ingest',
        external_task_id='holiday_api_ingestion',
        allowed_states=["success"],
        execution_delta = timedelta(minutes=1),
        timeout=300,
    )

ExternalTaskSensor 是一种传感器类型,只有当其 DAG 外部的另一个任务以在 allowed_states 参数上定义的特定状态完成时才会执行。此参数的默认值为 SUCCESS

传感器将使用 external_dag_idexternal_task_id 参数在 Airflow 中搜索特定的 DAG 和任务,我们将它们分别定义为 holiday_ingestholiday_api_ingestion。最后,execution_delta 将确定再次戳击外部 DAG 的时间间隔。

一旦完成,除非我们在默认参数中定义不同的行为,否则 DAG 将保持 SUCCESS 状态。如果我们清除其状态,它将返回到 RUNNING 模式,直到传感器条件再次满足。

还有更多…

我们知道 Airflow 是一个强大的工具,但它并非不会偶尔出现故障。内部,Airflow 有其路线来访问内部和外部 DAG,这些路线偶尔会失败。例如,这些错误之一可能是一个 DAG 未找到,这可能是由于各种原因,如配置错误或连接问题。你可以在这里看到这些错误之一的截图:

图 9.37 – Airflow 任务中的偶尔 403 错误日志

图 9.37 – Airflow 任务中的偶尔 403 错误日志

仔细观察,我们可以发现,在几秒钟内,Airflow 的一个工作节点失去了访问或从另一个工作节点检索信息的权限。如果这种情况发生在您身上,请禁用您的 DAG,然后再次启用它。

XCom

在这个练习中,我们使用输出文件来执行操作,但也可以使用任务输出而不需要它写入文件。相反,我们可以使用XCom(即跨通信)机制来帮助我们。

要在任务间使用 XCom,我们可以简单地使用xcom_pushxcom_pull方法在所需任务中推送拉取值。在幕后,Airflow 将这些值临时存储在其数据库之一中,使其更容易再次访问。

要在 Airflow UI 中检查您的存储 XComs,请点击管理员按钮并选择XCom

注意

在生产环境中,XComs 可能有一个清除程序。如果您需要保留值更长时间,请咨询 Airflow 管理员。

您可以在官方文档页面了解更多关于 XComs 的信息:airflow.apache.org/docs/apache-airflow/stable/core-concepts/xcoms.xhtml

参见

您可以在 Airflow 官方文档页面了解更多关于此操作符的信息:airflow.apache.org/docs/apache-airflow/stable/howto/operator/external_task_sensor.xhtml

进一步阅读

第十章:在 Airflow 中记录和监控您的数据摄取

我们已经知道日志记录和监控对于管理应用程序和系统是多么重要,Airflow 也不例外。事实上,Apache Airflow 已经内置了创建日志并导出的模块。但如何改进它们呢?

在上一章中,使用 Airflow 整合一切,我们介绍了 Airflow 的基本方面,如何启动我们的数据摄取,以及如何编排管道和使用最佳数据开发实践。现在,让我们将最佳技术付诸实践,以增强日志记录并监控 Airflow 管道。

在本章中,您将学习以下食谱:

  • 在 Airflow 中创建基本日志

  • 在远程位置存储日志文件

  • airflow.cfg 中配置日志

  • 设计高级监控

  • 使用通知操作员

  • 使用 SQL 操作员进行数据质量

技术要求

您可以在此 GitHub 仓库中找到本章的代码:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook

安装和运行 Airflow

本章要求您的本地机器上安装了 Airflow。您可以直接在您的 操作系统OS)上安装它,或者使用 Docker 镜像。有关更多信息,请参阅 第一章 中的 配置 Docker 以用于 Airflow 食谱。

在遵循 第一章 中描述的步骤之后,请确保您的 Airflow 运行正确。您可以通过检查 Airflow UI 来做到这一点:http://localhost:8080

如果您像我一样使用 Docker 容器来托管您的 Airflow 应用程序,您可以通过运行以下命令在终端检查其状态:

$ docker ps

您可以看到这里正在运行的命令:

图 10.1 – 运行的 Airflow 容器

图 10.1 – 运行的 Airflow 容器

对于 Docker,请检查 Docker Desktop 上的容器状态,如下面的截图所示:

图 10.2 – 运行的 Docker Desktop 版本 Airflow 容器

图 10.2 – 运行的 Docker Desktop 版本 Airflow 容器

docker-compose 中的 Airflow 环境变量

本节针对在 Docker 容器中运行 Airflow 的用户。如果您直接在您的机器上安装它,您可以跳过这一部分。

我们需要配置或更改 Airflow 环境变量来完成本章的大部分食谱。这种配置应该通过编辑 airflow.cfg 文件来完成。然而,如果您选择使用 docker-compose 运行您的 Airflow 应用程序,这可能会很棘手。

理想情况下,我们应该能够通过在 docker-compose.yaml 中挂载卷来访问 airflow.cfg 文件,如下所示:

图 10.3 – docker-compose.yaml 卷

图 10.3 – docker-compose.yaml 卷

然而,它不是在本地机器上反映文件,而是创建一个名为airflow.cfg的目录。这是社区已知的一个错误(见github.com/puckel/docker-airflow/issues/571),但没有解决方案。

为了解决这个问题,我们将使用环境变量在docker-compose.yaml中设置所有airflow.cfg配置,如下例所示:

# Remote logging configuration
AIRFLOW__LOGGING__REMOTE_LOGGING: "True"

对于直接在本地机器上安装和运行 Airflow 的用户,您可以按照指示如何编辑airflow.cfg文件的步骤进行操作。

在 Airflow 中创建基本日志

Airflow 的内部日志库基于 Python 内置的日志,它提供了灵活和可配置的形式来捕获和存储使用有向无环图DAGs)不同组件的日志消息。让我们从介绍 Airflow 日志的基本概念开始这一章。这些知识将使我们能够应用更高级的概念,并在实际项目中创建成熟的数据摄取管道。

在这个食谱中,我们将创建一个简单的 DAG,根据 Airflow 的默认配置生成日志。我们还将了解 Airflow 内部如何设置日志架构。

准备工作

参考此食谱的技术要求部分,因为我们将以相同的技术处理它。

由于我们将创建一个新的 DAG,让我们在dag/目录下创建一个名为basic_logging的文件夹,并在其中创建一个名为basic_logging_dag.py的文件来插入我们的脚本。最终,您的文件夹结构应该如下所示:

图 10.4 – 带有 basic_logging DAG 结构的 Airflow 目录

图 10.4 – 带有 basic_logging DAG 结构的 Airflow 目录

如何操作...

目标是理解如何在 Airflow 中正确创建日志,以便 DAG 脚本将非常简单明了:

  1. 让我们从导入 Airflow 和 Python 库开始:

    from airflow import DAG
    from airflow.operators.python_operator import PythonOperator
    from datetime import datetime, timedelta
    import logging
    
  2. 然后,让我们获取我们想要使用的日志配置:

    # Defining the log configuration
    logger = logging.getLogger("airflow.task")
    
  3. 现在,让我们定义default_args和 Airflow 可以创建的 DAG 对象:

    default_args = {
        'owner': 'airflow',
        'depends_on_past': False,
        'start_date': datetime(2023, 4, 1),
        'retries': 1,
        'retry_delay': timedelta(minutes=5)
    }
    dag = DAG(
        'basic_logging_dag',
        default_args=default_args,
        description='A simple ETL job using Python and Airflow',
        schedule_interval=timedelta(days=1),
    )
    

注意

第九章不同,在这里我们将通过将任务分配到本食谱的步骤 5中的操作实例化来定义哪些任务属于这个 DAG。

  1. 现在,让我们创建三个仅返回日志消息的示例函数。这些函数将根据 ETL 步骤命名,正如您在这里所看到的:

    def extract_data():
        logger.info("Let's extract data")
        pass
    def transform_data():
        logger.info("Then transform data")
        pass
    def load_data():
        logger.info("Finally load data")
        logger.error("Oh, where is the data?")
        pass
    

如果您想的话,可以插入更多的日志级别。

  1. 对于每个函数,我们将使用PythonOperator设置一个任务,并指定执行顺序:

    extract_task = PythonOperator(
        task_id='extract_data',
        python_callable=extract_data,
        dag=dag,
    )
    transform_task = PythonOperator(
        task_id='transform_data',
        python_callable=transform_data,
        dag=dag,
    )
    load_task = PythonOperator(
        task_id='load_data',
        python_callable=load_data,
        dag=dag,
    )
    extract_task >> transform_task >> load_task
    

您可以看到我们通过将步骤 4中定义的dag对象分配给dag参数来引用 DAG 到每个任务。

保存文件并转到 Airflow UI。

  1. 在 Airflow UI 中,查找basic_logging_dag DAG,并通过点击切换按钮来启用它。作业将立即开始,如果您检查 DAG 的图形视图,您应该会看到以下截图类似的内容:

图 10.5 – 显示任务成功状态的 DAG 图视图

图 10.5 – 显示任务成功状态的 DAG 图视图

这意味着管道运行成功!

  1. 让我们检查本地机器上的 logs/ 目录。这个目录与 DAGs 文件夹处于同一级别,我们将脚本放在那里。

  2. 如果您打开 logs/ 文件夹,您可以看到更多文件夹。寻找以 dag_id= basic_logging 开头的文件夹并打开它。

图 10.6 – basic_logging DAG 及其任务的 Airflow 日志文件夹

图 10.6 – basic_logging DAG 及其任务的 Airflow 日志文件夹

  1. 现在,选择名为 task_id=transform_data 的文件夹并打开其中的日志文件。您应该会看到以下截图中的内容:

图 10.7 – transform_data 任务的日志消息

图 10.7 – transform_data 任务的日志消息

如您所见,日志被打印在输出上,并且根据日志级别进行了相应的着色,其中 INFO 为绿色,ERROR 为红色。

它是如何工作的…

这个练习很简单,但如果我告诉你许多开发者都难以理解 Airflow 如何创建其日志呢?这通常有两个原因——开发者习惯于插入 print() 函数而不是日志方法,并且只检查 Airflow UI 中的记录。

根据 Airflow 的配置,它不会在 UI 上显示 print() 消息,用于调试或查找代码运行位置的日志消息可能会丢失。此外,Airflow UI 对显示的记录行数有限制,在这种情况下,Spark 错误消息很容易被省略。

正因如此,理解默认情况下 Airflow 将所有日志存储在 logs/ 目录下至关重要,甚至按照 dag_idrun_id 和每个任务分别组织,正如我们在 步骤 7 中所看到的。这个文件夹结构也可以根据您的需求进行更改或改进,您只需修改 airflow.cfg 中的 log_filename_template 变量。以下是其默认设置:

# Formatting for how airflow generates file names/paths for each task run.
log_filename_template = dag_id={{ ti.dag_id }}/run_id={{ ti.run_id }}/task_id={{ ti.task_id }}/{%% if ti.map_index >= 0 %%}map_index={{ ti.map_index }}/{%% endif %%}attempt={{ try_number }}.log

现在,查看日志文件,你可以看到它与 UI 上的内容相同,如下面的截图所示:

图 10.8 – 存储在本地 Airflow 日志文件夹中的日志文件中的完整日志消息

图 10.8 – 存储在本地 Airflow 日志文件夹中的日志文件中的完整日志消息

在前几行中,我们可以看到 Airflow 启动任务时调用的内部调用,甚至可以看到特定的函数名称,例如 taskinstance.pystandard_task_runner.py。这些都是内部脚本。然后,我们可以在文件下方看到我们的日志消息。

如果您仔细观察,您可以看到我们的日志格式与 Airflow 核心类似。这有两个原因:

  • 在我们的代码开头,我们使用了 getLogger() 方法来检索 airflow.task 模块使用的配置,如下所示:

    logger = logging.getLogger("airflow.task")
    
  • airflow.task 使用 Airflow 默认配置来格式化所有日志,这些配置也可以在 airflow.cfg 文件中找到。现在不用担心这个问题;我们将在 在 airflow.cfg 中配置日志 菜谱中稍后介绍。

在定义 logger 变量和设置日志类配置之后,脚本的其他部分就很简单了。

参见

你可以在 Astronomer 页面这里了解更多关于 Airflow 日志的详细信息:docs.astronomer.io/learn/logging

在远程位置存储日志文件

默认情况下,Airflow 将其日志存储和组织在本地文件夹中,便于开发者访问,这有助于在预期之外出现问题时的调试过程。然而,对于较大的项目或团队来说,让每个人都能够访问 Airflow 实例或服务器几乎是不切实际的。除了查看 DAG 控制台输出外,还有其他方法可以允许访问日志文件夹,而无需授予对 Airflow 服务器的访问权限。

最直接的一种解决方案是将日志导出到外部存储,例如 S3 或 Google Cloud Storage。好消息是 Airflow 已经原生支持将记录导出到云资源。

在这个菜谱中,我们将在 airflow.cfg 文件中设置一个配置,允许使用远程日志功能,并使用示例 DAG 进行测试。

准备工作

请参阅此菜谱的 技术要求 部分。

AWS S3

要完成这个练习,需要创建一个 AWS S3 存储桶。以下是完成此任务的步骤:

  1. 按照以下步骤创建 AWS 账户:docs.aws.amazon.com/accounts/latest/reference/manage-acct-creating.xhtml

  2. 然后,根据 AWS 文档在此处创建 S3 存储桶:docs.aws.amazon.com/AmazonS3/latest/userguide/creating-bucket.xhtml

在我的情况下,我将创建一个名为 airflow-cookbook 的 S3 存储桶,用于本菜谱,如下截图所示:

图 10.9 – AWS S3 创建存储桶页面

图 10.9 – AWS S3 创建存储桶页面

Airflow DAG 代码

为了避免冗余并专注于本菜谱的目标,即配置 Airflow 的远程日志,我们将使用与 在 Airflow 中创建基本日志 菜谱相同的 DAG,但你可以自由地创建另一个具有不同名称但相同代码的 DAG。

如何操作...

这里是执行此菜谱的步骤:

  1. 首先,让我们在我们的 AWS 账户中创建一个程序化用户。Airflow 将使用此用户在 AWS 上进行身份验证,并将能够写入日志。在您的 AWS 控制台中,选择IAM 服务,您将被重定向到一个类似于以下页面:

图 10.10 – AWS IAM 主页面

图 10.10 – AWS IAM 主页面

  1. 由于这是一个具有严格目的的测试账户,我将忽略 IAM 仪表板上的警报。

  2. 然后,选择用户添加用户,如图所示:

图 10.11 – AWS IAM 用户页面

图 10.11 – AWS IAM 用户页面

创建用户页面,插入一个易于记忆的用户名,如图所示:

图 10.12 – AWS IAM 新用户详情

图 10.12 – AWS IAM 新用户详情

保持复选框未勾选,并选择下一步以添加访问策略。

  1. 设置权限页面,选择直接附加策略,然后在权限策略复选框中查找AmazonS3FullAccess

图 10.13 – AWS IAM 为用户创建设置权限

图 10.13 – AWS IAM 为用户创建设置权限

由于这是一个测试练习,我们可以使用对 S3 资源的完全访问权限。然而,请记住,在生产环境中访问资源时附加特定的策略。

选择下一步,然后点击创建用户按钮。

  1. 现在,通过选择您创建的用户,转到安全凭证,然后向下滚动直到您看到访问密钥框。然后创建一个新的,并将 CSV 文件保存在易于访问的地方:

图 10.14 – 用户访问密钥创建

图 10.14 – 用户访问密钥创建

  1. 现在,回到 Airflow,让我们配置 Airflow 与我们的 AWS 账户之间的连接。

使用 Airflow UI 创建一个新的连接,并在连接类型字段中选择Amazon S3。在额外字段中,插入以下行,该行是在步骤 4中检索到的凭证:

{"aws_access_key_id": "your_key", "aws_secret_access_key": "your_secret"}

您的页面将看起来如下所示:

图 10.15 – 添加新的 AWS S3 连接时的 Airflow UI

图 10.15 – 添加新的 AWS S3 连接时的 Airflow UI

保存它,并在您的 Airflow 目录中打开您的代码编辑器。

  1. 现在,让我们将配置添加到我们的airflow.cfg文件中。如果您使用 Docker 托管 Airflow,请在环境设置下将以下行添加到您的docker-compose.yaml文件中:

    AIRFLOW__LOGGING__REMOTE_LOGGING: "True"
    AIRFLOW__LOGGING__REMOTE_BASE_LOG_FOLDER: "s3://airflow-cookbook"
    AIRFLOW__LOGGING__REMOTE_LOG_CONN_ID: conn_s3
    AIRFLOW__LOGGING__ENCRYPT_S3_LOGS: "False"
    

您的docker-compose.yaml文件将类似于以下内容:

图 10.16 – docker-compose.yaml 中的远程日志配置

图 10.16 – docker-compose.yaml 中的远程日志配置

如果您直接在本地机器上安装了 Airflow,您可以立即更改airflow.cfg文件。在airflow.cfg中更改以下行并保存它:

[logging]
# Users must supply a remote location URL (starting with either 's3://...') and an Airflow connection
# id that provides access to the storage location.
remote_logging = True
remote_base_log_folder = s3://airflow-cookbook
remote_log_conn_id = conn_s3
# Use server-side encryption for logs stored in S3
encrypt_s3_logs = False
  1. 在前面的更改之后,重新启动您的 Airflow 应用程序。

  2. 使用你更新的 Airflow,运行 basic_logging_dag 并打开你的 AWS S3. 选择你在 准备就绪 部分创建的桶,你应该能在其中看到一个新对象,如下所示:

图 10.17 – AWS S3 airflow-cookbook 中的 bucket 对象

图 10.17 – AWS S3 airflow-cookbook 中的 bucket 对象

  1. 然后,选择创建的对象,你应该能看到更多与执行的任务相关的文件夹,如下所示:

图 10.18 – AWS S3 airflow-cookbook 显示远程日志

图 10.18 – AWS S3 airflow-cookbook 显示远程日志

  1. 最后,如果你选择其中一个文件夹,你将看到与在 在 Airflow 中创建基本日志 菜单中看到相同的文件。我们在远程位置成功写入了日志!

它是如何工作的…

如果你从整体上查看这个菜谱,可能会觉得这是一项相当大的工作。然而,记住我们是从零开始进行配置,这通常需要时间。由于我们已经习惯了创建 AWS S3 桶和执行 DAG(分别见 第二章第九章),让我们专注于设置远程日志配置。

我们的第一步是在 Airflow 中创建一个连接,使用在 AWS 上生成的访问密钥。这一步是必需的,因为,内部上,Airflow 将使用这些密钥在 AWS 中进行身份验证并证明其身份。

然后,我们按以下方式更改了以下 Airflow 配置:

AIRFLOW__LOGGING__REMOTE_LOGGING: "True"
AIRFLOW__LOGGING__REMOTE_BASE_LOG_FOLDER: "s3://airflow-cookbook"
AIRFLOW__LOGGING__REMOTE_LOG_CONN_ID: conn_s3
AIRFLOW__LOGGING__ENCRYPT_S3_LOGS: "False"

前两行是字符串配置,用于在 Airflow 中设置是否启用远程日志以及将使用哪个 bucket 路径。最后两行与我们在 True 时创建的连接名称有关,如果我们处理敏感信息。

在重启 Airflow 后,配置将在我们的应用程序中体现出来,通过执行 DAG,我们就可以看到在 S3 桶中写入的日志。

如本菜谱介绍中所述,这种配置不仅在大项目中有益,而且在使用 Airflow 时也是一种良好的实践,允许开发者在不访问集群或服务器的情况下调试或检索有关代码输出的信息。

在这里,我们介绍了一个使用 AWS S3 的示例,但也可以使用 Google Cloud StorageAzure Blob Storage。你可以在这里了解更多信息:airflow.apache.org/docs/apache-airflow/1.10.13/howto/write-logs.xhtml

注意

如果你不再想使用远程日志,你可以简单地从你的 docker-compose.yaml 中移除环境变量,或者将 REMOTE_LOGGING 设置回 False

参见

你可以在 Apache Airflow 官方文档页面上了解更多关于 S3 中的远程日志信息:airflow.apache.org/docs/apache-airflow-providers-amazon/stable/logging/s3-task-handler.xhtml

在 airflow.cfg 中配置日志

我们在 将日志文件存储在远程位置 配方中第一次接触了 airflow.cfg 文件。一眼望去,我们看到了这个配置文件是多么强大和方便。只需编辑它,就有许多方法可以自定义和改进 Airflow。

这个练习将教会你如何通过在 airflow.cfg 文件中设置适当的配置来增强你的日志。

准备工作

请参阅本配方的 技术要求 部分,因为我们将以相同的技术来处理它。

Airflow DAG 代码

为了避免冗余并专注于本配方的目标,即配置 Airflow 的远程日志,我们将使用与 在 Airflow 中创建基本日志 配方相同的 DAG。然而,你也可以创建另一个具有不同名称但相同代码的 DAG。

如何操作…

由于我们将使用与 在 Airflow 中创建基本日志 相同的 DAG 代码,让我们直接跳到格式化日志所需配置:

  1. 让我们从在我们的 docker-compose.yaml 中设置配置开始。在环境部分,插入以下行并保存文件:

    AIRFLOW__LOGGING__LOG_FORMAT: "[%(asctime)s] [ %(process)s - %(name)s ] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"
    

你的 docker-compose 文件应该看起来像这样:

图 10.19 – 在 docker-compose.yaml 中格式化日志配置

图 10.19 – 在 docker-compose.yaml 中格式化日志配置

如果你直接编辑 airflow.cfg 文件,搜索 log_format 变量,并将其更改为以下行:

log_format = [%%(asctime)s] [ %%(process)s - %%(name)s ] {%%(filename)s:%%(lineno)d} %%(levelname)s - %%(message)s

你的代码将看起来像这样:

图 10.20 – airflow.cfg 中的 log_format

图 10.20 – airflow.cfg 中的 log_format

保存它,然后进行下一步。

我们在日志行中添加了一些更多项目,我们将在稍后介绍。

注意

在这里要非常注意。在 airflow.cfg 文件中,% 字符是双写的,与 docker-compose 文件不同。

  1. 现在,让我们重新启动 Airflow。你可以通过停止 Docker 容器并使用以下命令重新运行它来完成:

    $ docker-compose stop      # Or press Crtl-C
    $ docker-compose up
    
  2. 然后,让我们前往 Airflow UI 并运行我们称为 basic_logging_dag 的 DAG。在 DAG 页面上,查看右上角并选择播放按钮(由箭头表示),然后选择 触发 DAG,如下所示:

图 10.21 – 页面右侧的基本日志触发按钮

图 10.21 – 页面右侧的基本日志触发按钮

DAG 将立即开始运行。

  1. 现在,让我们看看一个任务生成的日志。我将选择 extract_data 任务,日志将看起来像这样:

图 10.22 – extract_data 任务的格式化日志输出

图 10.22 – extract_data 任务的格式化日志输出

如果你仔细观察,你会看到现在输出中显示了进程号。

注意

如果你选择从上一个配方中保持连续性,将日志文件存储在远程位置,请记住你的日志存储在远程位置。

它是如何工作的…

如我们所见,更改任何日志信息都很简单,因为 Airflow 在幕后使用 Python 日志库。现在,让我们看看我们的输出:

图 10.23 – extract_data 任务的格式化日志输出

图 10.23 – extract_data 任务的格式化日志输出

如您所见,在进程名称(例如,airflow.task)之前,我们还有运行进程的编号。当同时运行多个进程时,这可以是有用的信息,使我们能够了解哪个进程完成得较慢以及正在运行什么。

让我们看看我们插入的代码:

AIRFLOW__LOGGING__LOG_FORMAT: "[%(asctime)s] [ %(process)s - %(name)s ] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"

如您所见,变量如asctimeprocessfilename与我们之前在第八章中看到的相同。此外,由于底层是一个核心 Python 函数,我们可以根据允许的属性添加更多信息。您可以在这里找到列表:docs.python.org/3/library/logging.xhtml#logrecord-attributes

深入了解 airflow.cfg

现在,让我们更深入地了解 Airflow 配置。如您所观察到的,Airflow 资源是由airflow.cfg文件编排的。使用单个文件,我们可以确定如何发送电子邮件通知(我们将在使用通知操作符配方中介绍),DAG 何时反映代码更改,日志如何显示等等。

这些配置也可以通过导出环境变量来设置,并且这比airflow.cfg上的配置设置具有优先级。这种优先级的发生是因为,从内部来说,Airflow 将airflow.cfg的内容转换为环境变量,广义上讲。您可以在这里了解更多:airflow.apache.org/docs/apache-airflow/stable/cli-and-env-variables-ref.xhtml#environment-variable

让我们来看看 Airflow 的引用部分中的日志配置。我们可以看到许多其他定制可能性,例如着色、DAG 处理器的特定格式以及第三方应用的额外日志,如这里所示:

图 10.24 – Airflow 的日志配置文档

图 10.24 – Airflow 的日志配置文档

这份文档的精彩之处在于,我们可以在airflow.cfg或环境变量中直接配置引用。您可以在这里查看完整的引用列表:airflow.apache.org/docs/apache-airflow/stable/configurations-ref.xhtml#logging

在我们熟悉了 Airflow 的动态之后,测试新的配置或格式变得简单,尤其是当我们有一个测试服务器来做这件事时。然而,同时,我们在更改任何内部内容时需要谨慎;否则,我们可能会损害整个应用程序。

更多内容…

步骤 1中,我们提到了在docker-compose中设置变量时避免使用双%字符 – 现在我们来解决这个问题!

我们传递给docker-composestring变量将被一个内部的 Python 日志功能读取,它不会识别双%模式。相反,它将理解 Airflow 日志的默认格式需要等于该字符串变量,所有的 DAG 日志都将看起来像这样:

图 10.25 – 当 log_format 环境变量设置不正确时的错误

图 10.25 – 当 log_format 环境变量设置不正确时的错误

现在,在airflow.cfg文件中,双%字符是一个 Bash 格式模式,它像模运算符一样工作。

参见

在这里查看 Airflow 的完整配置列表:airflow.apache.org/docs/apache-airflow/stable/configurations-ref.xhtml

设计高级监控

在花费一些时间学习和实践日志概念之后,我们可以在监控主题上更进一步。我们可以监控来自所有日志收集工作的结果,并生成有洞察力的监控仪表板和警报,同时存储正确的监控信息。

在本菜谱中,我们将介绍与 StatsD 集成的 Airflow 指标,StatsD 是一个收集系统统计信息的平台,以及它们的目的,帮助我们实现成熟的管道。

准备工作

本练习将专注于使 Airflow 监控指标更加清晰,以及如何构建一个健壮的架构来组织它。

作为本菜谱的要求,牢记以下基本 Airflow 架构至关重要:

图 10.26 – Airflow 高级架构图

图 10.26 – Airflow 高级架构图

从高层次的角度来看,Airflow 组件由以下组成:

  • 一个Web 服务器,我们可以访问 Airflow UI。

  • 一个关系型数据库,用于存储元数据和 DAG 或任务中使用的其他有用信息。为了简化,我们将只使用一种类型的数据库;然而,可能会有多个。

  • 调度器,将咨询数据库中的信息并将其发送给工作者。

  • 一个Celery应用程序,负责排队来自调度器和工人的请求。

  • 工作者,将执行 DAG 和任务。

考虑到这一点,我们可以继续到下一节。

如何操作…

让我们看看设计高级监控的主要项目:

  • 计数器:正如其名所示,这个指标将提供关于 Airflow 内部操作计数的详细信息。此指标提供了正在运行的任务、失败的任务等的计数。在下面的图中,您可以看到一些示例:

图 10.27 – 监控 Airflow 工作流的计数器指标示例列表

图 10.27 – 监控 Airflow 的计数器指标示例列表

  • 定时器:这个指标告诉我们任务或 DAG 完成或加载文件所需的时间。在下面的图中,您可以看到更多:

图 10.28 – 监控 Airflow 工作流的定时器示例列表

图 10.28 – 监控 Airflow 工作流的定时器示例列表

  • 量规:最后,最后一种指标类型给我们提供了一个更直观的概览。量规使用定时器或计数器指标来表示我们是否达到了定义的阈值。在下面的图中,有一些量规的示例:

图 10.29 – 用于监控 Airflow 的量规示例列表

图 10.29 – 用于监控 Airflow 的量规示例列表

在定义了指标并将其纳入我们的视线后,我们可以继续进行架构设计以集成它。

  • StatsD:现在,让我们将StatsD添加到我们在“准备就绪”部分看到的架构图中。您将得到如下内容:

图 10.30 – StatsD 集成和覆盖 Airflow 组件架构

图 10.30 – StatsD 集成和覆盖 Airflow 组件架构

StatsD 可以从虚线框内的所有组件中收集指标并将它们直接发送到监控工具。

  • Prometheus 和 Grafana:然后,我们可以将 StatsD 连接到 Prometheus,它作为 Grafana 的数据源之一。将这些工具添加到我们的架构中看起来将如下所示:

图 10.31 – Prometheus 和 Grafana 与 StatsD 和 Airflow 的集成图

图 10.31 – Prometheus 和 Grafana 与 StatsD 和 Airflow 的集成图

现在,让我们了解这个架构背后的组件。

它是如何工作的...

让我们开始了解 StatsD 是什么。StatsD 是由 Etsy 公司开发的一个守护进程,用于聚合和收集应用程序指标。通常,任何应用程序都可以使用简单的协议,如用户数据报协议UDP)发送指标。使用此协议,发送者不需要等待 StatsD 的响应,这使得过程变得简单。在监听和聚合数据一段时间后,StatsD 将指标发送到输出存储,即 Prometheus。

StatsD 的集成和安装可以使用以下命令完成:

pip install 'apache-airflow[statsd]'

如果你想了解更多,可以参考 Airflow 文档此处:airflow.apache.org/docs/apache-airflow/2.5.1/administration-and-deployment/logging-monitoring/metrics.xhtml#counters

然后,Prometheus 和 Grafana 将收集指标并将它们转换成更直观的资源。你现在不需要担心这个问题;我们将在第十二章中了解更多。

对于在“如何做…”部分的前三个步骤中看到的每个指标,我们都可以设置一个阈值,当它超过阈值时触发警报。所有指标都在“如何做…”部分中展示,更多指标可以在这里找到:https://airflow.apache.org/docs/apache-airflow/2.5.1/administration-and-deployment/logging-monitoring/metrics.xhtml#counters。

还有更多…

除了 StatsD,我们还可以将其他工具集成到 Airflow 中以跟踪特定的指标或状态。例如,对于深度错误跟踪,我们可以使用 Sentry,这是一个由 IT 运维团队使用的专业工具,用于提供支持和见解。你可以在这里了解更多关于此集成:airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/logging-monitoring/errors.xhtml

另一方面,如果跟踪用户活动是一个关注点,可以将 Airflow 与 Google Analytics 集成。你可以在这里了解更多:airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/logging-monitoring/tracking-user-activity.xhtml

参见

使用通知操作员

到目前为止,我们一直专注于确保代码有良好的日志记录,并且有足够的信息来提供有效的监控。然而,拥有成熟和结构化的管道的目的是避免手动干预的需要。在忙碌的日程和其他项目之间,持续查看监控仪表板以检查一切是否正常是很困难的。

幸运的是,Airflow 还具有本机操作员,可以根据其配置的情况触发警报。在这个菜谱中,我们将配置一个电子邮件操作员,以便在管道成功或失败时触发消息,使我们能够快速解决问题。

准备工作

请参考此菜谱的技术要求部分,因为我们将以相同的技术来处理它。

此外,你还需要为你的 Google 账户创建一个应用密码。这个密码将允许我们的应用程序进行身份验证并使用 Google 的 简单邮件传输协议 (SMTP) 主机来触发电子邮件。你可以在以下链接中生成应用密码:security.google.com/settings/security/apppasswords

一旦你访问了链接,你将需要使用你的 Google 凭据进行身份验证,并将出现一个新页面,类似于以下内容:

图 10.32 – Google 应用密码生成页面

图 10.32 – Google 应用密码生成页面

在第一个框中,选择 邮件,在第二个框中,选择将使用应用密码的设备。由于我使用的是 Macbook,所以我将选择 Mac,如前述截图所示。然后,点击 生成

将会出现一个类似于以下窗口:

图 10.33 – Google 生成的应用密码弹出窗口

图 10.33 – Google 生成的应用密码弹出窗口

按照页面上的步骤操作,并将密码保存在你可以记住的地方。

Airflow DAG 代码

为了避免冗余并专注于此菜谱的目标,即配置 Airflow 中的远程日志,我们将使用与 在 Airflow 中创建基本日志 菜谱相同的 DAG。然而,你也可以创建另一个具有不同名称但相同代码的 DAG。

尽管如此,你仍然可以在以下 GitHub 仓库中找到最终的代码:

github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook/tree/main/Chapter_10/Using_notifications_operators

如何做…

执行以下步骤来尝试这个菜谱:

  1. 让我们先从在 Airflow 中配置 SMTP 服务器开始。在你的 docker-compose.yaml 文件的环境部分插入以下行:

        # SMTP settings
        AIRFLOW__SMTP__SMTP_HOST: "smtp.gmail.com"
        AIRFLOW__SMTP__SMTP_USER: "your_email_here"
        AIRFLOW__SMTP__SMTP_PASSWORD: "your_app_password_here"
        AIRFLOW__SMTP__SMTP_PORT: 587
    

你的文件应该看起来像这样:

图 10.34 – 包含 SMTP 环境变量的 docker-compose.yaml

图 10.34 – 包含 SMTP 环境变量的 docker-compose.yaml

如果你直接编辑 airflow.cfg 文件,编辑以下行:

[smtp]
# If you want airflow to send emails on retries, failure, and you want to use
# the airflow.utils.email.send_email_smtp function, you have to configure an
# smtp server here
smtp_host = smtp.gmail.com
smtp_starttls = True
smtp_ssl = False
# Example: smtp_user = airflow
smtp_user = your_email_here
# Example: smtp_password = airflow
smtp_password = your_app_password_here
smtp_port = 587
smtp_mail_from = airflow@example.com
smtp_timeout = 30
smtp_retry_limit = 5

在保存这些配置后,不要忘记重新启动 Airflow。

  1. 现在,让我们编辑我们的 basic_logging_dag DAG,以便它可以使用 EmailOperator 发送电子邮件。让我们向我们的导入中添加以下行:

    from airflow.operators.email import EmailOperator
    

导入将按照以下方式组织:

from airflow import DAG
from airflow.operators.python_operator import PythonOperator
from airflow.operators.email import EmailOperator
from datetime import datetime, timedelta
import logging
# basic_logging_dag DAG code
# ...
  1. default_args 中,我们将添加三个新参数 – emailemail_on_failureemail_on_retry。你在这里可以看到它的样子:

    # basic_logging_dag DAG imports above this line
    default_args = {
        'owner': 'airflow',
        'depends_on_past': False,
        'start_date': datetime(2023, 4, 1),
        'email': ['sample@gmail.com'],
        'email_on_failure': True,
        'email_on_retry': True,
        'retries': 1,
        'retry_delay': timedelta(minutes=5)
    }
    # basic_logging_dag DAG code
    # …
    

目前你不需要担心这些新参数。我们将在 它是如何工作的 部分介绍它们。

  1. 然后,让我们在我们的 DAG 中添加一个名为success_task的新任务。如果所有其他任务都成功,这个任务将触发EmailOperator来提醒我们。将以下代码添加到basic_logging_dag脚本中:

    success_task = EmailOperator(
        task_id="success_task",
        to= "g.esppen@gmail.com",
        subject="The pipeline finished successfully!",
        html_content="<h2> Hello World! </h2>",
        dag=dag
    )
    
  2. 最后,在脚本末尾,让我们添加工作流程:

    extract_task >> transform_task >> load_task >> success_task
    

不要忘记,您始终可以在这里检查最终代码的样式:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook/tree/main/Chapter_10/Using_noti%EF%AC%81cations_operators

  1. 如果你检查你的 DAG 图,你可以看到出现了一个名为success_task的新任务。它表明我们的操作符已经准备好使用。让我们通过在右上角选择播放按钮来触发我们的 DAG,就像我们在步骤 3中配置airflow.cfg日志时做的那样。

你的 Airflow UI 应该看起来像这样:

图 10.35 – basic_logging_dag 显示所有任务的成功运行

图 10.35 – basic_logging_dag 显示所有任务的成功运行

  1. 然后,让我们检查我们的电子邮件。如果一切配置正确,你应该会看到以下类似的电子邮件:

图 10.36 – 包含 Hello World!信息的电子邮件,表明 success_task 已成功执行

图 10.36 – 包含 Hello World!信息的电子邮件,表明 success_task 已成功执行

我们的EmailOperator工作得完全符合预期!

它是如何工作的…

让我们通过定义什么是 SMTP 服务器来开始解释代码。SMTP 服务器是电子邮件系统的一个关键组件,它使得服务器之间以及客户端到服务器的电子邮件消息传输成为可能。

在我们的案例中,Google 既作为发送者又作为接收者工作。我们借用一个 Gmail 主机来帮助我们从本地机器发送电子邮件。然而,当你在公司项目上工作时,你不需要担心这一点;你的 IT 运维团队会处理它。

现在,回到 Airflow – 一旦我们理解了 SMTP 的工作原理,其配置就很简单了。查阅 Airflow 配置的参考页面(airflow.apache.org/docs/apache-airflow/stable/configurations-ref.xhtml),我们可以看到有一个专门针对 SMTP 的部分,正如你在这里看到的:

图 10.37 – Airflow SMTP 环境变量的文档页面

图 10.37 – Airflow SMTP 环境变量的文档页面

然后,我们只需要设置必要的参数,以允许主机(smtp.gmail.com)和 Airflow 之间的连接,正如你在这里看到的:

图 10.38 – 仔细查看 docker-compose.yaml 的 SMTP 设置

图 10.38 – 仔细查看 docker-compose.yaml 的 SMTP 设置

一旦完成这一步,我们将转到我们的 DAG 并声明EmailOperator,如下面的代码所示:

success_task = EmailOperator(
    task_id="success_task",
    to="g.esppen@gmail.com",
    subject="The pipeline finished successfully!",
    html_content="<h2> Hello World! </h2>",
    dag=dag
)

电子邮件的参数非常直观,可以根据需要相应地设置。如果我们进一步深入,我们可以看到有很多可能性使这些字段的值更加抽象,以适应不同的功能结果。

还可以使用html_content中的格式化电子邮件模板,甚至附加完整的错误或日志消息。你可以在这里看到更多允许的参数:airflow.apache.org/docs/apache-airflow/stable/_api/airflow/operators/email/index.xhtml

在我们的案例中,这个操作员是在所有任务成功运行时触发的。但如果出现错误怎么办?让我们回到步骤 3并查看default_args

default_args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'start_date': datetime(2023, 4, 1),
    'email': ['sample@gmail.com'],
    'email_on_failure': True,
    'email_on_retry': True,
    'retries': 1,
    'retry_delay': timedelta(minutes=5)
}

新增的两个参数(email_on_failureemail_on_retry)解决了 DAG 失败或重试任务的情况。email参数列表中的值是这些电子邮件的收件人。

由错误消息触发的默认电子邮件看起来像这样:

图 10.39 – Airflow 任务实例中的默认错误电子邮件

图 10.39 – Airflow 任务实例中的默认错误电子邮件

还有更多...

Airflow 的通知系统不仅限于发送电子邮件和计数,还提供了与 Slack、Teams 和 Telegram 的有用集成。

TowardsDataScience 有一篇关于如何将 Airflow 与 Slack 集成的精彩博客文章,你可以在这里找到它:towardsdatascience.com/automated-alerts-for-airflow-with-slack-5c6ec766a823

不仅限于企业工具,Airflow 还有一个 Discord 钩子:airflow.apache.org/docs/apache-airflow-providers-discord/stable/_api/airflow/providers/discord/hooks/discord_webhook/index.xhtml

我能给出的最好建议是始终查看 Airflow 社区文档。作为一个开源和活跃的平台,总有新的实现来帮助我们自动化并使我们的日常工作更轻松。

使用 SQL 操作员进行数据质量

优秀的数据质量对于一个组织来说至关重要,以确保其数据系统的有效性。通过在 DAG 中执行质量检查,可以在错误数据被引入生产湖或仓库之前停止管道并通知利益相关者。

尽管市场上有很多可用的工具提供数据质量检查,但最受欢迎的方法之一是通过运行 SQL 查询来完成。正如你可能已经猜到的,Airflow 提供了支持这些操作的服务提供者。

这个食谱将涵盖数据摄入过程中的数据质量主要主题,指出在这些情况下运行的最佳SQLOperator类型。

准备工作

在开始我们的练习之前,让我们创建一个简单的customers表。你可以看到它的样子如下:

图 10.40 – 客户表列的一个示例

图 10.40 – 客户表列的一个示例

同样的表用其模式表示:

CREATE TABLE customers (
    customer_id INT PRIMARY KEY,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    email VARCHAR(100),
    phone_number VARCHAR(20),
    address VARCHAR(200),
    city VARCHAR(50),
    state VARCHAR(50),
    country VARCHAR(50),
    zip_code VARCHAR(20)
);

你不需要担心在 SQL 数据库中创建此表。这个练习将专注于要检查的数据质量因素,并以这个表为例。

如何做到这一点...

下面是执行此食谱的步骤:

  1. 让我们先定义以下基本数据质量检查:

图 10.41 – 数据质量基本要点

图 10.41 – 数据质量基本要点

  1. 让我们想象使用集成并安装在我们 Airflow 平台中的SQLColumnCheckOperator来实现它。现在,让我们创建一个简单的任务来检查我们的表是否有唯一的 ID,以及所有客户是否都有first_name。我们的示例代码如下:

    id_username_check = SQLColumnCheckOperator(
            task_id="id_username_check",
            conn_id= my_conn,
            table=my_table,
            column_mapping={
                "customer_id": {
                    "null_check": {
                        "equal_to": 0,
                        "tolerance": 0,
                    },
                    "distinct_check": {
                        "equal_to": 1,
                    },
                },
                "first_name": {
                    "null_check": {"equal_to": 0},
                },
            }
    )
    
  2. 现在,让我们验证是否使用SQLTableCheckOperator摄入了所需的行数,如下所示:

    customer_table_rows_count = SQLTableCheckOperator(
        task_id="customer_table_rows_count",
        conn_id= my_conn,
        table=my_table,
        checks={"row_count_check": {
                    "check_statement": "COUNT(*) >= 1000"
                }
            }
    )
    
  3. 最后,让我们确保数据库中的客户至少有一个订单。我们的示例代码如下:

    count_orders_check = SQLColumnCheckOperator(
        task_id="check_columns",
        conn_id=my-conn,
        table=my_table,
        column_mapping={
            "MY_NUM_COL": {
                "min": {"geq_to ": 1}
            }
        }
    )
    

geq_to键代表大于或等于

它是如何工作的...

数据质量是一个复杂的话题,涉及许多变量,如项目或公司背景、商业模式以及团队之间的服务水平协议SLAs)。基于此,本食谱的目标是提供数据质量的核心概念,并展示如何首先使用 Airflow SQLOperators 进行尝试。

让我们从步骤 1中的基本主题开始,如下所示:

图 10.42 – 数据质量基本要点

图 10.42 – 数据质量基本要点

在一个通用场景中,这些项目是我们要处理和实施的主要主题。它们将保证基于列是否是我们预期的、创建行数的平均值、确保 ID 唯一以及控制特定列中的null和唯一值的最小数据可靠性。

使用 Airflow,我们采用了 SQL 方法来检查数据。正如本食谱开头所述,SQL 检查因其简单性和灵活性而广泛流行。不幸的是,为了模拟此类场景,我们需要设置一个勤奋的本地基础设施,而我们能想到的最好的办法是在 Airflow 中模拟任务。

在这里,我们使用了两种SQLOperator子类型——SQLColumnCheckOperatorSQLTableCheckOperator。正如其名称所示,第一个操作员更专注于通过检查是否存在 null 或唯一值来验证列的内容。在customer_id的情况下,我们验证了两种情况,并且对于first_name只有 null 值,如下所示:

column_mapping={
            "customer_id": {
                "null_check": {
                    "equal_to": 0,
                    "tolerance": 0,
                },
                "distinct_check": {
                    "equal_to": 1,
                },
            },
            "first_name": {
                "null_check": {"equal_to": 0},
            },
        }

SQLTableCheckOperator将对整个表进行验证。它允许插入一个 SQL 查询来进行计数或其他操作,就像我们在步骤 3中验证预期行数一样,如以下代码片段所示:

    checks={"row_count_check": {
                "check_statement": "COUNT(*) >= 1000"
            }
        }

然而,SQLOperator并不局限于这两者。在 Airflow 文档中,你可以看到其他示例和这些函数接受的完整参数列表:airflow.apache.org/docs/apache-airflow/2.1.4/_api/airflow/operators/sql/index.xhtml#module-airflow.operators.sql

一个值得关注的出色操作符是SQLIntervalCheckOperator,用于验证历史数据并确保存储的信息简洁。

在你的数据生涯中,你会发现数据质量是团队日常讨论和关注的话题。在这里最好的建议是持续寻找工具和方法来改进这一方法。

还有更多…

我们可以使用额外的工具来增强我们的数据质量检查。为此推荐的工具之一是GreatExpectations,这是一个用 Python 编写的开源平台,具有丰富的集成,包括 Airflow、AWS S3Databricks等资源。

虽然这是一个你可以安装在任何集群上的平台,但GreatExpectations正在扩展到托管云版本。你可以在官方页面了解更多信息:greatexpectations.io/integrations

参见

进一步阅读

第十一章:自动化您的数据摄取管道

数据源经常更新,这需要我们更新我们的数据湖。然而,随着多个来源或项目的增加,手动触发数据管道变得不可能。数据管道自动化使摄取和处理数据变得机械化,消除了触发它的手动操作。自动化配置的重要性在于能够简化数据流并提高数据质量,减少错误和不一致性。

在本章中,我们将介绍如何在 Airflow 中自动化数据摄取管道,以及数据工程中的两个重要主题:数据复制和历史数据摄取,以及最佳实践。

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

  • 安排每日摄取

  • 安排历史数据摄取

  • 安排数据复制

  • 设置 schedule_interval 参数

  • 解决调度错误

技术要求

您可以从本章的 GitHub 仓库中找到代码,网址为 github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook/tree/main/Chapter_11

安装和运行 Airflow

本章要求在您的本地机器上安装 Airflow。您可以直接在 操作系统OS)上安装它,或使用 Docker 镜像。有关更多信息,请参阅 第一章 中的 配置 Docker 以用于 Airflow 菜谱。

在遵循 第一章 中描述的步骤后,请确保您的 Airflow 实例运行正确。您可以通过检查 http://localhost:8080 上的 Airflow UI 来做到这一点。

如果您像我一样使用 Docker 容器来托管您的 Airflow 应用程序,您可以使用以下命令在终端中检查其状态:

$ docker ps

这是容器状态:

图 11.1 – 运行的 Airflow 容器

图 11.1 – 运行的 Airflow 容器

或者,您可以在 Docker Desktop 上检查容器状态:

图 11.2 – Docker Desktop 显示运行中的 Airflow 容器

图 11.2 – Docker Desktop 显示运行中的 Airflow 容器

安排每日摄取

在我们动态的世界中,数据不断变化,每天都有新的信息被添加,甚至每秒都有。因此,定期更新我们的数据湖以反映最新的场景和信息至关重要。

在集成来自各种来源的新数据的同时,管理多个项目或管道并手动触发它们可能会很困难。为了解决这个问题,我们可以依赖调度器,Airflow 提供了一个简单直接的解决方案。

在这个菜谱中,我们将创建一个简单的 有向无环图DAG)在 Airflow 中,并探讨如何使用其参数来安排管道每天运行。

准备工作

请参考此菜谱的技术要求部分,因为我们将以这里提到的相同技术来处理它。

在这个练习中,我们将创建一个简单的 DAG。你的 Airflow 文件夹的结构应该如下所示:

图 11.3 – daily_ingestion_dag DAG 文件夹结构

图 11.3 – daily_ingestion_dag DAG 文件夹结构

此菜谱中的所有代码都将放置在daily_ingestion_dag.py文件中。确保你已经按照图 11.3中的文件夹结构创建了该文件。

如何做…

这是此菜谱的步骤:

  1. 让我们先导入所需的库:

    from airflow import DAG
    from airflow.operators.bash import BashOperator
    from datetime import datetime, timedelta
    
  2. 现在,我们将为我们的 DAG 定义default_args。对于start_date参数,插入今天的日期或你做这个练习前几天。对于end_date,插入今天日期几天后的日期。最后,它应该看起来像以下这样:

    default_args = {
        'owner': 'airflow',
        'depends_on_past': False,
        'email': ['airflow@example.com'],
        'email_on_failure': True,
        'email_on_retry': True,
        'retries': 1,
        'retry_delay': timedelta(minutes=5),
        'start_date': datetime(2023, 4, 12),
        'end_date': datetime(2023, 4, 30),
        'schedule_interval': '@daily
    }
    
  3. 然后,我们将定义我们的 DAG 及其内部的任务。由于我们想专注于如何安排每日摄取,我们的每个任务都将是一个BashOperator,因为它们可以简单地执行 Bash 命令,正如你在这里看到的:

    with DAG(
        'daily_ingestion_dag',
        default_args=default_args,
        description='A simple ETL job using Bash commands',
    ) as dag:
        t1 = BashOperator(
                    task_id="t1",
                    bash_command="echo 'This is task no1 '",
                )
        t2 = BashOperator(
                    task_id="t2",
                    bash_command="echo 'This is task no2 '",
                )
    t1 >> t2
    
  4. DAG 编写完成后,让我们在 Airflow UI 上启用它,DAG 应该立即运行。运行后,DAG 将有一个SUCCESS状态,如下所示:

图 11.4 – Airflow UI 中的 daily_ingestion_dag DAG

图 11.4 – Airflow UI 中的 daily_ingestion_dag DAG

如果我们检查日志,它将显示类似于以下内容的echo命令输出:

[2023-04-12, 19:54:38 UTC] [ 1686 - airflow.hooks.subprocess.SubprocessHook ] {subprocess.py:74} INFO - Running command: ['bash', '-c', "echo 'This is task no2 '"]
[2023-04-12, 19:54:38 UTC] [ 1686 - airflow.hooks.subprocess.SubprocessHook ] {subprocess.py:85} INFO - Output:
[2023-04-12, 19:54:38 UTC] [ 1686 - airflow.hooks.subprocess.SubprocessHook ] {subprocess.py:92} INFO - This is task no2
  1. 现在,我们需要确保 DAG 将每天运行。为了确认这一点,在你的 DAG 页面上选择日历选项。你会看到类似以下的内容:

图 11.5 – Airflow UI 中 DAG 的日历可视化

图 11.5 – Airflow UI 中 DAG 的日历可视化

如你所见,执行过程被描绘在左侧的阴影区域,表示成功的结果(end_date,用点标记,表示工作将在接下来的几天内每天运行)。

注意

图 11.5显示了工作成功执行的一些日子。这是向用户展示相同日历在先前执行中的行为。

它是如何工作的…

Airflow 的调度器主要由三个参数定义:start_dateend_dateschedule_interval。这三个参数定义了作业的开始和结束以及执行之间的间隔。

让我们来看看default_args

default_args = {
    'owner': 'airflow',
    ...
    'start_date': datetime(2023, 4, 12),
    'end_date': datetime(2023, 4, 30),
    'schedule_interval': '@daily
}

由于我是在 2023 年 4 月 12 日写这个练习的,我将我的start_date参数设置为同一天。这将使工作检索与 4 月 12 日相关的信息,如果我将它放在当前日期几天前,Airflow 将检索更早的日期。现在不用担心这个问题;我们将在调度历史数据摄取菜谱中更详细地介绍这一点。

关键在于schedule_interval参数。正如其名称所暗示的,该参数将定义每次执行的周期性或间隔,并且如您所观察到的,它简单地使用@daily值进行了设置。

DAG UI 页面上的日历选项是 Airflow 2.2 及以后版本的一个优秀功能。此功能允许开发者查看 DAG 的下一个执行日期,从而避免一些混淆。

更多内容…

DAG 参数不仅限于我们在本食谱中看到的那些。还有许多其他参数可供选择,可以使数据处理管道更加自动化和智能化。让我们看一下以下代码:

default_args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'email': ['airflow@example.com'],
    'email_on_failure': True,
    'email_on_retry': True,
    'retries': 1,
    'retry_delay': timedelta(minutes=5),
    'start_date': datetime(2023, 4, 12),
    'end_date': datetime(2023, 4, 30),
    'schedule_interval': '@daily,
    'queue': 'bash_queue',
    'pool': 'backfill',
    'priority_weight': 10
}

这里还有三个额外的参数:queuepoolpriority_weight。正如我们在第九章第十章中看到的,Airflow 架构包括一个队列(通常由pool参数限制同时作业的数量。最后,priority_weight,正如其名称所暗示的,定义了 DAG 相对于其他 DAG 的优先级。

您可以在 Airflow 官方文档中了解更多关于这些参数的信息:

airflow.apache.org/docs/apache-airflow/1.10.2/tutorial.xhtml

参考信息

您还可以在crontab.guru/上了解更多关于使用 crontab 进行调度的信息。

调度历史数据摄取

历史数据对于数据驱动决策至关重要,它提供了有价值的见解并支持决策过程。它也可以指代在一段时间内积累的数据。例如,一家销售公司可以使用以前营销活动的历史数据来查看它们如何影响特定产品多年的销售。

本练习将展示如何在 Airflow 中创建一个调度器,以使用最佳实践和与此过程相关的常见问题来摄取历史数据。

准备工作

请参考本食谱的技术要求部分,因为我们将会使用这里提到的相同技术来处理它。

在这个练习中,我们将在我们的 DAGs 文件夹内创建一个简单的 DAG。您的 Airflow 文件夹结构应该如下所示:

图 11.6 – 本地 Airflow 目录中 historical_data_dag 文件夹结构

图 11.6 – 本地 Airflow 目录中 historical_data_dag 文件夹结构

如何操作…

这里是这个食谱的步骤:

  1. 让我们从导入我们的库开始:

    from airflow import DAG
    from airflow.operators.python_operator import PythonOperator
    from datetime import datetime, timedelta
    
  2. 现在,让我们定义default_args。由于我们希望处理旧数据,我将设置start_datedatetime在当前日期之前,而end_date将接近当前日期。以下代码展示了这一点:

    default_args = {
        'owner': 'airflow',
        'depends_on_past': False,
        'email': ['airflow@example.com'],
        'email_on_failure': True,
        'email_on_retry': True,
        'retries': 1,
        'retry_delay': timedelta(minutes=5),
        'start_date': datetime(2023, 4, 2),
        'end_date': datetime(2023, 4, 10),
        'schedule_interval': '@daily'
    }
    
  3. 然后,我们将创建一个简单的函数来打印 Airflow 执行管道所使用的日期。您可以在以下位置看到它:

    def my_task(execution_date=None):
        print(f"execution_date:{execution_date}")
    
  4. 最后,我们将声明我们的 DAG 参数和一个PythonOperator任务来执行它,正如您所看到的:

    with DAG(
        'historical_data_dag',
        default_args=default_args,
        description='A simple ETL job using Python commands to retrieve historical data',
    ) as dag:
        p1 = PythonOperator(
                    task_id="p1",
                    python_callable=my_task,
            )
    p1
    
  5. 转向 Airflow UI,让我们按照常规步骤启用 DAG 并查看其执行情况。在historical_data_dag页面上,你应该看到以下截图类似的内容:

图 11.7 – Airflow UI 中的 historical_data_dag DAG

图 11.7 – Airflow UI 中的 historical_data_dag DAG

如您所见,任务成功运行。

  1. 现在,让我们检查我们的logs文件夹。如果我们选择与创建的 DAG 同名(historical_data_dag)的文件夹,我们将观察到不同日期的run_id实例,从 4 月 2 日开始,到 4 月 10 日结束:

图 11.8 – 显示回溯摄入的 Airflow 日志文件夹

图 11.8 – 显示回溯摄入的 Airflow 日志文件夹

  1. 让我们打开第一个run_id文件夹,以探索该次运行的日志:

图 11.9 – 2023 年 4 月 2 日的 DAG 日志

图 11.9 – 2023 年 4 月 2 日的 DAG 日志

日志告诉我们execution_date参数,它与start_date参数相同。

下面是日志的更详细查看:

[2023-04-12 20:10:25,205] [ ... ] {logging_mixin.py:115} INFO - execution_date:2023-04-02T00:00:00+00:00
[2023-04-12 20:10:25,205] [ ... ] {python.py:173} INFO - Done. Returned value was: None

我们将观察到 4 月 3 日run_id相同的模式:

图 11.10 – 2023 年 4 月 3 日的 DAG 日志

图 11.10 – 2023 年 4 月 3 日的 DAG 日志

下面是日志输出的更详细查看:

2023-04-12 20:10:25,276] [ ... ] {logging_mixin.py:115} INFO - execution_date:2023-04-03T00:00:00+00:00
[2023-04-12 20:10:25,276] [...] {python.py:173} INFO - Done. Returned value was: None

execution_date也指的是 4 月 3 日。

这表明 Airflow 已经使用了在start_dateend_date上声明的间隔来运行任务!

现在,让我们继续了解调度器是如何工作的。

它是如何工作的…

正如我们所见,使用 Airflow 安排和检索历史数据很简单,关键参数是start_dateend_dateschedule_interval。让我们更详细地讨论它们:

  • start_date参数定义了当管道被触发时 Airflow 将查看的第一个日期。在我们的例子中,它是 4 月 2 日。

  • 接下来是end_date。通常,即使对于周期性摄入,这也不是一个强制参数。然而,使用它的目的是为了展示我们可以设置一个日期作为停止摄入的限制。

  • 最后,schedule_interval决定了两个日期之间的间隔。在这个练习中,我们使用了日间隔,但如果我们需要更细粒度的历史摄入,我们也可以使用crontab。我们将在设置 schedule_interval 参数菜谱中更详细地探讨这一点。

有了这个信息,理解我们从 Airflow 获得的日志就更容易了:

图 11.11 – 显示历史摄入的 Airflow 日志文件夹

图 11.11 – 显示历史摄入的 Airflow 日志文件夹

每个文件夹代表每天发生一次的历史摄入。由于我们没有定义更细粒度的日期时间规范,文件夹名称使用的是作业被触发的时间。这些信息不包括在日志中。

为了展示 Airflow 在幕后使用的是哪个日期,我们创建了一个简单的函数:

def my_task(execution_date=None):
    print(f"execution_date:{execution_date}")

函数的唯一目的是显示任务的执行日期。execution_date 参数是一个内部参数,显示任务何时执行,并且可以被操作员或其他函数用于根据日期执行某些操作。

例如,假设我们需要检索存储为分区的历史数据。我们可以使用 execution_date 将日期时间信息传递给 Spark 函数,该函数将读取并检索具有相同日期信息的分区中的数据。

如您所见,在 Airflow 中检索历史数据/信息需要一些配置。一个好的做法是为历史数据处理保留一个单独且专门的 DAG,这样就不会影响当前的数据摄取。此外,如果需要重新处理数据,我们可以通过一些参数更改来完成。

还有更多...

在使用 Airflow 摄取历史数据的技巧中,有两个重要的概念:catchupbackfill

调度和运行可能因各种原因而遗漏的过去时期的 DAG,通常被称为 schedule_interval。默认情况下,此功能在 Airflow 中已启用。因此,如果暂停或未创建的 DAG 的 start_date 在过去,它将自动为遗漏的时间间隔进行调度和执行。以下图表说明了这一点:

图 11.12 – Airflow 补充时间线。来源:https://medium.com/nerd-for-tech/airflow-catchup-backfill-demystified-355def1b6f92

图 11.12 – Airflow 补充时间线。来源:https://medium.com/nerd-for-tech/airflow-catchup-backfill-demystified-355def1b6f92

另一方面,Airflow 的 backfill 功能允许您对过去可能因 DAG 暂停、尚未创建或其他任何原因而遗漏的 DAG 执行回溯性执行,以及它们相关的任务。在 Airflow 中,回填是一个强大的功能,可以帮助您填补空白并赶上过去可能遗漏的数据处理或工作流程执行。

您可以在以下链接的 Amit Singh Rathore 的博客页面上了解更多信息:medium.com/nerd-for-tech/airflow-catchup-backfill-demystified-355def1b6f92.

调度数据复制

在本书的第一章中,我们介绍了数据复制是什么以及为什么它很重要。我们看到了这个过程在防止数据丢失和促进灾难恢复中的重要性。

现在,是时候学习如何创建一个优化的调度窗口,以便数据复制能够发生。在这个菜谱中,我们将创建一个图表,帮助我们决定复制数据的最佳时机。

准备工作

这个练习不需要技术准备。然而,为了使其更接近真实场景,让我们假设我们需要决定最佳方式来确保医院的数据得到充分复制。

我们将有两个管道:一个包含患者信息,另一个包含财务信息。第一个管道从患者数据库收集信息,并将其综合成医疗团队使用的可读报告。第二个管道将为医院管理层使用的内部仪表板提供数据。

由于基础设施限制,运维团队只有一个要求:只能快速复制一个管道的数据。

如何做...

下面是这个菜谱的步骤:

  1. 确定要复制的目标:如“准备就绪”部分所述,我们已经确定了目标数据,即包含患者信息的管道和用于仪表板的财务数据管道。

然而,如果这个信息不是从利益相关者或其他相关人士那里及时获得的,那么我们必须始终从识别我们项目中最关键的表或数据库开始。

  1. 复制周期性:我们必须根据我们数据的紧迫性或相关性来定义复制计划。让我们看看下面的图表:

图 11.13 – 数据复制的周期性

图 11.13 – 数据复制的周期性

如我们所见,数据越重要,建议的复制频率就越高。在我们的场景中,患者报告更适合在摄入后 30 分钟到 3 小时内进行,而财务数据可以复制到 24 小时后。

  1. 设置复制的时间窗口:现在,我们需要创建一个复制数据的时间窗口。这个复制决策需要考虑两个重要因素,如下面的图表所示:

图 11.14 – 复制窗口

图 11.14 – 复制窗口

基于两条管道(并记住我们需要优先考虑其中一条),建议在业务工作结束后每天复制一次财务数据,而患者数据可以在新信息到达时同时处理。

如果这看起来有点混乱,请不要担心。让我们在“如何它工作”部分中探讨细节。

它是如何工作的...

数据复制是一个确保数据可用性和灾难恢复的重要过程。其概念比当前的 ETL 过程更早,并且在本地数据库中已经使用了多年。我们今天的优势在于我们可以在任何时刻执行此过程。相比之下,由于硬件限制,复制在几年前有一个严格的调度窗口。

在我们的例子中,我们处理了两个具有不同严重程度的管道。背后的想法是教会有警觉性的眼睛决定何时进行每次复制。

第一个管道,即患者报告管道,处理敏感数据,如个人信息和医疗历史。它还可能有助于医生和其他卫生工作者帮助患者。

基于此,最佳做法是在数据处理后的几分钟或几小时内复制这些数据,以实现高可用性和冗余。

初看之下,财务数据似乎非常关键,需要快速复制;我们需要记住这个管道为仪表板提供数据,因此,分析师可以使用原始数据生成报告。

决定调度数据复制时,除了涉及的数据之外,还必须考虑其他因素。了解谁对数据感兴趣或需要访问数据,以及当数据不可用时它如何影响项目、区域或业务,也是至关重要的。

还有更多...

本食谱涵盖了设置数据复制调度议程的简单示例。我们还在步骤 3中讨论了进行此类操作时需要考虑的两个主要点。尽管如此,许多其他因素都可能影响调度器的性能和执行。以下是一些例子:

  • Airflow(或类似平台)托管在服务器上的位置

  • CPU 容量

  • 调度器的数量

  • 网络吞吐量

如果你想了解更多信息,你可以在 Airflow 文档中找到这些因素的完整列表:airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/scheduler.xhtml#what-impacts-scheduler-s-performance

这份文档的伟大之处在于,其中许多点也适用于其他数据处理程序,并可以作为指南。

设置schedule_interval参数

在 Airflow DAG 调度器配置中最广泛使用的参数之一是schedule_interval。与start_date一起,它为管道创建了一个动态和连续的触发器。然而,在设置schedule_interval时,我们仍需注意一些小细节。

本食谱将涵盖设置schedule_interval参数的不同形式。我们还将探讨一个实际示例,以了解 Airflow 中的调度窗口是如何工作的,从而使管理管道执行更加简单。

准备工作

虽然这项练习不需要任何技术准备,但建议记录下管道应该开始的时间和每个触发器之间的间隔。

如何做到这一点...

在这里,我们将仅展示default_args字典以避免代码冗余。然而,你始终可以在 GitHub 仓库中查看完整的代码:github.com/PacktPublishing/Data-Ingestion-with-Python-Cookbook/tree/main/Chapter_11/settingup_schedule_interval

让我们看看如何声明schedule_interval

  • schedule_interval的值可以使用诸如@daily@hourly@weekly这样的可访问名称。以下代码展示了它的样子:

    default_args = {
        'owner': 'airflow',
        'depends_on_past': False,
        'retries': 1,
        'retry_delay': timedelta(minutes=5),
        'start_date': datetime(2023, 4, 2),
        'schedule_interval': '@daily'
    }
    
  • 使用 crontab 语法表示的 schedule_interval

    default_args = {
        'owner': 'airflow',
        'depends_on_past': False,
        'retries': 1,
        'retry_delay': timedelta(minutes=5),
        'start_date': datetime(2023, 4, 2),
        'schedule_interval': '0 22 * * 1-5'
    }
    

在这种情况下,我们将调度器设置为每周工作日(周一至周五)晚上 10 点(或 22:00 小时)启动。

  • schedule_interval 是通过使用 timedelta 方法实现的。在下面的代码中,我们可以设置管道以一天为间隔触发:

    default_args = {
        'owner': 'airflow',
        'depends_on_past': False,
        'retries': 1,
        'retry_delay': timedelta(minutes=5),
        'start_date': datetime(2023, 4, 2),
        'schedule_interval': timedelta(days=1)
    }
    

它是如何工作的…

schedule_interval 参数是 Airflow 中调度 DAG 的一个重要方面,它提供了一种灵活的方式来定义你的工作流程应该多久执行一次。我们可以将其视为 Airflow 调度器的核心。

这个菜谱的目标是展示设置 schedule_interval 的不同方法以及何时使用它们。让我们更深入地探讨它们:

  • 友好名称:正如其名所示,这种表示法使用用户友好的标签或别名。它提供了一种简单方便的方式来指定计划任务运行的精确时间和日期。如果你没有特定的日期和时间来运行调度器,这可能是一个简单且直接的解决方案。

  • Crontab 语法:Crontab 在应用程序和系统中已经广泛使用很长时间了。Crontab 语法由五个字段组成,分别代表分钟、小时、月份中的日期、月份和星期几。当处理复杂的调度时,这是一个很好的选择,例如,在星期一和星期五下午 1 点执行触发器,或其他组合。

  • timedelta(minutes=5))。它也是一个用户友好的表示法,但具有更细粒度的功能。

注意

虽然我们已经在这里看到了三种设置 schedule_interval 的方法,但请记住,Airflow 不是一个流式解决方案,并且当有多个 DAG 以小间隔运行时,可能会过载服务器。如果你或你的团队需要每 10-30 分钟或更短的时间进行调度,请考虑使用流式工具。

参见

TowardsDataScience 有一个关于 schedule_interval 在幕后如何工作的精彩博客文章。你可以在这里找到它:towardsdatascience.com/airflow-schedule-interval-101-bbdda31cc463

解决调度错误

在这个阶段,你可能已经遇到了一些问题,即管道调度没有按预期触发。如果没有,请不要担心;这迟早会发生,而且是完全正常的。当有多个管道并行运行,在不同的窗口或连接到不同的时区时,出现一些纠缠是预料之中的。

为了避免这种纠缠,在这个练习中,我们将创建一个图表来辅助调试过程,识别 Airflow 中调度器不正确工作的可能原因,并了解如何解决它。

准备工作

这个菜谱不需要任何技术准备。尽管如此,记录下我们将遵循的步骤可能会有所帮助。在学习新事物时记录下来可以帮助我们在脑海中固定知识,使其更容易在以后记住。

回到我们的练习;Airflow 中的调度器错误通常会使 DAG 状态显示为 None,如下所示:

图 11.15 – Airflow UI 中的 DAG,调度器存在错误

图 11.15 – Airflow UI 中的 DAG,调度器存在错误

我们现在将找出如何修复错误并再次运行作业。

如何做…

让我们尝试确定我们的调度器中错误可能的原因。不用担心理解我们为什么使用这些方法。我们将在 如何工作 中详细说明:

  1. 我们首先检查 start_date 是否已设置为 datetime.now()。如果是这种情况,这里最好的方法是将此参数值更改为一个特定日期,如下所示:

图 11.16 – 由 start_date 参数引起的错误

图 11.16 – 由 start_date 参数引起的错误

代码将看起来像这样:

default_args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'retries': 1,
    'retry_delay': timedelta(minutes=5),
    'start_date': datetime(2023, 4, 2),
    'schedule_interval': '@daily'
}
  1. 现在我们可以验证 schedule_interval 是否与 start_date 参数对齐。在下面的图中,你可以看到三种修复问题的可能性:

图 11.17 – 由 start_date 和 schedule_interval 参数引起的错误

图 11.17 – 由 start_date 和 schedule_interval 参数引起的错误

你可以通过在 schedule_interval 中使用 crontab 语法来防止此错误,如下所示:

schedule_interval='0 2 * * *'

如果你遇到时区问题,你可以通过使用 pendulum 库来定义 Airflow 将在哪个时区触发作业:

pendulum.now("Europe/Paris")
  1. 最后,另一个标准的错误场景是 DAG 运行一段时间后 schedule_interval 发生变化。在这种情况下,通常的解决方案是重新创建 DAG:

图 11.18 – 由 schedule_interval 变化引起的错误

图 11.18 – 由 schedule_interval 变化引起的错误

在这些步骤结束时,我们将得到一个类似的调试图:

图 11.19 – 帮助识别 Airflow 调度器中错误引起的图

图 11.19 – 帮助识别 Airflow 调度器中错误引起的图

它是如何工作的…

如你所见,这个菜谱的目标是展示三种常见的与调度器相关的错误场景。调度器中的错误通常会导致 DAG 状态显示为 None,正如我们在 准备就绪 部分所看到的。然而,一个不符合预期行为的触发器也被认为是错误。现在,让我们探讨这三种解决场景及其解决方案。

第一个场景通常发生在我们想要为start_date使用当前日期时。虽然使用datetime.now()函数来表示当前日期时间似乎是个好主意,但 Airflow 不会像我们那样解释它。datetime.now()函数将创建我们所说的动态调度器,触发器永远不会被执行。这是因为执行调度使用start_dateschedule_interval来确定何时执行触发器,正如您在这里看到的:

图 11.20 – Airflow 执行调度方程

图 11.20 – Airflow 执行调度方程

如果我们使用datetime.now(),它将随时间移动,永远不会被触发。我们建议使用静态调度定义,正如我们在步骤 1中看到的。

一个典型的错误是当start_dateschedule_interval不匹配时。根据图 11.20的解释,我们已能想象到为什么使这两个参数对齐并防止重叠如此重要。正如步骤 2中提到的,防止这种情况的一个好方法是通过使用 crontab 记法来设置schedule_interval

一个重要的话题是涉及过程中的时区。如果您仔细查看 Airflow UI 的顶部,您将看到一个时钟及其关联的时区,如下所示:

图 11.21 – 显示时区的 Airflow UI 时钟

图 11.21 – 显示时区的 Airflow UI 时钟

这表明 Airflow 服务器正在 UTC 时区运行,所有 DAG 和任务都将使用相同的逻辑执行。如果您在不同的时区工作并希望确保它将根据您的时区运行,您可以使用pendulum库,如下所示:

schedule_interval = pendulum.now("Europe/Paris")

pendulum是一个第三方 Python 库,它使用内置的datetimePython 包提供简单的日期时间操作。您可以在pendulum官方文档中了解更多信息:pendulum.eustace.io/

最后,最后一个场景有一个简单的解决方案:如果schedule_interval在执行一些操作后发生变化,则重新创建 DAG。尽管这种错误可能并不总是发生,但重新创建 DAG 以防止进一步的问题是良好的实践。

还有更多...

我们在这个菜谱中提供了一些示例,说明如果调度器不工作,您可以检查的内容,但 Airflow 中可能发生的其他常见错误。您可以在以下Astronomer博客页面上了解更多信息:www.astronomer.io/blog/7-common-errors-to-check-when-debugging-airflow-dag/

在博客中,您可以找到其他一些场景,其中 Airflow 会抛出静默错误(或没有明确错误消息的错误)以及如何解决它们。

进一步阅读

第十二章:使用数据可观察性进行调试、错误处理和预防停机

我们即将结束数据摄取之旅,已经涵盖了众多重要主题,并看到了它们如何应用于实际项目。现在,为了以精彩的方式结束这本书,最后一个主题是 数据可观察性 的概念。

数据可观察性指的是在大组织或小项目中监控、理解和调试数据健康、质量和其他关键方面的能力。简而言之,它确保数据在需要时准确、可靠且可用。

虽然本章中的每个食谱都可以单独执行,但目标是配置工具,当它们一起设置时,可以创建一个监控和可观察性架构,为项目或团队带来价值。

你将学习以下食谱:

  • 设置 StatsD 以进行监控

  • 设置 Prometheus 以存储指标

  • 设置 Grafana 以进行监控

  • 创建可观察性仪表板

  • 设置自定义警报或通知

技术要求

本章要求 Airflow 安装在你的本地机器上。你可以直接在你的 操作系统OS)上安装它,或者使用 Docker 镜像。有关更多信息,请参阅 第一章配置 Docker 以支持 Airflow 的食谱。

在遵循了 第一章 中描述的步骤之后,请确保 Airflow 运行正确。你可以通过检查此链接的 Airflow UI 来做到这一点:http://localhost:8080

如果你像我一样使用 Docker 容器来托管你的 Airflow 应用程序,你可以通过以下命令在终端中检查其状态:

$ docker ps

下面是容器的状态:

图 12.1 – 运行的 Airflow 容器

图 12.1 – 运行的 Airflow 容器

图 12.2 – 运行的 Airflow 容器在 Docker Desktop 中的视图

图 12.2 – 运行的 Airflow 容器在 Docker Desktop 中的视图

Docker 镜像

本章要求创建其他 Docker 容器来构建监控和可观察性架构。如果你使用 docker-compose.yaml 文件来运行你的 Airflow 应用程序,你可以将此处提到的其他镜像添加到同一个 docker-compose.yaml 文件中,并一起运行。

如果你在本地上运行 Airflow,你可以单独创建和配置每个 Docker 镜像,或者只为本章中监控工具的方法创建一个 docker-compose.yaml 文件。

设置 StatsD 以进行监控

如在 第十章 中所述,StatsD 是一个开源守护进程,它收集和汇总关于应用程序行为的指标。由于其灵活性和轻量级,StatsD 被用于多个监控和可观察性工具,如 GrafanaPrometheusElasticSearch,以可视化和分析收集到的指标。

在此菜谱中,我们将使用 Docker 镜像作为构建监控管道的第一步来配置 StatsD。在这里,StatsD 将收集和汇总 Airflow 信息,并在 为存储指标设置 Prometheus 菜谱中使其对我们的监控数据库 Prometheus 可用。

准备工作

请参阅此菜谱的 技术要求 部分,因为我们将以相同的技术来处理它。

如何操作…

下面是执行此菜谱的步骤:

  1. 让我们从定义我们的 StatsD Docker 配置开始。这些行将被添加到 docker-compose 文件中的 services 部分下:

      statsd-exporter:
        image: prom/statsd-exporter
        container_name: statsd-exporter
        command: "--statsd.listen-udp=:8125 --web.listen-address=:9102"
        ports:
          - 9102:9102
          - 8125:8125/udp
    
  2. 接下来,让我们设置 Airflow 环境变量以安装 StatsD 并将其指标导出到它,如下所示:

    # StatsD configuration
    AIRFLOW__SCHEDULER__STATSD_ON: 'true'
    AIRFLOW__SCHEDULER__STATSD_HOST: statsd-exporter
    AIRFLOW__SCHEDULER__STATSD_PORT: 8125
    AIRFLOW__SCHEDULER__STATSD_PREFIX: airflow
    _PIP_ADDITIONAL_REQUIREMENTS: ${_PIP_ADDITIONAL_REQUIREMENTS:-apache-airflow[statsd]}
    

如果您需要帮助在 Airflow 中设置这些变量,请参阅 第十章在 airflow.cfg 中配置日志 菜谱。

您在 docker-compose 文件中的 Airflow 变量应如下所示:

图 12.3 – 带有 StatsD 配置的 Airflow 环境变量

图 12.3 – 带有 StatsD 配置的 Airflow 环境变量

  1. 现在,重新启动您的 Docker 容器以应用配置。

  2. 一旦这样做,并且所有容器都启动并运行,让我们在浏览器中检查 http://localhost:9102/ 地址。您应该看到以下页面:

图 12.4 – 浏览器中的 StatsD 页面

图 12.4 – 浏览器中的 StatsD 页面

  1. 然后,点击 指标,将出现一个新页面,显示以下内容类似:

图 12.5 – 浏览器中显示的 StatsD 指标

图 12.5 – 浏览器中显示的 StatsD 指标

浏览器中显示的行确认 StatsD 已成功安装并从 Airflow 收集数据。

它是如何工作的…

如您所观察到的,使用 Airflow 配置 StatsD 非常简单。实际上,StatsD 对我们来说并不陌生,因为我们已经在 第十章 中介绍了它,在 设计高级监控 菜谱中。然而,让我们回顾一些概念。

StatsD 是由 Etsy 员工构建的开源守护程序工具,它通过 用户数据报协议UDP)接收信息,由于它不需要向发送者发送确认消息,因此使其快速且轻量级。

现在,查看代码,我们首先做的事情是将 Docker 容器设置为运行 StatsD。除了运行容器的所有常规参数外,关键点是 command 参数,如下所示:

    command: "--statsd.listen-udp=:8125 --web.listen-address=:9102"
# StatsD configuration
AIRFLOW__SCHEDULER__STATSD_ON: 'true'
AIRFLOW__SCHEDULER__STATSD_HOST: statsd-exporter
AIRFLOW__SCHEDULER__STATSD_PORT: 8125
AIRFLOW__SCHEDULER__STATSD_PREFIX: airflow
_PIP_ADDITIONAL_REQUIREMENTS: ${_PIP_ADDITIONAL_REQUIREMENTS:-apache-airflow[statsd]}

相关内容

您可以在 Docker Hub 页面上检查 StatsD 的 Docker 镜像:hub.docker.com/r/prom/statsd-exporter

为存储指标设置 Prometheus

虽然它通常被称为数据库,但 Prometheus 并不是像 MySQL 这样的传统数据库。相反,它的结构更类似于为监控和可观察性目的设计的时序数据库。

由于其灵活性和强大功能,此工具被 DevOps 和站点可靠性工程师SREs)广泛用于存储系统与应用程序的相关指标和其他信息。与 Grafana(我们将在后续菜谱中探讨)一起,它是项目中团队最常用的监控工具之一。

此菜谱将配置一个 Docker 镜像以运行 Prometheus 应用程序。我们还将将其连接到 StatsD 以存储所有生成的指标。

准备工作

请参阅此菜谱的技术要求部分,因为我们将以相同的技术处理它。

如何操作…

执行此菜谱的步骤如下:

  1. 让我们从在docker-compose文件的services部分添加以下行开始:

      prometheus:
        image: prom/prometheus
        ports:
        - 9090:9090
        links:
          - statsd-exporter # Use the same name as your statsd container
        volumes:
          - ./prometheus:/etc/prometheus
        command:
          - '--config.file=/etc/prometheus/prometheus.yml'
          - --log.level=debug
          - --web.listen-address=:9090
          - --web.page-title='Prometheus - Airflow Metrics'
    
  2. 现在,在docker-compose文件同一级别创建一个名为prometheus的文件夹。在该文件夹内,创建一个名为prometheus.yml的新文件,并按照以下代码进行保存:

    scrape_configs:
      - job_name: 'prometheus'
        static_configs:
          - targets: ['prometheus:9090']
      - job_name: 'statsd-exporter'
        static_configs:
          - targets: ['statsd-exporter:9102']
    

static_configs中,确保目标具有与 StatsD 容器相同的名称和暴露的端口。否则,你将面临与容器建立连接的问题。

  1. 现在,重新启动你的 Docker 容器。

  2. 当容器恢复并运行时,在浏览器中访问以下链接:http://localhost:9090/

你应该会看到一个如下页面:

图 12.6 – Prometheus UI

图 12.6 – Prometheus UI

  1. 现在,点击页面右侧执行按钮旁边的列表图标。这将打开一个包含所有可用指标的列表。如果一切配置正确,你应该会看到以下类似的内容:

图 12.7 – Prometheus 可用指标列表

图 12.7 – Prometheus 可用指标列表

我们已成功设置 Prometheus,它已经存储了 StatsD 发送的指标!

它是如何工作的…

让我们通过检查步骤 1中的容器定义来更深入地探讨我们在这次练习中所做的工作。由于我们已经对 Docker 有基本了解,我们将涵盖容器设置中最关键的部分。

引人注目的是docker-compose文件中的links部分。在这个部分中,我们声明 Prometheus 容器必须连接并链接到在设置 StatsD 以 监控 菜单中配置的 StatsD 容器:

    links:
      - statsd-exporter # Use the same name as your statsd container

接下来,我们将volumes设置为将本地文件夹映射到容器内的文件夹。这一步至关重要,因为这样我们还可以镜像 Prometheus 的配置文件:

    volumes:
      - ./prometheus:/etc/prometheus

最后,在command部分,我们声明了配置文件将在容器内放置的位置以及其他一些小设置:

    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - --log.level=debug
      - --web.listen-address=:9090
      - --web.page-title='Prometheus - Airflow Metrics'

然后,以下步骤是专门用于设置 Prometheus 配置文件的,正如你在这里可以看到的:

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['prometheus:9090']
  - job_name: 'statsd-exporter'
    static_configs:
      - targets: ['statsd-exporter:9102']

根据定义,Prometheus 通过 HTTP 请求从自身和其他应用程序收集指标。换句话说,它解析响应并摄取收集的样本以进行存储。这就是为什么我们使用了 scrape_configs

如果你仔细观察,你会注意到我们声明了两个抓取作业:一个用于 Prometheus,另一个用于 StatsD。由于这个配置,我们可以在 Prometheus 指标列表中看到 Airflow 指标。如果我们需要包含任何其他抓取配置,我们只需编辑本地的 prometheus.yml 文件并重新启动服务器即可。

Prometheus 中还有许多其他配置可用,例如设置抓取间隔。你可以在官方文档页面上了解更多关于其配置的信息:prometheus.io/docs/prometheus/latest/getting_started/

更多内容...

在这个菜谱中,我们看到了如何设置 Prometheus 以存储来自 StatsD 的指标。这个时序数据库还有其他功能,例如在 Web UI 中创建小型可视化,以及与其他客户端库连接,并有一个名为 Alertmanager 的警报系统。

如果你想要深入了解 Prometheus 的工作原理和其他功能,Sudip Sengupta 有一个关于它的精彩博客文章,你可以在这里阅读:sudipsengupta.com/prometheus/

www.airplane.dev/blog/prometheus-metrics

设置 Grafana 以进行监控

Grafana 是一个开源工具,用于创建可视化并监控来自其他系统和应用程序的数据。与 Prometheus 一起,由于其灵活性和丰富的功能,它成为最受欢迎的 DevOps 工具之一。

在这个练习中,我们将配置一个 Docker 镜像以运行 Grafana 并将其连接到 Prometheus。这个配置不仅将使我们能够进一步探索 Airflow 指标,而且还有机会在实践中学习如何使用一组最流行的监控和可观察性工具。

准备工作

参考此菜谱的 技术要求 部分,因为我们将以相同的技术来处理它。

在这个菜谱中,我将使用 Airflow 的相同 docker-compose.yaml 文件,并保留从 设置 StatsD 以进行监控设置 Prometheus 以存储指标 菜谱中的配置,以将它们连接起来并继续进行监控和可观察性架构。

如何操作...

按照以下步骤尝试这个菜谱:

  1. 如下所示,让我们像往常一样将 Grafana 容器信息添加到我们的 docker-compose 文件中。确保它在 services 部分下:

      grafana:
        image: grafana/grafana:latest
        container_name: grafana
        environment:
          GF_SECURITY_ADMIN_USER: admin
          GF_SECURITY_ADMIN_PASSWORD: admin
          GF_PATHS_PROVISIONING: /grafana/provisioning
        links:
          - prometheus # use the same name of your Prometheus docker container
        ports:
          - 3000:3000
        volumes:
          - ./grafana/provisioning:/grafana/provisioning
    

随意使用不同的管理员用户名作为密码。

  1. 现在,在你的 Docker 文件同一级别创建一个名为 grafana 的文件夹,并重新启动你的容器。

  2. 在它恢复并运行后,将 http://localhost:3000/login 链接插入到你的浏览器中。将出现一个类似于这样的登录页面:

图 12.8 – Grafana 登录页面

图 12.8 – Grafana 登录页面

这确认了 Grafana 已正确设置!

  1. 然后,让我们使用管理员凭据登录到 Grafana 仪表板。认证后,你应该看到以下主页面:

图 12.9 – Grafana 主页面

图 12.9 – Grafana 主页面

由于这是我们第一次登录,此页面没有任何显示。我们将在 创建可观察性 仪表板 菜单中处理可视化。

  1. 现在,让我们将 Prometheus 添加为 Grafana 的数据源。在页面左下角,将鼠标悬停在引擎图标上。在 配置 菜单中,选择 数据源。以下截图供参考:

图 12.10 – Grafana 配置菜单

图 12.10 – Grafana 配置菜单

  1. 数据源 页面上,选择 Prometheus 图标。你将被重定向到一个新页面,显示插入 Prometheus 设置的字段,如你所见:

图 12.11 – Grafana 中的数据源页面

图 12.11 – Grafana 中的数据源页面

为此数据源插入一个名称。在 http://prometheus:9090。确保它与你的 Prometheus Docker 容器名称相同。

保存此配置,我们已经成功配置了带有 Prometheus 的 Grafana!

它是如何工作的…

在这个练习中,我们看到了如何简单配置 Grafana 并将其与 Prometheus 作为数据源集成。实际上,几乎所有的 Grafana 集成都非常简单,只需要几条信息。

现在,让我们探索一些我们的 Grafana 容器设置。尽管有标准的 Docker 容器设置,但有一些项目需要关注,如你所见:

  grafana:
    ...
    environment:
      GF_SECURITY_ADMIN_USER: admin
      GF_SECURITY_ADMIN_PASSWORD: admin
      GF_PATHS_PROVISIONING: /grafana/provisioning
    ...
    volumes:
      - ./grafana/provisioning:/grafana/provisioning

第一件事是 环境变量,我们在这里定义了允许第一次登录的管理员凭据。然后,我们声明了 Grafana 配置的路径,并且,正如你所注意到的,我们还在 volumes 部分插入了这个路径。

provisioning 文件夹中,我们将有数据源连接、插件、仪表板等的配置文件。这样的配置允许仪表板和面板有更高的可靠性和版本控制。我们也可以使用 .yaml 配置文件创建 Prometheus 数据源连接,并将其放置在 provisioningdatasources 文件夹下。它看起来可能如下所示:

apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090

任何额外的数据源都可以放置在这个 YAML 文件中。你可以在 Grafana 的官方文档页面了解更多关于配置配置的信息:grafana.com/docs/grafana/latest/administration/provisioning/

通过这种方式,我们创建了一个简单高效的监控和可观察性架构,能够从 Airflow(或任何其他应用程序,如果需要)收集指标,存储并显示它们。该架构可以定义为以下:

图 12.12 – 监控和可观察性高级架构

图 12.12 – 监控和可观察性高级架构

我们现在可以开始创建我们本章最后两个菜谱中的第一个仪表板和警报了!

更多内容…

除了 Prometheus 之外,Grafana 还为许多应用程序内置了核心数据源集成。它允许轻松配置和快速设置,这为项目带来了很多价值和成熟度。您可以在以下位置找到更多信息:grafana.com/docs/grafana/latest/datasources/#built-in-core-data-sources

Grafana Cloud

Grafana Labs 还将平台作为完全托管和云部署的服务提供。这对于没有专门运营团队来支持和维护 Grafana 的团队来说是一个很好的解决方案。更多信息请参阅:grafana.com/products/cloud/

创建可观察性仪表板

现在,随着我们的工具启动并运行,我们终于可以进入可视化仪表板了。监控和可观察性仪表板旨在帮助我们深入了解系统的健康和行为。您将在本练习中观察到 Grafana 如何帮助我们创建一个可观察性仪表板以及其中的许多功能。

在这个菜谱中,我们将创建一个包含几个面板的第一个仪表板,以更好地监控我们的 Airflow 应用程序。您将注意到,只需几个步骤,就可以对 Airflow 随时间的行为有一个概览,并准备好构建未来的面板。

准备工作

请参考此菜谱的 技术要求 部分,因为我们将以相同的技术来处理它。

要完成此练习,请确保 StatsD、Prometheus 和 Grafana 已正确配置并正在运行。

如何操作…

让我们创建我们的仪表板以跟踪 Airflow:

  1. 在 Grafana 主页上,将光标悬停在左侧面板的四个方块图标上。然后,选择 新建仪表板,如以下截图所示:

图 12.13 – Grafana 仪表板菜单

图 12.13 – Grafana 仪表板菜单

如果您需要帮助访问 Grafana,请参考 设置 Grafana 进行 监控 菜谱。

  1. 您将被重定向到一个标题为 新仪表板 的空白页面。在页面右上角,选择 保存,输入您仪表板的名称,然后再次点击 保存 按钮。请参考以下截图:

图 12.14 – 新仪表板页面

图 12.14 – 新仪表板页面

  1. 现在,让我们通过点击仪表板页面右上角的 添加面板 图标来创建我们的第一个面板,如图所示:

图 12.15 – 添加面板图标

图 12.15 – 添加面板图标

  1. 现在,让我们创建一个面板来显示 Airflow 内部的 DAG 数量。在 编辑面板 页面上,设置以下信息:

    • 指标airflow_dagbag_size

    • 标签过滤器jobstatsd-exporter

    • 可视化类型:统计

您可以在以下屏幕截图中看到填写的信息:

图 12.16 – Airflow DAG 数量面板计数

图 12.16 – Airflow DAG 数量面板计数

点击 应用 保存并返回仪表板页面。

  1. 让我们按照 步骤 3 的相同方法创建另一个面板。这次我们将创建一个面板来显示 Airflow 导入错误的数量。填写以下值:

    • 指标airflow_dag_processing_import_errors

    • 标签过滤器jobstatsd-exporter

    • 可视化类型:统计

您可以在以下屏幕截图中看到添加的信息:

图 12.17 – DAG 导入错误面板计数

图 12.17 – DAG 导入错误面板计数

  1. 现在,让我们创建两个带有以下信息的面板:

    • airflow_executor_queued_tasks

    • jobstatsd-exporter

    • 可视化类型:统计

    • 指标airflow_scheduler_tasks_running

    • 标签过滤器jobstatsd-exporter

    • 可视化类型:统计

  2. 让我们再创建两个面板来展示两个不同 DAG 的执行时间。创建两个面板,并填写以下值:

    • 指标airflow_dag_processing_last_duration_basic_logging_dag

    • 标签过滤器分位数0.99

    • 可视化类型:时间序列

请参考以下屏幕截图:

图 12.18 – basic_logging_dag 执行运行面板

图 12.18 – basic_logging_dag 执行运行面板

  • 指标airflow_dag_processing_last_duration_holiday_ingest_dag

  • 标签过滤器分位数0.99

  • 可视化类型:时间序列

您可以在以下屏幕截图中看到完成的字段:

图 12.19 – holiday_ingest_dag 执行运行面板

图 12.19 – holiday_ingest_dag 执行运行面板

最后,您将得到一个类似于以下仪表板的界面:

图 12.20 – 完整的 Airflow 监控仪表板视图

图 12.20 – 完整的 Airflow 监控仪表板视图

如果您的仪表板布局与 图 12.20 完全不同,请不要担心。您可以随意调整面板布局,以添加您自己的风格!

它是如何工作的...

市场上有很多 DevOps 可视化工具。然而,大多数都需要付费订阅或培训有素的人员来构建面板。正如你在这次练习中观察到的,使用 Grafana 创建第一个仪表板和面板可以相当简单。当然,随着你练习并学习 Grafana 的高级概念,你会观察到许多改进和增强仪表板的机会。

现在,让我们探索我们创建的六个面板。这些面板背后的想法是创建一个包含最少信息但已能带来价值的简易仪表板。

前四个面板提供了关于 Airflow 的快速和相关信息,如下所示:

图 12.21 –Airflow 监控计数面板

图 12.21 –Airflow 监控计数面板

它们显示了关于 DAG 数量、我们有多少个导入错误、等待执行的任务数量以及正在执行的任务数量的信息。尽管看起来很简单,但这些信息提供了 Airflow 当前行为的概述(因此,可观察性)。

最后两个面板显示了两个 DAG 执行持续时间的相关信息:

图 12.22 – Airflow 监控时间序列面板

图 12.22 – Airflow 监控时间序列面板

了解 DAG 运行所需的时间是至关重要的信息,它可以提供改进代码或检查管道中使用的数据的可靠性的洞察。例如,如果 DAG 在预期时间的一半以下完成所有任务,这可能是一个没有正确处理数据的信号。

最后,你可以创建更多仪表板并将它们根据主题组织到文件夹中。你可以在 Grafana 的官方文档中查看仪表板组织的推荐最佳实践:grafana.com/docs/grafana/latest/dashboards/build-dashboards/best-practices/

还有更多...

不幸的是,由于我们在仪表板上展示的数据有限,这次练习可能不像你预期的那么花哨。然而,你可以探索 Grafana 面板配置并掌握它们,以便在 Grafana playground 的进一步项目中使用:play.grafana.org/d/000000012/grafana-play-home?orgId=1

Grafana Play Home页面上,你将能够看到不同类型的面板应用,并探索它们是如何构建的。

设置自定义警报或通知

在配置我们的第一个仪表板以了解 Airflow 应用程序后,我们必须确保我们的监控始终处于观察之下。随着团队忙于其他任务,创建警报是确保我们仍然对应用程序保持监督的最佳方式。

创建警报和通知有许多方法,之前我们实现了一种类似的方法,当发生错误时通过发送电子邮件通知来监控我们的 DAG。现在,我们将尝试不同的方法,使用与 Telegram 的集成。

在这个菜谱中,我们将集成 Grafana 警报与 Telegram。使用不同的工具提供系统警报可以帮助我们理解最佳方法来建议我们的团队并打破总是使用电子邮件的循环。

准备中

请参考此菜谱的 技术要求 部分,因为我们将以相同的技术来处理它。

要完成这个练习,请确保 StatsD、Prometheus 和 Grafana 已正确配置并正在运行。此练习还需要一个 Telegram 账户。你可以在以下链接中找到创建账户的步骤:www.businessinsider.com/guides/tech/how-to-make-a-telegram-account

如何做到这一点...

执行此菜谱的步骤如下:

  1. 让我们从在 Telegram 上创建一个用于 Grafana 发送警报的机器人开始。在 Telegram 主页上,搜索 @BotFather 并按照以下方式开始对话:

图 12.23 – Telegram BotFather

图 12.23 – Telegram BotFather

  1. 然后,输入 /newbot 并遵循提示指令。BotFather 将会发送给你一个机器人令牌。请将其保存在安全的地方;我们稍后会用到它。消息看起来如下所示:

图 12.24 – 新建机器人消息

图 12.24 – 新建机器人消息

  1. 接下来,在 Telegram 上创建一个群组,并以管理员权限邀请你的机器人加入。

  2. 现在,让我们使用 Telegram API 来检查机器人所在的频道 ID。你可以在浏览器中使用以下地址来完成此操作:

    https://api.telegram.org/bot<YOUR CODE HERE>/getUpdates
    

你应该在浏览器中看到类似的输出:

图 12.25 – Telegram API 消息与 Chat ID

图 12.25 – Telegram API 消息与 Chat ID

我们稍后会使用 id 值,所以也请将其保存在安全的地方。

  1. 然后,让我们继续创建一个 Grafana 通知组。在左侧菜单栏中,将鼠标悬停在铃铛图标上,并选择如下所示的 联系人点

图 12.26 – Grafana 警报菜单

图 12.26 – Grafana 警报菜单

  1. 联系人点 选项卡上,按照以下方式选择 添加联系人点

图 12.27 – Grafana 中的联系人点选项卡

图 12.27 – Grafana 中的联系人点选项卡

  1. 新建联系人点 页面上添加一个名称,并在 集成 下拉菜单中选择 Telegram。然后,完成 Bot API 令牌Chat ID 字段。你可以在这里看到它的样子:

图 12.28 – 新联系人点页面

图 12.28 – 新联系人点页面

  1. 现在,让我们确保在点击测试按钮时正确地插入了值。如果一切配置得当,你将在你的机器人所在的频道收到以下消息:

图 12.29 – Grafana 测试消息成功工作

图 12.29 – Grafana 测试消息成功工作

这意味着我们的机器人已经准备好了!保存接触点并返回警报页面。

  1. 通知策略中,编辑根策略的接触点为以下内容:

图 12.30 – Grafana 通知策略选项卡

图 12.30 – Grafana 通知策略选项卡

  1. 最后,让我们创建一个警报规则来触发警报通知。在警报规则页面,选择创建警报规则以跳转到新页面。在此页面的字段中插入以下值:

    • 规则名称: 导入错误

    • 指标: airflow_dag_processing_import_errors

    • 标签过滤器: 实例, statsd-exporter:9102

    • 阈值: 输入 AIS ABOVE 1

    • 文件夹: 在评估组中创建一个名为Errorstest_group的新文件夹

    • 规则组评估间隔: 3 分钟

你应该会有以下类似的截图。你也可以将其用作参考来填写字段:

图 12.31 – Grafana 上 Airflow 导入错误的新的警报规则

图 12.31 – Grafana 上 Airflow 导入错误的新的警报规则

保存它,然后在 Airflow 中模拟一个导入错误。

  1. 在 Airflow 的 DAG 中创建任何导入错误后,你将在 Telegram 频道中收到类似以下的通知:

图 12.32 – 被 Grafana 警报触发的 Telegram 机器人显示通知

图 12.32 – 被 Grafana 警报触发的 Telegram 机器人显示通知

由于这是一个本地测试,目前你不需要担心注释部分。

我们的 Grafana 通知工作正常,并且已经完全集成到 Telegram 中!

它是如何工作的…

虽然这个食谱有很多步骤,但内容并不复杂。这个练习的目的是给你一个配置简单机器人以在需要时创建警报的实用端到端示例。

在 DevOps 中,机器人经常被用作通知动作的工具,这里也不例外。从步骤 1步骤 4,我们专注于在 Telegram 中配置一个机器人以及一个可以发送 Grafana 通知的频道。选择 Telegram 作为我们的通讯工具没有特别的原因,只是因为创建账户的便利性。通常,像SlackMicrosoft Teams这样的通讯工具是运维团队的首选,而且有很多在线教程展示了如何使用它们。

配置好机器人后,我们继续将其与 Grafana 连接。配置只需要提供少量信息,例如认证令牌(用于控制机器人)和渠道 ID。正如你所观察到的,有许多类型的集成可用,并且在安装插件时可以添加更多。你可以在这里查看完整的插件列表:Grafana 插件列表

如果我们需要多个联系点,我们可以在联系点选项卡上创建它,并创建一个通知策略,将新联系点包括在内作为通知对象。

最后,我们根据 Airflow 导入错误的数量创建了一条警报规则。导入错误可能会影响一个或多个 DAG 的执行;因此,它们是值得监控的相关项目。

创建警报和通知有两种方式:在警报规则页面和直接在仪表板面板上。后者取决于面板类型,并非所有面板都支持集成警报。最安全的选择,也是最佳实践,是在警报规则页面上创建警报规则。

创建警报类似于面板,我们需要识别指标和标签,关键点是阈值警报评估条件。这两个配置将决定指标值接受的极限以及它可能需要的时间。为了测试目的,我们设置了一个浅阈值和短评估时间,并故意引发了一个错误。然而,标准的警报规则可以有更多的时间容忍度和基于团队需求的阈值。

最后,在一切设置妥当后,我们看到了机器人的实际操作,一旦触发条件满足,它就会立即提供警报。

进一步阅读

posted @ 2025-10-23 15:20  绝不原创的飞龙  阅读(2)  评论(0)    收藏  举报