DataBrick-现代数据应用构建指南-全-
DataBrick 现代数据应用构建指南(全)
原文:
annas-archive.org/md5/bc35a4f08676703f400a5d1765ae165d
译者:飞龙
前言
随着数据集大小的爆炸性增长,廉价云存储的引入,以及近实时数据处理成为行业标准,许多组织转向湖屋架构,它将传统数据仓库的快速商业智能(BI)速度与云中大数据的可扩展 ETL 处理相结合。Databricks 数据智能平台——建立在包括 Apache Spark、Delta Lake、MLflow 和 Unity Catalog 在内的多个开源技术之上——消除了摩擦点,加速了现代数据应用的设计和部署,这些应用是为湖屋而构建的。
在本书中,您将首先概览 Delta Lake 格式,涵盖 Databricks 数据智能平台的核心概念,并掌握使用 Delta Live Tables 框架构建数据管道的技巧。我们将深入探讨数据转换的应用,如何实现 Databricks 镶嵌架构,以及如何持续监控数据在数据湖屋中的质量。您将学习如何使用 Databricks Auto Loader 功能对接收到的数据做出响应,并通过 Databricks 工作流实现实时数据处理的自动化。您还将学习如何使用 CI/CD 工具,如Terraform和Databricks 资产包(DABs),自动化部署数据管道更改到不同的部署环境,并在此过程中监控、控制和优化云成本。在本书结束时,您将掌握如何使用 Databricks 数据智能平台构建一个生产就绪的现代数据应用。
由于 Databricks 最近被评为 2024 年 Gartner 数据科学与机器学习平台魔力象限的领导者,预计未来几年对掌握 Databricks 数据智能平台技能的需求将持续增长。
本书适用人群
本书适用于数据工程师、数据科学家和负责组织企业数据处理的数据管理员。本书将简化在 Databricks 上学习高级数据工程技术的过程,使实现前沿的湖屋架构对具有不同技术水平的个人都变得可访问。然而,为了最大化利用本书中的代码示例,您需要具备 Apache Spark 和 Python 的初级知识。
本书内容
第一章,Delta Live Tables 介绍,讨论了如何使用 Delta Live Tables 框架构建近实时数据管道。它涵盖了管道设计的基础知识,以及 Delta Lake 格式的核心概念。本章最后通过一个简单的示例展示了如何从头到尾构建一个 Delta Live Table 数据管道。
第二章,使用 Delta Live Tables 应用数据转化,探讨了如何使用 Delta Live Tables 进行数据转化,指导你清洗、精炼和丰富数据以满足特定的业务需求。你将学习如何使用 Delta Live Tables 从多种输入源中摄取数据,在 Unity Catalog 中注册数据集,并有效地将变更应用于下游表。
第三章,使用 Delta Live Tables 管理数据质量,介绍了对新到数据强制执行数据质量要求的几种方法。你将学习如何使用 Delta Live Tables 框架中的期望定义数据质量约束,并实时监控管道的数据质量。
第四章,扩展 DLT 管道,解释了如何扩展Delta Live Tables(DLT)管道,以应对典型生产环境中不可预测的需求。你将深入了解如何使用 DLT UI 和 Databricks Pipeline REST API 配置管道设置。此外,你还将更好地理解日常运行在后台的 DLT 维护任务,以及如何优化表格布局以提升性能。
第五章,通过 Unity Catalog 精通湖仓中的数据治理,提供了一个全面的指南,帮助你使用 Unity Catalog 增强湖仓的数据治理和合规性。你将学习如何在 Databricks 工作区启用 Unity Catalog,使用元数据标签启用数据发现,并实施数据集的精细粒度行级和列级访问控制。
第六章,在 Unity Catalog 中管理数据位置,探讨了如何使用 Unity Catalog 有效地管理存储位置。你将学习如何在组织内部跨不同角色和部门管理数据访问,同时确保安全性和可审计性,利用 Databricks 数据智能平台实现这一目标。
第七章,使用 Unity Catalog 查看数据血缘,讨论了追踪数据来源、可视化数据转化过程,以及通过在 Unity Catalog 中追踪数据血缘来识别上下游依赖关系。在本章结束时,你将掌握验证数据来源是否可信的技能。
第八章,使用 Terraform 部署、维护和管理 DLT 管道,介绍了如何使用 Databricks Terraform 提供程序来部署 DLT 管道。你将学习如何设置本地开发环境,并自动化构建和部署管道,同时涵盖最佳实践和未来考虑事项。
第九章,利用 Databricks 资产包简化数据管道部署,探讨了如何使用 DABs 简化数据分析项目的部署,并促进跨团队协作。你将通过几个实践案例深入理解 DABs 的实际应用。
第十章,监控生产中的数据管道,深入探讨了在 Databricks 中监控数据管道这一关键任务。你将学习到在 Databricks 数据智能平台中追踪管道健康、性能和数据质量的各种机制。
为了充分利用本书
虽然这不是强制要求,但为了充分利用本书,建议你具备 Python 和 Apache Spark 的初学者水平知识,并且至少对如何在 Databricks 数据智能平台中导航有一定了解。为了配合本书中的实践练习和代码示例,建议在本地安装以下依赖项:
书中涵盖的软件/硬件 | 操作系统要求 |
---|---|
Python 3.6+ | Windows、macOS 或 Linux |
Databricks CLI 0.205+ |
此外,建议你拥有一个 Databricks 账户和工作区,以便登录、导入笔记本、创建集群和创建新的数据管道。如果你没有 Databricks 账户,可以在 Databricks 网站上注册免费试用:www.databricks.com/try-databricks
。
如果你使用的是本书的数字版本,我们建议你自己输入代码,或者从本书的 GitHub 仓库获取代码(下一个章节中有链接)。这样做可以帮助你避免与代码复制和粘贴相关的潜在错误 。
下载示例代码文件
你可以从 GitHub 下载本书的示例代码文件:github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse
。如果代码有更新,将会在 GitHub 仓库中更新。
我们还从丰富的书籍和视频目录中提供其他代码包,可以在 github.com/PacktPublishing/
找到。快来看看吧!
使用的约定
本书中使用了若干文本约定。
文本中的代码:表示文本中的代码词汇、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。举个例子:“数据生成器笔记本的结果应为三个表:youtube_channels,youtube_channel_artists,和 combined_table。”
代码块的设置如下:
@dlt.table(
name="random_trip_data_raw",
comment="The raw taxi trip data ingested from a landing zone.",
table_properties={
"quality": "bronze"
}
)
当我们希望引起您对代码块中特定部分的注意时,相关的行或项目会以粗体显示:
@dlt.table(
name="random_trip_data_raw",
comment="The raw taxi trip data ingested from a landing zone.",
table_properties={
"quality": "bronze",
"pipelines.autoOptimize.managed": "false"
}
)
任何命令行输入或输出均按如下格式书写:
$ databricks bundle validate
粗体:表示新术语、重要词汇或您在屏幕上看到的词语。例如,菜单或对话框中的文字会以粗体显示。以下是一个例子:“点击 Databricks 工作区右上方的运行全部按钮,执行所有笔记本单元格,验证所有单元格是否成功执行。”
提示或重要说明
显示如下。
与我们联系
我们始终欢迎读者的反馈。
常见反馈:如果您对本书的任何内容有疑问,请通过电子邮件联系我们:[email protected],并在邮件主题中注明书名。
勘误:虽然我们已经尽力确保内容的准确性,但错误还是可能发生。如果您在本书中发现了错误,我们将非常感激您向我们报告。请访问www.packtpub.com/support/errata并填写表格。
盗版:如果您在互联网上发现我们作品的任何非法副本,我们将非常感激您提供该位置地址或网站名称。请通过版权@packtpub.com 与我们联系,附上相关链接。
如果您有兴趣成为作者:如果您在某个领域拥有专业知识,并且有兴趣撰写或参与编写书籍,请访问authors.packtpub.com。
分享您的想法
阅读完《使用 Databricks Lakehouse 构建现代数据应用》后,我们非常期待听到您的想法!请点击这里直接访问亚马逊的书评页面,并分享您的反馈。
您的反馈对我们和技术社区非常重要,它将帮助我们确保提供优质的内容。
下载本书的免费 PDF 副本
感谢您购买本书!
喜欢随时随地阅读,但又无法随身携带纸质书籍吗?
您的电子书购买无法与您选择的设备兼容吗?
不用担心,现在每本 Packt 书籍都会提供免费的无 DRM 版本 PDF。
任何地方、任何设备上都能阅读。可以搜索、复制并粘贴您最喜欢的技术书籍中的代码,直接用于您的应用程序。
优惠不止于此,您还可以独享折扣、新闻通讯和每日发送到您邮箱的优质免费内容
按照这些简单步骤获取福利:
- 扫描二维码或访问以下链接
packt.link/free-ebook/978-1-80107-323-3
-
提交您的购买凭证
-
就是这样!我们会将您的免费 PDF 和其他福利直接发送到您的邮箱
第一部分:湖仓的近实时数据管道
本书的第一部分将介绍Delta Live Tables(DLT)框架的核心概念。我们将讨论如何从各种输入源中获取数据,并将最新的更改应用到下游表格中。我们还将探讨如何对传入的数据施加要求,以便在可能污染湖仓的数据质量问题发生时,及时通知数据团队。
本部分包含以下章节:
-
第一章 , Delta Live Tables 简介
-
第二章 , 使用 Delta Live Tables 应用数据转换
-
第三章 , 使用 Delta Live Tables 管理数据质量
-
第四章 , 扩展 DLT 数据管道
第一章:Delta Live Tables 简介
在本章中,我们将探讨过去几十年来数据行业如何发展。我们还将讨论为何实时数据处理与企业如何应对数据中的最新信号有着密切关系。我们将说明为什么从零开始构建自己的流处理解决方案可能无法持续,并且为什么维护随着时间推移并不容易扩展。到本章结束时,你应该完全理解 Delta Live Tables(DLT)框架解决的问题类型,以及该框架为数据工程团队带来的价值。
本章我们将覆盖以下主要主题:
-
湖仓的出现
-
实时数据在湖仓中的重要性
-
流应用程序的维护困境
-
什么是 Delta Live Tables 框架?
-
Delta Live Tables 与 Delta Lake 有何关系?
-
Delta Live Tables 概念介绍
-
Delta Lake 快速入门
-
实战示例 – 创建你的第一个 Delta Live Tables 管道
技术要求
推荐访问 Databricks 高级工作区,以便跟随章节末尾的代码示例进行操作。还建议具有 Databricks 工作区权限,能够创建通用集群和使用集群策略创建 DLT 管道。用户将创建并将笔记本附加到集群,并执行笔记本单元格。所有代码示例可以从本章的 GitHub 仓库下载,地址为 github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter01
。本章将使用核心产品版本创建并运行一个新的 DLT 管道。因此,预计该管道将消耗约 5–10 Databricks 单位 (DBU)。
湖仓的出现
在 1980 年代初期,数据仓库是处理结构化数据的好工具。结合适当的索引方法,数据仓库使我们能够以极快的速度提供商业智能(BI)报告。然而,进入 21 世纪后,数据仓库无法跟上像 JSON 这样的新数据格式,以及音频和视频等新型数据模态。简单来说,数据仓库在处理大多数企业使用的半结构化和非结构化数据时遇到困难。此外,数据仓库在处理数百万或数十亿行数据时也力不从心,这在 2000 年代初期的信息时代变得越来越普遍。突然之间,批处理数据处理任务很快与在早晨商业工作时段安排刷新的 BI 报告发生冲突。
与此同时,云计算成为组织中的流行选择,因为它提供了一种弹性计算能力,可以根据当前的计算需求快速扩展或收缩,而无需处理预配和安装额外的硬件设备在本地。
现代抽取、转换和加载(ETL)处理引擎,如 Apache Hadoop 和 Apache Spark™,解决了处理大数据 ETL 流水线的性能问题,引入了一个新概念,即数据湖。然而,数据湖在为 BI 报告提供服务方面表现不佳,并且通常会为许多并发用户会话提供性能下降的体验。此外,数据湖的数据治理也很差。它们容易出现松散的数据整理模式,导致多个昂贵的数据副本,这些副本经常偏离数据真相。因此,这些数据湖很快就被称为数据沼泽。大数据行业需要变革。湖屋模式正是这种变革,旨在结合两者的优势 – 云中结构化、半结构化和非结构化数据的快速 BI 报告和快速 ETL 处理。
Lambda 架构模式
在 2010 年代初期,数据流处理在数据行业占据了一席之地,许多企业需要一种方法来支持批量 ETL 处理和仅附加数据流。此外,具有许多并发 ETL 进程的数据架构需要同时读取和更改底层数据。组织经常遇到频繁的冲突写入失败,导致数据损坏甚至数据丢失。因此,在许多早期数据架构中,构建了两叉 Lambda 架构,以在这些过程之间提供一层隔离。
图 1.1 – Lambda 架构经常被创建以支持实时流式工作负载和批处理过程,例如 BI 报告
使用 Lambda 架构,下游过程(例如 BI 报告或机器学习(ML)模型训练)可以在数据快照上执行计算,而流处理过程可以单独应用几乎实时的数据更改。然而,这些 Lambda 架构会复制数据以支持并发的批处理和流式工作负载,导致不一致的数据更改,需要在每个工作日结束时进行调解。
引入勋章架构
为了清理数据湖并防止不良数据操作,数据湖架构师需要一种能够满足现代 ETL 处理高需求的数据处理模式。此外,组织还需要一种简化的架构,来处理批量和流式工作负载,支持数据回滚、良好的数据审计以及强大的数据隔离,并能够扩展到每天处理数 TB 甚至 PB 级的数据。
结果,Lakehouse 中出现了一种设计模式,通常称为徽章架构。该数据处理模式通过在连续的数据跳跃中应用业务级转换,物理隔离数据处理并提高数据质量,这些跳跃也被称为数据层。
图 1.2 – Lakehouse 徽章架构
一种典型的组织 Lakehouse 内部数据的设计模式(如 图 1.2 所示)包括三个不同的数据层——青铜层、银层和最后的金层:
-
青铜层作为原始未处理数据的登陆区。
-
经过过滤、清理和增强的数据,具有定义的结构和强制执行的模式,将存储在银层中。
-
最后,经过精炼的金层,将提供洁净的业务级聚合,准备供下游 BI 和 ML 系统使用。
此外,这种简化的数据架构通过将数据集存储在支持并发批量和流式数据操作的大数据格式中,统一了批量和流式工作负载。
Databricks Lakehouse
Databricks Lakehouse 将一种新的高性能处理引擎——光子引擎的处理能力与 Apache Spark 的增强功能相结合。结合开源数据格式进行数据存储,并支持广泛的数据类型,包括结构化数据、半结构化数据和非结构化数据,光子引擎可以使用数据的单一一致性快照,处理各种工作负载,同时采用廉价且具备弹性的云存储。此外,Databricks Lakehouse 通过统一批处理和流处理,使用单一 API——Spark DataFrame API,简化了数据架构。最后,Databricks Lakehouse 考虑到数据治理和数据安全,允许组织集中定义数据访问模式,并在整个业务中一致地应用这些模式。
在本书中,我们将讨论 Databricks Lakehouse 的三个主要特性:
-
Delta Lake 格式
-
光子引擎
-
Unity Catalog
虽然 Delta Lake 可以同时处理批量和流式工作负载,但大多数数据团队选择使用批量执行模型来实现他们的 ETL 管道,主要是出于简便考虑。让我们看看为什么会是这样。
流式应用程序的维护困境
Spark Structured Streaming 提供近实时流处理,具备容错性,并通过使用几乎与 Spark 批处理相同的 DataFrame API 保证精确一次的处理。因此,由于 DataFrame API 的统一,数据工程团队可以以最小的努力将现有的批处理 Spark 工作负载转换为流处理。然而,随着数据量的增加以及摄取源和数据管道数量的自然增长,数据工程团队面临着增加现有数据管道的负担,以跟上新的数据转换或变化的业务逻辑。此外,Spark Streaming 还需要额外的配置维护,如更新检查点位置、管理水印和触发器,甚至在发生重大数据变化或数据更正时回填表格。高级数据工程团队甚至可能需要构建数据验证和系统监控能力,为了维护更多的自定义管道功能。随着时间的推移,数据管道的复杂性会增加,数据工程团队会花费大部分时间来维护生产环境中数据管道的运行,而不是从企业数据中提取洞察。显然,需要一个框架,使数据工程师能够快速声明数据转换、管理数据质量,并快速将变更部署到生产环境中,在 UI 或其他通知系统中监控管道操作。
什么是 DLT 框架?
DLT 是一个声明式框架,旨在通过抽象掉许多模板复杂性,简化数据管道的开发和维护操作。例如,数据工程师可以声明要对新到达的数据应用哪些转换,而不是声明如何转换、丰富和验证数据。此外,DLT 还支持强制执行数据质量,防止数据湖变成数据沼泽。DLT 使数据团队能够选择如何处理低质量数据,无论是将警告信息打印到系统日志、丢弃无效数据,还是完全失败数据管道的运行。最后,DLT 自动处理日常的数据工程任务,如维护底层表的优化数据文件大小,以及清理 Delta 事务日志中不再存在的过时数据文件(优化和清理操作将在后面的Delta Lake 快速入门部分介绍)。DLT 旨在减轻数据工程团队的维护和运营负担,让他们能够将时间集中在从存储在湖仓中的数据中发现业务价值,而不是花时间管理运营复杂性。
DLT 与 Delta Lake 有什么关系?
DLT 框架在每一步中都严重依赖 Delta Lake 格式来逐步处理数据。例如,在 DLT 管道中定义的流式表和物化视图是由 Delta 表支持的。使 Delta Lake 成为流式管道理想存储格式的功能包括支持原子性、一致性、隔离性和持久性(ACID)事务,因此可以将并发的数据修改(如插入、更新和删除)逐步应用到流式表中。此外,Delta Lake 具有可扩展的元数据处理能力,允许 Delta Lake 容易扩展到 PB 级别及更大。如果数据计算有误,Delta Lake 提供时间旅行——恢复表的某个历史快照的功能。最后,Delta Lake 本身会跟踪每个表的事务日志中的审计信息。诸如修改表的操作类型、操作的集群、操作的用户和具体的时间戳等溯源信息,都与数据文件一起被捕获。让我们来看看 DLT 如何利用 Delta 表快速高效地定义随时间扩展的数据管道。
介绍 DLT 概念
DLT 框架自动管理任务调度、集群创建和异常处理,使数据工程师能够专注于定义转换、数据丰富和数据验证逻辑。数据工程师将使用一个或多个数据集类型定义数据管道。在幕后,DLT 系统会确定如何保持这些数据集的最新状态。使用 DLT 框架的数据管道由流式表、物化视图和视图数据集类型组成,我们将在接下来的部分详细讨论这些内容。我们还将简要讨论如何可视化管道,查看其触发方式,并从鸟瞰图查看整个管道数据流。我们还将简要了解不同类型的 Databricks 计算和运行时,以及 Unity Catalog。让我们开始吧。
流式表
流式表利用 Delta Lake 和 Spark Structured Streaming 的优势,逐步处理到达的新数据。此数据集类型在需要高吞吐量和低延迟地摄取、转换或丰富数据时非常有用。流式表专为仅追加新数据且不包括数据修改(如更新或删除)的数据源设计。因此,这种数据集类型可以扩展到大数据量,因为它可以在新数据到达时逐步应用数据转换,并且在管道更新时不需要重新计算整个表的历史记录。
物化视图
物化视图利用 Delta Lake 计算数据集的最新变更,并将结果物化存储在云存储中。当数据源包含如更新和删除等数据修改,或必须执行数据聚合时,这种数据集类型非常适合。在后台,DLT 框架将执行计算,重新计算数据集的最新数据变更,使用整个表的历史记录。该计算的输出存储在云存储中,以便未来的查询可以引用预计算的结果,而不是每次查询表时重新执行完整的计算。因此,每次物化视图更新时,这种类型的数据集将产生额外的存储和计算成本。此外,物化视图可以发布到 Unity Catalog 中,这样结果就可以在 DLT 数据管道之外进行查询。这在你需要跨多个数据管道共享查询结果时非常有用。
视图
视图也会重新计算特定查询的最新结果,但不会将结果物化到云存储中,从而帮助节省存储成本。这种数据集类型非常适合当你想快速检查数据管道中数据转换的中间结果或执行其他临时数据验证时。此外,这种数据集类型的结果不能发布到 Unity Catalog,仅在数据管道上下文中可用。
下表总结了 DLT 框架中不同数据集类型的差异,以及在何时使用某种数据集类型比其他类型更合适:
数据集类型 | 何时使用 |
---|---|
流表 | 数据摄取工作负载,当你需要以高吞吐量和低延迟不断将新数据追加到目标表时。 |
物化视图 | 包含数据修改的操作,如更新和删除,或者你需要对整个表的历史记录执行聚合。 |
视图 | 当你需要查询中间数据但不将结果发布到 Unity Catalog 时(例如,执行中间转换的数据质量检查) |
表 1.1 – DLT 中的每种数据集类型都有不同的用途
管道
DLT 管道是一个或多个流表、物化视图或视图的逻辑数据处理图。DLT 框架将使用 Python API 或 SQL API 进行数据集声明,并推断每个数据集之间的依赖关系。一旦管道更新运行,DLT 框架将使用依赖图按正确的顺序更新数据集,这个依赖图被称为数据流图。
管道触发器
管道将根据某些触发事件执行。DLT 提供三种类型的触发器——手动触发、定时触发和连续触发。一旦触发,管道将初始化并执行数据流图,更新每个数据集的状态。
工作流
Databricks 工作流是Databricks 数据智能平台的一个托管编排功能,允许数据工程师将一个或多个相互依赖的数据处理任务串联起来。对于更复杂的数据处理用例,可能需要使用多个嵌套的 DLT 管道来构建数据管道。在这些用例中,Databricks 工作流可以简化这些数据处理任务的编排。
Databricks 计算类型
Databricks 数据智能平台为用户提供四种类型的计算资源。
作业计算
作业计算是一个包含安装有Databricks Runtime(DBR)的短期虚拟机(VMs)的集合,这些虚拟机会根据计划作业的时间动态配置。作业完成后,虚拟机会立即释放回云服务提供商。由于作业集群不使用 Databricks 数据智能平台的 UI 组件(例如,笔记本和查询编辑器),作业集群在执行期间会评估较低的Databricks 单位(DBU)。
通用计算
通用计算是一个包含安装有 DBR 的短期虚拟机的集合,用户可以通过点击 Databricks 用户界面上的按钮,或通过 Databricks REST API(例如使用/api/2.0/clusters/create 端点)动态创建它们,且虚拟机会一直运行,直到用户或自动终止计时器终止该集群。终止后,虚拟机会返回给云服务提供商,Databricks 停止评估额外的 DBU。
实例池
实例池是 Databricks 的一个功能,帮助减少配置额外虚拟机并安装 DBR 所需的时间。实例池会从云服务提供商预先配置虚拟机,并将它们保存在一个逻辑容器中,类似于代客停车场中的代客停车员保持您的汽车运转。
对于某些云服务提供商,提供额外的虚拟机可能需要 15 分钟或更长时间,这会导致更长的故障排除周期或临时开发任务,例如在开发新特性时检查日志或重新运行失败的笔记本单元格。
此外,当许多作业计划紧密或重叠执行时,实例池可以提高效率。例如,当一个作业完成时,虚拟机不会释放回云服务提供商,而是可以将虚拟机放入实例池,以供下一个作业重复使用。
在将虚拟机返回到实例池之前,安装在虚拟机上的 Databricks 容器会被销毁,并且当下一个计划作业请求虚拟机时,安装有 DBR 的新容器会被安装到虚拟机上。
重要说明
当虚拟机(VM)正在运行时,Databricks 不会额外评估 DBU。然而,云服务提供商会继续收取虚拟机在实例池中的费用。
为了帮助控制成本,实例池提供了一个自动扩展功能,允许池的大小根据需求自动增长或缩小。例如,在高峰时段,实例池可能会增长到 10 个虚拟机,但在处理需求低谷时,池的大小会缩小到 1 或 2 个虚拟机。
Databricks SQL 仓库
Databricks 数据智能平台中的最后一种计算资源是 Databricks SQL (DBSQL) 仓库。DBSQL 仓库旨在运行 SQL 工作负载,如查询、报告和仪表板。此外,DBSQL 仓库是预配置的计算资源,旨在限制数据分析师或 SQL 分析师在临时数据探索和查询执行中需要优化的配置。DBSQL 仓库预先配置了最新的 DBR,利用 Databricks Photon 引擎,并具有预配置的高级 Spark 配置设置以优化性能。DBSQL 仓库还包括其他性能特性,如结果缓存和磁盘缓存,这可以通过将数据更靠近执行查询计算的硬件来加速工作负载。结合 Photon 引擎的处理速度,DBSQL 仓库实现了 Apache Spark 曾经难以达到的云仓库速度。
Databricks Runtime
Databricks Runtime 是一组在集群初始化期间预先安装在集群的驱动节点和工作节点上的库。这些库包括流行的 Java、R 和 Python 库,帮助最终用户进行临时数据整理或其他开发任务。这些库包含与 Databricks 后端服务接口的核心组件,支持丰富的平台功能,如协作笔记本、工作流和集群指标。此外,DBR 还包括其他性能特性,如数据文件缓存(称为磁盘缓存)、用于加速 Spark 处理的 Databricks Photon 引擎,以及其他计算加速功能。DBR 有两种版本,Standard 和 ML,分别针对不同的工作负载进行优化,依据最终用户的角色来调整。例如,适用于 ML 的 DBR 会预先安装流行的 Python 库,如 TensorFlow 和 scikit-learn,以帮助最终用户进行 ML 模型训练、特征工程和其他 ML 开发任务。
Unity Catalog
顾名思义,Unity Catalog 是一个集中式治理存储,旨在跨多个 Databricks 工作区进行管理。与其在每个 Databricks 工作区中重复定义用户和组的数据治理策略,Unity Catalog 允许数据管理员在一个集中位置定义访问策略。结果,Unity Catalog 成为数据治理的单一真实来源。
除了数据访问策略外,Unity Catalog 还具有数据审计、数据血统、数据发现和数据共享功能,这些内容将在第五章、第六章 和第七章中介绍。
Unity Catalog 紧密集成到 Databricks Lakehouse 中,使得构建具备强大数据安全性的近实时数据管道变得简单,使用 Delta Lake 等开放的 Lakehouse 存储格式。
快速了解 Delta Lake
Delta Lake 是一种大数据文件协议,围绕多版本事务日志构建,提供诸如 ACID 事务、模式强制、时间旅行、数据文件管理等性能特性,基于现有的 Lakehouse 数据文件。
最初,大数据架构有许多并发的进程,这些进程既读取又修改数据,导致数据损坏甚至丢失。如前所述,创建了一个双管齐下的 Lambda 架构,在对数据进行流式更新的进程与需要一致数据快照的下游进程(如生成每日报告或刷新仪表盘的 BI 工作负载)之间提供了一个隔离层。然而,这些 Lambda 架构重复了数据,以支持这些批处理和流式工作负载,导致了不一致的数据变化,需要在每个工作日结束时进行调和。
幸运的是,Delta Lake 格式提供了一个共同的存储层,能够跨不同工作负载统一批处理和流式工作负载。因此,Delta 表成为 Delta Live 表的基础。在后台,Delta Live 表由一个 Delta 表支持,该表被添加到数据流图中,并且每当 DLT 管道更新执行时,DLT 系统会更新其状态。
Delta 表的架构
Delta 事务日志是该大数据格式架构的关键部分。每个 Delta 表都包含一个事务日志,这是一个目录名为_delta_log,位于表的根目录下。事务日志是一个多版本的记录系统,用于跟踪表在一段线性时间内的状态。
图 1.3 – Delta 事务日志与分区目录和数据文件一起,存储在一个名为 _delta_log 的单独目录中
事务日志通知 Delta Lake 引擎读取哪些数据文件来回答特定的查询。
在每个事务日志目录中,将存储一个或多个 JSON 格式的文件,以及其他元数据,帮助快速有效地计算 Delta 表的状态(将在下一节中介绍)。
随着新数据的附加、更新或甚至删除,这些变化会被记录或提交到该目录作为元数据,以原子 JSON 文件的形式存储。JSON 文件的命名使用有序整数,从00…0.json开始,每次成功提交事务后递增一个。
如果 Delta 表是分区的,则表的根目录中将包含一个或多个子目录,存储分区列信息。Hive 风格的表分区是一种常见的性能优化技术,可以通过将相似数据存储在同一目录中来加速查询。数据按特定列的值(例如,"date=2024-01-05")进行聚集。根据表分区的列数,分区目录中甚至可能包含更多嵌套的子目录。
在这些分区子目录中包含一个或多个数据文件,数据文件采用 Apache Parquet 格式存储。Apache Parquet 是一种流行的列式存储格式,具有高效的数据压缩和编码方案,能够快速存储和检索大数据工作负载的数据。因此,选择这种开放格式作为存储 Delta 表数据文件的基础。
事务提交的内容
如前所述,事务日志是 Delta 表的唯一真实来源。每个已提交的事务(_delta_log 目录下的 JSON 文件)将包含关于操作或应用于特定 Delta 表的操作的元数据信息。这些 JSON 文件可以视为一组操作。虽然可以有多个并发事务,但事务提交历史会被表读取器按线性顺序回放,结果就是 Delta 表的最新状态。
每个 JSON 文件可能包含以下 Delta Lake 协议中列出的操作:
-
更改元数据:此类型的操作用于更新表的名称、模式或分区信息。
-
添加文件:可能是最常见的操作,此操作将一个新的数据文件添加到表中,并附带有关 Delta 表前 32 列的统计信息。
-
移除文件:此操作将逻辑删除特定数据文件。请注意,物理数据文件将在此事务提交后仍保留在云存储中(有关此主题的更多内容,请参见 墓碑数据 文件 部分)。
-
添加变更数据捕获(CDC)信息:此操作用于添加一个 CDC 文件,该文件将包含由于特定表操作而发生变化的所有数据。
-
事务标识符:此操作用于结构化流式工作负载,将包含特定流的唯一标识符,以及最近提交的结构化流微批次的纪元标识符。
-
协议演进:提供向后兼容性,确保旧版 Delta Lake 表读取器能够读取事务日志中的元数据。
-
提交溯源信息:这种操作将包含关于将特定数据事务提交到表的过程的信息。这将包括时间戳、操作类型、集群标识符和用户信息等信息。
-
领域元数据:这种操作为特定领域设置配置。领域元数据有两种类型——系统领域和用户控制领域。
-
侧车文件信息:这种操作会将一个单独的元数据文件提交到事务日志,其中包含有关创建的检查点文件的汇总信息(检查点将在接下来的部分中讲解)。
支持并发的表格读取与写入
存储系统中有两种并发控制方法——悲观并发控制和乐观并发控制。悲观并发控制通过锁定整个表,直到当前事务完成,来防止可能的表冲突。相反,乐观并发控制不会锁定表格,而是允许潜在的事务冲突发生。
Delta Lake 协议的作者选择使用乐观并发控制来实现 Delta Lake 格式。做出这一设计选择的原因是,大多数大数据工作负载会将新数据附加到现有表中,而不是修改现有数据。
作为示例,让我们看看 Delta Lake 如何处理两个表写入者之间的并发写入冲突——表写入者 A 和表写入者 B:
图 1.4 – Delta Lake 实现了乐观并发控制方案来处理并发写入冲突
假设两个表的写入者修改了相同的数据文件,并尝试提交彼此冲突的数据更改。我们来看看 Delta Lake 是如何处理这种情况的。
-
写入者 A 首先会记录它尝试提交到事务日志的事务的起始版本标识符。
-
写入者 A 随后将写入所有它希望提交的事务数据文件。
-
接下来,写入者 A 将尝试将事务提交到 Delta 事务日志。
-
与此同时,写入者 B 已经使用相同的表版本标识符提交了他们的事务。
-
写入者 A 检测到写入者 B 的提交,并重放提交信息,以确定底层数据文件是否发生了变化(例如,数据是否已被更新)。
-
如果没有数据发生变化(例如,写入者 A 和写入者 B 都只是将附加操作提交到事务日志),那么写入者 A 会将版本标识符加 1,并尝试重新提交该事务到事务日志。
-
如果数据发生变化,那么写入者 A 将需要从头开始重新计算事务,递增版本标识符,并尝试重新提交事务。
删除数据文件
当对 Delta 表应用更新,且需要更新文件中的数据时,将使用AddFile操作创建一个新文件。同样,包含过时数据的文件将通过RemoveFile操作逻辑删除。然后,这两个操作将提交到事务日志中。
默认情况下,Delta Lake 会保留表元数据(事务日志数据)30 天,之后会自动从云存储中删除。当某个数据文件从 Delta 事务日志中删除时,通常称之为已删除文件。
为了帮助控制云存储成本,这些已删除的文件,或者那些不再是最新 Delta 表状态的一部分并且不再在事务日志中被引用的文件,可以从云存储中彻底删除。一个独立的 Delta Lake 文件管理工具,称为Vacuum命令,可以作为独立进程运行,识别所有已删除的数据文件并将其移除。
此外,Vacuum命令是可配置的,可以通过可选的输入参数指定删除表文件的时间长度。例如,以下代码片段将对 Delta 表yellow_taxi执行Vacuum命令,移除表历史中最后 14 天的数据文件:
%sql
-- Vacuums the Delta table `yellow_taxi`
-- and retains 14 days of table history
VACUUM yellow_taxi RETAIN 336 HOURS
正如我们将在接下来的章节中看到的那样,这个过程会自动运行并管理 DLT 管道。
计算 Delta 表状态
如前一节所提到的,Delta Lake 会自动在事务日志中压缩元数据。正如你可以想象的那样,在拥有成千上万甚至数百万个每日事务的大数据工作负载中,Delta 表的大小会迅速增长。同样,事务日志中的提交信息也会相应增长。
对于每第 10 次提交,Delta Lake 将创建一个检查点文件,使用 Apache Parquet 格式,包含最新的表状态信息。
图 1.5 – 对于每第 10 次提交,Delta Lake 将写入一个检查点文件
在底层,Delta Lake 读取器会创建一个独立的 Apache Spark 作业来高效读取 Delta 表的提交日志。例如,要计算最新的表状态,Delta Lake 读取器将首先读取最新的检查点文件,并应用在文件创建之后可能发生的事务提交。
将表状态信息存储在与数据文件一起的检查点文件中,这是 Delta Lake 格式的另一个关键设计选择。通过这种方法,计算表的状态比其他方法(例如使用 Hive Metastore 提供表元数据)能够更好地扩展。当许多大型、活跃度高的表同时查询时,传统的大数据元数据存储(如 Hive Metastore)在检索表元数据以回答查询时难以扩展。
为了进一步加快查询速度,Delta Lake 读者还将表状态缓存到本地内存中;这样,表读取器可以更快地计算哪些数据文件能回答特定的表查询。
时间旅行
Delta Lake 中的另一个文件管理工具是时间旅行功能,它允许最终用户查询表的先前版本状态。时间旅行提供了两种方法来指定表的状态——使用事务日志中分配的表版本号或使用时间戳。
用户可以使用 SQL 语法直接查询先前的 Delta 表状态:
%sql
SELECT *
FROM yellow_taxi
TIMESTAMP AS OF '2023-12-31'
同样,Databricks 数据智能平台上的 Python 用户可以使用 Python API:
%py
display(
(spark.read
.format("delta")
.option("timestampAsOf", "2023-12-31")
.load("s3a://my-data-lake/yellow_taxi/"))
)
需要注意的是,默认情况下,Vacuum 工具将删除特定 Delta 表中过去七天的所有数据文件。
因此,如果运行 Vacuum 命令并且用户尝试查询超过最后七天的表历史记录,最终用户将收到运行时异常,说明事务日志中引用的数据不再存在。
此外,Delta Lake 的时间旅行功能是为修复近期数据问题而设计的,因此不应用于长期数据存储要求,例如实现跨越数年的审计系统。
使用变更数据馈送跟踪表的更改
Delta Lake 的 变更数据馈送(CDF)功能跟踪已对 Delta 表进行的行级更改,以及有关这些更改的元数据。例如,CDF 会捕获有关操作类型、确认更改发生的时间戳以及其他来源信息(如集群标识和用户信息)。
对于更新操作,CDF 会捕获更新前行的快照,以及更新应用后行的快照。
图 1.6 – CDF 捕获操作类型、提交版本和时间戳
此功能默认未启用,但可以通过更新 Delta 表的属性进行配置。例如,可以通过修改表格并使用 SQL ALTER 语句来启用 CDF:
%sql
ALTER TABLE yellow_taxi
SET TBLPROPERTIES (delta.enableChangeDataFeed = true)
同样,在创建表时,也可以通过将表属性包含在 CREATE TABLE 语句中来启用 CDF:
%sql
CREATE TABLE IF NOT EXISTS yellow_taxi
TBLPROPERTIES (delta.enableChangeDataFeed = true)
正如我们将在下一章中看到的那样,这个功能在 DLT 如何高效地将源表的变更应用到下游数据集并实现慢变化维度(SCDs)方面非常重要。
动手示例 – 创建您的第一个 Delta Live Tables 数据管道
在本节中,我们将使用 NYC 出租车示例数据集来声明一个数据管道,使用 DLT 框架,并应用基本的转换来丰富数据。
重要提示
为了从本节中获得最大的价值,建议具有 Databricks 工作区权限,允许创建一个通用集群和 DLT 数据管道,使用集群策略。在本节中,您将把笔记本连接到集群,执行笔记本单元格,并创建和运行一个新的 DLT 数据管道。
让我们从创建一个新的通用集群开始。通过从左侧导航栏选择 计算 按钮,导航到 Databricks 计算 UI。
图 1.7 – 从左侧边栏导航到计算 UI
点击右上角的 创建计算 按钮。接下来,为集群提供一个名称。在此练习中,集群可以是一个小型单节点集群。选择 单节点 单选按钮作为集群类型。在运行时下拉菜单中选择最新的 DBR。接受默认设置后,再次点击 创建计算 按钮。
现在我们已经启动了一个集群,可以开始开发我们的第一个数据管道。
让我们首先在您的工作区主页目录下创建一个新的 Databricks 笔记本。通过点击左侧边栏的 工作区 按钮,点击 添加 下拉菜单并选择 笔记本,创建一个新的笔记本。给笔记本起一个有意义的名字,例如 我的第一个 DLT 数据管道。这个新笔记本将用于声明构成我们 Delta Live Table 数据管道的数据集和依赖关系。
所有 Databricks 工作区都附带一组示例数据集,位于 Databricks 文件系统中的/databricks-datasets。您可以通过列出目录内容,使用 Databricks 文件系统工具浏览可用的数据集列表:
%py
display(
dbutils.fs.ls('/databricks-datasets')
)
接下来,我们需要导入 dlt Python 模块。dlt 模块包含函数装饰器,这些装饰器将指示 DLT 系统如何构建我们的数据管道、依赖关系以及一个名为数据流图的内部数据处理图。
将以下内容添加到一个新的笔记本单元格中:
import dlt
DLT 基于 PySpark 构建,因此我们可以利用 Spark DataFrames 来定义如何从云存储中摄取数据以及如何应用数据转换。我们从定义一个函数开始,该函数将使用 Spark 从 / databricks-datasets 目录读取 NYC 出租车示例数据集:
def yellow_taxi_raw():
path = "/databricks-datasets/nyctaxi/tripdata/yellow"
return (spark.readStream
.schema(schema)
.format("csv")
.option("header", True)
.load(path))
在这个例子中,我们声明了一个有意义名称的简单函数,当调用时,该函数将使用 Spark 读取存储在黄出租车数据集中的原始数据,并将其作为流式 DataFrame 返回。
现在,我们需要告诉 DLT 框架,我们应该将此声明的函数作为数据管道的一部分使用。我们可以通过添加 @dlt.table() 函数装饰器来实现。这个函数装饰器将从函数创建一个 Delta Live Table,并将其添加到管道的数据流图中。让我们还向这个函数装饰器的可选 comment 参数添加一些描述性文本:
@dlt.table(
comment="The raw NYC taxi cab trip dataset located in `/databricks-datasets/`"
)
def yellow_taxi_raw():
path = "/databricks-datasets/nyctaxi/tripdata/yellow"
return (spark.readStream
.schema(schema)
.format("csv")
.option("header", True)
.load(path))
执行笔记本单元格后,Databricks 数据智能平台将检测到一个 DLT 表,打印输出架构,并提示您创建一个新的 DLT 管道。
图 1.8 – Databricks 将解析 DLT 表声明并打印输出架构
让我们点击 创建管道 按钮以生成一个新的 DLT 管道。为数据管道指定一个有意义的名称,例如 黄出租车管道。选择 Core 作为产品版本,并将 触发式 作为管道执行模式。
图 1.9 – 使用 Core 产品版本创建一个新的 DLT 管道
接下来,在 目标位置 设置中,选择 Unity Catalog 单选按钮,并指定您希望存储数据集的目标目录和模式。在 计算 设置下,将 最小工作节点数 设置为 1,将 最大工作节点数 设置为 1。然后,点击 创建 按钮接受默认设置。最后,点击 启动 按钮执行数据管道。您将看到数据流图的可视化表示。
图 1.10 – 数据流图将包含我们在笔记本中声明的流式表
如您所见,我们的数据流图由一个单一的流式表组成,这是一个新的数据集,将从 Databricks 文件系统中的 /databricks-datasets/ 位置获取原始纽约市出租车旅行数据。虽然这是一个简单的示例,但它展示了 DLT 框架的声明式特性,以及我们如何快速使用熟悉的 PySpark API 声明数据管道。此外,您现在应该对如何从 DLT UI 监控和查看我们数据管道的最新状态有了一个基本的了解。
摘要
在本章中,我们探讨了数据行业为何以及如何选择湖仓架构,这种架构旨在将 ETL 处理的可扩展性与 BI 工作负载下的数据仓库速度融合到一个统一的架构中。我们了解了实时数据处理如何成为从最新数据中发掘价值的关键,但随着时间的推移,实时数据管道的复杂性增加可能会影响数据工程团队的生产力。最后,我们学习了 Delta Live Tables 框架的核心概念,以及如何通过几行 PySpark 代码和函数装饰器,快速声明一个能够高效且低延迟地增量处理数据的实时数据管道。
在下一章中,我们将深入探讨 Delta Live Tables 管道的高级设置,以及该框架如何为我们优化底层数据集。然后,我们将通过一个实际的案例来开发数据管道,学习更复杂的数据转换。
第二章:使用 Delta Live Tables 应用数据转换
在本章中,我们将深入探讨 Delta Live Tables(DLT)如何简化从各种输入源获取数据的过程,无论是落在云存储中的文件,还是连接到外部存储系统,如关系数据库管理系统(RDBMS)。接着,我们将探讨如何使用APPLY CHANGES命令高效且准确地将输入数据源的更改应用到下游数据集。最后,本章将以深入了解高级数据流水线设置作为结尾。
总结一下,在本章中,我们将涵盖以下主要内容:
-
从输入源获取数据
-
应用下游表的更改
-
将数据集发布到 Unity Catalog
-
数据流水线设置
-
实践操作——应用 SCD 类型 2 更改
技术要求
为了跟随本章内容,建议拥有 Databricks 工作区权限,以创建通用集群和 DLT 流水线,并使用集群策略。还建议拥有 Unity Catalog 权限,以创建和使用目录、模式和表。所有代码示例可以从本章的 GitHub 仓库下载,网址为 https://github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter02。本章将使用Core产品版本创建并运行多个新的笔记本和 DLT 流水线。预计这些流水线将消耗约 10–15 Databricks Units(DBUs)。
从输入源获取数据
DLT 简化了从各种输入源获取数据的过程。例如,DLT 可以高效处理全天候落入云存储位置的新文件,通过连接到外部存储系统(如关系数据库)摄取结构化数据,或读取可以缓存到内存中的静态参考表。让我们看看如何使用 DLT 持续摄取云存储位置中到达的新数据。
使用 Databricks Auto Loader 获取数据
Databricks 数据智能平台的一个关键特性是名为Auto Loader的功能,它是一个简单而强大的数据摄取机制,用于高效地从云存储中读取输入文件。可以通过使用cloudFiles数据源,在 DataFrame 定义中引用 Auto Loader。例如,以下代码片段将使用 Databricks Auto Loader 功能,从存储容器中获取新到的 JSON 文件:
df = (spark.readStream
.format("cloudFiles")
.option("cloudFiles.format", "json")
.option("cloudFiles.schemaLocation", schema_path)
.load(raw_data_path))
自动加载器可以扩展到高效处理云存储中的数十亿文件。Databricks 自动加载器支持摄取存储在 CSV、JSON、XML、Apache Parquet、Apache Avro 和 Apache Orc 文件格式中的文件,以及文本和二进制文件。此外,在前面的代码片段中您可能注意到的一点是,并没有为输入流指定模式定义,而是指定了目标模式位置。这是因为自动加载器将自动推断数据源模式,并在单独的存储位置跟踪模式定义的更改。在幕后,自动加载器将对最多前 1000 个云文件对象进行采样,以推断云文件源的模式结构。对于诸如 JSON 这样的半结构化格式,其中模式可以随时间变化,这可以通过不需要维护最新模式定义的数据工程团队来减轻巨大负担。
结构化流中的可扩展性挑战
传统上,使用 Spark 结构化流来摄取新文件的数据管道,在数据量增长到 GB 甚至 TB 时很难扩展。当新文件被写入云存储容器时,结构化流将执行目录列表。对于大型数据集(即由数百万个或更多文件组成的数据集),仅目录列表过程就将耗费大量时间。此外,云提供商还会对这些目录列表调用评估 API 费用,增加总体云提供商费用。对于已经处理过的文件,这种目录列表既昂贵又低效。
Databricks 自动加载器支持两种云文件检测模式 – 通知模式和传统目录列表模式。在通知模式下,自动加载器通过在幕后自动部署更可扩展的架构完全避开了这种昂贵的目录列表过程。仅需几行 Python 代码,Databricks 预先配置了后端云服务,这些服务将自动跟踪新落地在云存储中的文件,以及已经处理过的文件。
图 2.1 – 在通知模式下,Databricks 自动加载器使用事件流来跟踪云存储中新的未处理文件
让我们通过一个示例来演示自动加载器功能的工作方式,配置为通知模式,它将有效地一起处理新到达的云文件对象:
-
过程始于 Databricks 自动加载器监听特定云存储路径,以获取新文件对象创建事件,也称为PUT事件,名称源自用于创建对象的 HTTP 动词。
-
当创建一个新文件对象时,关于该新文件的元数据将持久化到键值存储中,该存储作为检查点位置,以防系统故障。
-
接下来,关于文件对象的信息将发布到事件流,cloudFiles数据源将从中读取。
-
在从事件流读取时,Databricks 中的 Auto Loader 进程将仅获取与云存储中新未处理文件对象相关的数据。
-
最后,Auto Loader 进程将更新键值存储,标记新文件在系统中已被处理。
这种基于通知的文件处理实现避免了昂贵且低效的目录列出过程,确保该过程能够从故障中恢复,并且文件被精确处理一次。
在 DLT 中使用 Auto Loader
Databricks Auto Loader 可用于在 DLT 管道中创建流式表。现在我们了解了幕后发生的事情,构建一个能够扩展到数十亿个文件的强大且可扩展的流式表,只需要几行 Python 代码。事实上,对于那些向云存储中追加新文件的数据源,建议始终使用 Auto Loader 来摄取数据。让我们从前面部分获取一个流式 DataFrame 定义,并将其与 DLT 数据集注解结合起来,在我们的管道中定义一个新的数据流:
@dlt.table(
comment="Raw cloud files stream of completed taxi trips"
)
def yellow_taxi_events_raw():
return (spark.readStream
.format("cloudFiles")
.option("cloudFiles.format", "json")
.option("cloudFiles.path", schema_path)
.load(raw_landing_zone_path))
需要注意的是,在前面的代码片段中,我们提供了两个云存储路径。第一个存储路径,schema_path,指的是云存储路径,在该路径中将写入架构信息和键值存储。第二个存储路径,raw_landing_zone_path,指向外部数据源将写入的新未处理文件的位置。
重要说明
推荐使用由 Unity Catalog 管理的外部位置,以便在 Databricks 工作区内的不同用户和组之间强制执行精细的数据访问控制。
现在我们有了一个可靠高效的方式从云存储输入源中摄取原始数据,我们需要将数据进行转换,并将输出应用到数据管道中的下游数据集。让我们看看 DLT 框架如何使得应用下游变更变得简单直接。
将变更应用到下游表
传统上,Delta Lake 提供了一个MERGE INTO命令,允许变更数据捕获通过在特定条件下匹配来合并到目标表中。然而,如果新数据恰好是无序的,合并后的变更会导致错误的结果,进而产生不准确和误导的输出。为了解决这个问题,数据工程团队需要构建复杂的对账过程来处理无序数据,这为数据管道增加了额外的管理和维护层。
APPLY CHANGES 命令
DLT 提供了一个新的 API,可以自动将变更应用到下游表格,甚至可以基于一个或多个序列列处理乱序数据。慢变维(SCDs)是传统数据仓库中的维度,允许追踪数据的当前和历史快照。DLT 使数据工程团队能够在数据管道中更新下游数据集,以反映上游数据源中的变更。例如,DLT 允许用户捕获 SCD 类型 1(不保留先前行历史)和 SCD 类型 2(保留行的历史版本)。
DLT 提供了 Python API 和 SQL 语法来应用变更数据捕获:
-
APPLY CHANGES – 用于使用 SQL 语法编写的数据管道
-
apply_changes() – 用于使用 Python 编写的数据管道
假设我们有一个表格,用来记录来自智能温控器的室内温度,且需要保留温度更新的历史记录。以下代码片段将使用 apply_changes() API 将 SCD 类型 2 变更应用到我们的数据管道输出表格:
import dlt
import pyspark.sql.functions as F
dlt.create_streaming_table("iot_device_temperatures")
dlt.apply_changes(
target = "iot_device_temperatures",
source = "smart_thermostats",
keys = ["device_id"],
sequence_by = F.col("sequence_num"),
apply_as_deletes = F.expr("operation = 'DELETE'"),
except_column_list = ["operation", "sequence_num"],
stored_as_scd_type = "2"
)
此外,DLT 将捕捉在完成 apply_changes() 命令时应用的数据变更的高级操作指标。例如,DLT 系统将跟踪每次执行 apply_changes() 命令时更新、插入或删除的行数。
DLT 对账过程
在后台,DLT 将创建两个数据集对象,以便准确地将表格变更应用到数据管道数据集中。第一个数据对象是一个隐藏的、后端的 Delta 表,它包含了所有变更的完整历史记录。这个数据集用于执行一个对已处理的乱序行更新能够进行处理的对账过程。此外,这个后端表的名称将使用 APPLY CHANGES 或 apply_changes() 函数调用中提供的名称参数,并与 _apply_changes_storage 字符串进行连接。
例如,如果表格的名称是 iot_readings,则会创建一个名为 __apply_changes_storage_iot_readings 的后端表。
这个特定的表只有在 DLT 管道将数据集发布到传统的 Hive Metastore 时才会可见。然而,Unity Catalog 会将这些低级细节抽象化,最终用户无法通过 Catalog Explorer UI 查看该数据集。不过,该表可以通过笔记本或在 SQL 仓库上执行的查询进行 查询。
其次,DLT 系统将使用 apply_changes() 函数提供的名称创建另一个数据集 – 视图。该视图将包含应用所有变更后表格的最新快照。视图将使用表键指定的列或列组合来唯一标识后端表格中的每一行。然后,DLT 使用 apply_changes() 函数中 sequence_by 参数指定的列或列序列来为每个唯一行排序表格变更,选择出最新的行变更以计算视图的结果集。
图 2.2 – DLT 创建后端表格来应用表格变更,并创建一个视图来查询最新数据。
正如您所看到的,DLT 使得在源中发生的数据更改保持与下游数据源一致非常简单。只需进行几个参数更改,您就可以使用强大的 apply_changes() API 来应用 SCD 数据。
现在我们了解了如何利用 DLT 框架来定义数据转换并将更改应用于下游表格,让我们把注意力转向如何在我们的管道数据集上加强数据治理。
将数据集发布到 Unity Catalog
DLT 提供两种存储数据集的方法在 Databricks 数据智能平台上 – 传统的 Hive Metastore 和 Unity Catalog。
如 第一章 所述,Unity Catalog 是一个集中式管理存储库,跨所有特定全球区域内的您的 Databricks 工作区。因此,数据访问策略可以在一个集中位置定义一次,并且将一致地应用于整个组织。
然而,在 DLT 管道的上下文中,这两种存储输出数据集的方法是彼此互斥的 – 也就是说,特定的 DLT 管道不能将某些数据集存储在 Unity Catalog 中,而将其他数据集存储在 Hive Metastore 中。您必须为整个数据管道输出选择单一的元数据存储位置。
为什么要将数据集存储在 Unity Catalog 中?
Unity Catalog 是在湖屋中存储数据和查询数据集的新的最佳方法。您可能会选择在数据管道中将数据着陆到 Unity Catalog 而不是 Hive Metastore,原因包括以下几点:
-
数据默认受到保护。
-
有一套与定义数据访问策略的组和用户相一致的访问策略的一致定义,而不是为每个单独的工作区定义数据访问策略。
-
开源技术,无供应商锁定风险。
此外,Unity Catalog 提供了与 Hive 兼容的 API,允许第三方工具与 Unity Catalog 元数据存储集成,就像它是 Hive Metastore 一样。
创建一个新的目录
Unity Catalog 和 Hive Metastore 之间的一个主要区别在于,前者在定义表时引入了三层命名空间。父命名空间将指向目录对象。目录是一个逻辑容器,用于存储一个或多个模式,或数据库。
构建新的 DLT 流水线的第一步是定义一个集中的位置来存储输出数据集。在 Unity Catalog 中创建一个新的目录非常简单。可以通过多种方式完成,例如通过 Catalog Explorer UI、使用在笔记本中执行的 SQL 语句,或者使用 Databricks REST API。
我们将使用 Databricks Catalog Explorer UI 来创建一个新的目录:
-
首先,通过点击导航侧边栏中的 Catalog Explorer 标签,导航到 Catalog Explorer。
-
接下来,点击 创建 目录 按钮。
-
给目录起个有意义的名字。
-
选择 标准 作为目录类型。
-
最后,点击 创建 按钮来创建新的目录。
分配目录权限
如前所述,使用 Unity Catalog 的一个好处是,默认情况下你的数据是受到保护的。换句话说,除非明确允许,否则默认情况下拒绝访问存储在 Unity Catalog 中的数据。要在新创建的目录中创建新表,我们需要授予创建和操作新表的权限。
重要提示
如果你是目标目录和模式对象的创建者和所有者,并且是 DLT 流水线的创建者和所有者,那么你不需要执行以下 GRANT 语句。GRANT 语句旨在展示在典型的 Unity Catalog 元存储中跨多个小组和用户共享数据资产时所需的权限类型。
首先,让我们授予使用目录的权限。在一个新的笔记本中,执行以下 SQL 语法来授予使用新创建的目录的权限,其中 my_user 是 Databricks 用户的名字,chp2_transforming_data 是在前面的示例中创建的目录名称:
%sql
GRANT USE CATALOG, CREATE SCHEMA ON CATALOG `chp2_transforming_data` TO `my_user`;
接下来,我们需要创建一个模式,用来存储来自 DLT 流水线的输出数据集。在相同的笔记本中,执行以下 SQL 语句来创建一个新的模式:
%sql
USE CATALOG `chp2_transforming_data`;
CREATE SCHEMA IF NOT EXISTS `ride_hailing`;
USE SCHEMA `ride_hailing`;
执行以下语句来授予在新创建的模式中创建物化视图的权限:
%sql
GRANT USE SCHEMA, CREATE TABLE, CREATE MATERIALIZED VIEW ON SCHEMA `ride_hailing` TO `my_user`;
到现在为止,你应该能够看到 Unity Catalog 如何简单而强大地将一致的数据安全性应用到你的数据流水线数据集,提供数据管理员多种选项来强制执行组织内的数据集权限。接下来,让我们关注如何配置 DLT 流水线的一些高级功能和设置。
数据流水线设置
到目前为止,我们只讨论了如何使用 DLT 框架声明表、视图和对到达数据的转换。然而,执行特定数据流水线的计算资源在将最新数据加载到湖仓中也起着重要作用。
本节将讨论不同的数据管道设置,以及如何在运行时控制计算资源,例如集群。
以下管道设置可以通过 DLT UI 或 Databricks REST API 直接配置。
DLT 产品版本
数据管道产品版本告诉 DLT 框架你的数据管道将使用哪些功能集。更高版本的产品包含更多功能,因此,Databricks 会评估更高的价格(以 DBU 计费)。
Databricks 为 DLT 管道提供了三种类型的产品版本,按功能集的多少从少到多排列:
-
基础版:基础版是最基本的产品版本。此产品版本仅适用于将新数据追加到流表的流式工作负载。数据期望(数据质量执行将在下一章讨论)和应用变更数据捕获的工具在此版本中不可用。
-
专业版:专业版产品版本是比 Core 版本更高的版本。此产品版本专为流式工作负载设计,能够向流表中追加新数据,并使用APPLY CHANGES命令应用更新和删除操作。然而,该产品版本不提供数据质量期望。
-
高级版:高级版产品版本是功能最为丰富的版本。此产品版本支持数据质量期望,同时支持向流表中追加新数据,并对上游数据源中发生的插入、更新和删除操作进行处理。
有时你的需求可能会随着时间的推移发生变化。例如,你可能需要严格的数据质量执行,以防止第三方商业智能(BI)报告工具在下游发生故障。在这种情况下,你可以随时更新现有 DLT 管道的产品版本,使你的数据管道能够根据功能需求和预算的变化进行调整。
管道执行模式
DLT 提供了一种方式,通知系统数据管道的更改是实验性的。这个功能被称为数据管道环境模式。共有两种环境模式——开发和生产。它们的主要区别在于计算资源的行为。
在开发环境模式下,如果遇到故障,数据流任务不会自动重试。这允许数据工程师在临时开发周期中干预并修正任何程序性错误。
此外,在开发模式下发生故障时,执行数据管道更新的集群将保持运行。这允许数据工程师查看集群的驱动日志和集群指标,同时防止每次管道执行时集群的重新配置和重新初始化运行时,这个过程在不同云服务商中可能需要 10 到 15 分钟才能完成。预计会有较短且迭代的开发和测试周期,通过保持集群的持续运行,帮助数据工程师更顺利地完成开发生命周期。
数据管道环境模式可以通过点击 DLT UI 中数据管道最顶部导航栏的环境模式切换开关来设置。
图 2.3 – DLT 管道执行模式可以通过 UI 中的切换开关进行设置
或者,环境模式也可以通过 Databricks REST API 来设置。在下面的代码示例中,我们将使用 Python requests 库发送一个 PUT 请求到 Databricks DLT 管道 REST API,设置 DLT 管道的开发模式。请注意,终端 URL 会根据你的 Databricks 工作区部署而有所不同,下面的代码只是一个示例:
import requests
response = requests.put(
"https://<your_databricks_workspace>/api/2.0/pipelines/1234",
headers={"Authorization": f"Bearer {api_token}"},
json={
"id": "1234",
"name": "Clickstream Pipeline",
"storage": "/Volumes/clickstream/data",
"clusters": [{
"label": "default",
"autoscale": {
"min_workers": 1,
"max_workers": 3,
"mode": "ENHANCED"}
}],
"development": True,
"target": "clickstream_data",
"continuous": False
}
)
Databricks 运行时
DLT 是 Databricks 数据智能平台上的一个无版本产品功能。换句话说,Databricks 管理数据管道所使用的集群底层Databricks 运行时(DBR)。
此外,Databricks 会自动升级数据管道集群,以使用最新的稳定运行时版本。运行时升级非常重要,因为它们引入了错误修复、新的性能特性和其他增强功能。这意味着你的数据管道将执行得更快,从而节省更多时间和金钱,帮助你更高效地转换最新的数据到湖仓中。
你甚至可能迫不及待地想要测试最新的性能特性。每个 DLT 管道都有一个通道设置,允许数据工程师选择两种通道选项之一——当前和预览。预览通道允许数据工程师配置数据管道,使用包含新性能特性和其他增强功能的最新实验性运行时进行执行。然而,由于这是一个实验性运行时,不建议在生产环境中使用 Databricks 运行时的预览通道。相反,建议使用“当前”选项,选择 Databricks 运行时的最新稳定版本。
此外,DLT 系统将主动捕获在生产模式中部署的数据管道的运行时异常。例如,如果新的运行时版本引入了运行时错误(也称为运行时回归)或库版本冲突,DLT 将尝试将集群降级到一个已知能够成功执行数据管道的较低版本,并将重试执行管道更新。
以下图表展示了自动运行时升级异常处理。
图 2.4 – 在生产模式下,DLT 将尝试使用较低版本的 Databricks 运行时重新运行失败的数据管道执行。
管道集群类型
每个数据管道将有两个相关的集群——一个用于执行数据集更新,一个用于执行表维护任务。
这两种类型的集群的设置在管道的管道设置中通过 JSON 集群配置定义表达。可以在管道设置中表达三种类型的集群配置——更新集群配置、维护集群配置,以及作为默认集群配置的第三种选择,适用于更新和维护集群的通用设置。该 JSON 配置的架构与 Databricks 集群 REST API 的架构非常相似。
除了配置集群的物理属性,如工作节点数量和虚拟机实例类型外,集群配置还可以包含高级的 Spark 配置。让我们一起浏览一个示例集群配置。
以下示例包含两个独立的集群配置——一个默认集群配置,将应用于更新和维护 DLT 集群;另一个集群配置,仅应用于更新 DLT 集群。
在第一个集群配置中,我们将使用标签属性指定集群配置为默认集群配置。这意味着该集群配置将应用于用于更新数据集的 DLT 集群以及用于运行表维护任务的集群。接下来,我们将为 DLT 集群启用自动扩展,指定所有集群将从一个虚拟机开始部署,但随着处理需求的增加,可以扩展到最多五个虚拟机。我们还将指定使用增强版的集群自动扩展算法。
在第二组集群配置中,我们将指定集群配置应仅应用于 DLT 更新集群,仍然使用标签属性。接着,我们将指定为更新集群的驱动节点和工作节点配置哪些实例类型。对于负责协调任务的驱动节点,我们将指定使用 i4i.2xlarge EC2 实例类型,而所有工作节点将使用 i4i.xlarge EC2 实例类型。最后,我们还将启用 Databricks Runtime 性能特性,称为 自动优化 Shuffle(AOS)。AOS 将在运行时自动调整 Spark shuffle 分区的数量,从而在广泛的 Spark 转换操作(如连接、聚合和合并操作)期间提高性能。
重要说明
在以下示例中,我们选择使用虚拟机实例来说明 AWS 云的集群配置设置。 然而,如果您的工作空间位于不同的云服务提供商上,我们建议使用类似大小的 Delta 缓存加速 VM 实例——驱动节点为八个核心,工作节点为四个核心(docs.databricks.com/en/optimizations/disk-cache.html
):
{
"clusters": [{
"label": "default",
"autoscale": {
"min_workers": 1,
"max_workers": 5,
"mode": "ENHANCED"}
},
{
"label": "updates",
"node_type_id": "i4i.xlarge",
"driver_node_type_id": "i4i.2xlarge",
"spark_conf": {"spark.sql.suffle.partitions": "auto"}
}]
}
如您所见,集群配置是一个强大的工具,提供了数据工程师应用通用集群设置、针对特定集群设置或两者结合的能力。这是为特定工作负载调整集群并为 DLT 管道提供额外性能的绝佳方式。
无服务器计算与传统计算
数据管道可以使用配置了传统计算或无服务器计算的集群来执行。
传统计算为用户提供了最大的计算资源控制。然而,使用传统计算时,用户需要管理底层集群的多个方面。例如,数据工程团队需要配置集群属性,如自动扩展行为、是否使用 Photon 引擎或传统的 Catalyst 引擎在 Spark 中执行管道,以及可选的集群标签。此外,传统计算允许用户对为集群的驱动节点和工作节点选择的虚拟机实例类型拥有完全控制权。正如我们在前一部分看到的那样,可以在管道设置中通过在 JSON 配置中列出特定实例类型来指定虚拟机实例类型。例如,以下集群配置指定了 i4i.xlarge 和 i4i.2xlarge EC2 实例类型用于 DLT 管道中的所有更新集群:
{
"clusters": [{
"label": "updates",
"node_type_id": "i4i.xlarge",
"driver_node_type_id": "i4i.2xlarge"
}]
}
然而,配置为使用无服务器计算的 DLT 管道将会抽象掉所有底层集群设置,例如集群虚拟机实例类型、工作节点数量以及自动扩展设置。正如无服务器计算这一名称所示,计算资源将由 Databricks 在 Databricks 云提供商帐户中进行配置和管理。在后台,Databricks 将维持一个预配置计算资源池,以便集群配置快速完成。一旦数据管道的更新被触发,DLT 系统将在 Databricks 云提供商帐户中创建一个逻辑网络,并初始化一个集群来执行管道的数据流图。Databricks 将自动选择虚拟机实例类型、Photon 执行引擎以及自动扩展行为。
作为一层额外的安全性,逻辑网络之间或与外部互联网之间不允许通信,并且计算资源不会在无服务器工作负载之间重复使用。当数据管道处理完成并且集群已终止时,计算资源将被释放回云提供商并销毁。
你可能会选择无服务器计算来消除维护和更新多个集群策略的基础设施开销,同时也能在应对处理需求激增时,利用快速集群配置。此外,无服务器执行还可以启用其他平台功能,例如在连续处理模式下更新物化视图(处理模式将在下一节中介绍)。
加载外部依赖项
数据管道可能需要加载外部依赖项,如帮助工具或第三方库。因此,DLT 提供了三种方式来安装数据管道的运行时依赖项:
-
从笔记本单元格中,使用%pip魔法命令(
docs.databricks.com/en/notebooks/notebooks-code.html#mix-languages
) -
从工作区文件或 Databricks 仓库加载模块
-
使用集群初始化脚本
流行的 Python 包管理器 pip 可以通过 %pip Databricks 魔法命令,从数据管道源代码中的任何笔记本安装 Python 模块。%pip 是在数据管道中安装库依赖项的最简单方法。在运行时,DLT 系统会检测到所有包含 %pip 魔法命令的笔记本单元,并首先执行这些单元,然后再进行管道更新。此外,声明的数据管道源代码中的所有笔记本将共享一个虚拟环境,因此库依赖项将在一个隔离的环境中一起安装,并对数据管道源代码中的所有笔记本全局可用。相反,数据管道中的笔记本不能安装相同 Python 库的不同版本。例如,以下代码示例将使用 pip 包管理器安装流行的库 numpy、pandas 和 scikit-learn,以及来自 Databricks Volumes 位置的自定义 Python 库:
%pip install numpy pandas scikit-learn /Volumes/tradingutils/tech-analysis-utils-v1.whl
最佳实践是,这些依赖项安装语句应放在笔记本的顶部,以便能够更快速地引用管道依赖项。
另外,库依赖项也可以作为 Python 模块安装。在这种情况下,库可以作为工作区文件或从 Databricks Repo 安装并加载到 DLT 管道中,前提是该模块使用 Git 提供者(如 GitHub 或 Bitbucket)进行版本控制。
最后,集群初始化脚本也可以用来安装外部依赖项。这些脚本在集群配置好虚拟机并安装好 Databricks 运行时后运行,但在数据管道开始执行之前。例如,这种类型的依赖项安装可能适用于在所有数据工程平台上需要一致安装公司范围的库的场景。
重要说明
你可能已经注意到,前面的选项仅涵盖了安装 Python 依赖项。DLT 不支持安装 JVM 库,因为它仅提供 Python 和 SQL 编程接口。
数据管道处理模式
数据管道处理模式决定了管道内的表和物化视图更新的频率。DLT 提供了两种管道处理模式——触发式处理模式和连续处理模式。
触发式处理模式将在管道内更新数据集一次,然后立即终止为运行管道而配置的集群,将计算资源释放回云提供商,从而终止额外的云费用产生。顾名思义,触发式处理模式可以以临时的方式运行,并会在触发事件发生时立即执行,例如用户在 DLT 用户界面上点击按钮或调用 Databricks REST API。
图 2.5 – 一个触发的数据管道将刷新每个数据集,然后立即终止集群
触发处理模式也可以通过 cron 调度触发运行,调度可以通过 UI 或 REST API 进行配置。如 图 2.6 所示,点击 DLT UI 中的 Schedule 下拉按钮,点击 Add schedule 按钮,最后选择触发管道更新的时间,即可创建一个循环调度。每天,管道中的数据集将在预定时间刷新。
图 2.6 – DLT 管道可以根据重复的计划安排来刷新数据集
相反,持续处理模式将配置计算资源来刷新管道中的数据集,但会持续执行,处理数据并在数据从源端到达时刷新表格和物化视图。持续处理管道将保持计算资源运行,并持续产生云成本,换取最小的数据陈旧性。当数据延迟优先于云计算成本时,应选择这种类型的管道模式。
幸运的是,管道处理模式和其他管道设置可以在数据管道的生命周期内进行更新,使得管道能够灵活应对处理延迟和计算成本。例如,经济衰退可能迫使一个组织优先考虑节省成本而非延迟,但之后可能又会重新强调延迟。
让我们一起使用本章所学的所有内容,构建一个 DLT 管道,将 SCD 类型 2 的变更应用到数据管道中的下游数据集。
实践练习 – 应用 SCD 类型 2 的变更
在本次实践练习中,我们将使用 Databricks Auto Loader 增量加载写入云存储账户原始着陆区的 JSON 文件。接下来,我们将转换下游列并连接从外部 Postgres 数据库中获取的数据。
重要提示
从远程 Postgres 数据库读取数据是可选的。此步骤旨在展示 Databricks 数据智能平台的灵活性,向你展示如何轻松地从远程关系型数据库管理系统(RDBMS)读取结构化数据,并将其与半结构化数据结合。如果你没有 Postgres 数据库,我们为你提供了一个包含出租车司机信息的静态 DataFrame。
如果你还没有这样做,你需要从本章的 GitHub 仓库克隆随附的笔记本,仓库地址是:github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter02
。
我们首先导入数据生成器笔记本,名为生成模拟出租车行程数据。该笔记本将创建一个包含虚拟出租车行程信息的模拟数据集。一旦模拟数据集生成,该笔记本会将出租车行程数据集存储为多个 JSON 文件到我们的云存储账户中,之后这些文件将由我们的 DLT 管道摄取。将出租车行程数据生成器笔记本附加到通用集群,并执行所有单元格以生成模拟数据。
接下来,让我们创建我们的 DLT 管道定义。在左侧边栏点击工作区表格,点击添加下拉菜单,选择笔记本,创建一个新的笔记本。将笔记本重命名为一个有意义的名称,例如出租车行程 DLT 管道。我们将在这个笔记本中声明 DLT 管道的数据集和转换。
接下来,导入 DLT Python 模块,以访问 DLT 函数装饰器来定义数据集和依赖关系,以及 PySpark 函数模块:
import dlt
import pyspark.sql.functions as F
我们需要创建一个流式表格,来摄取已写入云存储着陆区的出租车行程 JSON 数据。我们首先定义一个新的流式表格,使用cloudFiles数据源监听原始着陆区中的新文件事件:
# This location keeps track of schema changes
SCHEMA_LOCATION = "/tmp/chp_02/taxi_data_chkpnt"
# This location contains the raw, unprocessed trip data
RAW_DATA_LOCATION = "/tmp/chp_02/taxi_data/"
@dlt.table(
name="raw_taxi_trip_data",
comment="Raw taxi trip data generated by the data generator notebook"
)
def raw_taxi_trip_data():
return (
spark.readStream.format("cloudFiles")
.option("cloudFiles.format", "json")
.option("cloudFiles.schemaLocation", SCHEMA_LOCATION)
.load(RAW_DATA_LOCATION) )
随着新的出租车行程数据到达,我们的 DLT 管道将通过 Auto Loader 高效加载数据,仅获取与未处理文件相关的信息。
现在我们已经摄取了原始出租车行程数据,可以开始将记录的更改应用到下游表中。让我们首先定义一个目标流式表格,用于应用由模拟出租车行程数据源报告的 SCD 类型 2 更改:
# Define a new streaming table to apply SCD Type 2 changes
dlt.create_streaming_table("taxi_trip_data_merged")
接下来,我们将利用之前讲解过的apply_changes()函数来指示 DLT 系统如何应用更改,哪些列应在下游表中省略,以及使用哪种 SCD 类型。请将以下函数调用添加到笔记本中:
dlt.apply_changes(
target="taxi_trip_data_merged",
source="raw_taxi_trip_data",
keys = ["trip_id"],
sequence_by = F.col("sequence_num"),
apply_as_deletes = F.expr("op_type = 'D'"),
except_column_list = ["op_type", "op_date", "sequence_num"],
stored_as_scd_type = 2
)
在最后一步,我们将对上游表中的一些列进行转换,例如将浮动数据类型的列四舍五入到小数点后两位,以及将trip_distance列拆分为一个以英里为单位的列和另一个以千米为单位的列。接下来,我们将连接到远程 Postgres 数据库并读取最新的出租车司机信息。如果你可以访问 Postgres 数据库,你可以导入标题为生成 Postgres 表的笔记本并执行单元格来生成一个测试用的表格。我们的最终流式表将丰富我们的数据并连接最新的出租车司机参考数据,其外观如下所示:
@dlt.table(
name="raw_driver_data",
comment="Dataset containing info about the taxi drivers"
)
def raw_driver_data():
postgresdb_url = f"jdbc:postgresql://{POSTGRES_HOSTNAME}:{POSTGRES_PORT}/{POSTGRES_DB}"
conn_props = {
"user": POSTGRES_USERNAME,
"password": POSTGRES_PW,
"driver": "org.postgresql.Driver",
"fetchsize": "1000"
}
return (
spark.read
.jdbc(postgresdb_url,
table=POSTGRES_TABLENAME,
properties=conn_props))
@dlt.table(
name="taxi_trip_silver",
comment="Taxi trip data with transformed columns"
)
def taxi_trip_silver():
return (
dlt.read("taxi_trip_data_merged")
.withColumn("fare_amount_usd",
F.round(F.col("trip_amount"), 2))
.withColumn("taxes_amount_usd",
F.round(F.col("trip_amount") * 0.05, 2))
.withColumn("trip_distance_miles",
F.round(F.col("trip_distance"), 2))
.withColumn("trip_distance_km",
F.round(F.col("trip_distance")
* 1.60934, 2)) # 1 mile = 1.60934 km
).join(
dlt.read("raw_driver_data"),
on="taxi_number",
how="left"
)
在最后一个函数定义中,我们使用dlt.read()函数来检索早期的数据集声明。在后台,DLT 框架将数据集添加到数据流图中,创建taxi_trip_data_merged和taxi_trip_silver数据集之间的依赖关系。
现在,是时候创建我们的 DLT 管道了。将前一步的笔记本附加到通用集群并执行笔记本单元格。当提示时,点击蓝色的创建管道按钮打开管道UI 页面。为管道起一个有意义的名称,比如出租车行程数据管道。由于我们使用了apply_changes()函数,因此我们需要选择高级产品版本。确保选择了触发处理模式单选按钮。为了查看由apply_changes()函数创建的后台表格,选择 Hive Metastore 作为存储位置,并提供一个目标模式来存储管道数据集。接受其余的默认值,然后点击创建按钮来创建管道。
最后,通过点击 DLT UI 中的开始按钮来运行新创建的管道。很快,你会看到一个数据流图,该图会从原始 JSON 数据中获取更改,丰富下游列,并将来自 Postgres 数据库的远程结构化数据进行连接。
图 2.7 – 由 DLT 系统生成的数据流图
当模拟出租车行程数据源中的数据发生变化时,DML 更改的完整历史记录会被采集并应用到目标taxi_trip_data_merged表。我们数据管道的输出将是一个精心策划的流式表,包含有关出租车行程、出租车司机和出租车车辆的信息。最棒的是,只需几行代码,我们就部署了一个完全可扩展、成本高效的数据管道,可以轻松处理数十亿个文件。
总结
在这一章中,我们探讨了 DLT 如何通过抽象化处理 Spark 中数据的许多低级细节,简化了我们的数据管道。我们看到 Databricks Auto Loader 如何解决了从云存储中流式处理文件的可扩展性问题。只需几行代码,我们就部署了一个可扩展的后端系统,能够在云存储位置有新文件出现时立即高效读取。当涉及到将数据变更应用到管道中的下游数据集时,DLT 框架再次简化了数据对账工作,特别是在数据事件发布延迟或乱序的情况下。我们还看到,只需在apply_changes() API 中做几个参数调整,就能应用慢变维度。最后,我们揭示了数据管道设置的细节,根据计算需求和我们在数据管道中所需的 DLT 功能集优化了管道的计算过程。我们还看到 DLT 如何自动处理管道故障,主动采取措施并尝试修复某些运行时异常。
在下一章,我们将探讨如何在分布式账本技术(DLT)中使用期望值来强制执行数据质量规则,确保数据在每一跳的传输过程中都符合要求,并在数据质量规则被违反时采取相应的行动。
第三章:使用 Delta Live Tables 管理数据质量
本章介绍了几种在数据管道中管理数据集数据质量的技术。我们将介绍 Delta Live Tables(DLT)中的 expectations,这是一种在将数据合并到下游表之前,强制执行某些数据质量约束的方法。稍后我们将探讨一些更高级的技术,例如将不良数据隔离以供人工干预。接下来,我们还将展示如何解耦约束,以便可以由组织中非技术人员单独管理。到本章结束时,你应该能清楚地了解如何采取措施确保湖仓中数据集的数据完整性,并采取适当的行动处理不符合预期标准的数据。
本章将覆盖以下主题:
-
在 Delta Lake 中定义数据约束
-
使用临时数据集验证数据处理
-
期望介绍
-
实践练习:编写你的第一个数据质量期望
-
对失败的期望采取行动
-
应用多个数据质量期望
-
将期望与 DLT 管道解耦
-
实践练习 – 隔离低质量数据进行修正
技术要求
为了跟随本章的内容,建议拥有 Databricks 工作区权限,以创建通用集群和使用集群策略创建 DLT 管道。还建议拥有 Unity Catalog 权限,以创建和使用目录、模式和表。所有代码示例可以从本章的 GitHub 仓库下载,网址为 github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter03
。我们将使用纽约市黄出租车数据集,数据集位于 Databricks 文件系统的 /databricks-datasets/nyctaxi/tripdata/yellow。本章将创建并运行多个新的笔记本和 DLT 管道,使用 Advanced 产品版本。因此,预计这些管道将消耗约 10-20 Databricks Units(DBUs)。
在 Delta Lake 中定义数据约束
数据约束是一种有效的方式,用于定义进入 Delta 表之前必须满足的标准。约束是按列定义的,并作为额外的表元数据存储在 Delta 表中。
在 Databricks 数据智能平台中提供了四种不同类型的约束:
-
NOT NULL:确保表中某列的数据不为 null。NOT NULL 约束最初在 Apache Spark 的 StructField 类定义中引入。
-
检查:一个布尔表达式,在每一行插入之前,必须评估为真。检查约束允许数据工程师强制执行复杂的验证逻辑,确保特定列满足条件。
-
主键:确保某一列在表的所有行中具有唯一性。主键约束是一种特殊的约束,它仅用于说明,不会强制执行传入的数据。正如我们在接下来的示例中看到的,非空约束必须与主键约束一起使用。
-
外键:建立某一列与另一个表之间的关系。像主键约束一样,外键约束也是纯粹的说明性约束。
此外,只有非空和检查约束会强制执行传入的数据。
约束 | 强制执行 | 说明性 |
---|---|---|
非空 | ✔️ | ✖️ |
检查 | ✔️ | ✖️ |
主键 | ✖️ | ✔️ |
外键 | ✖️ | ✔️ |
表 3.1 – 数据质量约束可以在 Databricks 数据智能平台上强制执行或不强制执行
重要说明
主键约束和外键约束要求 Delta 表存储在 Unity Catalog 中,否则将抛出运行时错误。
让我们看看如何使用约束来定义湖仓中两个 Delta 表之间的层级关系。首先,在 Databricks 笔记本中创建一个新的基于 SQL 的笔记本。我们从定义一个子表开始,该表包含有关出租车司机的数据,名为drivers,并在driver_id列上定义主键。将以下代码片段添加到新的笔记本单元格中:
%sql
CREATE CATALOG IF NOT EXISTS yellow_taxi_catalog;
CREATE SCHEMA IF NOT EXISTS yellow_taxi_catalog.yellow_taxi;
CREATE TABLE yellow_taxi_catalog.yellow_taxi.drivers(
driver_id INTEGER NOT NULL,
first_name STRING,
last_name STRING,
CONSTRAINT drivers_pk PRIMARY KEY(driver_id));
接下来,让我们定义一个父表,rides,为ride_id列定义主键,并为其添加一个引用drivers表的外键。将以下代码片段添加到第一个笔记本单元格下方:
%sql
CREATE TABLE yellow_taxi_catalog.yellow_taxi.rides(
ride_id INTEGER NOT NULL,
driver_id INTEGER,
passenger_count INTEGER,
total_amount DOUBLE,
CONSTRAINT rides_pk PRIMARY KEY (ride_id),
CONSTRAINT drivers_fk FOREIGN KEY (driver_id)
REFERENCES yellow_taxi_catalog.yellow_taxi.drivers);
将新创建的笔记本附加到一个多用途集群,并执行笔记本单元格,以创建父表和子表。最后,让我们在目录资源管理器中导航到新定义的表,并直接从 Databricks 数据智能平台生成实体关系图(ERD)。在 Databricks 工作区中,点击左侧边栏的目录资源管理器。导航到上述示例中的yellow_taxi_catalog目录。在 Unity Catalog 中点击定义的模式,然后点击父表。侧边栏将展开,显示有关 Delta 表的元数据。点击标题为查看关系的按钮,查看 ERD。
图 3.1 – 数据约束可用于定义 Delta 表之间的主键和外键关系
如前所述,主键和外键约束仅作为信息性约束,并不会强制应用于传入的数据。相反,建议实施额外的保护措施,以确保 Delta 表中主键列的数据完整性。让我们来看一些有效的策略,帮助我们维护在湖仓表中定义的主键列的完整性。
使用临时数据集来验证数据处理
正如我们在本节中将看到的,创建视图是一种有效的验证主键列唯一性的方法。此外,我们还可以在 Databricks 数据智能平台中定义警报,通知数据管理员潜在的数据质量问题,以便他们能够采取适当的措施来纠正数据完整性问题。
我们可以利用视图来验证主键列的唯一性。回顾前面部分我们定义的rides和drivers表。在这个例子中,我们将定义一个视图,用于验证rides Delta 表中主键列的唯一性。通过返回到工作区并右键点击打开对话框,创建一个新的查询。选择新建 | 查询,在编辑器中打开一个新的查询。接下来,给查询重新命名,起一个有意义的名字,比如rides_pk_validation_vw。最后,添加以下查询文本并点击运行按钮,验证查询是否按预期运行:
CREATE VIEW yellow_taxi_catalog.yellow_taxi.rides_pk_validation_vw AS
SELECT *
FROM (
SELECT count(*) AS num_occurrences
FROM yellow_taxi_catalog.yellow_taxi.rides
GROUP BY ride_id
) WHERE num_occurrences > 1
结果表明,主键唯一性对 Yellow Taxi Corporation 的下游报告至关重要。让我们在 Databricks 数据智能平台中创建一个新的警报,提醒我们的数据管理员可能存在的数据损坏问题,以便他们能在插入重复主键时采取适当的措施。
首先,让我们创建一个将由警报运行的查询。从侧边栏点击查询按钮,再点击创建查询按钮,进入 Databricks 数据智能平台中的查询编辑器。将查询重命名为一个有意义的名称,如Rides 主键唯一性。输入以下 SQL 文本作为查询主体,点击保存按钮,并选择一个工作区文件夹来保存查询。点击运行按钮,确保查询成功运行:
SELECT count(*) AS num_invalid_pks
FROM yellow_taxi_catalog.yellow_taxi.rides_pk_validation_vw;
接下来,从侧边栏点击警报按钮,进入警报界面。然后,点击创建警报按钮,开始创建一个新的警报,并在警报名称文本框中输入描述性名称,如无效的 Rides 表主键。在查询下拉菜单中选择我们刚刚创建的查询。勾选发送通知复选框,并通过点击创建警报按钮接受默认设置。在实际场景中,这可以是一个发送给值班数据工程师的电子邮件链,或其他流行的通知渠道,如 Slack 或 Microsoft Teams。
这个示例在现实世界的数据管道中非常实用。然而,视图需要在每次运行管道时计算最新的表状态,还需要维护通知警报的配置。这需要大量的配置来维护,随着我们向管道中添加更多的表格,这种方式显然无法扩展。如果我们有一种更简单的方法,将数据质量声明作为我们 DLT 管道声明的一部分,会怎样呢?
期望介绍
期望是在 DLT 管道中与数据集定义一起定义的数据质量规则。数据质量规则是应用于每条通过特定数据集定义的记录的布尔表达式。表达式必须评估为True,才会标记该记录为通过,否则会导致记录失败,表明该记录未通过数据质量验证。
此外,DLT 管道将记录每一行在数据管道中处理时的数据质量指标。例如,DLT 将记录通过数据质量验证的记录数,以及未通过的记录数。
期望组合
每个期望由三个主要组成部分组成:描述、布尔表达式和要采取的行动。
图 3.2 – DLT 期望的主要组成部分
期望是通过 DLT 函数装饰器声明的。该函数装饰器指定当某个特定约束或一组约束评估为False时应该采取的操作。此外,函数装饰器接受两个输入参数:一个简短的描述,用于描述数据质量约束,以及一个布尔表达式,必须评估为True,才能将某一行标记为通过验证。
实操练习 – 编写你的第一个数据质量期望
为了熟悉 DLT 语法,让我们通过一个现实世界的例子来编写一个数据管道,模拟一个名为 Yellow Taxi Corporation 的纽约市出租车公司。我们将编写一个简单的数据管道,强制执行一个数据质量约束,适用于我们接收到的 NYC 出租车数据,并在有记录不符合我们数据质量规范时提醒我们。在这个场景中,我们希望确保接收的旅行数据中没有任何负数的总金额,因为我们的出租车司机不可能欠乘客任何钱。
生成出租车旅行数据
让我们从登录 Databricks 工作区开始。本练习需要使用附带的 NYC Yellow Taxi 旅行数据生成器,可以从本章的 GitHub 仓库下载。你可以将数据生成器笔记本导入到 Databricks 工作区,或者创建一个新的 Python 笔记本并使用以下代码片段。
首先,我们需要下载 dbldatagen Python 库,它将帮助我们随机生成新的出租车行程数据。将以下代码片段添加到您的笔记本中,该代码片段使用 %pip 魔法命令来下载该库:
%pip install dbldatagen==0.4.0
现在库已经安装完成,让我们定义一个 Python 函数,根据我们的 schema 生成新的出租车行程数据。我们将指定列以记录典型的出租车行程细节,包括乘客数量、车费、行程距离等:
def generate_taxi_trip_data():
"""Generates random taxi trip data"""
import dbldatagen as dg
from pyspark.sql.types import (
IntegerType, StringType, FloatType, DateType
)
ds = (
dg.DataGenerator(spark, name="random_taxi_trip_dataset",
rows=100000, partitions=8)
.withColumn("trip_id", IntegerType(),
minValue=1000000, maxValue=2000000)
.withColumn("taxi_number", IntegerType(),
uniqueValues=10000, random=True)
.withColumn("passenger_count", IntegerType(),
minValue=1, maxValue=4)
.withColumn("trip_amount", FloatType(), minValue=-100.0,
maxValue=1000.0, random=True)
.withColumn("trip_distance", FloatType(),
minValue=0.1, maxValue=1000.0)
.withColumn("trip_date", DateType(),
uniqueValues=300, random=True))
return ds.build()
现在我们已经定义了随机生成新行程数据的方法,我们需要定义一个位置来存储这些新数据,以便 DLT 管道处理。 在新的笔记本单元中,让我们在 Databricks 文件系统(DBFS)上创建一个空目录,用于存储我们的行程数据:
dbutils.fs.mkdirs("/tmp/chp_03/taxi_data")
最后,我们需要一种方式将所有内容连接起来。在新的笔记本单元中,添加以下 for 循环,该循环将调用 generate_taxi_trip_data 函数并将数据写入 DBFS 位置:
import random
max_num_files = 100
for i in range(int(max_num_files)):
df = generate_taxi_trip_data()
file_name = f"/tmp/chp_03/taxi_data/taxi_data_{random.randint(1, 1000000)}.json"
df.write.mode("append").json(file_name)
接下来,创建一个通用集群来执行行程数据生成器笔记本。一旦创建了通用集群,导航到新的笔记本并点击 Databricks 数据智能平台顶部导航栏中的集群下拉菜单。选择您创建的集群名称,然后选择 Attach,将行程数据生成器笔记本附加到集群并执行所有单元。出租车行程数据生成器将向 DBFS 位置追加几个包含随机生成的行程数据的 JSON 文件。
创建一个新的 DLT 管道定义
现在我们已经生成了新数据,让我们为 DLT 管道定义创建另一个新的笔记本。导航到侧边栏的工作区选项卡,深入到您用户的主目录,右键点击并选择 添加笔记本 来创建一个新的笔记本。
给新的笔记本起一个有意义的名字,例如 第三章 – 强制数据质量。首先导入 DLT Python 模块以及 PySpark 函数:
import dlt
from pyspark.sql.functions import *
接下来,让我们定义一个铜表,yellow_taxi_raw,该表将接收由我们的出租车行程数据生成器写入 DBFS 位置的出租车行程数据:
@dlt.table(
comment="The randomly generated taxi trip dataset"
)
def yellow_taxi_raw():
path = "/tmp/chp_03/taxi_data"
schema = "trip_id INT, taxi_number INT, passenger_count INT, trip_amount FLOAT, trip_distance FLOAT, trip_date DATE"
return (spark.readStream
.schema(schema)
.format("json")
.load(path))
对于我们数据管道的下一层,我们的组织中的利益相关者要求我们提供一种方式,让他们的业务能够实时报告我们 incoming 行程数据的财务分析。因此,让我们添加一个银表,将传入的行程数据流进行转换,计算我们出租车公司 Yellow Taxi Corporation 的预期利润和损失。在这个示例中,我们将取乘客支付的总金额,开始计算这笔钱如何分配到资助业务的不同部分,并计算潜在的利润。
让我们定义我们的 silver 表定义,trip_data_financials。表定义开始就像任何普通的流式表定义一样。我们首先定义一个返回流式表的 Python 函数。接下来,我们使用 DLT 函数注解来声明此函数为流式表,并可选地为其指定名称 trip_data_financials,以及带有描述性文本的注释,描述此流式表。创建一个新的笔记本单元格,并为 silver 表添加以下 DLT 数据集定义:
@dlt.table(name="trip_data_financials",
comment="Financial information from incoming taxi trips.")
@dlt.expect("valid_total_amount", "trip_amount > 0.0")
def trip_data_financials():
return (dlt.readStream("yellow_taxi_raw")
.withColumn("driver_payment",
expr("trip_amount * 0.40"))
.withColumn("vehicle_maintenance_fee",
expr("trip_amount * 0.05"))
.withColumn("adminstrative_fee",
expr("trip_amount * 0.1"))
.withColumn("potential_profits",
expr("trip_amount * 0.45")))
在我们的 silver 表声明中,您可能注意到一个新函数装饰器,用于强制执行数据质量约束。在这种情况下,我们希望确保报告的旅行数据总金额大于零。
当我们的数据管道触发并更新铜级和银级数据集时,DLT 系统将检查每一行处理的情况,并评估该行的布尔表达式是否为真,从而符合我们的数据质量约束:
@dlt.expect("valid_total_amount", "trip_amount > 0.0")
在函数定义的主体部分,我们使用内置的 PySpark withColumn() 和 expr() 函数为铜级表的输出添加四个新列——driver_payment、vehicle_maintenance_fee、administrative_fee 和 potential_profits。这些列通过从原始的 trip_amount 列中提取一定比例来计算。在商业术语中,我们将从乘客收取的总金额分配为司机的支付、公司运营费用和公司潜在的利润。
在接下来的章节中,我们将探讨当期望布尔表达式的值为假时,DLT 系统将采取的不同类型的操作。默认情况下,DLT 系统将仅记录该行未通过特定行的布尔表达式,并将数据质量指标记录到系统日志中。在我们的 silver 表声明中,假设默认行为是记录警告消息。
运行数据管道
让我们从我们在笔记本中的数据集声明创建一个新的数据管道。执行笔记本单元格并确保没有语法错误。接下来,Databricks 数据智能平台将提示您创建一个新的数据管道。点击创建管道按钮以创建新的 DLT 数据管道。接下来,在目标设置中,选择一个 Unity Catalog 中的目录和模式,您希望将管道数据集存储到其中。在计算设置中,将最小工作节点数设置为1,最大工作节点数设置为2。点击创建按钮以接受默认设置。最后,点击启动按钮执行数据管道。您将进入数据流图的可视化表示。
图 3.3 – 我们的 NYC Yellow Taxi Corp. 管道的数据流图
在后台,DLT 系统将通过创建并初始化一个新的 Databricks 集群,开始解析我们笔记本中的数据集定义,将其转换为数据流图。如您所见,DLT 系统将从我们的 DBFS 位置获取原始旅行数据文件,导入到流式表格yellow_taxi_raw中。接下来,系统检测到我们的银色表格trip_data_financials的依赖关系,并将立即开始计算银色表格中的额外四列。在此过程中,我们的数据质量约束正在实时评估传入数据。
让我们实时查看数据质量。点击银色表格,DLT 用户界面将在右侧展开一个面板,汇总银色表格的内容。点击数据质量标签查看数据质量指标。注意到图表正在实时更新,因为我们的数据正在被处理。在所有已经被数据管道处理的数据中,您会注意到大约有 10%未通过valid_total_amount的期望——这是预期的结果。数据生成笔记本会故意将总金额为负数的记录发布到我们的云存储位置。我们可以轻松地看到有多少数据符合我们定义的数据质量标准,多少不符合。
图 3.4 – DLT 用户界面将实时汇总我们数据管道的数据质量指标。
恭喜!您已经在 Delta Live Tables 中编写了您的第一个数据质量约束。到目前为止,您应该已经看到了 DLT 框架是多么简单又强大。通过几行代码,我们能够强制执行传入数据的数据质量约束,并实时监控数据质量。这为数据工程团队提供了更多对数据管道的控制。
在下一节中,我们将看到数据工程团队如何利用 DLT 期望来应对潜在的数据质量问题,从而在数据损坏之前进行反应。
对失败的期望采取行动
当某个记录违反了在 DLT 数据集上定义的数据约束时,DLT 可以采取三种类型的动作:
-
警告:当 DLT 遇到表达式违规时,该记录将被记录为指标,并将继续写入下游目标数据集。
-
丢弃:当 DLT 遇到表达式违规时,该记录将被记录为指标,并被阻止进入下游目标数据集。
-
失败:当 DLT 遇到表达式违规时,管道更新将完全失败,直到数据工程团队成员能够调查并修正数据违规或可能的数据损坏。
你应该始终根据具体的用例选择一种动作,并根据如何处理不符合数据质量规则的数据来做出决定。例如,可能有时候数据未能满足定义的数据质量约束,但在 DLT 系统中记录违反的行并监控数据质量就能满足某个特定用例的要求。另一方面,可能会出现某些场景,其中必须满足特定的数据质量约束,否则传入的数据将破坏下游的处理流程。在这种情况下,更积极的行动,如使数据管道运行失败并回滚事务,是适当的行为。在这两种情况下,Delta Live Tables 框架为数据工程团队提供了完全的控制权,以决定违反的行应该如何处理,并定义系统应如何响应。
实操示例 – 由于数据质量差导致管道运行失败
在某些情况下,你可能希望立即停止数据管道更新的执行,以便干预并修正数据。例如,在这种情况下,DLT 预期值提供了一个能力,使用 @dlt.expect_or_fail() 函数装饰器立即使数据管道运行失败。
如果操作是表更新,事务会立即回滚,以防止污染坏数据。此外,DLT 将跟踪有关处理记录的附加元数据,以便数据工程团队能够找出数据集中的哪条记录导致了失败。
让我们看看如何更新之前的黄出租公司数据管道示例。在这种情况下,如果总金额为负数,会破坏下游的财务报告。在这种情况下,我们希望不是简单地记录违反预期的行,而是让管道运行失败,这样我们的数据工程团队可以调查数据中的潜在问题,并采取适当的措施,例如手动修正数据。
在 Delta Live Tables 框架中,调整数据管道的行为就像更新银色表定义的函数装饰器一样简单。让我们通过 expect_or_fail 动作来更新预期值:
@dlt.expect_or_fail("valid_total_amount", "trip_amount > 0.0")
银色表 trip_data_financials 的完整数据集定义应如下所示:
@dlt.table(
name="trip_data_financials",
comment="Financial information from completed taxi trips."
)
@dlt.expect_or_fail("valid_total_amount", "trip_amount > 0.0")
def trip_data_financials():
return (
dlt.readStream("yellow_taxi_raw")
.withColumn("driver_payment",
expr("trip_amount*0.40"))
.withColumn("vehicle_maintenance_fee",
expr("trip_amount*0.05"))
.withColumn("adminstrative_fee",
expr("trip_amount*0.1"))
.withColumn("potential_profits",
expr("trip_amount*0.45")))
接下来,让我们重新运行行程数据生成器,将附加文件追加到 Databricks 文件系统中的原始登录区。一旦行程数据生成器完成,返回到之前创建的黄出租公司数据管道,并点击 Start 按钮以触发数据管道的另一次执行。在本章的示例中,行程数据生成器会随机生成具有负总金额的行程数据。
你应该观察数据管道在本次运行中是否由于错误状态而导致更新失败。
图 3.5 – 当数据质量约束被违反时,数据流图将更新并显示错误。
扩展失败信息,你可以看到管道失败的原因是违反了期望约束。
图 3.6 – 数据管道日志将显示由于违反期望检查而导致的更新失败
应用多个数据质量期望
有时,数据集作者可能希望对数据集的每一行应用多个业务规则或数据质量约束。在这种情况下,DLT 提供了一套特殊的函数装饰器,用于指定多个数据质量约束定义。
@dlt.expect_all() 函数装饰器可以用于为特定数据集组合多个数据质量约束。类似地,当传入数据必须满足所有数据质量约束中的标准,否则不允许进入目标表时,可以指定 expect_all_or_drop()。最后,如果传入数据未满足数据质量约束集中的任何标准,expect_all_or_fail() 将导致数据管道运行失败。
让我们看看当出租车行程数据条目在未通过验证标准时,如何从下游数据集管道中丢弃这些无效数据:
assertions = {
"total_amount_constraint": "trip_amount > 0.0",
"passenger_count": "passenger_count >= 1"
}
@dlt.table(
name="yellow_taxi_validated",
comment="A dataset containing trip data that has been validated.")
@dlt.expect_all_or_drop(assertions)
def yellow_taxi_validated():
return (
dlt.readStream("yellow_taxi_raw")
.withColumn("nyc_congestion_tax",
expr("trip_amount * 0.05")))
在之前的示例中,我们使用期望函数装饰器定义了一组数据约束,并将它们集体应用到传入的数据。假设丢失一些出租车行程数据不会对下游过程造成威胁。因此,我们决定丢弃那些在我们期望声明中未通过验证步骤的记录。只需添加几行额外的配置,我们的数据管道就能在传入数据上执行数据质量约束,并自动对不符合定义标准的数据作出反应。
虽然我们仅仅是在我们的 DLT 数据管道中查看数据,但让我们看看 DLT 框架如何跨多个数据系统验证数据。
将期望从 DLT 管道中解耦
直到现在,我们仅仅是在表格定义中定义了数据质量约束。然而,可能会有一些场景,你希望将数据质量约束与数据管道定义解耦,这样可以让数据工程团队与数据分析团队分开工作。当一群非技术人员决定数据质量标准时,这种方法尤其有用。此外,这种设计还提供了更多的灵活性,以便随着业务的变化维护和更改业务规则。例如,一个实际的例子是验证随时间变化的季节性折扣代码。
假设我们有一组非技术业务分析人员,他们希望通过浏览器窗口中的 Web 门户与数据质量约束进行交互。在这种情况下,我们可以将数据质量约束加载并保存到一个独立的 Delta 表中,然后在运行时动态加载这些数据质量约束。
让我们从定义数据质量规则表开始。我们将引入三列:一列用于规则名称,一列定义数据质量规则表达式,另一列用于标识数据集名称——这些都是使用 DLT 创建期望所需的内容:
%sql
CREATE TABLE IF NOT EXISTS<catalog_name>.<schema_name>.data_quality_rules
(rule_name STRING, rule_expression STRING, dataset_name STRING)
USING DELTA
让我们回顾一下之前通过 Python 字典指定多个期望的示例。在那个示例中,我们定义了一个名为assertions的dict数据结构。在这个例子中,让我们将其转换为表格格式,将条目插入到我们的 Delta 表中。接下来,在新的笔记本单元中添加以下 SQL 语句:
%sql
INSERT INTO
data_quality_rules
VALUES
(
'valid_total_amount',
'trip_amount > 0.0',
'yellow_taxi_raw'
),(
'valid_passenger_count',
'passenger_count > 0',
'yellow_taxi_raw'
);
接下来,在数据管道笔记本中,我们可以创建一个辅助函数,它将直接从我们的数据质量规则表读取,并将每一行转换为 DLT 期望可以理解的格式:
def compile_data_quality_rules(rules_table_name, dataset_name):
"""A helper function that reads from the data_quality_rules table and coverts to a format interpreted by a DLT Expectation."""
rules = spark.sql(f"""SELECT * FROM {rules_table_name} WHERE dataset_name='{dataset_name}'""").collect()
rules_dict = {}
# Short circuit if there are no rules found
if len(rules) == 0:
raise Exception(f"No rules found for dataset '{dataset_name}'")
for rule in rules:
rules_dict[rule.rule_name] = rule.rule_expression
return rules_dict
现在我们有了一个 Delta 表,非技术数据分析人员可以通过与数据管道分离的 UI 进行更新;我们还创建了一个辅助函数,它可以从 Delta 表读取数据,并将表中的数据条目转换成 DLT 期望可以理解的格式。接下来,让我们看看这些部分如何结合在一起,创建一个新的数据集,动态加载数据质量要求:
import dlt
from pyspark.sql.functions import *
RULES_TABLE = "<catalog_name>.<schema_name>.data_quality_rules"
DATASET_NAME = "yellow_taxi_raw"
@dlt.table(
comment="Randomly generated taxi trip data."
)
def yellow_taxi_raw():
path = "/tmp/chp_03/taxi_data"
schema = "trip_id INT, taxi_number INT, passenger_count INT, trip_amount FLOAT, trip_distance FLOAT, trip_date DATE"
return (spark.readStream
.schema(schema)
.format("json")
.load(path))
@dlt.table(
name="yellow_taxi_validated",
comment="A dataset containing trip data that has been validated.")
@dlt.expect_all(compile_data_quality_rules(RULES_TABLE, DATASET_NAME))
def yellow_taxi_validated():
return (
dlt.readStream("yellow_taxi_raw")
.withColumn("nyc_congestion_tax",
expr("trip_amount * 0.05"))
)
这种设计模式提供了灵活性,可以将数据质量规则与数据管道定义分开维护,以便非技术人员确定数据质量标准。但是,如果我们有一个技术团队,想要保持参与并确保流经数据管道的数据质量如何呢?而且,如果这个团队需要接收到低质量数据的通知,以便他们能够介入,甚至手动修正数据以确保下游流程的正常运作,该如何处理呢?接下来,我们将展示如何在下一个实操练习中实现这样的数据修复过程。
实操练习 – 隔离错误数据以便修正
在这个示例中,我们将构建一个条件数据流,用于处理不符合数据质量要求的数据。这将允许我们隔离违反数据质量规则的数据,以便稍后采取适当的措施,甚至报告违反数据质量约束的数据。
我们将使用相同的黄出租公司(Yellow Taxi Corporation)示例来说明如何构建数据隔离区的概念。我们从一个铜级表(bronze table)开始,该表从行程数据生成器写入的原始 JSON 数据中获取数据,数据存储在 DBFS 位置:
%py
import dlt
from pyspark.sql.functions import *
@dlt.table(
name="yellow_taxi_raw",
comment="The randomly generated taxi trip dataset"
)
def yellow_taxi_raw():
path = "/tmp/chp_03/taxi_data"
schema = "trip_id INT, taxi_number INT, passenger_count INT, trip_amount FLOAT, trip_distance FLOAT, trip_date DATE"
return (spark.readStream
.schema(schema)
.format("json")
.load(path))
接下来,我们开始定义一些关于传入数据的数据质量规则。我们确保发布到 DBFS 位置的行程数据是合理的。我们会确保总车费大于 0,并且行程至少有 1 名乘客,否则我们会将行程数据隔离以供进一步审核:
data_quality_rules = {
"total_amount_assertion": "trip_amount > 0.0",
"passenger_count": "passenger_count >= 1"
}
现在,我们通过创建另一个带有计算列 is_valid 的数据集,将这两个数据质量规则应用于传入数据。该列将包含每一行数据所评估的数据质量规则结果:
@dlt.table(
name="yellow_taxi_validated",
comment="Validation table that applies data quality rules to the incoming data"
)
def yellow_taxi_validated():
return (
dlt.readStream("yellow_taxi_raw")
.withColumn("is_valid",
when(expr(" AND ".join(data_quality_rules.values())),
lit(True)).otherwise(lit(False)))
)
最后,我们可以使用 is_valid 计算列将流式数据表拆分为两个数据流——一个用于所有通过数据质量断言的传入数据,另一个用于未通过数据质量断言的传入数据。
让我们在数据管道中定义一个隔离表,将数据按照评估的数据质量规则进行路由:
@dlt.table(
name="yellow_taxi_quarantine",
comment="A quarantine table for incoming data that has not met the validation criteria"
)
def yellow_taxi_quarantine():
return (
dlt.readStream("yellow_taxi_validated")
.where(expr("is_valid == False"))
)
@dlt.table(
name="yellow_taxi_passing"
)
def yellow_taxi_passing():
return (
dlt.readStream("yellow_taxi_validated")
.where(expr("is_valid == True"))
)
最后,使用新的笔记本作为源,创建一个新的 DLT 管道。为管道提供一个有意义的名称,如 第三章 隔离无效数据。选择 Core 作为产品版本,并选择 Triggered 作为执行模式。接下来,在 Unity Catalog 中选择一个目标目录和模式来存储管道数据集。接受剩余的默认值,点击 创建 按钮以创建新的 DLT 管道。最后,点击 开始 按钮以触发新的管道执行。注意数据如何被分配到两个下游表——一个包含通过数据质量规则的数据行,另一个是包含未通过数据质量规则的数据行的隔离表。
图 3.7 – 未通过数据质量规则的数据被分配到隔离表中
通过实施隔离表,我们可以实时报告指标,让我们组织中的利益相关者能够及时了解传入数据的质量。此外,我们湖仓的数据管理员可以审查未通过验证逻辑的数据,甚至采取适当的措施,如手动修正无效数据。
小结
在本章中,我们涵盖了围绕我们数据湖中数据质量的许多主题。我们学习了如何使用NOT NULL和CHECK约束在 Delta Lake 中强制表的完整性。我们还使用PRIMARY KEY和FOREIGN KEY约束定义了我们数据湖中表之间的关系。接下来,我们看到了如何使用视图跨我们的 Delta 表强制主键唯一性,以验证我们表中的数据。我们还看到了当传入行违反数据质量约束时,如何轻松更新数据流水线行为的示例,从而允许数据工程团队对可能因低质量数据而导致下游流程中断做出反应。最后,我们看到了一个实际的示例,展示了如何使用期望在我们的管道中创建有条件的数据流,使我们的数据监护人能够隔离和纠正不符合预期数据质量的数据。
在下一章中,我们将深入探讨在生产环境中维护数据流水线的更高级主题。我们将看到如何调整数据流水线的许多不同方面,以扩展到大数据量,并满足实时流处理需求,如高吞吐量和低延迟。
第四章:扩展 DLT 流水线
本章中,我们将探讨几种扩展 Delta Live Tables (DLT) 流水线的方法,以应对典型生产环境中的处理需求。我们将涵盖调优 DLT 流水线的多个方面,从优化 DLT 集群设置,使得流水线能够快速扩展以应对大量处理需求的高峰,到优化底层云存储中表格的数据布局。本章结束时,你应该已经掌握如何让 DLT 集群自动扩展以应对需求。你还应该对由 DLT 系统在后台自动运行的表格维护任务对数据流水线性能的影响有充分的理解。最后,你应当理解如何利用 Delta Lake 优化技术进一步提升 DLT 流水线的执行性能。
本章我们将涵盖以下主要主题:
-
扩展计算能力以应对需求
-
实践示例 – 使用 Databricks REST API 设置自动扩展属性
-
自动化的表格维护任务
-
为更快的表格更新优化表格布局
-
无服务器 DLT 流水线
-
介绍 Enzyme,一种性能优化层
技术要求
要跟随本章内容,你需要拥有 Databricks 工作区权限,能够创建并启动通用集群,以及至少拥有一个集群策略来创建新的 DLT 流水线。本章的所有代码示例可以从本章的 GitHub 仓库下载,网址为 github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter04
。本章将创建并运行几个新的笔记本,以及使用 Core 产品版本创建一个新的 DLT 流水线。因此,本章的代码示例预计将消耗大约 10-15 Databricks 单位 (DBUs)。
扩展计算能力以应对需求
数据流水线的不同部分可能涉及大量计算,比如进行计算时,而流水线的其他部分则不需要如此强大的处理能力。为了在优化成本的同时获得最佳性能,任何数据流水线都需要能够在需要时增加额外的处理能力,并在处理需求随时间减少时释放计算资源。幸运的是,Databricks 提供了内置的自动扩展功能,用于 DLT 流水线,因此 虚拟机 (VMs) 可以根据数据流水线执行期间的处理需求,添加到流水线集群中或从中移除。
实际上,Databricks 为 DLT 数据管道提供了两种类型的集群自动扩缩模式:传统模式和增强模式。两种自动扩缩模式都会在管道运行过程中,根据处理需求的增减,自动增加或删除虚拟机。然而,虚拟机何时被添加或删除,在两种模式之间有所不同。
在传统自动扩缩模式下,当处理需求持续增加时,管道集群会增加额外的虚拟机。此外,在传统模式下,管道集群仅在虚拟机闲置一段时间且当前没有正在执行的 Spark 任务时,才会缩小规模。
另一方面,在增强自动扩缩模式下,DLT 系统只有在系统预测增加额外的计算资源能够加速管道更新执行时,才会增加额外的虚拟机——例如,如果 Spark 任务的执行受限于可用的 CPU 核心数量,增加 CPU 数量可以帮助并行执行大量的 Spark 任务。此外,增强型自动扩缩功能将主动寻找管道集群缩小规模的机会,通过驱逐正在运行的 Spark 任务并减少云计算成本。在驱逐过程中,增强型自动扩缩模式会确保被驱逐的 Spark 任务在剩余的运行虚拟机上成功恢复,然后再终止过度配置的虚拟机。
最后,增强型自动扩缩仅适用于用于管道更新任务的集群,而传统自动扩缩模式则由 DLT 系统用于执行维护任务。
以下表格概述了 DLT 数据管道集群上可用的两种自动扩缩模式之间的差异,以及每种自动扩缩模式适用于哪些 DLT 任务。
自动扩缩模式 | 预测性 自动扩缩 | 主动 缩小规模 | 更新任务 | 维护任务 |
---|---|---|---|---|
传统模式 | ✖️ | ✖️ | ✔️ | ✔️ |
增强模式 | ✔️ | ✔️ | ✔️ | ✖️ |
表 4.1 – DLT 数据管道集群中可用的自动扩缩模式之间的差异
你可以通过 DLT UI 或 Databricks REST API 配置集群的自动扩缩模式。在接下来的部分中,我们将使用 Databricks REST API 来更新现有数据管道集群的自动扩缩模式。
实践示例 – 使用 Databricks REST API 设置自动扩缩属性
在本节中,您需要从本章的 GitHub 仓库下载代码示例,仓库地址为 github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter04
。在该仓库中有一个名为 Random Taxi Trip Data Generator.py 的辅助笔记本,我们将使用它向云存储着陆区生成随机数据流,以模拟在生产环境中可能遇到的不可预测行为。
首先,让我们开始导入本章的数据管道定义笔记本,名为 Taxi Trip Data Pipeline.py,并将其打开到 Databricks 工作区中。
您会注意到,我们在数据管道中定义了两个数据集。第一个数据集使用 Databricks Auto Loader 功能来摄取新到达的 JSON 文件,这些文件存储在我们的原始着陆区。一旦数据被摄取,第二个数据集——我们的银表——将包含转换后的出租车行程数据的结果,并附加包含财务分析数据的额外列:
@dlt.table(
name="random_trip_data_raw",
comment="The raw taxi trip data ingested from a landing zone.",
table_properties={
"quality": "bronze"
}
)
def random_trip_data_raw():
raw_trip_data_schema = StructType([
StructField('Id', IntegerType(), True),
StructField('driver_id', IntegerType(), True),
StructField('Trip_Pickup_DateTime',
TimestampType(), True),
StructField('Trip_Dropoff_DateTime',
TimestampType(), True),
StructField('Passenger_Count', IntegerType(), True),
StructField('Trip_Distance', DoubleType(), True),
StructField('Start_Lon', DoubleType(), True),
StructField('Start_Lat', DoubleType(), True),
StructField('Rate_Code', StringType(), True),
StructField('store_and_forward', IntegerType(), True),
StructField('End_Lon', DoubleType(), True),
StructField('End_Lat', DoubleType(), True),
StructField('Payment_Type', StringType(), True),
StructField('Fare_Amt', DoubleType(), True),
StructField('surcharge', DoubleType(), True),
StructField('mta_tax', StringType(), True),
StructField('Tip_Amt', DoubleType(), True),
StructField('Tolls_Amt', DoubleType(), True),
StructField('Total_Amt', DoubleType(), True)
])
return (spark.readStream
.format("cloudFiles")
.option("cloudFiles.format", "json")
.schema(raw_trip_data_schema)
.load(raw_landing_zone))
接下来,将笔记本附加到一个通用集群并执行所有笔记本单元格。确保所有笔记本单元格都成功执行。当提示时,使用 Core 产品版本创建一个新的 DLT 数据管道。选择 Continuous 处理模式作为管道执行模式。(如果需要复习,请参考本书中 第二章 的 数据管道设置 部分。)接下来,选择一个目标 Unity Catalog 存储位置来存储数据管道数据集的输出,并接受其余所有默认值。最后,记录新创建的 DLT 数据管道的管道 ID。
在本部分练习中,我们将使用一个流行的 Python 库 requests 来与 Databricks REST API 进行交互。在 Databricks 工作区内创建一个新的笔记本,并在笔记本的第一个单元格中开始导入 requests 库:
import requests
接下来,我们将创建一个新的请求,向 Databricks REST API 更新数据管道的集群设置。在请求负载中,我们将指定自动扩展模式、数据管道集群的最小工作节点数量以及最大工作节点数量。根据公开的 Databricks 文档,我们还需要使用 PUT 动词来更新 DLT 数据管道的设置。将以下代码片段添加到新创建的笔记本中,并更新变量为您环境的特定值:
databricks_workspace_url = "<your_databricks_workspace>"
pipeline_id = "<your_pipeline_id>"
pat_token = "<your_api_token>"
response = requests.put(
f"{databricks_workspace_url}/api/2.0/pipelines/{pipeline_id}",
headers={"Authentication": pat_token},
json={
...
"clusters":[{
"autoscale": {
"min_workers": 2,
"max_workers": 5,
"mode": "ENHANCED"
}
}]
...
}
)
print(response.json())
或者,你可以通过从 DLT UI 进入流水线设置,将流水线的自动扩展模式更新为 ENHANCED。现在我们已经将 DLT 流水线更新为使用增强型自动扩展,让我们执行流水线更新。进入新创建的数据流水线的 UI,在右上角选择 Start 按钮触发流水线更新。
与此同时,我们也可以使用随机数据生成器来模拟处理需求的峰值。导入名为 Random Taxi Trip Data Generator.py 的数据生成器笔记本,该笔记本位于本章的 GitHub 仓库中。顾名思义,Random Taxi Trip Data Generator.py 将随机生成新的出租车行程数据,且数据的数量和频率各不相同,从而模拟生产环境中的典型工作负载。将该笔记本附加到一个多用途集群,并点击 Run all 按钮执行所有单元格。确保笔记本单元格都已成功完成。
接下来,切换回我们创建的 DLT 流水线的 DLT UI。我们将监控流水线的事件日志,以确保我们的 DLT 集群会自动增加工作实例的数量。
图 4.1 – 自动扩展事件将记录在 DLT UI 的事件日志中
同样,监控事件日志,以确保在额外数据流结束并且处理需求减少后,DLT 更新集群能够自动缩小规模。
到现在为止,你应该已经对如何让 DLT 集群根据处理需求的波动自动扩展和收缩有了坚实的基础。如你所见,我们的 DLT 流水线只会配置它所需的计算资源,以高效地保持我们的数据集最新,然后释放额外的计算实例以降低我们的运营成本。接下来,让我们关注一下 DLT 系统自动为我们完成的其他效率优化任务,比如 DLT 系统如何自动维护底层 Delta 表的最佳状态。
自动化的表格维护任务
如前章所述,每个 DLT 流水线将与两个集群相关联——一个集群用于对流水线定义中的每个数据集进行更新,另一个集群用于对每个数据集执行维护活动。这些维护任务包括对数据流水线定义中的每个 Delta 表执行 VACUUM 和 OPTIMIZE 操作。之前,数据工程师需要负责创建并维护一个单独的 Databricks 工作流,该工作流会执行每个 Delta 表的 VACUUM 和 OPTIMIZE 命令,通常安排在夜间运行。正如你所想,随着你开始向流水线中添加越来越多的表,这可能会变得相当繁琐。幸运的是,DLT 框架为我们提供了开箱即用的重负担支持。此外,每次 VACUUM 和 OPTIMIZE 维护活动都会在上次流水线执行后的 24 小时内执行。
让我们逐个查看每个操作,了解维护任务对基础数据集的总体好处。
为什么自动压缩很重要
在每次为特定 DLT 流水线执行新的更新时,DLT 流水线将初始化数据流图,并执行每个数据集定义中规定的基础计算。因此,新的数据会被附加到特定的 Delta 表中,或与之合并。每次写入数据时,Apache Spark 将把写操作分发到执行器,可能会生成许多小文件。随着更多更新的执行,这些小文件会在云存储中增多。当下游过程读取这些 Delta 表时,它们需要为每个唯一文件分配一个 Spark 任务,以响应特定的表查询。更多的文件意味着更多的 Spark 任务——或者更准确地说,意味着 Spark 引擎需要处理的工作量增加。这通常被称为“小文件问题”,因为那些经历大量新数据的表会生成许多小文件,从而拖慢整体查询性能。作为解决方案,最好将这些小文件合并成更大的文件,这一过程称为文件压缩。
幸运的是,作为数据工程师,我们不需要编写自己的工具来将较小的文件合并成更大的文件。事实上,Delta Lake 提供了一个非常有用的命令 OPTIMIZE,用于执行此类维护任务。默认情况下,Delta Lake 的 OPTIMIZE 命令会尝试将较小的文件合并成更大的 1 GB 文件。
图 4.2 – DLT 将自动在 Delta 表上运行 OPTIMIZE 命令,将较小的文件合并成更大的 1 GB 文件
当然,您也可以选择通过禁用 DLT 管道中表定义中的autoOptimize表属性来禁用自动优化功能:
@dlt.table(
name="random_trip_data_raw",
comment="The raw taxi trip data ingested from a landing zone.",
table_properties={
"quality": "bronze",
"pipelines.autoOptimize.managed": "false"
}
)
在某些情况下,您可能希望覆盖默认行为,例如实现自己的表优化工作流。
每当执行OPTIMIZE维护活动时,它也会为每个 Delta 表生成额外的文件。为了防止云存储成本失控,我们还必须清理过时的表文件,确保作为组织,我们不会为不必要的云存储付费。
清理过时的表文件
VACUUM操作旨在移除不再在最新表快照中的 Delta 表的旧版本表文件,并且这些文件的年龄超过了保留阈值属性。默认情况下,所有 Delta 表的保留阈值为七天,这意味着VACUUM操作将移除比当前快照日期早于七天的过时表文件。在运行时,VACUUM实用程序会搜索 Delta 表的根目录以及所有子目录,从云存储中删除超过保留阈值的旧表文件。
这是平衡云存储成本与维护和查看特定 Delta 表旧快照能力的好方法。如在第一章 中所述,Delta Lake 的时间旅行功能依赖于表历史记录来查询 Delta 表的先前版本。然而,该功能并非为长期归档使用场景设计,而是为了较短期的表历史。因此,可以合理预期,我们不需要存储 Delta 表的所有历史记录,也不需要为此付出相应的存储费用,这可能会变得相当昂贵。
与自动优化功能类似,Delta 表的历史保留阈值是通过表属性确定的,并且可以通过在表定义中使用deletedFileRetentionDuration表属性来指定:
@dlt.table(
name="random_trip_data_silver",
comment="Taxi trip data transformed with financial data.",
table_properties={
"quality": "silver",
"pipelines.autoOptimize.zOrderCols": "driver_id",
"delta.deletedFileRetentionDuration": "INTERVAL 14 days"
}
)
同样,Delta 事务日志——记录每个提交的表事务细节的元数据文件(在第一章 中有详细介绍)——也可能导致不必要的存储成本。然而,这些日志文件在日志检查点操作时会自动删除(每十次事务提交一次)。默认情况下,Delta Lake 会保留最多 30 天的表历史记录在事务日志中。
图 4.3 – DLT 将自动对所有 Delta 表执行 VACUUM 操作
由于事务日志文件仅包含元数据,因此它们很小,只包含几兆字节的信息。然而,可以通过设置logRetentionDuration表属性来配置此历史记录保留:
@dlt.table(
name="random_trip_data_silver",
comment="Taxi trip data transformed with financial data.",
table_properties={
"quality": "silver",
"pipelines.autoOptimize.zOrderCols": "driver_id",
"delta.deletedFileRetentionDuration": "INTERVAL 9 days",
"delta.logRetentionDuration": "INTERVAL 35 days"
}
)
删除过时的云文件是控制云成本并防止组织支付不必要的云存储费用的好方法。让我们来看看如何优化 DLT 管道的其他方面,以提高操作效率,同时继续降低运营成本。
将计算移近数据
确保数据管道高效执行的最简单方法之一是确保 DLT 管道集群在与正在处理的数据相同的全球区域内启动。这是一个古老的调优概念,即将硬件靠近数据,以减少数据处理过程中网络延迟。例如,你不会希望你的 DLT 管道集群在云服务提供商的美国西部区域执行,而数据却存储在完全不同的地理位置,比如该云服务提供商的美国东部区域。这样做会导致跨地理区域传输数据、处理数据转换或其他计算,并将结果重新存储回原始地理区域时,产生相当大的网络延迟。此外,大多数云服务提供商会评估与地理数据传输相关的数据出站和入站费用。
图 4.4 – DLT 集群和存储容器的地理位置可能引入显著的网络延迟
DLT 集群的地理区域可以通过在管道集群策略定义中定义云区域位置来设置。例如,以下代码片段定义了一个集群策略,可以用来配置 DLT 管道集群在 AWS 云服务提供商的美国东部区域启动:
{
...
"aws_attributes.zone_id": "us-east-1",
"custom_tags.lob": {
"type": "fixed",
"value": "ad analytics team"
}
}
通过确保你的 DLT 集群与组织数据位于相同的地理区域,你可以确保从管道集群中获得最佳的操作性能。同时,由于你的管道运行得更快,并且云资源的使用时间更短,这意味着你的组织可以节省开支。除了优化数据管道的计算资源外,我们还可以有效地组织表格数据,以进一步提高数据管道更新的性能。让我们看看其他一些优化表格数据布局来提高 DLT 管道处理效率的技术。
优化表格布局以加速表格更新
一个典型的 DLT 管道可能包括一个或多个数据集,这些数据集追加新数据并用新值更新现有数据,甚至完全删除某些行。让我们深入了解这个后者的场景,分析“引擎盖下”发生了什么,以便我们在向 DLT 表中添加新数据时优化 DLT 数据集以提高性能。
更新期间重写表文件
在表更新期间,DLT 引擎将执行两次扫描以识别所有符合特定更新条件的行,并相应地重写已更改的表数据。在第一次表扫描过程中,DLT 引擎将识别所有包含符合 apply_changes()(如果使用 SQL,则为 APPLY CHANGES)表达式中的谓词子句的行的表文件,例如。
图 4.5 – DLT 将通过使用匹配操作识别匹配的行,并将更改应用到目标 DLT 表
接下来,DLT 引擎将编译一个包含这些行的所有表文件的列表。使用这个表文件列表,DLT 引擎将在第二次表扫描操作中重写包含新更新行的每个文件。如你所想,随着向 DLT 表中添加更多数据,定位这些匹配行并确定要重写的文件列表的过程可能会随着时间的推移变得非常昂贵。幸运的是,Delta Lake 有一些功能可以帮助我们优化这个搜索过程,并加快匹配过程。
使用表分区进行数据跳过
加速这个搜索过程的一种方法是限制 DLT 引擎的搜索空间。一种技术是使用 Hive 风格的表分区。表分区将相关数据组织成物理上分开的子目录,这些子目录位于表的根存储位置内。子目录与一个或多个表列相对应。
在匹配过程中,DLT 引擎可以排除那些不符合谓词条件的整个子目录,避免扫描不必要的数据。
通过 MERGE 列进行表分区,这些列用于将数据更改应用到表中,可以显著提升更新过程的性能。另一方面,由于表分区会创建物理上分开的目录,因此表分区可能难以正确设置,并且修改起来非常昂贵,通常需要重写整个表以调整分区方案。
另一个挑战是识别一个表分区方案,使得分区目录中包含均衡的数据量。很容易通过 MERGE 列进行表分区,但这可能导致某些分区目录包含较少的数据,而其他分区目录包含大量数据。这种情况通常被称为 数据倾斜。尽管如此,表分区仍然是你数据管道调优工具中的一个强大工具。让我们来看看如何将表分区与另一种表优化技术结合起来,进一步提升管道性能。
Delta Lake 在 MERGE 列上的 Z-order 排序
优化 Delta 表格布局的一种方法是组织每个表格文件中的数据,以便在文件扫描操作期间可以高效读取。这通常被称为数据聚类。幸运的是,Delta Lake 提供了一种数据聚类算法,称为 Z-order 聚类。Z-order 聚类通过将相关数据聚集在一起,形成“Z”形模式来写入表格数据。根据这种模式存储表格数据,将提高 DLT 引擎跳过表格文件中不相关数据的概率,仅读取符合更新匹配条件的数据。
传统上,在没有 Z-order 聚类的情况下,Delta Lake 将以线性模式存储数据。因此,在更新匹配过程中,Delta Lake 需要打开表格的每个文件并以线性排序顺序扫描每一行。有时,只有一行可能匹配合并条件。反过来,DLT 引擎将读取所有不匹配的多余行,最终也许只能找到 1 或 2 行匹配更新条件。
通过使用 Z-order 聚类技术将文件中的数据进行聚类,DLT 引擎可以准确定位特定文件中相关数据所在的位置,从而限制它必须扫描的数据量。对于需要大量扫描的大型表格,这可以显著提高 DLT 管道的更新过程。
图 4.6 – 表格文件中的 Z-order 聚类数据可以可视化为数据聚集形成“Z”形状
通过在数据集定义中设置适当的表格属性,可以启用 DLT 数据集上的 Z-order 聚类。让我们看看如何为我们接收大量更新的银表yellow_taxi_transformed配置 Z-order 聚类:
我们首先像定义 DLT 管道中的任何数据集一样定义数据集。你会注意到,我们为数据集指定了一个名称yellow_taxi_transformed,并添加了一个注释,提供了一些关于表格的描述性文本。然而,在 DLT 功能注释中,我们添加了更多的参数,用于设置此数据集的表格属性。在表格属性参数中,我们添加了几个描述数据集的属性。首先,我们添加了一个表格属性,描述了此数据集的质量,这是我们在金字塔架构中的银表。接下来,我们还添加了另一个表格属性,指定我们希望应用 Z-order 聚类的表格列:
import dlt
@dlt.table(
name="yellow_taxi_transformed",
comment="Taxi cab trip data containing additional columns about the financial data.",
table_properties={
"quality": "silver",
"pipelines.autoOptimize.zOrderCols": "zip_code, driver_id"
}
)
在执行我们日常的维护任务时,维护任务将动态解析这些 Z-order 列,并将在该 DLT 数据集后面的基础 Delta 表上运行以下 Z-order 命令:
OPTIMIZE yellow_taxi_transformed
ZORDER BY (zip_code, driver_id)
那么,你应该根据哪些表列来进行 Z-order 排序,应该指定多少列呢?一个合适的范围是 1 到 3 列,最多不超过 5 列。随着你添加更多的列,会使得表文件内的数据聚类变得更复杂,从而降低可能发生的数据跳过效果。
此外,你应当尽量选择数据类型为数值型的列。原因在于,每当新数据写入 Delta 表时,Delta 引擎会捕捉前 32 列的统计信息——包括最小值、最大值和空值数量等列信息。这些统计信息将被用于更新搜索过程中,帮助有效地定位哪些行符合更新条件。对于字符串类型的数据,这些统计信息并不会提供太多有用的信息,因为例如没有一个“平均字符串”。然而,对于浮动数据类型的列,比如浮点数列,就可以计算出平均值。
此外,在APPLY CHANGES谓词中使用的列、连接列以及进行聚合操作的列,都是理想的 Z-order 排序候选列。最后,这些列的基数应该高于用于创建表分区方案的列。
重要提示
有时,你可能想尝试使用不同的列或不同的列集来进行 Z-order 排序。更改这个 Z-order 排序方案是非常简单的——只需更新 DLT 管道定义中的table_properties参数即可。然而,需要注意的是,新的 Z-order 聚类只会对写入表中的新数据生效。若要将新的 Z-order 聚类应用于现有数据,必须完全刷新整个表,以便根据聚类模式重新组织表文件。因此,你可能需要在重新写入表数据所需的时间和成本与表 Z-order 优化所带来的性能收益之间做出权衡。
正如你现在看到的,Z-order 优化是优化 DLT 表布局的一个极佳方式,可以提升数据管道的性能。拥有有效的数据布局可以提高 DLT 引擎的数据跳过能力,限制 DLT 引擎在应用更新到数据管道中目标表时需要扫描的数据量。结合 Hive 风格的表分区,这是一种确保最大化数据管道性能的好方法,从而缩短执行时间,并减少维护更新集群的时间和成本。
然而,如果你只是在特定的表文件中更新少量数据呢?这意味着你需要重写整个文件,仅仅是为了更新例如 1 或 2 行。让我们看看如何进一步优化 DLT 管道的性能,以避免这种代价高昂的操作。
使用删除向量提高写入性能
在表格更新期间,DLT 引擎通过将匹配的文件与新更改的行写入新目标文件来应用更新。在这种表格更新策略中,称为写时复制(COW),没有接收到任何更新的行需要按名称所示被复制到新文件中。对于需要在多个文件中仅更改少量行的表更新,这种方式效率较低。
一种更好的优化技术是将所有已更改的行跟踪到一个单独的数据结构中,并将更新后的行写入单独的文件中。然后,在查询表格时,表格客户端可以使用该数据结构来过滤掉任何已更新的行。这种技术称为读取时合并(MOR),并通过名为删除向量的功能在 Delta Lake 中实现。
删除向量是一种特殊的数据结构,用于跟踪在 Delta 表上的更新或合并操作中所有更新的行 ID。可以通过设置底层 Delta 表的表属性来启用删除向量。与 Delta 表列的统计信息类似,删除向量与表数据一起存储在云存储上。
图 4.7 – Delta Lake 表将跟踪每行的行 ID,并将其存储在单独的数据结构中
此外,删除向量可以默认自动启用,适用于在 Databricks 工作区中创建的所有新表。工作区管理员可以在工作区管理员设置界面的高级选项卡中启用或禁用此行为。
图 4.8 – 删除向量可以在 Databricks 数据智能平台中自动启用
可以通过在 DLT 表定义中设置enableDeletionVectors表属性,显式地为数据集设置删除向量:
@dlt.table(
name="random_trip_data_silver",
comment="Taxi trip data transformed with financial data.",
table_properties={
"quality": "silver",
"pipelines.autoOptimize.zOrderCols": "driver_id",
"delta.enableDeletionVectors": "true"
}
)
此外,删除向量在 Databricks 数据智能平台上解锁了一类新的更新性能特性,统称为预测性 I/O。预测性 I/O 利用深度学习和文件统计信息,准确预测与更新条件匹配的行在文件中的位置。因此,扫描匹配文件并在更新、合并和删除期间重写数据所需的时间大幅减少。
Hive 风格的表分区、Z-order 数据聚类和删除向量都是非常好的优化技术,可以高效地存储我们的表数据,并提高管道更新的速度。让我们再次将注意力转向数据管道的计算资源,并分析另一种提高生产环境中 DLT 管道性能的技术,尤其是在处理需求可能激增并变得不可预测时。
无服务器 DLT 管道
在 第二章 中,我们简要描述了无服务器 DLT 集群是什么以及它们如何快速高效地扩展计算资源以应对需求的激增,同时又能缩减规模以节省云成本。虽然我们不会再次讨论无服务器集群的架构,但我们将讨论无服务器 DLT 集群如何帮助组织在增加更多数据管道时扩展数据管道。
使用无服务器 DLT 集群时,集群的基础设施和设置由 Databricks 云服务提供商账户自动处理。这意味着无需选择虚拟机实例类型来平衡性能与成本。无服务器计算的费用是固定的、统一的定价,使得成本具有可预测性。此外,由于计算资源由 Databricks 云服务提供商账户管理,Databricks 可以通过每个云服务提供商以折扣价预留大量虚拟机实例。这些折扣价可以传递给无服务器 DLT 消费者。
此外,无服务器 DLT 集群通过减少每个数据管道所需的配置量,从而简化数据管道维护。配置的减少使得数据工程团队可以将精力从数据管道的维护中解放出来,更加专注于业务中的关键事项,例如修改业务逻辑、数据验证以及增加更多数据管道等。此外,随着数据管道的增长和数据集体积的不断增加,您可能需要配置更多的虚拟机实例。最终,您可能会遇到某些实例类型的云服务提供商限制,这时需要额外的流程去提高这些限制。使用无服务器 DLT 计算时,这些限制已经与云服务提供商协商过了,这意味着 DLT 无服务器消费者无需担心这一负担。
无服务器数据管道还可以帮助减少成本。例如,在传统的客户管理计算中,集群只能按照云服务提供商能够提供额外实例的速度,快速增加虚拟机(VM)实例,并定期运行诊断检查。而且,Databricks 运行时容器和用户库需要安装到这些额外实例上,这会消耗更多时间。这可能导致很多分钟——有时会超过 15 分钟,具体取决于云服务提供商——才能扩展 DLT 集群以应对不可预测的计算需求峰值。因此,传统计算上运行的 DLT 管道与无服务器 DLT 集群相比,执行时间会显著更长。在无服务器 DLT 集群中,虚拟机实例已经预配置好,安装并启动了最新的 Databricks 运行时容器,并且在预分配的实例池中已经启动。在处理需求激增时,DLT 管道可以在几秒钟内响应额外资源以满足需求,而非几分钟。这些额外的分钟数在多个数据管道运行和整个云计费周期中会累计起来。通过缩短扩展所需的时间并通过增强的自动扩缩能力积极地进行缩减,无服务器 DLT 管道可以大幅降低运营成本,同时提高数据湖仓中 ETL 处理的效率。
移除管理数据管道计算设置的基础设施负担以及控制云成本,是选择无服务器 DLT(数据传输层)管道而非传统的客户管理计算的主要动因之一。然而,我们也可以看看选择无服务器 DLT 集群的另一个动因,比如这种计算资源所带来的性能特性。
引入 Enzyme,性能优化层
在某些场景中,数据管道可能已经部署到生产环境中。然而,随着时间的推移,业务需求可能会发生重大变化,导致需要从头开始重新计算数据集。在这些场景下,重新计算这些数据集的历史数据可能会非常昂贵。
Enzyme 是一个全新的优化层,仅适用于无服务器 DLT 管道,旨在通过动态计算成本模型来保持数据集的物化结果最新,从而降低 ETL 成本。像 Spark 查询规划中的成本模型一样,Enzyme 会在多种 ETL 技术之间计算成本模型,从传统的 DLT 物化视图到 Delta 流表,再到另一个 Delta 流表,或手动 ETL 技术。例如,Enzyme 引擎可能会模拟使用物化技术刷新数据集的成本,这相当于 10 个 Spark 作业,每个作业包含 200 个 Spark 任务。这个成本模型可能会节省两次 Spark 作业,并预测另一种 ETL 技术会将整体执行时间减少五分钟,因此 Enzyme 引擎会选择第一种技术。
图 4.9 – Enzyme 优化层将使用成本模型自动选择最具成本效益的 ETL 刷新技术
Enzyme 层将在运行时动态选择最有效且具成本效益的方法,以重新计算给定数据集的结果。由于 Enzyme 是一种无服务器 DLT 特性,它默认已启用,免去了 DLT 管理员管理管道集群设置的需求。
到现在为止,您应该已经了解了无服务器 DLT 管道所带来的强大功能,例如 Enzyme 优化层,以及它的基础设施管理和节省成本的优势。
概述
在本章中,我们探讨了扩展数据管道以处理大规模数据并在高峰期和不可预测的处理需求下保持良好性能的各种方法。我们研究了扩展 DLT 管道的两个属性——计算和数据布局。我们考察了 Databricks 数据智能平台的增强型自动扩展特性,该特性可以自动扩展数据管道执行时的计算资源。我们还研究了优化底层表数据存储的方式,通过将相关数据聚类到表文件中,从而实现更快的表查询和更短的管道处理时间。此外,我们还探讨了定期的维护活动,以保持高性能的表查询,并防止过时数据文件导致的云存储成本膨胀。
数据安全至关重要,但常常在湖仓实施的最后阶段才被重视。然而,这可能意味着湖仓成功与否的差异,甚至可能成为新闻头条——而且并不是因为好消息。下一章中,我们将探讨如何在湖仓中有效实施强有力的数据治理,无论是在单一地理区域内,还是跨越全球不同地区的故障转移区域。
第二部分:使用 Unity Catalog 保护湖仓
在本部分,我们将探讨如何在 Databricks 数据智能平台中使用 Unity Catalog 实现有效的数据治理策略。我们将讨论如何在组织中的不同角色和部门之间执行细粒度的数据访问策略。最后,我们将探讨如何追溯 Unity Catalog 中数据资产的来源,确保数据来自可信的来源。
本部分包含以下章节:
-
第五章,在湖仓中掌握数据治理 与 Unity Catalog
-
第六章,在 Unity Catalog 中管理数据位置
-
第七章,使用 Unity Catalog 查看数据血缘
第五章:使用 Unity Catalog 掌握湖仓中的数据治理
在本章节中,我们将深入探讨如何使用 Unity Catalog 实施湖仓中的有效数据治理。我们将介绍如何在现有的 Databricks 工作空间启用 Unity Catalog,如何为数据发现实施数据目录管理,如何在表、行和列级别执行细粒度的数据访问控制,以及如何跟踪数据血统。到章节结束时,你将掌握数据治理的行业最佳实践,并获得提升数据安全性和合规性的真实世界经验。
在本章节中,我们将涵盖以下主要主题:
-
理解湖仓中的数据治理
-
在现有 Databricks 工作空间启用 Unity Catalog
-
Unity Catalog 中的身份联合
-
数据发现与目录管理
-
动手实验室 – 数据掩码处理医疗数据集
技术要求
为了跟随本章节的内容,你需要拥有 Databricks 工作空间的权限,能够创建和启动通用集群,以便执行本章节附带的笔记本。还建议将你的 Databricks 用户提升为账户管理员和元存储管理员,这样你才能部署新的 Unity Catalog 元存储并将其附加到现有的 Databricks 工作空间。所有代码示例可以从本章节的 GitHub 仓库下载,链接地址为github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter05
。本章节将创建并运行几个新的笔记本,预计会消耗约 5 到 10 Databricks 单位 (DBU )。
理解湖仓中的数据治理
湖仓实现中通常会利用多个处理引擎来应对不同的使用场景。然而,每个处理引擎都有自己独特的数据安全实现,并且这些不同的数据安全解决方案往往无法互相集成。大多数湖仓系统的不足之处在于,由于有多个安全层,实施一致的、全球性的安全政策几乎是不可能的。确保湖仓中的数据完全且一致地安全、私密,并且仅向正确的用户授予访问权限,是建设数据湖仓时最为重要的任务。因此,拥有一个简单的数据治理解决方案,能够覆盖你组织湖仓中的所有数据,对组织的成功至关重要。
介绍 Databricks Unity Catalog
Unity Catalog 是一个集中式的数据治理解决方案,通过将工作区对象访问策略组织到一个单一的管理“统一视图”中,简化了湖仓中数据的安全保护。除了访问策略,Unity Catalog 的设计还充分考虑了强大的审计功能,允许管理员捕捉所有用户对工作区对象的访问模式,以便管理员可以观察访问模式、工作区使用情况以及跨所有 Databricks 工作区的计费模式。此外,Unity Catalog 的设计还允许数据专业人员在组织内部发现数据集、追踪数据血缘、查看实体关系图、分享精选数据集以及监控系统健康状况。Unity Catalog 的一个重要优势是,一旦组织的数据进入 Unity Catalog,它会默认得到安全保护——无论是 Databricks 工作区内的内部过程,还是与 Databricks 数据智能平台外部数据交互的外部过程,除非数据管理员明确授予访问权限,否则任何过程都无法访问这些数据。Unity Catalog 的设计旨在跨越湖仓的边界,从工作区到工作区,并超越 Databricks 工作区,坐落在组织的数据之上,以便能够将一个单一的治理模型简单且一致地应用于所有访问组织湖仓数据的方。
图 5.1 – Unity Catalog 在组织的云数据之上提供了一个单一的统一治理层
然而,拥有一个全球范围的数据和工作区对象安全层并不总是像今天在 Unity Catalog 中那样无缝。让我们回顾一下 Databricks 以前的安全模型中的经验教训,以及 Unity Catalog 如何最终实现今天的成果。
一个值得解决的问题
以前,数据访问控制策略是通过一种称为表访问控制列表(ACLs)的机制在每个工作区定义的。当这些策略被正确执行时,表 ACLs 提供了强大的数据治理解决方案。管理员可以为工作区内的不同用户和组定义数据访问策略,并且在执行访问底层数据集的 Spark 代码时,Databricks 集群可以强制执行这些访问策略,这些数据集是在Hive Metastore(HMS)中注册的。
然而,来自表 ACL 安全模型的四个主要问题迅速浮现。第一个问题是,使用表 ACL 安全模型定义的数据访问策略需要为每个独立的 Databricks 工作区重复定义。大多数组织更倾向于为每个逻辑工作环境拥有单独的工作区——例如,一个用于开发的工作区,一个用于验收测试的工作区,最后是一个专门用于运行生产工作负载的工作区。除了重复相同的共享数据访问策略外,如果某个工作区中的数据访问策略发生变化,通常意味着所有工作区的数据访问策略也需要更改。这导致了不必要的维护开销,因为在 Databricks 数据智能平台中并没有一个单一的位置可以轻松定义这些数据访问模式。
其次,表 ACL 仅在交互式笔记本或自动化作业在启用了表 ACL 的集群上执行时,才会强制执行对底层数据的访问控制。没有启用表 ACL 安全模型的集群可以直接访问底层数据集,完全绕过安全模型!虽然集群策略(详见第一章)可以用来缓解这个问题,并防止任何潜在的恶意访问特权数据集,但集群策略的编写十分复杂。它们需要了解集群策略架构,并且需要有将配置表示为 JSON 的经验,这使得在整个组织中扩展变得困难。通常情况下,用户常常向组织领导抱怨,称他们需要管理员工作区访问权限,以便启动自己喜欢的集群并完成日常活动。一旦用户获得了管理员工作区访问权限,他们也能将管理员权限授予其他用户,从而形成像滚雪球一样的效应,导致一个工作区有过多的管理员。此类不当操作很容易绕过启用了表 ACL 的集群,导致数据泄露。
此外,由于在共享计算资源上运行Java 虚拟机(JVM)语言的隔离问题,启用表 ACL 的集群限制最终用户只能使用 SQL 或 Python 编程语言运行工作负载。希望使用 Scala、Java 或 R 编程语言执行工作负载的用户,需要被授予例外权限,使用未启用表 ACL 的集群,这为组织的数据治理解决方案打开了一个巨大的漏洞。
第四个出现的主要问题与 HMS 的可扩展性有关。Databricks 数据智能平台利用 HMS 在工作区中注册数据集,这使得用户可以从头开始创建新数据集,将其组织到架构中,甚至跨组织共享用户和组的访问权限。然而,随着一个工作区逐步增加数千名用户,这些用户需要同时执行临时查询,并且还需要执行数百甚至数千个定时任务。最终,HMS 很难跟上最苛刻工作区所需的并发处理能力。
很明显,需要进行一次巨大的变革,因此 Databricks 开始着手从零开始完全重新设计数据治理解决方案。
Unity Catalog 架构概述
Unity Catalog 旨在解决的一个主要痛点是实施一个完整的端到端数据治理解决方案,覆盖组织的所有工作区,消除需要为每个 Databricks 工作区重新定义数据访问策略的冗余问题。相反,通过 Unity Catalog,数据管理员可以在一个集中位置定义数据访问控制一次,并且可以放心,数据访问策略将在整个组织中一致地应用,无论使用何种计算资源或处理引擎与 Unity Catalog 中的数据集交互。
图 5.2 – Unity Catalog 集中管理数据访问策略,并在多个 Databricks 工作区中一致应用这些策略
除了集中式数据治理外,Unity Catalog 还有多个其他关键驱动因素,使其成为现代湖仓环境理想的数据治理解决方案:
-
默认安全:用户在未使用启用 Unity Catalog 的集群(Unity Catalog 集群将在下节中介绍)且未被授予特定数据集的使用和选择权限的情况下,无法访问任何计算资源中的数据。
-
舒适的管理员界面:Unity Catalog 中的数据访问策略与美国国家标准学会(ANSI)SQL 紧密集成,使管理员可以在熟悉的数据库对象上表达数据访问权限,例如目录、数据库、表、函数和视图。数据访问权限还可以通过 Databricks Web 应用程序中的管理员 UI 设置,或者使用自动化部署工具,如 Terraform。
-
数据发现:Unity Catalog 使数据管理员可以轻松地为数据集添加描述性元数据,从而使组织中的用户能够搜索和发现可用的数据集。
-
强大的审计功能:Unity Catalog 会自动捕捉用户级别的访问模式和数据操作,允许管理员查看和审计用户在与湖仓数据交互时的行为。
-
数据血缘追踪:追踪表和列如何从上游源生成,对于确保下游数据集是通过受信任的源形成的至关重要。通过强大的数据血缘 API 和系统表,Unity Catalog 使数据和工作空间资产的追踪变得简单。
-
可观察性:由于 Unity Catalog 跨多个 Databricks 工作空间,因此可以将系统指标和审计事件汇总到一组集中式的只读表中,以便进行监控和系统可观察性,这些表被称为系统表(在系统表的可观察性部分将详细介绍)。
为了实现一种默认数据安全且没有外部访问数据的能力,用户必须通过 Unity Catalog 来访问数据,Databricks 需要根据用户角色设计不同类型的集群。让我们来看一下在启用 Unity Catalog 的工作空间中,用户可以使用的不同集群类型。
启用 Unity Catalog 的集群类型
启用 Unity Catalog 的工作空间有三种主要类型的集群:
-
单用户集群:只有单个用户或服务主体才有权限在这种集群上执行笔记本单元格或工作流。包含 Scala、Java、R、Python 和 SQL 语言的工作负载只能在这种集群上执行。注册在 Unity Catalog 中的数据集可以从这种集群中查询。
-
共享集群:多个用户或服务主体可以在这种集群上执行笔记本单元格或工作流。此类集群仅限于 Python、SQL 和 Scala 工作负载。注册在 Unity Catalog 中的数据集可以从这种集群中查询。
-
独立集群:单个用户或多个用户可以将笔记本附加到此类集群并执行笔记本单元格。然而,Unity Catalog 中注册的数据集 无法 被此类集群查询,若用户尝试查询 Unity Catalog 中注册的数据集,将导致运行时异常。此类集群可用于读取在遗留 HMS 中注册的数据集。
现在我们已经概述了可以用来与 Unity Catalog 中的数据互动的不同计算资源类型,接下来让我们关注一下数据和其他资产在 Unity Catalog 中是如何组织的,主要是理解 Unity Catalog 的对象模型。
Unity Catalog 对象模型
理解 Unity Catalog 中的对象模型非常重要,它将帮助用户理解哪些类型的对象可以被 Unity Catalog 安全管理和治理。此外,它还将帮助元存储管理员设计数据访问策略。
Unity Catalog 引入的主要变化之一是三层命名空间的概念。传统上,在 HMS 中,用户与数据交互时可以通过模式(或数据库)和表名的组合来引用数据集。然而,Unity Catalog 增加了第三个逻辑容器,称为目录(catalog),它可以包含一个或多个模式。要在 Unity Catalog 中引用完全限定的数据集,数据工作者需要提供目录、模式和表的名称。
图 5.3 – Unity Catalog 包含了许多不同的可安全管理对象,不仅仅是数据集
让我们深入了解 Unity Catalog 对象模型,从组织物理数据集的相关对象开始:
-
元存储:Unity Catalog 的“物理”实现。一个特定的云区域最多只能包含一个元存储。
-
目录:Unity Catalog 中数据集的顶级容器。一个目录可以包含一个或多个模式对象的集合。
-
模式:作为 Unity Catalog 中的第二级对象。一个模式可以包含一个或多个表、视图、函数、模型和卷的集合。
-
表:表示一个包含定义模式并按行和列组织的数据集。
-
视图:一个计算的数据集,可能是通过联接表、筛选列或应用复杂的业务逻辑的结果。此外,视图是只读的,不能向其写入数据或通过 数据操作语言 (DML) 语句更新数据。
-
模型:一个使用 MLflow 注册到跟踪服务器的机器学习模型。
-
函数:一个自定义的、用户定义的函数,通常包含复杂的业务逻辑,这些逻辑通常无法仅通过内置的 Spark 函数实现。
-
卷:用于存储结构化、半结构化或非结构化数据的数据存储位置(我们将在下一章中详细讲解卷,精通 Unity Catalog 中的数据位置)。
接下来,让我们将注意力转向 Unity Catalog 中用于与 Databricks 数据智能平台之外的数据交互的对象:
-
连接:表示一个只读连接,包含访问外部 关系型数据库管理系统 (RDBMS) 或数据仓库中的数据所需的凭据。
-
存储凭证:表示访问云存储位置的身份验证凭证。
-
外部位置:表示 Databricks 管理的根存储位置之外的云存储位置。
最后,让我们看看 Unity Catalog 对象模型中用于通过 Delta Sharing 协议共享和接收数据集的元素:
-
提供方:表示数据提供方。此实体将一个或多个策划的数据集创建为一个逻辑分组,称为共享。数据提供方可以随时撤销对共享数据集的访问权限。
-
共享:一个或多个共享数据集的逻辑分组,可以与数据接收方共享。
-
接收方:表示数据接收方。此实体可以访问数据共享并查询其工作区中的数据集。
现在我们已经概述了 Unity Catalog 的主要构建块,接下来我们来看如何在现有的 Databricks 工作区启用 Unity Catalog,以便管理员可以开始充分利用强大的数据治理解决方案。
在现有的 Databricks 工作区启用 Unity Catalog
从 2023 年 11 月初开始,在亚马逊 Web 服务(AWS)或 Azure 上创建的所有新 Databricks 工作区默认启用 Unity Catalog,因此,如果您的工作区是在此日期之后在这两个云提供商上创建的,则无需额外配置。同样,在创建新的 Databricks 工作区时,将为您的工作区预配一个区域性的 Unity Catalog 元存储。该区域元存储内有一个默认目录,名称与工作区相同,并且仅绑定到该工作区(我们将在下一节讨论目录绑定)。此外,新创建的工作区的所有用户将具有对该工作区目录中名为default的模式的读写访问权限。
重要提示
一旦为 Databricks 工作区启用了 Unity Catalog,工作区管理员无法禁用 Unity Catalog。然而,数据集始终可以迁移回 HMS 实现,但该工作区将始终启用 Unity Catalog 元存储。
升级现有 Databricks 工作区以使用 Unity Catalog 的第一步是部署一个新的元存储。元存储是 Unity Catalog 的“物理”实现。管理员需要为每个云区域部署一个元存储。元存储可以通过多种方式进行部署,但为了简便起见,我们将介绍如何通过 Databricks UI 来部署新的元存储:
-
首先,确保您已登录到账户控制台,网址是
accounts.cloud.databricks.com
。 -
在账户控制台中,点击侧边栏中的目录菜单项,然后点击创建元存储按钮,开始部署新的元存储。
-
为您的元存储输入一个有意义的名称,选择适当的区域,并可选地选择一个默认存储路径,以存储托管的数据集。
-
最后,点击创建按钮。几分钟后,您的新元存储将在您选择的云区域中进行配置。
注意
在 Databricks 中,像目录绑定、网络配置或用户配置等全局配置都集中在一个单一的管理控制台中,有时简称为“账户控制台”。
现在元存储已经部署,剩下的就是选择您希望将元存储链接到哪些 Databricks 工作区,从而启用 Unity Catalog:
-
从账户控制台中,再次选择侧边栏中的目录菜单项。
-
接下来,点击新创建的元存储的名称,然后点击分配到 工作区按钮。
-
最后,点击您希望启用 Unity Catalog 的工作区,并通过点击分配按钮确认您的选择。
恭喜!您现在已为现有的 Databricks 工作区启用了 Unity Catalog,并可以开始享受数据湖数据将由完整的数据安全解决方案进行治理的安心感。同样重要的是,一旦将 Unity Catalog 元存储附加到 Databricks 工作区,您就启用了工作区的身份联合。
Unity Catalog 中的身份联合
无论是部署了全新的 Databricks 工作区,还是手动升级了现有工作区以使用 Unity Catalog,接下来的自然步骤是设置新用户,以便他们登录到 Databricks 工作区并利用 Unity Catalog 的优势。
在 Databricks 中,用户管理以前是通过每个工作区来管理的。Unity Catalog 将用户管理集中到一个单一的集中式治理面板——账户控制台中。与其在工作区层级管理工作区身份(如果同一个用户访问多个 Databricks 工作区,这会变得重复),不如在账户层级管理身份。这样,管理员只需定义一次用户及其权限,并可以在全局层级轻松管理身份角色和权限。
在 Unity Catalog 之前,工作区管理员需要从身份提供者(如 Okta、Ping 或 Azure Active Directory(AAD))同步组织身份。通过 Unity Catalog,身份会在账户层级通过一次性的跨域身份管理系统(SCIM)同步。然后,Unity Catalog 会自动处理身份的同步,将其分配到适当的工作区,这个过程被称为身份联合。这使得组织可以继续在其组织的身份提供者中管理身份,同时确保更改会自动传播到各个 Databricks 工作区。
图 5.4 – 身份在账户控制台中管理,并会自动跨多个 Databricks 工作区进行身份联合
事实上,管理员在 Databricks 数据智能平台中是一个负载过重的术语。我们来看一下不同的管理角色,以及每个角色在启用 Unity Catalog 的工作区中所拥有的权限级别:
-
账户所有者:最初创建 Databricks 账户的个人。默认情况下,该用户将有权访问账户控制台,并且将被添加为账户管理员和工作区管理员。
-
账户管理员:一个拥有访问账户控制台权限并能够进行账户级别更改的高级用户,例如部署新的 Unity Catalog 元存储、更改网络配置,或将用户、组和服务主体添加到工作区等。此用户有权限授予其他账户管理员和元存储管理员权限。
-
元存储管理员:一个具有进行元存储级别更改权限的管理员用户,例如更改目录所有权、授予用户创建或删除新目录的权限,或配置通过 Delta Sharing 协议共享的新数据集等。此用户无法访问账户控制台。
-
工作区管理员:一个具有进行工作区级别配置更改权限的管理员用户,包括创建集群策略和实例池、创建新集群和 DBSQL 仓库,或配置工作区外观设置等。此用户无法访问账户控制台。
图 5.5 – Databricks 数据智能平台中的管理员级别权限
要开始为新工作区用户配置环境,您需要登录到账户控制台,地址是accounts.cloud.databricks.com/login
。然而,只有账户所有者、最初创建 Databricks 组织的个人,或账户管理员才能访问管理员控制台。
开始为新工作区用户入职的第一步是启用单点登录(SSO)功能。在账户控制台的设置菜单中提供您组织身份提供商的详细信息即可完成此操作。输入配置信息后,点击测试 SSO按钮以验证连接是否成功。
图 5.6 – SSO 需要进行身份联合,以便与您的身份提供商同步
在身份提供者集成成功验证后,下一步是将用户和组分配到相应的 Databricks 工作区。如果只有一个 Databricks 工作区,那么这只是一个简单的操作。然而,如果有多个 Databricks 工作区,则由您的组织决定谁可以访问特定工作区。您可以通过导航到帐户控制台,点击菜单中的用户管理选项卡,然后在用户级别或组级别将用户分配到相应的工作区。
让我们来看看如何在组织内推广安全的数据探索。
数据发现与目录管理
数据标签是有用的数据目录构造,允许数据管理员将描述性元数据与数据集和其他可安全访问的对象(如目录、卷或机器学习模型)关联,在 Unity Catalog 中使用。通过将描述性标签附加到数据集和其他可安全访问的对象,组织内的用户可以搜索和发现可能在日常活动中有用的数据资产。这有助于促进团队之间的协作,通过避免重复创建相似的数据资产来节省时间和资源,从而更快地完成特定活动。Unity Catalog 支持以下数据对象的标签:目录、数据库、表、卷、视图和机器学习模型。
让我们看一个例子,展示如何将描述性标签应用于我们现有的出租车行程数据集,从而使组织内的用户更容易在 Unity Catalog 中搜索、发现和使用我们发布的数据集。可以通过多种方法轻松将标签添加到 Unity Catalog 中的表。最简单的方法是通过 Databricks 数据智能平台中的 Catalog Explorer UI 直接操作。从 Catalog Explorer 开始,搜索上一章实际操作中创建的目录,该目录将我们 DLT 管道中的数据存储到yellow_taxi_raw数据集中。接下来,展开架构并选择yellow_taxi_raw数据集,查看数据集详情。最后,点击添加标签按钮以开始添加标签数据。
图 5.7 – 标签可以直接从 Catalog Explorer 添加到数据集中
标签作为键值对添加,其中键作为唯一标识符,例如类别,值则包含您想要分配给可安全访问对象的内容。在此案例中,我们希望添加一些标签来标记数据集的敏感性,以及一个数据集所有者的标签。添加一些您自己选择的标签,并点击保存标签按钮,以便将您的更改保存到数据集。
图 5.8 – 标签是帮助区分 Unity Catalog 中数据集与其他数据集的关键值对
类似地,标签数据也可以使用 SQL 语法进行添加、更改或移除。在下一个示例中,在您的 Databricks 工作区主目录中创建一个新的笔记本,并在笔记本的第一个单元格中添加以下 SQL 语句。在此示例中,我们将更新我们数据集的数据集描述标签:
ALTER TABLE yellow_taxi_raw
SET TAGS ('description'='Unprocessed taxi trip data')
最后,标签支持更精细的粒度,并且可以添加到 Unity Catalog 中数据集的列级别。在这种情况下非常有用,当您需要区分列的数据敏感性时,可以动态地为 Unity Catalog 中的视图应用数据蒙版。
图 5.9 – 标签可以添加到 Unity Catalog 中数据集的列级别
相反,用户可以使用目录资源管理器的搜索文本字段中的以下语法搜索应用了标签的视图、表或列:tag:<区分大小写的标签名称>。
如您所见,标签在帮助促进组织中数据集的可发现性方面非常有用,并帮助用户区分 Unity Catalog 中的数据集。
除了发现组织中的数据集之外,了解数据集如何形成以及上游源是否可信也是至关重要的。数据血统是用户了解他们在 Unity Catalog 中发现的数据集确切形成方式以及各列来源的一种方法。
使用血统追踪跟踪数据集关系
当数据通过数据管道在您的湖屋中进行转换时,组织数据集的内容可以通过多种过程经历一系列的演变。这可能包括数据清洗、数据类型转换、列转换或数据增强等过程。可以想象,数据与最初从其源头摄取时相比,可能会有很大的偏差。对于使用您湖屋中数据的下游消费者来说,能够验证数据集的有效性非常重要。数据血统是一种验证机制,允许用户跟踪表和列的来源,从而确保您正在使用来自可信源的数据。
图 5.10 – 一个湖屋表可能是多个上游表组合的结果
数据血统可以从 Databricks 数据智能平台的多种机制中查看:
-
通过查看血统图直接从目录资源管理器获取
-
使用 Lineage Tracking REST API 检索
-
在 Unity Catalog 中查询 Lineage Tracking 系统表
让我们看看如何追踪下游表中的一些列的来源,追溯到 Databricks 中的上游数据源。如果你还没有这样做,你可以从 GitHub 仓库克隆本章的示例笔记本,地址为 github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter05
。首先,将名为Data Lineage Demo.sql的示例笔记本导入你的 Databricks 工作区。将该笔记本附加到一个正在运行的多用途集群,并执行所有笔记本单元格。该笔记本将生成两个表——一个父表和一个子表,其列是从上游父表中构建的。
一旦笔记本执行完成并且表已保存至 Unity Catalog,请通过点击左侧导航菜单中的Catalog菜单项进入 Catalog Explorer。在 Catalog Explorer 中,输入表名到搜索文本框来查找子表。点击子表以查看表的详细信息。最后,点击标有查看 lineage 图的蓝色按钮来生成 lineage 图。你会注意到,图中清晰地展示了两个数据资产——父表和子表之间的关系。接下来,点击子表中标为car_description的列。你会看到 lineage 图已更新,清晰地说明了父表中的哪些列被用来构建下游子表中的此列。
图 5.11 – 可以直接从 Catalog Explorer 查看表的 lineage 图
事实上,得益于 Unity Catalog 的统一特性,数据 lineage 可以用于追踪跨多个工作区的数据关系。此外,数据 lineage 会实时捕捉关系信息,确保用户无论使用哪个 Databricks 工作区,都能始终获得最新的数据集关系视图。
使用系统表进行可观察性
强大的审计和系统可观察性是 Unity Catalog 的另一大核心优势,它通过系统表在 Databricks 中实现。系统表是 Databricks 工作区中的一组只读表,用于捕捉关于活动操作的数据。此外,系统表会记录 Databricks 账户中所有工作区的数据,作为获取与 Databricks 工作区相关的操作数据的单一真实数据源。
系统表记录关于你 Databricks 工作区以下方面的可观察性信息(最新的系统表可用列表可以在 docs.databricks.com/en/admin/system-tables/index.html#which-system-tables-are-available
中找到):
类别 | 服务 | 描述 |
---|---|---|
系统计费 | 可计费使用 | 捕获关于使用的计算资源(如仓库和集群)的计费信息 |
定价 | 捕获系统服务定价的历史变化(或 库存单元(SKU)) | |
系统访问 | 系统审计 | 包含来自工作区服务的事件数据,包括作业、工作流、集群、笔记本、仓库、密钥等 |
表格血缘 | 捕获 Unity Catalog 中表格的读写数据 | |
列血缘信息 | 捕获 Unity Catalog 表格中列的读写数据 | |
计算 | 集群 | 捕获关于集群的信息——例如,随着时间推移的配置变化 |
节点类型信息 | 包含关于集群节点类型的硬件信息 | |
SQL 仓库事件 | 捕获对 SQL 仓库所做的更改,例如扩展事件 | |
系统存储 | 预测优化 | 捕获在数据处理过程中发生的预测 I/O 优化 |
市场 | 市场漏斗事件 | 捕获市场分析信息,例如展示次数和关于数据集列表的漏斗数据 |
市场列表事件 | 记录关于数据集列表的市场消费者信息 |
表 5.1 – 系统表将记录 Databricks 数据智能平台各个部分的操作信息
像 Unity Catalog 中的所有表格一样,系统表默认是不可访问的。相反,元存储管理员需要授予适当用户和组对这些表的读取权限(SELECT 权限)。例如,为了授权部门领导在工作日跟踪他们的仓库扩展事件,元存储管理员需要明确授权给组 dept_leads 查询 SQL 仓库系统表的权限:
GRANT SELECT ON system.compute.warehouse_events TO dept_leads
正如你所想象的那样,非常活跃的 Databricks 工作区将在一天中记录许多事件,随着时间的推移,这些表格可能会变得非常庞大。为了防止可观察性信息积累到产生巨大的云存储账单,系统信息将在你的 Databricks 账户中最多保留一年。
对于需要保存多年的审计信息的使用场景,你需要设置一个次要流程,将系统信息复制到长期归档系统中。例如,跟踪数据集的变化对于确保湖仓的强大可观察性至关重要。Unity Catalog 的另一个强大功能是可观察性不仅仅局限于数据集,还涵盖了所有可以在 Unity Catalog 管理下保护的对象。
追溯其他资产的血缘关系
如前所述,Unity Catalog 实现了一个统一的治理解决方案,涵盖了组织的数据资产,不仅仅是表格。通过 Unity Catalog,你可以追溯其他数据资产的血缘关系,比如工作流、笔记本和机器学习模型等。
让我们关注 Unity Catalog 如何通过评估给定 Databricks 用户的用户和组权限,动态生成不同的查询结果集。
细粒度数据访问
动态视图是 Databricks 数据智能平台中的一种特殊类型的视图,它为数据管理员提供了在数据集内控制细粒度数据访问的能力。例如,管理员可以根据用户的组成员身份指定某个特定个人可以访问哪些行和列。Databricks 数据智能平台引入了几个内置函数,用于在特定用户查询视图内容时动态评估组成员身份:
-
current_user() 返回查询视图的用户的电子邮件地址。
-
is_member() 返回一个布尔值(True 或 False),表示查询视图的用户是否是 Databricks 工作区级别组的成员。
-
is_account_group_member() 返回一个布尔值(True 或 False),表示查询视图的用户是否是 Databricks 账户级别组的成员(而不是工作区级别组的成员)。
重要提示
对于在 Unity Catalog 中创建的针对表和视图的动态视图,建议使用 is_account_group_member() 函数来评估用户是否是某个组的成员,因为它会在 Databricks 账户级别评估组成员身份。另一方面,is_member() 函数将评估用户是否是某个特定工作区本地组的成员,可能会提供错误或意外的结果。
此外,动态视图还允许数据管理员模糊化特定列的值,以防敏感数据被意外泄露。使用内置的 Spark SQL 函数,如 concat()、regexp_extract(),甚至是 lit(),是一种简单而强大的工具,用于保护 Databricks 平台上最敏感数据集的内容。
在下一部分中,我们将查看如何利用动态视图,允许数据科学团队的成员对敏感数据集进行临时数据清洗,同时保护包含个人可识别信息(PII)数据的列内容。
实操示例 – 数据屏蔽医疗保健数据集
在此示例中,我们将创建一个动态视图,限制对数据集中特定行和列的访问。我们将使用位于 Databricks 数据集中的 COVID 示例数据集,路径为/databricks-datasets/COVID/covid-19-data/us-counties.csv。该数据集包含了 2020 年全球疫情期间美国各县的 COVID-19 感染数据。由于该数据集可能包含敏感数据,我们将应用一个简单的数据屏蔽,防止将敏感数据暴露给非特权用户。
让我们首先定义一些全局变量,以及将用于存储动态视图的目录和模式:
CATALOG_NAME = "building_modern_dapps"
SCHEMA_NAME = "dynamic_views_demo"
PERSISTENT_TABLE_NAME = "covid_us_counties"
COVID_DATASET_PATH = "/databricks-datasets/COVID/covid-19-data/us-counties.csv"
spark.sql(f"CREATE CATALOG IF NOT EXISTS {CATALOG_NAME}")
spark.sql(f"USE CATALOG {CATALOG_NAME}")
spark.sql(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA_NAME}")
spark.sql(f"USE SCHEMA {SCHEMA_NAME}")
接下来,我们需要在 Unity Catalog 中定义一个持久化表对象,用于创建视图。让我们开始使用示例的美国县级 COVID 数据集创建一个新表:
covid_df = (spark.read
.option("header", True)
.option("inferSchema", True)
.csv(COVID_DATASET_PATH))
(covid_df.write
.mode("overwrite")
.saveAsTable(f"{CATALOG_NAME}.{SCHEMA_NAME}.{PERSISTENT_TABLE_NAME}"));
接下来,让我们查询 Unity Catalog 中新创建的表。请注意,由于我们没有指定任何筛选条件,所有列和行都将被返回:
spark.table(f"{CATALOG_NAME}.{SCHEMA_NAME}.{PERSISTENT_TABLE_NAME}").display()
让我们创建一个视图,动态评估查询用户在 Unity Catalog 中的组成员身份。在此情况下,我们希望如果用户不是admins组的成员,则限制对某些列的访问。根据组成员身份,我们可以授予用户访问权限,或者限制其访问数据。
让我们还利用内置的 Spark SQL 函数,对敏感数据列应用一个简单而强大的数据屏蔽,仅允许admins组的特权成员查看文本:
RESTRICTED_VIEW_NAME = "covid_us_counties_restricted_vw"
spark.sql(f"""
CREATE OR REPLACE VIEW {RESTRICTED_VIEW_NAME} AS
SELECT
date,
county,
state,
CASE WHEN is_account_group_member('admins')
THEN fips
ELSE concat('***', substring(fips, length(fips)-1,
length(fips)))
END AS fips_id,
cases,
CASE WHEN is_account_group_member('admins')
THEN deaths
ELSE 'UNKNOWN'
END AS mortality_cases
FROM {CATALOG_NAME}.{SCHEMA_NAME}.{PERSISTENT_TABLE_NAME}
""")
在之前的视图定义中,我们已将数据访问限制为美国县级 COVID 数据集中某些特定列。使用动态视图,我们还可以通过查询谓词限制对特定行的访问。在最终的视图定义中,我们将根据用户是否属于admins组,限制用户可以查看哪些美国州:
COL_AND_ROW_RESTRICTED_VIEW_NAME = "covid_us_counties_final_vw"
spark.sql(f"""
CREATE OR REPLACE VIEW {COL_AND_ROW_RESTRICTED_VIEW_NAME} AS
SELECT
date,
county,
state,
CASE WHEN is_account_group_member('admins')
THEN fips
ELSE concat('***', substring(fips, length(fips)-1,
length(fips)))
END AS fips_id,
cases,
CASE WHEN is_account_group_member('admins')
THEN deaths
ELSE 'UNKNOWN'
END AS mortality_cases
FROM {CATALOG_NAME}.{SCHEMA_NAME}.{PERSISTENT_TABLE_NAME}
WHERE
CASE WHEN is_account_group_member('admins')
THEN 1=1
ELSE state IN ('Alabamba', 'Colorado', 'California',
'Delaware', 'New York', 'Texas', 'Florida')
END
""")
现在,其他组的成员可以进行临时数据探索和其他数据实验。然而,我们不会无意中暴露任何敏感的医疗保健数据。例如,假设有一个名为data-science的组。该组可以查询动态视图,但其结果将与admins组成员查询视图时的结果不同。例如,以下聚合查询将返回不同的结果集,具体取决于用户是否属于admins组或data-science组:
(spark.table(COL_AND_ROW_RESTRICTED_VIEW_NAME)
.groupBy("state", "county")
.agg({"cases": "count"})
.orderBy("state", "county")
).withColumnRenamed("count(cases)", "total_covid_cases").display()
我们得到以下结果:
图 5.12 – 动态视图可以根据组成员身份生成定制化的结果
到目前为止,你应该能够意识到 Databricks 数据智能平台中动态视图的强大功能。仅凭几个内置函数,我们就能在湖仓中实现强大的数据治理,管理不同用户和团队与组织数据的互动。
总结
在本章中,我们讨论了湖仓数据治理的具体挑战以及 Unity Catalog 如何解决这些挑战。我们还介绍了如何在现有的 Databricks 工作空间中启用 Unity Catalog,以及如何通过元存储管理员与外部数据源建立连接。最后,我们讲解了在湖仓中发现和编目数据资产的技术,以及如何通过元数据标签注释数据资产,从而创建一个可搜索且井井有条的数据目录。
在下一章中,我们将探讨如何使用 Unity Catalog 有效管理输入和输出数据位置。你将学习如何在组织内跨角色和部门治理数据访问,确保 Databricks 数据智能平台中的安全性和可审计性。
第六章:在 Unity Catalog 中管理数据位置
在本章中,我们将探讨如何使用Unity Catalog中的可安全管理对象有效地管理数据存储位置——这些对象允许管理员授予用户、群组和服务主体细粒度的权限。我们将介绍六种在 Unity Catalog 中存储数据的可安全管理对象:目录、模式、表、卷、外部位置和连接。我们还将探讨如何有效地管理组织内不同角色和部门的数据存储访问,确保在 Databricks 数据智能平台内的数据安全性和可审计性。最后,我们将概述如何在 Unity Catalog 中组织和结构化不同存储位置的数据。
在本章中,我们将涵盖以下主要内容:
-
在 Unity Catalog 中创建和管理数据目录
-
设置 Unity Catalog 中数据的默认存储位置
-
在 Unity Catalog 中创建和管理外部存储位置
-
实践实验 – 为生成性 AI 流水线提取文档文本
技术要求
为了跟随本章提供的示例,您需要具备 Databricks 工作区的权限,以便创建并启动一个通用集群,这样您就可以导入并执行本章附带的笔记本。所有代码示例可以从本章的 GitHub 仓库下载,地址为 github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter06
。我们还建议将您的 Databricks 用户提升为元存储管理员(参见第五章),这样您就可以添加和删除外部位置、安全凭证、外部连接,并将目录绑定到 Databricks 工作区。本章将创建并运行多个新的笔记本,预计消耗约 5-10 Databricks 单位 (DBUs)。
在 Unity Catalog 中创建和管理数据目录
目录是 Unity Catalog 对象模型层次结构中用于存储数据资产的最顶层容器。一个目录将包含一个或多个模式(或数据库),这些模式可以包含一个或多个表、视图、模型、函数或卷。
图 6.1 – 数据通过目录在 Unity Catalog 中进行隔离
一个常见的问题是:“我的工作区应该拥有多少个目录?”虽然没有准确的标准来回答应该为工作区创建多少个目录,但一个好的经验法则是根据自然的划分因素来划分工作区目录,例如业务线、逻辑工作环境、团队或使用案例等。此外,您还应考虑哪些组和用户将拥有使用数据资产的权限,这也是决定如何创建目录隔离时的一个因素。
重要提示
最佳实践是避免创建过少的目录,以至于无法合理地将数据集相互区分。同样,也应该避免在一个工作区内创建过多目录,因为这会让用户在浏览和发现数据集时感到困难。目标应该是找到两者之间的平衡。
元数据存储管理员或 Unity Catalog 中的特权用户,可以授予其他用户在元数据存储中创建附加目录的权限。例如,以下由元数据存储管理员执行的授权语句将授予 Databricks 用户[email protected]在其 Databricks 工作区附加的元数据存储中创建新目录的权限:
GRANT CREATE CATALOG ON METASTORE TO `[email protected]`;
此外,对于 2023 年 11 月 8 日之后创建的 Databricks 工作区,会在 Unity Catalog 元数据存储中创建一个默认工作区目录<workspace_name>_catalog。默认情况下,工作区中的所有用户都可以访问此目录并创建数据资产。
管理数据与外部数据
当您部署 Unity Catalog 元数据存储时,部署过程的一部分是设置一个新的默认云存储容器,位于元数据存储级别。这个云存储容器作为 Databricks 数据智能平台上创建的所有数据资产的默认位置。例如,当用户创建一个新表时,如果他们没有在数据定义语言(DDL)语句中指定LOCATION属性,Databricks 数据智能平台将把表数据存储在默认存储容器中。因此,平台会负责管理此表的生命周期,包括数据文件、元数据,甚至表的特性,如调整表布局和文件大小。这种数据资产被称为托管表,因为 Databricks 数据智能平台会管理其生命周期。此外,如果该表被删除,平台将负责移除所有表的元数据和数据文件。
然而,如果用户在 DDL 语句中提供了LOCATION属性,则会覆盖默认行为。相反,用户明确指示 Databricks 数据智能平台将数据存储在 Unity Catalog 默认存储容器之外的位置。因此,这种类型的数据资产被称为外部表。Databricks 不会管理表的性能特性,例如文件的大小或文件的布局。与托管表不同,如果删除外部表,仅会从 Unity Catalog 中移除表的条目,而表的元数据和数据文件不会从其外部位置中删除。相反,表的所有者需要负责从云位置删除表文件,因为他们已经接管了表生命周期的管理。
一般来说,托管表示 Databricks 平台管理生命周期,数据将存储在默认存储容器中;而外部意味着对象所有者控制对象生命周期,数据应该存储在外部存储位置。
实际上,您可能有充分的理由希望将数据资产存储在不同于元数据存储(metastore)默认位置的位置。例如,对于包含敏感数据的特权数据集,如个人身份信息(PII)/受保护的健康信息(PHI)数据,您可能希望将这些数据集存储在单独的存储账户中。或者,您可能有合同义务要求数据必须单独存储在隔离的存储账户中。无论如何,数据隔离的需求是很常见的。在下一节中,我们将介绍 Unity Catalog 中的另一个可安全管理的对象,允许数据管理员在保持与传统表和视图的强隔离的同时,安全地存储任意类型的数据。
将数据保存到 Unity Catalog 中的存储卷
卷,即存储卷,可用于存储各种格式类型的文件。此外,卷可以与表和视图一起存储在 Unity Catalog 中的模式下。表和视图用于存储结构化数据,而卷可以用来存储结构化、半结构化或非结构化数据。
图 6.2 – 存储卷与表和视图一起存储在 Unity Catalog 中的模式中
卷可以由 Databricks 数据智能平台管理,一旦删除,存储容器及其所有内容将被完全移除。另一方面,卷可以是外部卷,意味着卷的所有者管理存储卷的存储位置,一旦删除,存储容器的内容不会被移除。
存储卷通过消除在 Unity Catalog 中创建和管理外部存储位置及存储凭证对象的开销,简化了文件的存储。而外部位置则需要创建一个伴随的存储凭证,这使得配置和撤销配置稍微复杂一些。
存储卷为特定模式的用户提供了将任意文件存储在由 Databricks 数据智能平台管理的安全存储位置中的灵活性。默认情况下,存储卷将在父模式的默认存储位置中持久化数据。例如,如果在创建模式时没有提供存储位置,则存储卷中的数据将存储在 Unity Catalog 元数据存储的默认存储帐户中。而如果模式在创建时指定了明确的存储位置,则默认情况下,存储卷会将其内容存储在该云位置中。
元数据存储管理员或具有明确权限的特权用户可以在目录中创建或删除存储卷。以下示例授予 Databricks 用户在开发目录中创建存储卷的明确权限:
GRANT CREATE VOLUME
ON CATALOG development_catalog
TO `[email protected]`;
完全限定的存储卷路径是通过 /Volumes/ 后跟目录、模式和卷名称来构建的。例如,可以使用以下路径引用一个任意文本文件:
/ Volumes/catalog_name/schema_name/volume_name/subdirectory_name/arbitrary_file.txt
在前面的示例中,我们让 Databricks 数据智能平台决定如何使用模式、表格、视图和存储卷存储数据。然而,我们也可以为某些可安全控制的对象设置规定的云位置。让我们看看如何使用 Unity Catalog 中的几种技术来控制存储位置。
设置 Unity Catalog 中数据的默认位置
您可以使用多种技术来控制 Unity Catalog 中数据的存储位置:
-
目录级别的默认位置:在创建新目录时,数据管理员可以指定存储位置。在创建数据资产时,例如创建表格,且未指定位置,则数据将存储在目录位置中。
-
模式级别的默认位置:类似地,您可以在模式级别指定默认位置。模式位置将覆盖在目录级别指定的任何默认位置。在创建数据资产时,例如创建表格,且未指定位置,则数据将存储在模式位置中。
-
表格级别的外部位置:这是数据管理者对其数据集的最精细粒度的控制。表格位置将覆盖在目录或模式级别指定的任何默认位置。
-
卷位置:与外部位置密切相关(见《在 Unity Catalog 中创建和管理外部存储位置》部分),卷允许控制表数据存储在云存储位置的方式。
将目录隔离到特定工作空间
默认情况下,当您在 Unity Catalog 中创建目录时,该目录将对元存储管理员可见,允许他们授予用户在使用该元存储的所有 Databricks 工作空间中访问权限。然而,在某些情况下,您可能希望覆盖此行为,并强制实施更强的数据集隔离。例如,敏感数据集可能只允许在生产工作空间中用于数据管道处理,但不应在较低环境(如开发工作空间)中使用。Unity Catalog 的一个功能,称为目录绑定,有助于解决此类场景。通过目录绑定,目录管理员(如元存储管理员或目录所有者)可以控制哪些工作空间可以访问特定目录。对于那些未绑定到特定目录的 Databricks 工作空间,该目录将不会出现在 Catalog Explorer UI 的搜索结果中。
图 6.3 – 目录绑定允许数据管理员控制每个工作空间的数据隔离和隔离级别
此外,数据管理员可以指定可对绑定到特定工作空间的数据集执行的操作类型。例如,假设您希望限制对测试环境中目录内数据集的只读访问。数据管理员可以通过 UI、在 Databricks 数据智能平台中使用 Catalog Explorer,或者通过自动化工具(如 Terraform 或 REST API)更改目录的绑定设置。让我们来看一个示例,如何利用 Databricks REST API 将包含 PII 数据的测试目录绑定到生产工作空间。
首先,让我们通过更新目录的默认设置来开始,使得目录不再可以从所有使用我们 Unity Catalog 元存储的工作空间访问。默认情况下,该属性设置为开放,我们希望将目录隔离到指定的工作空间:
import requests
catalog = "testing_catalog"
response = requests.patch(
f"https://{workspace_name}/api/2.1/unity-catalog/catalogs/{catalog}",
headers = {"Authorization": f"Bearer {api_token}"},
json = {"isolation_mode": "ISOLATED"}
)
print(response.json())
我们还可以使用 Catalog Explorer 来验证我们的目录隔离模式是否已根据之前的请求进行了更新。从 Databricks 工作空间,导航到左侧菜单中的 Catalog Explorer。接下来,在搜索框中输入目录名称以筛选目录,然后点击目录的名称。在 Catalog Explorer UI 中,验证详细信息,确保名为所有工作空间均可访问的复选框不再被选中。
图 6.4 – 目录绑定信息可以通过 Databricks UI 配置
现在,我们的目录不再对元存储管理员开放,以便他们从所有使用 Unity Catalog 元存储的工作区授予访问权限,我们希望将目录仅绑定到我们希望用户访问的工作区。
在下一个示例中,我们将再次使用 Databricks REST API,允许生产工作区的数据管理员为目录中的数据集分配只读访问权限:
response = requests.patch(
f"https://{workspace_name}/api/2.1/unity-catalog/bindings/catalog/{catalog}",
headers = {"Authorization": f"Bearer {api_token}"},
json = {"add": [{
"workspace_id": <production_workspace_id>,
"binding_type": "BINDING_TYPE_READ_ONLY"}]
}
)
print(response.json())
重要提示
在前面的示例中,我们提供了工作区标识符,作为绑定 Unity Catalog 中目录到工作区的请求载荷。如果你不确定你的工作区标识符是什么,可以通过检查 Databricks 工作区的 URL 快速找到它。工作区标识符可以在 URL 的第一个 URI 部分中找到,并遵循 https://<workspace_name>.cloud.databricks.com/o=<workspace_id> 模式。工作区标识符是紧随 o= URL 参数后的数字值。
到目前为止,你应该已经了解目录绑定对数据管理员控制数据访问方式的重要影响,并进一步在 Databricks 数据智能平台中隔离数据集。然而,可能会有一些特定场景,数据管理员需要控制云存储位置,比如在管道处理过程中履行不将数据集共同存储的合同义务。在接下来的部分中,我们将讨论数据管理员如何为数据集分配特定的云存储位置。
在 Unity Catalog 中创建和管理外部存储位置
Databricks 的一个强项是数据的开放性,这意味着用户可以连接到存储在各种云原生存储系统中的数据。例如,用户可以连接到存储在 Amazon 的 S3 服务中的数据,并将这些数据与存储在 Azure Data Lake Storage (ADLS) Gen2 存储容器中的另一个数据集进行连接。然而,缺点之一是与这些云原生存储服务的集成需要复杂的配置设置,这些设置通常需要在笔记本执行开始时进行配置,或者可能在集群启动时通过初始化脚本进行配置。这些配置设置非常复杂,至少需要将其存储在 Databricks 密钥中,并且身份验证令牌需要由云管理员轮换——对于一个本应简单的任务(使用 Spark 的 DataFrameReader 加载远程数据),这是一种非常复杂的维护生命周期。Unity Catalog 提供的一个关键好处是一个可安全管理的对象,称为 存储凭证,它旨在简化这一维护任务,同时允许最终用户存储和连接外部数据集,而这些数据集并不在 Databricks 数据智能平台内。云管理员或元数据存储管理员可以将云服务认证详细信息存储在一个地方,从而避免最终用户(可能并不具备技术背景)需要配置云认证的复杂细节,比如 IAM 角色标识符。为了举例说明这些配置细节可能有多复杂,以下代码片段可以用来配置使用在代码执行过程中设置的配置来进行对 ADLS Gen2 容器的认证:
# Connect to data stored in an ADLS Gen2 container
account_name = "some_storage_account"
spark.conf.set(f"fs.azure.account.auth.type.{account_name}.dfs.core.windows.net", "SAS")
spark.conf.set(f"fs.azure.sas.token.provider.type.{account_name}.dfs.core.windows.net", "org.apache.hadoop.fs.azurebfs.sas.FixedSASTokenProvider")
# Use a Databricks Secret to safely store and retrieve a SAS key for authenticating with ADLS service
spark.conf.set(
f"fs.azure.sas.fixed.token.{account_name}.dfs.core.windows.net",
dbutils.secrets.get(scope="sas_token_scope",
key="sas_ token_key"))
使用存储凭证存储云服务认证信息
存储凭证 是 Unity Catalog 中的一个可安全管理的对象,它将云原生凭证抽象化,用于访问云存储账户。例如,存储凭证可能代表 亚马逊网络服务(AWS)云中的 身份与访问管理(IAM)角色。存储凭证也可能代表 Azure 云中的 托管身份 或服务主体。一旦创建了存储凭证,访问该存储凭证的权限可以通过显式授权语句授予 Unity Catalog 中的用户和用户组。与 Unity Catalog 中的其他可安全管理对象类似,Databricks 数据智能平台提供了多种创建新安全凭证的方法。例如,元数据存储管理员可以选择使用 美国国家标准协会结构化查询语言(ANSI SQL)来创建存储凭证,或者他们可能使用 Databricks UI。
图 6.5 – 可以使用 Databricks UI 创建存储凭证
存储凭证与 Unity Catalog 中的另一个可保护对象外部位置配对使用,这种组合用于在特定的云存储帐户中存储和访问数据。
图 6.6 – 存储凭证封装了一个云身份,并由 Unity Catalog 用于访问外部存储位置
您必须是 Databricks 帐户管理员或 Unity Catalog 元数据存储的元数据存储管理员,且该角色包括CREATE STORAGE CREDENTIAL 权限。以下示例使用 Databricks 命令行接口(CLI)工具,通过 AWS 中的 IAM 角色在 Unity Catalog 中创建一个新的存储凭证:
databricks storage-credentials create \
--json '{"name": "my_storage_cred", ' \
'"aws_iam_role": {"role_arn": ' \
'"arn:aws:iam::<role_identifier>:role/<account_name>"}}'
这次我们使用 SQL API 来授予数据科学小组使用凭证访问云存储的权限:
-- Grant access to create an external location using the storage cred
GRANT CREATE EXTERNAL LOCATION
ON STORAGE CREDENTIAL my_s3_bucket_cred
TO `data-science`;
然而,Databricks 数据智能平台外部的存储容器可能并不是您希望从湖仓连接的唯一数据源。例如,可能会有某些场景,您可能需要连接到外部系统,例如现有的数据仓库或关系型数据库,以交叉引用数据。让我们将注意力转向Lakehouse Federation,它允许湖仓用户查询 Databricks 数据智能平台之外的数据集。
使用 Lakehouse Federation 查询外部系统
Lakehouse Federation 是 Databricks 数据智能平台中的一项功能,允许用户在不需要将数据迁移到湖仓的情况下,对外部存储系统执行查询。Unity Catalog 中的另一个可保护对象,称为连接,可以用来将查询联合到外部系统。连接代表与外部系统的只读连接,如关系型数据库管理系统(RDBMS),例如 Postgres 或 MySQL,或是像 Amazon Redshift 这样的云数据仓库。这是一种快速查询外部数据的方法,可以在湖仓中快速原型化新的管道。也许,您甚至需要交叉引用一个外部数据集,但又不想立即经过创建另一个提取、转换和加载(ETL)管道的繁琐过程来引入新的数据源。
可以通过导航到 Databricks 数据智能平台中的 Catalog Explorer,展开连接窗格,并点击外部连接来查看所有连接的列表,查看先前创建的连接的详细信息。
让我们看一个例子,展示如何使用 Databricks 中的 SQL 连接 API 创建一个新的外部连接到 MySQL 数据库。Databricks 建议将所有凭据存储在 Databricks 密钥库中,并通过 secret() SQL 函数轻松地从 SQL 中检索,提供密钥库范围和密钥:
CREATE CONNECTION my_mysql_connection TYPE mysql
OPTIONS (
host '<fully_qualified_hostname>',
port '3306',
user secret('mysql_scope', 'mysql_username'),
password secret('mysql_scope', 'mysql_password')
)
接下来,通过点击左侧导航栏中的目录资源管理器,展开 外部数据 面板,点击 连接 菜单项,导航到 连接 界面。此时,你应该能看到新创建的 MySQL 数据库连接,点击它将显示关于连接的详细信息。
图 6.7 – 可以从目录资源管理器查看连接到外部存储系统的情况
让我们将之前各节所学内容结合起来,构建一个现代数据管道,以支持生成式 AI 的应用场景。
动手实验 – 提取文档文本以用于生成式 AI 管道
在这个例子中,我们将展示一个典型的管道,旨在提取文档中的文本,用于生成式 AI。这是一个非常常见的架构模式,特别适用于诸如训练聊天机器人等实际应用场景。在过程中,我们将看到 Databricks 数据智能平台上的存储卷如何非常适合处理来自外部云存储位置的任意文件。所有代码示例可以从本章的 GitHub 仓库下载,地址为 github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter06
。
生成虚拟文档
数据管道的第一步将是生成任意文本文件以提取文本的过程。让我们开始在 Databricks 工作区中创建一个新的笔记本,用于训练我们组织的聊天机器人。以下代码示例使用流行的 faker Python 库来随机生成文档内容,并使用 reportlab Python 库生成 PDF 文件。
首先,使用 %pip 魔法命令在第一个笔记本单元中安装库依赖项:
%pip install faker reportlab
定义辅助函数
让我们定义几个辅助函数,这些函数将接收随机生成的段落文本并将其保存为文档。我们将定义三个辅助函数——每个文档格式类型一个辅助函数——纯文本、可移植文档格式(PDF)和逗号分隔 值(CSV):
-
让我们定义一个纯文本文件的辅助函数:
from shutil import copyfile def save_doc_as_text(file_name, save_path, paragraph): """Helper function that saves a paragraph of text as a text file""" tmp_path = f"/local_disk0/tmp/{file_name}" volume_path = f"{save_path}/{file_name}" print(f"Saving text file at : {tmp_path}") txtfile = open(tmp_path, "a") txtfile.write(paragraph) txtfile.close() copyfile(tmp_path, volume_path)
-
接下来,我们定义一个用于 PDF 文件的辅助函数:
def save_doc_as_pdf(file_name, save_path, paragraph): """Helper function that saves a paragraph of text as a PDF file""" from reportlab.pdfgen.canvas import Canvas from reportlab.lib.pagesizes import letter from reportlab.lib.units import cm tmp_path = f"/local_disk0/tmp/{file_name}" volume_path = f"{save_path}/{file_name}" canvas = Canvas(tmp_path, pagesize=letter) lines = paragraph.split(".") textobject = canvas.beginText(5*cm, 25*cm) for line in lines: textobject.textLine(line) canvas.drawText(textobject) canvas.save() print(f"Saving PDF file at : {tmp_path}") copyfile(tmp_path, volume_path)
-
最后,我们定义一个用于 CSV 文件的辅助函数:
def save_doc_as_csv(file_name, save_path, paragraph): """Helper function that saves a paragraph of text as a CSV file""" import csv tmp_path = f"/local_disk0/tmp/{file_name}" volume_path = f"{save_path}/{file_name}" print(f"Saving CSV file at : {tmp_path}") with open(tmp_path, 'w', newline='') as file: writer = csv.writer(file) writer.writerow(["Id", "Sentence"]) i = 1 for line in paragraph.split("."): writer.writerow([i, line]) i = i + 1 copyfile(tmp_path, volume_path)
这是一种很好的方式来模拟你所在组织随着时间的推移可能会积累的各种文档。
随机选择文件格式
接下来,我们需要随机选择文件格式来保存生成的文档。首先,我们导入 faker 库和一些 Python 实用库,这些库将帮助我们创建不可预测的行为。我们还将定义一些全局变量,用于确定我们随机生成文档的特征,比如要生成的文档数量、每个文档生成的句子数量,以及存储文档的文件格式类型。将以下代码片段添加到笔记本中:
from faker import Faker
import time
import random
Faker.seed(631)
fake = Faker()
# Randomly generate documents
num_docs = 5
num_sentences_per_doc = 100
doc_types = ["txt", "pdf", "csv"]
volume_path = f"/Volumes/{catalog_name}/{schema_name}/{volume_name}"
接下来,让我们创建一个简单的 for 循环,作为我们随机文档生成器的主干。在 for 循环中,我们将使用 faker 库生成一段随机文本,文本中的句子数量等于由我们全局变量 num_sentences_per_doc 设置的数量:
for _ in range(num_docs):
paragraph = fake.paragraph(nb_sentences=num_sentences_per_doc)
在生成了一段随机文本后,就该选择以哪种文件格式存储文本了。我们将利用 random Python 库,从 doc_types 全局变量中定义的文件格式列表中随机选择一种文件格式类型。将以下代码片段添加到 for 循环体内:
# Randomly choose a document format type
doc_type = doc_types[random.randrange(2)]
print(doc_type)
if doc_type == "txt":
doc_name = f"{fake.pystr()}.txt"
save_doc_as_text(doc_name, volume_path, paragraph)
elif doc_type == "pdf":
doc_name = f"{fake.pystr()}.pdf"
save_doc_as_pdf(doc_name, volume_path, paragraph)
elif doc_type == "csv":
doc_name = f"{fake.pystr()}.csv"
save_doc_as_csv(doc_name, volume_path, paragraph)
最后,我们将添加一个休眠定时器来模拟文本文档生成中的不可预测的高峰和低谷——这是在典型的生产环境中可能出现的现象。将以下代码片段添加到 for 循环体的底部:
# Sleep for a random interval
sleep_time = random.randint(3, 30)
print(f"Sleeping for {sleep_time} seconds...\n\n")
time.sleep(sleep_time)
在笔记本的全局变量部分,你还会注意到,我们已经为我们的过程定义了一个卷路径,用于保存随机生成的文档:
volume_path = f"/Volumes/{catalog_name}/{schema_name}/{volume_name}"
这是一种方便的方式,可以将云存储位置引用为本地存储路径。而且,我们还享受 Unity Catalog 存储卷所带来的强大数据治理优势。例如,所有数据默认都是安全的,其他用户或进程在我们获得存储卷访问权限之前无法读取这些文档。最后,让我们将新笔记本附加到 Databricks 数据智能平台上一个正在运行的通用集群,并点击笔记本顶部的 Run all 按钮,开始生成并保存新文档到我们的存储卷位置。
创建/组装 DLT 管道
现在我们已经生成了一些文本文档,接下来让我们创建一个新的 DLT 管道,该管道将流式传输随机生成的文档并执行简单的文本提取。从本章的 GitHub 仓库中导入名为Preprocess Text Documents.py的笔记本到你的 Databricks 工作区。你会注意到我们定义了三个新的流式表,这些表负责摄取随机生成的文本、PDF 和 CSV 文档。在进行最小的预处理后,这些数据源的文本字段被提取并在第四个表格text_docs_silver中进行合并。这个第四个表将作为我们聊天机器人训练的输入:
@dlt.table(
name="text_docs_silver",
comment="Combined textual documents for Generative AI pipeline."
)
def text_docs_silver():
text_docs_df = dlt.read("text_docs_raw").withColumn(
"type", F.lit("text"))
csv_docs_df = dlt.read("csv_docs_raw").withColumn(
"type", F.lit("csv"))
pdf_docs_df = dlt.read("pdf_docs_raw").withColumn(
"type", F.lit("pdf"))
combined_df = text_docs_df.union(csv_docs_df).union(
pdf_docs_df)
return combined_df
在将笔记本连接到正在运行的集群后,系统将提示你创建一个新的 DLT 管道。继续创建一个全新的 DLT 管道(在第二章中介绍),并为管道命名一个有意义的名称,例如doc_ingestion_pipeline。选择处理模式为Triggered,产品版本选择Core,其余选项保持默认设置。最后,点击Start以开始更新执行新创建的 DLT 管道。
图 6.8 – 一个 DLT 管道的概述,提取保存在 Unity Catalog 中的卷位置的任意文档的文本。
你应该能看到 DLT 管道正在增量处理随机生成的文本文档,从每种不同的文件类型中提取文本,并将它们合并成一个下游的汇总数据集。这是一个简单而强大的例子,展示了如何将 DLT 与 Unity Catalog 中的存储卷结合起来,在真实世界的使用案例中处理任意文件格式。
总结
在本章中,我们介绍了多种存储数据的方法,同时通过 Unity Catalog 中的不同可安全对象保持细粒度的访问控制。我们讲解了如何使用 Unity Catalog 中的目录、模式、表、视图、卷和外部位置来存储数据。我们还看到组织如何将目录绑定到单个 Databricks 工作区,以隔离数据集,甚至设置只读的访问权限。我们讨论了 Databricks 数据智能平台中受管数据集的区别,以及如何使用目录、模式、表、卷和外部位置设置存储位置来存储数据。我们介绍了如何使用 Lakehouse Federation 在不迁移数据的情况下直接查询外部数据源,如数据仓库。最后,我们通过一个实践操作,展示了如何实现生成式 AI 管道的开始,利用 Unity Catalog 中的卷提取文档中的文本。
现在我们已经为存储数据和其他资产打下了坚实的基础,在下一章中,我们将介绍如何在 Databricks 数据智能平台中跟踪各个对象的血统。
第七章:使用 Unity Catalog 查看数据血缘
本章中,我们将深入探讨数据血缘在 Databricks 数据智能平台中的关键作用。你将学习如何追踪数据来源、可视化数据集转换、识别上下游依赖关系,并使用 Catalog Explorer 的血缘图功能来记录血缘。到本章结束时,你将掌握确保数据来自可信来源、并在问题发生前识别出破坏性变化的技能。
本章将涵盖以下主要主题:
-
在 Unity Catalog 中引入数据血缘
-
使用数据血缘 REST API 跟踪数据来源
-
可视化上下游数据转换
-
确定依赖关系和影响
-
实操实验 – 记录整个组织的数据血缘
技术要求
要跟随本章提供的示例,你需要拥有 Databricks 工作区的权限,以便创建和启动一个通用集群,从而导入并执行本章配套的笔记本。所有代码示例可以从本章的 GitHub 仓库下载,地址是 github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter07
。本章将使用通用集群创建并运行多个新的笔记本,预计将消耗大约 5-10 Databricks 单位(DBUs)。
在 Unity Catalog 中引入数据血缘
数据血缘是指在 Unity Catalog(UC)中追踪可安全访问对象之间关系的能力,例如表格,使用户能够查看数据资产如何从上游来源形成,并验证下游的依赖关系。
图 7.1 – 数据血缘追踪数据流动及其在内部过程中的转化
在 Databricks 中,用户可以几乎实时地追踪数据资产的血缘,这样数据管理员就能确保他们使用的是最新的资产。此外,Unity Catalog 中的数据血缘跨越多个工作区,这些工作区连接到同一个 Unity Catalog 元存储,使得数据专业人士能够获得一个完整的、全面的视图,了解数据集如何被转化并相互关联。
数据血缘可以跨越 Databricks 数据智能平台中的多种可安全访问对象进行追踪,以下是一些例子:
-
查询
-
表格
-
表格列
-
笔记本
-
工作流
-
机器学习模型
-
Delta Live Tables(DLT)管道
-
仪表板
就像 Databricks 数据智能平台中的许多对象一样,你可以通过多种机制追踪血缘信息,包括通过 Databricks UI 使用 Catalog Explorer,或者通过消费 Data Lineage REST API。实际上,数据血缘信息会自动由 Databricks 数据智能平台捕获并记录在系统表中(详见第五章)。与其他保存在 Databricks 系统表中的系统信息一样,血缘信息可能会累积不少。为了节省存储成本,默认情况下,这些信息会保留一年。对于更长期的血缘存储需求,建议建立一个替代流程,将血缘信息附加到长期归档存储中。例如,假设一个组织需要保留多年的系统审计信息,那么就需要一个长期归档的 ETL 管道,将血缘数据复制到归档存储中。
在接下来的章节中,我们将介绍在 Databricks 中查看数据资产血缘的各种方式。
使用 Data Lineage REST API 跟踪数据来源
像 Databricks 数据智能平台中的许多可安全访问的对象一样,存在多种方式可以检索与对象相关的详细血缘信息。Databricks 中检索某个特定对象血缘信息的一种常见方式是通过 Data Lineage REST API。目前,Data Lineage REST API 仅限于检索表血缘信息的只读视图以及列血缘信息。
UC 对象 | HTTP 动词 | 端点 | 描述 |
---|---|---|---|
表 | GET | / api/2.0/lineage-tracking/table-lineage | 给定一个 UC 表名称,检索上游和下游表连接的列表,以及它们相关的笔记本连接信息 |
列 | GET | / api/2.0/lineage-tracking/column-lineage | 给定一个 UC 表名称和列名称,检索上游和下游列连接的列表 |
表 7.1 – Data Lineage REST API 获取与 UC 表和列对象的上游和下游连接相关的信息
然而,预计 Data Lineage REST API 将随着时间的推移不断发展,增加更多的功能,使数据管理员能够检索信息,甚至操作平台内数据资产的端到端血缘信息。
让我们来看一下如何使用 Lineage Tracking API 来检索本章附带的 GitHub 仓库中,由数据集生成器笔记本创建的表的上游和下游连接信息,仓库地址为github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter07
。
首先,我们将在 Databricks 工作空间中创建一个全新的笔记本,并导入 requests Python 库。我们将专门使用 Python 的 requests 库来向 Databricks REST API 发送数据溯源请求,并解析来自 Databricks 控制平面的响应:
import requests
创建并启动一个通用集群,以便将笔记本附加到该集群并运行笔记本单元格。你需要生成一个 个人访问令牌(PAT),用于与 Databricks REST 端点进行身份验证并发送数据溯源 API 请求。强烈建议将 PAT 存储在 Databricks 秘密对象中(docs.databricks.com/en/security/secrets/secrets.html
),以避免不小心泄露身份验证信息到你的 Databricks 工作空间。
重要提示
以下代码片段仅用于示范。你需要更新工作空间名称以匹配你 Databricks 工作空间的名称,并设置 API 令牌的值。
让我们使用 requests 库通过指定完全合格的端点,向数据溯源 API 发送请求:
response = requests.get(
f"https://{WORKSPACE_NAME}.cloud.databricks.com/api/2.0/lineage-tracking/table-lineage",
headers={
"Authorization": f"Bearer {API_TOKEN}"
},
json={
"table_name": FULLY_QUALIFIED_TABLE_NAME,
"include_entity_lineage": "true"
}
)
print(response.json())
接下来,让我们添加一些辅助函数,用于解析来自数据溯源 API 的响应,并以易于理解的格式打印连接信息。请在你的笔记本中添加一个新的单元格,并粘贴以下辅助函数:
def print_table_info(conn_type, table_info_json):
info = table_info_json["tableInfo"]
print(f"""
+---------------------------------------------+
| {conn_type.upper()} Table Connection Info
|---------------------------------------------|
| Table name: {info['name']}
|---------------------------------------------|
| Catalog name: {info['catalog_name']}
|---------------------------------------------|
| Table type: {info['table_type']}
|---------------------------------------------|
| Lineage timestamp: {info['lineage_timestamp']}
+---------------------------------------------+
""")
if conn_type.upper() == "UPSTREAMS":
print(f"""
|
\|/
""")
def print_notebook_info(conn_type, notebook_info):
print(f"""
+---------------------------------------------+
| {conn_type.upper()} Notebook Connection Info:
|---------------------------------------------|
| Workspace id: {str(notebook_info['workspace_id'])}
|---------------------------------------------|
| Notebook id: {str(notebook_info['notebook_id'])}
|---------------------------------------------|
| Timestamp: {notebook_info['lineage_timestamp']}
+---------------------------------------------+
""")
现在,让我们更新我们之前用于获取表格溯源信息的代码片段的响应部分,但这次我们将调用这些辅助函数:
if response.status_code == 200:
connection_flows = ["upstreams", "downstreams"]
for flow in connection_flows:
if flow in response.json():
connections = response.json()[flow]
for conn in connections:
if "tableInfo" in conn:
print_table_info(flow, conn)
elif "notebookInfos" in conn:
for notebook_info in conn["notebookInfos"]:
print_notebook_info(flow, notebook_info)
输出现在应该是来自我们数据溯源 API 的更加易读的响应,允许我们清晰地查看来自 Unity Catalog 中表的上游和下游表连接。
图 7.2 – 来自 Databricks Data Lineage REST API 的表格溯源响应输出
数据溯源 API 非常适合追踪 Unity Catalog 中数据集之间的连接。然而,我们还可以检索有关我们表的 列 的更精细的溯源信息。在下一个示例中,让我们检索有关我们表的 description 列的信息。我们还将定义另一个辅助函数,以便更好地显示列连接信息:
def print_column_info(conn_type, column_info):
print(f"""
Connection flow: {conn_type.upper()}
Column name: {column_info['name']}
Catalog name: {column_info['catalog_name']}
Schema name: {column_info['schema_name']}
Table name: {column_info['table_name']}
Table type: {column_info['table_type']}
Lineage timestamp: {column_info['lineage_timestamp']}
""")
column_name = "description"
response = requests.get(
f"https://{WORKSPACE_NAME}.cloud.databricks.com/api/2.0/lineage-tracking/column-lineage",
headers={
"Authorization": f"Bearer {API_TOKEN}"
},
json={
"table_name": FULLY_QUALIFIED_TABLE_NAME,
"column_name": column_name
}
)
if response.status_code == 200:
if "upstream_cols" in response.json():
print("| Upstream cols:")
for column_info in response.json()['upstream_cols']:
print_column_info("Upstream", column_info)
if "downstream_cols" in response.json():
print("| Downstream cols:")
for column_info in response.json()['downstream_cols']:
print_column_info("Downstream", column_info)
在这种情况下,我们表中的 description 列尤其引人注目,因为它是将两个不同列的文本字符串连接起来的结果。如果你使用不同的列名更新之前的列溯源请求,你会注意到上游源的数量会发生变化,以反映该列特有的连接数量。
图 7.3 – 来自 Databricks Lineage API 的列溯源响应输出
到目前为止,你应该已经能够熟练地使用 Databricks 数据血缘 API 来追踪数据集之间的连接,甚至是更精细的数据转换,如列连接。正如你所见,Data Lineage API 的请求和响应需要有处理 JSON 数据的经验。对于一些响应,我们需要创建辅助函数,将响应解析为更易读的格式。
在下一节中,我们将探讨如何使用 Databricks 用户界面来追踪数据集之间的关系,使得非技术数据管理员也能通过点击按钮轻松查看上下游数据源。
可视化上下游转换
在本节中,我们将利用数据集生成器笔记本,在 Unity Catalog 中创建多个数据集,以便通过 Databricks 用户界面追踪数据集血缘。如果你还没有这样做,请克隆本章附带的 GitHub 仓库,仓库地址是 github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter07
。接下来,启动一个现有的通用集群,或创建一个新的集群,并开始将数据生成器笔记本附加到该集群。在 Databricks 工作区的右上角点击 Run all 按钮,执行所有笔记本单元格,验证所有单元格都成功执行。如果遇到运行时错误,请验证你是否拥有正确的元存储权限,以在 Unity Catalog 元存储中创建新的目录、架构和表格。
重要提示
你需要获得创建新的目录和架构的权限,才能在你的 Unity Catalog 元存储中进行操作。如果无法获得此权限,可以重新使用现有的目录和架构来生成示例表格。你需要相应地更新 DDL 和 DML 语句,以匹配你自己 Databricks 工作区中的值。
数据生成器笔记本的结果应包含三张表格:youtube_channels、youtube_channel_artists 和 combined_table。在 Databricks 数据智能平台中,数据血缘可以通过多种方式轻松追踪。在这个示例中,让我们使用 Databricks 用户界面来追踪一个数据资产——combined_table 表的血缘。从你的 Databricks 工作区中,点击左侧导航菜单中的 Catalog Explorer 菜单选项。接下来,可以深入目录和架构以找到 combined_table 表,或者直接在 Catalog Explorer 顶部的搜索框中输入 combined_table,该框会过滤出匹配文本的所有数据资产。点击 combined_table 表,这将会在用户界面的右侧面板中打开数据资产的 概览 详情。
图 7.4 – 数据血缘可以直接从 Databricks 的 Catalog Explorer 中追溯
从 UI 面板中,点击血缘标签以显示表的血缘详情。导航到血缘标签后,你应该能看到与combined_table数据集相关的所有连接的摘要,清晰地标识出构建此表所使用的所有上游源,以及任何利用此表的下游依赖项。
图 7.5 – Catalog Explorer 中的血缘标签包含关于表的血缘信息
在这种情况下,应该有两行包含有关上游源的信息——youtube_channels父表和youtube_channel_artists表。由于我们刚刚使用数据生成器笔记本创建了这个表,所以不应该有任何带有下游依赖关系的行。正如你可以想象的,这个表将实时更新,列出所有以某种方式使用该数据集的对象,明确标识出数据的任何下游依赖项。
最后,让我们可视化我们的表格血缘关系。点击标有查看血缘图的蓝色按钮,打开血缘可视化。
现在你应该清楚地看到,两个上游表连接形成了combined_table表。
图 7.6 – 可以通过点击血缘图中的连接链接生成血缘连接信息
接下来,点击连接上游表和下游表combined_table的箭头,揭示有关血缘连接的更多详细信息。你会注意到,侧边面板会打开,显示关于血缘连接的信息,如源表和目标表,但它还会显示这些数据资产如何在 Databricks 数据智能平台的各种其他对象中被使用。例如,UI 面板将列出这些数据集当前是如何在笔记本、工作流、DLT 管道和 DBSQL 查询中被利用的。在这种情况下,我们只是通过数据生成器笔记本生成了这些表,所以它是血缘连接信息中唯一列出的对象。
图 7.7 – 可以从血缘图中查看数据集之间的连接详情
列的血缘关系也可以通过 Catalog Explorer 来追踪。在相同的血缘图中,点击 combined_table 表中的不同列以显示血缘信息。例如,通过点击 description 表列,血缘图将更新并清晰地展示 description 列的计算方式。在这个例子中,列是通过将一串文本与父表的类别列以及子表中的艺术家名称连接起来计算得出的。
图 7.8 – 可以通过点击列来追踪血缘关系,揭示上游血缘连接
如您所见,从 Catalog Explorer 生成血缘图提供了 Unity Catalog 中数据集之间最新关系的准确快照。这些关系可以帮助我们识别数据变更对下游依赖关系的影响,例如更改列的数据类型或删除数据集等。
在下一部分,我们将了解数据血缘如何帮助我们识别数据集之间的关系,发现利用这些数据集的依赖笔记本,并避免在整个组织中引入破坏性更改。
确定依赖关系和影响
在这一部分,我们将再次利用 Catalog Explorer 中的血缘图 UI,深入了解更改某一列的数据类型和数值将如何影响下游数据集和下游流程(如笔记本和工作流),并在我们的 Databricks 工作区中查看这些影响。
首先,在我们的 Databricks 工作区创建一个新的笔记本,其中包含一个新的 DLT 流水线的定义。我们 DLT 流水线中的第一个数据集将导入存储在默认Databricks 文件系统(DBFS)中的商业航空公司航班信息原始 CSV 文件,这些文件位于/databricks-datasets目录下。每个 Databricks 工作区都可以访问这个数据集。创建一个新的笔记本单元,并添加以下代码片段,用于在我们的数据流水线中定义一个 bronze 表:
import dlt
@dlt.table(
name="commercial_airliner_flights_bronze",
comment="The commercial airliner flight data dataset located in `/databricks-datasets/`"
)
def commercial_airliner_flights_bronze():
path = "/databricks-datasets/airlines/"
return (spark.readStream
.format("csv")
.schema(schema)
.option("header", True)
.load(path))
我们希望通过商业喷气式飞机的信息来增强航班数据。创建一个新的笔记本单元,并添加以下代码片段,定义一个静态参考表,包含有关流行商业航空公司喷气式飞机的信息,包括制造商名称、飞机型号、原产国和燃油容量等:
commercial_airliners = [
("Airbus A220", "Canada", 2, 2013, 2016, 287, 287, 5790),
("Airbus A330neo", "Multinational", 2, 2017, 2018, 123,
123, 36744 ),
("Airbus A350 XWB", "Multinational", 2, 2013, 2014, 557,
556, 44000),
("Antonov An-148/An-158", "Ukraine", 2, 2004, 2009, 37,
8, 98567 ),
("Boeing 737", "United States", 2, 1967, 1968, 11513, 7649,
6875),
("Boeing 767", "United States", 2, 1981, 1982, 1283, 764,
23980),
("Boeing 777", "United States", 2, 1994, 1995, 1713, 1483,
47890),
("Boeing 787 Dreamliner", "United States", 2, 2009, 2011,
1072, 1069, 33340),
("Embraer E-Jet family", "Brazil", 2, 2002, 2004, 1671,
1443, 3071),
("Embraer E-Jet E2 family", "Brazil", 2, 2016, 2018, 81,
23, 3071)
]
commercial_airliners_schema = "jet_model string, Country_of_Origin string, Engines int, First_Flight int, Airline_Service_Entry int, Number_Built int, Currently_In_Service int, Fuel_Capacity int"
airliners_df = spark.createDataFrame(
data=commercial_airpliners,
schema=commercial_airliners_schema
)
接下来,我们将把航空公司喷气式飞机参考表保存到之前在 Unity Catalog 中创建的模式中:
airliners_table_name = f"{catalog_name}.{schema_name}.{table_name}"
(airliners_df.write
.format("delta")
.mode("overwrite")
.option("mergeSchema", True)
.saveAsTable(airliners_table_name))
让我们向数据流水线添加另一个步骤,这将把我们的静态商业喷气机航空公司参考表与我们的航空航班数据流连接起来。在新的笔记本单元中,创建以下用户定义函数(UDF),它将为商业航空数据集中的每个条目生成一个尾号:
from pyspark.sql.types import StringType
from pyspark.sql.functions import udf
@udf(returnType=StringType())
def generate_jet_model():
import random
commercial_jets = [
"Airbus A220",
"Airbus A320",
"Airbus A330",
"Airbus A330neo",
"Airbus A350 XWB",
"Antonov An-148/An-158",
"Boeing 737",
"Boeing 767",
"Boeing 777",
"Boeing 787 Dreamliner",
"Comac ARJ21 Xiangfeng",
"Comac C919",
"Embraer E-Jet family",
"Embraer E-Jet E2 family",
"Ilyushin Il-96",
"Sukhoi Superjet SSJ100",
"Tupolev Tu-204/Tu-214"
]
random_index = random.randint(0, 16)
return commercial_jets[random_index]
最后,创建一个新的笔记本单元,并添加以下 DLT 数据集定义,用于我们的银表:
@dlt.table(
name="commercial_airliner_flights_silver",
comment="The commercial airliner flight data augmented with randomly generated jet model and used fuel amount."
)
def commercial_airliner_flights_silver():
return (dlt.read_stream(
"commercial_airliner_flights_bronze")
.withColumn("jet_model", generate_jet_model())
.join(spark.table(airliners_table_name),
["jet_model"], "left"))
当出现提示时,点击笔记本单元输出底部的蓝色按钮 创建管道 来创建一个新的 DLT 管道。为管道命名一个有意义的名字,例如 商业航班管道。选择 触发式 作为执行模式,并选择 Core 作为产品版本。接下来,选择之前代码示例中的目标目录和模式,作为我们 DLT 管道的目标数据集位置。最后,点击 开始 按钮以触发管道更新。
图 7.9 – 用于获取商业航班数据的 DLT 管道
假设有一个外部过程,旨在计算每个商业航班的碳足迹。在这个例子中,该过程是另一个 Databricks 笔记本,它读取我们银表的输出,并计算美国境内每个航班的二氧化碳排放量。
在您的 Databricks 工作区内创建另一个笔记本,并为笔记本起一个有意义的名字,比如 计算商业航班碳足迹。接下来,添加一个新的笔记本单元,读取银表并使用简单公式计算二氧化碳排放量:
碳足迹 = 燃烧的燃料量 * 系数 / 乘客人数
在这种情况下,我们只关心计算每架航班的碳足迹;因此,我们将避免除以乘客人数。将以下代码片段添加到新创建的笔记本中,该代码将为每个航班条目分配计算出的碳足迹:
# 3.1kg of CO2 is created for every 1kg of fuel used.
# So we multiply the fuel mass above by 3.1 to estimate the CO2 emitted
# Source: https://ecotree.green/en/calculate-flight-co2
# 1 gallon of jet fuel weighs approximately 3.03907 kilograms
def calc_carbon_footprint(fuel_consumed_gallons):
return (fuel_consumed_gallons * 3.03907) * 3.1
让我们再次假设我们 DLT 管道中的银表的燃料容量目前是以加仑为单位。然而,我们的欧洲业务合作伙伴希望改用升作为数据集的单位。让我们使用目录浏览器来查看银表的血缘关系图,以更好地理解将 fuel_capacity 列的度量单位转换为升会对数据集的使用者产生什么影响。通过点击左侧导航栏中的目录浏览器,按目录名称在搜索框中过滤,然后点击银表 commercial_airliner_flights_silver 来进入血缘关系图。
图 7.10 – 列的血缘关系有助于我们理解更改列将如何影响下游依赖关系 – 概览
通过生成血缘图,我们能够实时查看所有可能依赖此列的下游列。此外,我们还可以看到所有依赖此列的 Unity Catalog 对象的实时列表,例如笔记本、工作流、DLT 管道和机器学习模型。因此,实际上,我们可以快速了解更改计量单位可能对共享该数据集的组织产生的影响。
在下一节中,我们将继续这个示例,找出更新该数据集的另一种方法,以包含燃料容量、行驶距离和到达时间,使其适应欧洲标准,而不会影响任何现有的数据消费者。
实操实验 – 跨组织文档化数据血缘
在本节中,我们将查看 Databricks 中的系统表是如何自动记录数据集和其他数据资产之间关系随时间变化的情况。正如之前提到的,Unity Catalog 会在所有连接到同一 Unity Catalog 元存储的工作空间中保留数据血缘。这在组织需要对其数据资产进行强有力的端到端审计时尤其有用。
我们再次从在 Databricks 工作空间中创建一个新的笔记本开始,并为其设置一个有意义的标题,例如查看文档化的数据血缘。接下来,创建一个新的通用集群,或者将笔记本附加到一个已经运行的集群上,以开始执行笔记本单元格。
与数据血缘 API 类似,Unity Catalog 中有两个系统表提供血缘信息的只读视图——system.access.table_lineage 表和 system.access.column_lineage 表。数据血缘系统表会自动记录与 UC 表和列对象的上游和下游连接相关的信息。
UC 对象 | 表名 | 描述 |
---|---|---|
表格 | system.access.table_lineage | 包含上游和下游表连接的列表,以及与其相关的笔记本连接信息 |
列 | system.access.column_lineage | 包含上游和下游列连接的列表 |
表 7.2 – 数据血缘系统表捕获有关表和列的连接信息
让我们查询前面示例中的上游和下游血缘信息。在一个新的笔记本单元格中,添加以下查询并执行单元格:
SELECT *
FROM system.access.table_lineage
WHERE source_table_name LIKE '%commercial_airliners_silver%';
我们得到如下输出:
图 7.11 – 可以从系统表中查询血缘信息
从输出结果可以看出,系统表自动记录了上下游数据源的连接信息。此外,系统表还会自动捕获审计信息,包括数据集创建者的信息以及对象创建事件的时间戳。这是记录、审查甚至报告组织数据集血统的绝佳方式。
总结
在本章中,我们介绍了在 Databricks 数据智能平台中追踪数据血统的各种方法。我们看到,Data Lineage REST API 使我们能够快速查看 Unity Catalog 中特定表格或列的上下游连接。接下来,我们展示了使用 Unity Catalog 中的 Catalog Explorer 生成血统图的简便方法。血统图对于深入了解数据集的变化如何影响下游数据消费者至关重要。最后,我们介绍了如何通过 Unity Catalog 中的系统表来记录数据资产关系的演变。
在下一章中,我们将重点介绍如何使用工具(如 Terraform)自动化部署数据管道及其所有依赖项。
第三部分:持续集成、持续部署与持续监控
在本书的最后部分,我们将探讨如何使用流行的自动化工具,如 Terraform 和 Databricks Asset Bundles(DABs),自动化管道变更的部署。我们将以如何使用 Databricks 数据智能平台中的各种工具持续监控 DLT 管道的教程作为本书的总结。
本部分包含以下章节:
-
第八章,使用 Terraform 部署、维护和管理 DLT 管道
-
第九章,利用 Databricks Asset Bundles 简化数据管道的部署
-
第十章,生产环境中的数据管道监控
第八章:使用 Terraform 部署、维护和管理 DLT 数据管道
在本章中,我们将探讨如何使用像 Terraform 这样的自动化工具将数据管道以代码的形式表达,这通常被称为基础设施即代码(IAC),并应用于 Databricks。我们将学习如何使用流行的代码编辑器,如 VS Code,设置本地 Terraform 开发环境,以便我们可以尝试将不同的资源部署到 Databricks 工作区。接下来,我们将深入探讨如何使用 Terraform 表示数据管道,并如何配置 Delta Live Tables(DLT)管道的不同方面。我们还将学习如何自动化 IaC 的验证和部署到不同的 Databricks 工作区,包括生产工作区。最后,我们将讨论行业最佳实践和未来的考虑事项。
在本章中,我们将涵盖以下主要内容:
-
介绍 Terraform 的 Databricks 提供程序
-
设置本地环境
-
使用 Terraform 配置 DLT 数据管道
-
自动化 DLT 数据管道部署
-
实践操作 - 使用 VS Code 部署 DLT 数据管道
技术要求
为了跟随本章提供的示例,你需要拥有 Databricks 工作区权限,以创建并启动通用集群,这样你才能导入并执行本章附带的笔记本。所有的代码示例可以从本章的 GitHub 仓库下载,地址为:github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter08
。本章将创建并运行几个新的笔记本,还将使用产品的高级版运行一个新的 DLT 数据管道,预计会消耗大约 10—15 Databricks Units(DBUs)。
介绍 Terraform 的 Databricks 提供程序
Terraform 是一个开源部署自动化工具,可以用于以可重复和可预测的方式自动化部署云基础设施。Terraform 成为如此受欢迎的部署工具的原因之一是,它支持将基础设施部署到三个主要云提供商:Amazon Web Services (AWS)、Azure 和 Google Cloud Platform (GCP) 。Terraform 的核心概念是定义基础设施即代码(IaC),即通过代码文件表达云组件(如网络对象、虚拟机或存储容器)的配置,而不是手动部署这些组件。此外,Terraform 文件是配置驱动的。云管理员专注于通过配置表达环境之间的变化,而不是表达如何部署基础设施。最后,Terraform 维护着你的架构状态,意味着该工具将跟踪云资源的状态,并在每次执行 Terraform 配置文件时相应地更新状态。
最后,Terraform 文件可以直接从本地机器执行,允许你远程与云资源进行交互。
图 8.1 – Terraform 将通过配置文件反映环境变化
Terraform 配置文件定义了在云提供商中应用的云基础设施,基础设施状态会同步回本地环境。此外,Databricks 提供了一个 Terraform 提供者,用于将 Databricks 工作区和工作区对象部署到主要云提供商中。
Terraform 提供者是 Terraform 工具的插件,使用户能够与特定的 API 进行交互。在这个案例中,Terraform 提供者与 Databricks REST API 交互,允许工作区管理员自动化部署即便是最复杂的数据处理环境。
使用 Terraform 自动化部署组织内的数据管道有许多优势,包括以下几点:
-
在主要云提供商之间部署基础设施非常容易,使得如果需要,迁移云平台变得轻而易举。
-
通过专注于定义配置而不是手动部署和维护数据管道,轻松实现数百个数据管道的扩展。
-
管道定义简洁,允许云管理员专注于表达需要更改的内容,而不是如何部署基础设施。
让我们来看看如何轻松开始定义 Databricks 资源并将其应用于目标工作区。
设置本地 Terraform 环境
在开始将数据管道对象部署到 Databricks 工作区之前,我们需要安装 Terraform 命令行界面(CLI)工具。如果你还没有安装,你需要下载 Terraform CLI,可以从 HashiCorp 网站免费获取(developer.hashicorp.com/terraform/install
)。
接下来,我们需要将 Terraform 配置文件组织到一个目录中。我们可以创建一个新的目录,命名为 chp8_databricks_terraform。
在新创建的目录中,我们将创建一个全新的 Terraform 配置文件,在该文件中定义我们的数据管道和其他相关的工作区对象。创建一个新文件,命名为 main.tf。
重要提示
Terraform 配置文件使用 Terraform 语言,并以 . tf 扩展名结尾。
导入 Databricks Terraform 提供程序
使用 Terraform 部署 Databricks 工作区对象的第一步是导入 Databricks Terraform 提供程序。如果你第一次使用 Terraform 提供程序,Terraform 将自动从 Terraform 注册表下载 Databricks 提供程序。Terraform 注册表是一个公共中心,用于下载第三方提供程序、模块和安全策略,帮助开发 Terraform 配置文件以部署云基础设施。
将以下代码片段添加到新创建的 Terraform 配置文件 main.tf 的顶部:
terraform {
required_providers {
databricks = {
source = "databricks/databricks"
}
}
}
这段代码将指示 Terraform CLI 工具下载并导入一个名为 databricks 的 Terraform 提供程序,该提供程序已经由 Databricks 组织发布到 Terraform 注册表中。
现在我们已经导入了 Databricks Terraform 提供程序,可以开始将数据管道对象部署到 Databricks 工作区。但在此之前,我们必须先与 Databricks 工作区进行身份验证,以便进行更改,例如创建新的 DLT 管道。
配置工作区身份验证
如果你还记得,Databricks Terraform 提供程序将在后台与 Databricks REST API 交互。因此,用于与 Databricks REST API 进行身份验证并进行工作区更改的相同身份验证机制也可以通过 Terraform 使用。
总的来说,使用 Terraform 提供程序进行 Databricks 工作区身份验证的支持方法大约有九种(最新的列表可以在此找到:registry.terraform.io/providers/databricks/databricks/latest/docs#authentication
)。其中一些常见的身份验证方法包括:
-
使用工作区管理员用户名和密码
-
使用 Databricks 个人访问 令牌(PAT)
-
使用 Azure CLI 或 Google Cloud CLI
-
如果使用 Azure 云提供商,需使用服务主体或托管服务标识
-
使用 Databricks CLI(用户对机器身份验证)
重要提示
由于我们正在进行本地开发和测试,在以下示例中,我们将使用 Databricks CLI 生成一个 OAuth 令牌,并手动登录到我们的 Databricks 工作区。然而,对于生产部署,建议将工作区凭据安全地存储在像 Azure Key Vault、AWS Secrets Manager 或 HashiCorp Vault 等秘密管理器中。
有几种选项可以存储与 Terraform 一起使用的认证令牌——直接存储在 Terraform 配置文件中,作为 Databricks 提供程序导入的一部分,或者存储在本地机器中的配置文件中。我们建议选择后者,以避免在将代码文件提交到代码库时意外暴露凭证。填充此配置文件的最简单方法是使用 Databricks CLI。
Databricks CLI 支持 Windows、Linux 或 macOS 操作系统,使其成为跨平台兼容的多功能工具。如果你的本地机器使用的是 macOS 或 Linux 操作系统,你可以通过命令行提示符使用 Homebrew 包管理器下载 Databricks CLI。或者,你也可以轻松地升级现有 Databricks CLI 安装的版本。例如,以下命令将在 Mac 上使用 Homebrew 安装或升级现有的 Databricks CLI 安装:
$ brew tap databricks/tap
$ brew install databricks
在 Windows 机器上,你可以使用流行的包管理器winget(learn.microsoft.com/windows/package-manager/winget/
)安装 Databricks CLI。以下命令将使用 winget 工具下载并安装 Databricks CLI:
$ winget search databricks
$ winget install Databricks.DatabricksCLI
下载完成后,你可以通过执行 configure 命令在 Databricks CLI 中配置认证:
$ databricks configure
当将 Terraform 配置文件应用于目标环境时,Terraform CLI 会首先检查是否在配置文件中直接提供了认证信息。否则,Terraform CLI 会查找本地的 Databricks 配置文件,该文件存储在名为 .databrickscfg 的特殊隐藏文件中,位于用户的主文件夹下。
你还可以指定一个配置文件名称,这在你有多个 Databricks 工作区,并且需要在不同工作区之间部署基础设施组件时非常有用。使用配置文件,你可以单独存储认证信息,并在部署期间轻松引用它们。你可以在此了解更多关于创建/测试配置文件的信息:docs.databricks.com/dev-tools/cli/profiles.html
。
定义 DLT 管道源笔记本
在下一个示例中,我们将定义一个笔记本,包含一个简单 DLT 管道的开始部分,并将该笔记本部署到目标 Databricks 工作区中的用户工作区目录。
要构建部署笔记本的工作区位置,我们需要获取你当前在 Databricks 中的用户。为了避免硬编码此值,我们可以使用 databricks_current_user 数据源,它在部署时获取当前用户的 Databricks 用户名。将以下配置块添加到 main.tf 文件中:
data "databricks_current_user" "my_user" {}
接下来,我们将使用 databricks_notebook 资源定义一个新的 Python 笔记本,使用之前的数据源构造笔记本路径。由于笔记本相当简单,仅包含一个 DLT 数据集定义,我们将在其中内联定义笔记本内容。将以下配置块添加到 main.tf 文件中:
resource "databricks_notebook" "dlt_pipeline_notebook" {
path = "${data.databricks_current_user.my_user.home}/chp_8_terraform/my_simple_dlt_pipeline.py"
language = "PYTHON"
content_base64 = base64encode(<<-EOT
import dlt
@dlt.table(
comment="The raw NYC taxi cab trip dataset located in `/databricks-datasets/`"
)
def yellow_taxi_raw():
path = "/databricks-datasets/nyctaxi/tripdata/yellow"
schema = "vendor_id string, pickup_datetime timestamp, dropoff_datetime timestamp, passenger_count integer, trip_distance float, pickup_longitude float, pickup_latitude float, rate_code integer, store_and_fwd_flag integer, dropoff_longitude float, dropoff_lattitude float, payment_type string, fare_amount float, surcharge float, mta_tax float, tip_amount float, tolls_amount float, total_amount float"
return (spark.readStream
.schema(schema)
.format("csv")
.option("header", True)
.load(path))
EOT
)
}
最后,让我们向 main.tf 配置文件添加最后一个块,打印已部署笔记本的 URL:
output "notebook_url" {
value = databricks_notebook.dlt_pipeline_notebook.url
}
点击 Save 保存配置文件。在终端窗口中,导航到包含 main.tf 配置文件的目录。
应用工作区更改
应该运行的第一个命令是 terraform init 命令,该命令执行多个初始化步骤,以准备当前工作目录以便使用 Terraform 部署云资源。从终端窗口或 shell 提示符执行以下命令:
terraform init
接下来,Terraform CLI 提供了一种方法,让我们在应用更改之前验证 Terraform 配置文件的效果。执行 validate 命令:
terraform validate
最后,我们可以通过列出 Terraform 计划中的所有预定更改来查看拟议的基础设施更改。执行以下命令查看拟议的 Terraform 计划:
terraform plan
你会注意到计划中将定义一个单一的资源。在这种情况下,它将是包含我们 DLT 数据集定义的新 Databricks 笔记本。
一旦我们验证计划看起来没有问题,就可以将更改应用到目标 Databricks 工作区。通过执行 apply 命令应用 Terraform 计划:
terraform apply
输出将是新创建笔记本的完整 URL。复制输出的 URL 并粘贴到浏览器窗口中。验证是否有一个新的笔记本,Python 被设置为默认编程语言,且包含一个包含单一 DLT 数据集定义的笔记本单元,yellow_taxi_raw。
恭喜!你已经编写了第一个 Terraform 配置文件,并且在自动化部署 Databricks 资产到各个环境的道路上迈出了坚实的步伐。在下一节中,我们将基于前面的示例展开,看看 Databricks Terraform 提供者如何用于将 DLT 管道部署到工作区。
使用 Terraform 配置 DLT 管道
我们将使用 Databricks Terraform 提供程序中的 databricks_pipeline 资源,将 DLT 管道部署到目标 Databricks 工作区。databricks_pipeline 资源是我们 Terraform 配置文件的主要构建块。在此 Terraform 资源中,我们可以指定许多不同的配置选项,这些选项将影响 DLT 管道的部署。例如,我们可以配置 DLT 生产版、目标 Unity Catalog 位置、库依赖项、更新集群大小等。让我们深入探讨这些具体配置,以便更好地了解您对部署的 DLT 管道的控制。
有几个参数用于定义使用 Databricks 提供程序进行 Terraform 配置的 DLT 管道的配置和行为。为了更好地了解这些参数,以下部分涵盖了 Databricks 提供程序中 Terraform 的所有可用参数(最新版本可以在此找到:registry.terraform.io/providers/databricks/databricks/latest/docs/resources/pipeline
)。
一般来说,databricks_pipeline 参数可以被看作是属于以下三类之一:
-
运行时配置:名称,频道,开发,持续,版本,光子,配置,和 库
-
管道计算 配置:集群
-
管道数据集存储配置:目录,目标,和 存储
让我们更详细地了解每个参数,以更好地理解我们的 Terraform 配置对目标 DLT 管道的影响。
name
name 参数为 DLT 管道分配一个字母数字名称,以便标识该管道。name 参数应为一个字符串,可以包含大小写字母、数字、空格和特殊字符(包括表情符号字符)。此外,管道 name 参数不一定需要唯一;Databricks Terraform 提供程序并不强制名称唯一。在创建 DLT 管道时,Databricks 数据智能平台会为每个管道分配一个唯一的管道标识符,因此 name 参数仅作为数据工程师区分其 DLT 管道与其他管道的一种方便方式。
notification
notification 参数用于指定在特定管道事件期间将收到电子邮件通知的收件人列表。触发通知的 DLT 管道事件类型包括 on-update-success,on-update-failure,on-update-fatal-failure 和 on-flow-failure。
channel
通道参数控制 DLT 管道更新集群应使用的 Databricks 运行时类型。只有两个选项可供选择:CURRENT和PREVIEW。CURRENT选择最新的稳定 Databricks 运行时版本,并且是默认选项。如果您的 DLT 管道在开发环境中运行,并且您想尝试尚未进入当前 Databricks 运行时的未来性能功能和优化,您可能希望选择PREVIEW。
development
开发参数是一个布尔标志,用于控制是否希望以开发模式执行 DLT 管道。当设置为true时,Terraform 将部署一个 DLT 管道,并将管道模式设置为开发模式。这也会通过 DLT UI 右上角的切换按钮在 DLT UI 中显示出来。
图 8.2 – 在 DLT UI 中,开发模式可以通过切换按钮显示
同样,当该参数设置为false时,Terraform 将把管道模式设置为生产模式。如果您还记得第二章,我们提到过在开发模式下,DLT 在运行时异常发生时不会重试管道更新,并且还会保持更新集群的运行状态,以帮助数据工程师排查和修复错误,从而缩短调试周期。
continuous
持续参数是一个布尔标志,用于控制管道更新执行的频率。当设置为true时,Terraform 将部署一个 DLT 管道,持续更新管道中的数据集。同样,当设置为false时,DLT 管道将以触发执行模式进行部署。在这种执行模式下,数据工程师需要通过点击 DLT UI 上的开始按钮或通过调用 Pipelines REST API 来触发管道更新的开始。
edition
版本参数选择您在部署 DLT 管道时希望使用的产品版本。可以选择的选项只有三个:CORE,PRO,和ADVANCED。如果您还记得第二章,产品版本选择了您在运行 DLT 管道时希望启用的功能集。因此,管道定价反映了通过版本启用的功能数量。例如,PRO产品版本将允许数据工程师使用期望来强制执行数据质量,但也会产生最高的操作费用。另一方面,CORE产品版本可用于将传入数据追加到流表中,并将产生最少的操作费用来进行更新。
photon
Photon参数是一个布尔标志,控制是否使用 Photon 处理引擎来更新 DLT 管道。当设置为true时,Terraform 将部署一个启用 Photon 引擎的更新集群。在数据集更新过程中,您的 DLT 管道可以利用这个快速的向量化处理引擎,使连接、聚合、窗口和排序的速度比默认集群快。设置为false时,DLT 将创建一个使用传统 Catalyst 引擎的更新集群。由于更快的处理速度和更好的性能,启用 Photon 执行将导致更高的 DBU 定价。
配置
配置参数允许数据工程师部署一个具有可选运行时配置的 DLT 管道。配置参数是一个可选的键值对列表。例如,可以使用该参数来填充环境变量、云存储位置或集群关闭设置等。
库
库参数可用于安装 DLT 管道更新可能依赖的集群库,以便将更新应用于管道。库参数还支持引用本地笔记本或任意文件依赖项,如果数据工程师希望使用本地文件而不是构建工件来包含依赖的代码文件。例如,以下库块可以用于包含一个在用户工作区的主目录中定义的自定义日期实用程序,作为 Python 文件:
library {
notebook {
path = "/Users/<username>/common/utils/date_utils.py"
}
}
集群
集群参数控制在更新、维护活动期间或在更新和维护任务中使用的默认集群类型。如果没有指定集群块,DLT 将创建一个默认集群,用于应用更新到管道的数据集。此外,集群参数还将包含一个模式参数,您可以在其中指定使用哪种类型的自动扩展。如果您还记得,在第二章中,我们描述了 DLT 中的两种自动扩展模式:传统模式和增强模式。例如,以下配置将创建一个更新集群,使用增强模式自动扩展,从最小的三台工作节点扩展到最多八个节点:
cluster {
node_type_id = "i3.xlarge"
autoscale {
min_workers = 3
max_workers = 8
mode = "ENHANCED"
}
}
目录
目录参数确定用于在 Unity Catalog 中存储 DLT 管道输出数据集的目录。随着 DLT 管道执行数据集的定义,这些数据集需要指定一些目标位置。您可以指定目录和模式的组合(在下节目标中介绍),或者可以指定一个云存储位置——但不能同时指定两者。此参数与下一个参数存储参数是互斥的。或者,数据工程师可以继续将 DLT 管道的数据集存储在传统的 Hive 元数据存储中,指定以下配置:
catalog {
name = "hive_metastore"
}
重要提示
如果在 Terraform 配置文件中更改了 catalog 或 storage 参数,并且应用了这些更改,Terraform 将会重新创建整个 DLT 流水线并应用新的更改。这些值在原始 DLT 流水线部署后无法更新。
target
target 参数指定了在 DLT 流水线中存储输出数据集的架构。这个参数与前面的 catalog 参数一起,指定了在 Unity Catalog 或传统 Hive Metastore 中的完全限定架构。数据工程师可以选择使用 catalog 和 target 参数中设置的值,作为查询 DLT 流水线中间数据集的便捷方式。这可以用于常见的任务,例如数据验证、调试或一般的数据整理。
storage
storage 参数可用于指定一个云存储位置,用于存储 DLT 流水线的输出数据集和其他相关元数据。请注意,这个参数与前面的 catalog 参数互斥。storage 参数可以包含完全限定的存储位置路径、卷位置,或 Databricks 文件系统(DBFS)中的位置。例如,以下配置块将创建一个 DLT 流水线,其输出数据集将存储在 DBFS 中:
storage {
path = "/pipelines/my-dlt-pipeline-output/"
}
重要提示
storage 和 catalog 参数是互斥的。在编写 Terraform 配置文件时,您只能指定其中一个。
到目前为止,您应该已经能够自信地使用 databricks_pipeline 资源,通过 Terraform 的 Databricks 提供程序声明 DLT 流水线。您还应该对可用的不同类型的配置选项有更深入的理解,以便自定义目标 DLT 流水线。在接下来的部分中,我们将探讨如何使用现有的版本控制系统自动化 DLT 流水线的部署,以便最新的更改能够在发布后尽快在目标工作区中同步。
自动化 DLT 流水线部署
Terraform 可以与自动化的 持续集成/持续部署(CI/CD)工具结合使用,例如 GitHub Actions 或 Azure DevOps Pipelines,自动将代码更改部署到您的 Databricks 工作区。由于 Terraform 是跨平台的,目标 Databricks 工作区可以位于主要的云服务提供商之一:GCP、AWS 或 Azure。这使得您的开发团队能够在一组代码工件中维护基础设施,同时又能灵活地将相同的资源应用于其他云提供商。
让我们了解一个典型的 CI/CD 流程,该流程使用 Databricks 提供的 Terraform 提供程序将 Databricks 资源部署到目标工作区。CI/CD 流程将包含两个自动化构建流水线——一个用于验证在功能分支中所做的更改,另一个用于将已批准并合并到主代码分支的更改部署到 Databricks 工作区。
首先,一名团队成员创建一个新的功能分支,用于跟踪他们组织的基础设施即代码(IaC)代码库的更改。完成后,工程师将打开一个拉取请求,要求一个或多个团队成员审查更改,留下反馈,并批准或拒绝更改。打开拉取请求后,构建流水线将被触发运行,流水线将检出功能分支,使用 Terraform init 命令初始化当前工作目录,使用 Terraform validate 命令验证 Terraform 计划,并生成一个 Terraform 计划的输出。可选地,这个 Terraform 计划可以作为评论自动包含在拉取请求中,供团队成员审查。
当拉取请求获得团队成员的批准后,功能分支可以合并到主代码库分支——简称为主分支。
一旦功能分支合并到主分支,构建发布流水线就会被触发运行。构建发布流水线将检出最新的主分支副本,并使用 Terraform apply 命令应用更改。在应用 Terraform 计划后,组织基础设施的更新将反映在目标 Databricks 工作区中。
图 8.3 – 使用构建工具自动部署 Databricks 资源
到目前为止,你应该完全理解如何使用像 Azure DevOps 这样的工具设计自动化的 Databricks 部署,借助 Terraform 同步基础设施的更改。让我们将前面各节中学到的内容结合起来,使用典型的开发环境将我们自己的 DLT 流水线部署到目标 Databricks 工作区。
实践练习 – 使用 VS Code 部署 DLT 流水线
在这个动手实践中,我们将使用流行的代码编辑器 Visual Studio Code (VS Code) 来编写新的 Terraform 配置文件,部署 DLT 流水线到目标 Databricks 工作区。多年来,VS Code 因其易用性、轻便的内存占用、友好的代码导航、语法高亮和代码重构功能,以及丰富的扩展社区而获得了极大的欢迎。此外,VS Code 是围绕开源社区构建的,意味着它可以免费下载安装并使用。更重要的是,VS Code 是一个跨平台的代码编辑器,支持 Windows、macOS 和 Linux 操作系统。在这个动手实践中,我们将使用一个社区扩展——HashiCorp 编写的 Terraform 插件,用于帮助开发 Terraform 配置文件。例如,VS Code 的 Terraform 插件提供了 Terraform 语法高亮、自动补全、代码格式化、通过 VS Code 命令面板访问 Terraform 命令,并且总的来说,提供了一个轻松的体验,帮助我们导航 Terraform 配置文件,以便部署 Databricks 工作区对象。
设置 VS Code
VS Code 可以从其官方网站下载,网址为 code.visualstudio.com/download
。如果你还没有安装 VS Code,选择适合本地操作系统的安装程序下载。下载过程可能需要几分钟,具体取决于你的网络连接速度。下载完成后,解压 ZIP 文件以查看下载的内容。接下来,双击应用程序文件,Visual Studio Code,启动代码编辑器。或者,你也可以将应用程序文件移动到本地操作系统的 Applications 目录中。
接下来,让我们安装 HashiCorp 提供的 Terraform 扩展。在一个网页浏览器窗口中,访问 Visual Studio Marketplace 上的 Terraform 扩展,网址为 marketplace.visualstudio.com/items?itemName=HashiCorp.terraform
。或者,你可以在 VS Code 中的 Marketplace 搜索框中搜索该扩展。点击 安装 按钮,下载并安装适用于 VS Code 的 Terraform 扩展。
图 8.4 – HashiCorp 提供的 Terraform 扩展可以从 Visual Studio Marketplace 安装。
你可能会被提示允许浏览器打开本地的 VS Code 应用程序。如果是这样,点击 允许 按钮以打开 VS Code 并安装扩展。扩展将在几分钟内下载并安装完成。安装完成后,你应该可以在 VS Code 左侧导航栏中看到 HashiCorp Terraform 的菜单项。
图 8.5 – HashiCorp Terraform 扩展将在左侧导航栏中创建新的菜单项
现在,Terraform 扩展已经成功安装,当代码编辑器检测到 Terraform 文件时,扩展会自动激活。你可以通过在打开的 Terraform 文件右下角看到 Terraform 的标志来确认扩展是否已激活。
创建一个新的 Terraform 项目
让我们为我们的动手练习创建一个新的目录:
$ mkdir chapter_8_hands_on
$ cd chapter_8_hands_on
创建一个空的 Terraform 配置文件,命名为 main.tf,可以从命令行提示符或者使用 VS Code 创建:
$ touch main.tf
(可选)你可以从本章的 GitHub 仓库克隆示例项目,仓库地址为 github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter08
。接下来,通过选择 文件 | 打开文件夹,并导航到目录的位置,在 VS Code 中打开该目录。
定义 Terraform 资源
让我们从扩展本章开始时 设置本地 Terraform 环境 部分的 Terraform 示例入手。你可以复制现有的 main.tf 文件,或者直接编辑现有 main.tf 配置文件的内容。
首先,让我们通过在 databricks_notebook 资源定义中添加第二个数据集来开始定义 DLT 流水线(出于简洁性考虑,定义 DLT 流水线源笔记本部分的代码在下面的代码块中已被省略)。现在我们将有一个包含两个数据集的数据流水线——一个铜层,接着是银层。请在 main.tf 文件中更新 databricks_notebook 资源定义,内容如下:
resource "databricks_notebook" "dlt_pipeline_notebook" {
path = "${data.databricks_current_user.my_user.home}/chp_8_terraform/taxi_trips_pipeline.py"
...
.load(path))
@dlt.table(
name="yellow_taxi_silver",
comment="Financial information from incoming taxi trips."
)
@dlt.expect_or_fail("valid_total_amount", "total_amount > 0.0")
def yellow_taxi_silver():
return (dlt.readStream("yellow_taxi_bronze")
.withColumn("driver_payment",
F.expr("total_amount * 0.40"))
.withColumn("vehicle_maintenance_fee",
F.expr("total_amount * 0.05"))
.withColumn("adminstrative_fee",
F.expr("total_amount * 0.1"))
.withColumn("potential_profits",
F.expr("total_amount * 0.45")))
EOT
)
}
接下来,在我们创建新的 DLT 流水线之前,我们需要在 Unity Catalog 中定义一个位置来存储流水线数据集。请将以下目录和架构资源定义添加到 main.tf 文件的底部:
resource "databricks_catalog" "dlt_target_catalog" {
name = "chp8_deploying_pipelines_w_terraform"
comment = "The target catalog for Taxi Trips DLT pipeline"
}
resource "databricks_schema" "dlt_target_schema" {
catalog_name = databricks_catalog.dlt_target_catalog.id
name = "terraform_demo"
comment = "The target schema for Taxi Trips DLT pipeline"
}
现在我们有了包含 DLT 流水线定义的更新源笔记本,以及一个存储流水线数据集的位置,我们可以定义一个 DLT 流水线。请将以下流水线定义添加到 main.tf 文件:
resource "databricks_pipeline" "taxi_trips_pipeline" {
name = "Taxi Trips Pipeline"
library {
notebook {
path = "${data.databricks_current_user.my_user.home}/chp_8_terraform/taxi_trips_pipeline.py"
}
}
cluster {
label = "default"
num_workers = 2
autoscale {
min_workers = 2
max_workers = 4
mode = "ENHANCED"
}
driver_node_type_id = "i3.2xlarge"
node_type_id = "i3.xlarge"
}
continuous = false
development = true
photon = false
serverless = false
catalog = databricks_catalog.dlt_target_catalog.name
target = databricks_schema.dlt_target_schema.name
edition = "ADVANCED"
channel = "CURRENT"
}
您会注意到,我们已经定义了包含 DLT 管道定义的笔记本的位置、用于管道更新和维护任务的默认集群,以及其他运行时设置,如开发模式、产品版本、频道等。
接下来,我们需要协调对 DLT 管道的更新,以便我们可以在重复的时间表上触发运行,配置警报通知或设置超时阈值。将以下工作流定义添加到main.tf文件的底部:
resource "databricks_job" "taxi_trips_pipeline_job" {
name = "Taxi Trips Pipeline Update Job"
description = "Databricks Workflow that executes a pipeline update of the Taxi Trips DLT pipeline."
job_cluster {
job_cluster_key = "taxi_trips_pipeline_update_job_cluster"
new_cluster {
num_workers = 2
spark_version = "15.4.x-scala2.12"
node_type_id = "i3.xlarge"
driver_node_type_id = "i3.2xlarge"
}
}
task {
task_key = "update_taxi_trips_pipeline"
pipeline_task {
pipeline_id = databricks_pipeline.taxi_trips_pipeline.id
}
}
trigger {
pause_status = "PAUSED"
periodic {
interval = "1"
unit = "HOURS"
}
}
}
最后,我们需要输出已部署资源的工作流 URL,以便我们可以轻松地从浏览器打开工作流 UI。将以下输出定义添加到main.tf文件中:
output "workflow_url" {
value = databricks_job.taxi_trips_pipeline_job.url
}
部署 Terraform 项目
在我们开始部署新资源之前,第一步是初始化 Terraform 项目。在父目录中执行 Terraform 的init命令,可以通过 VS Code 命令面板或 Shell 提示符来完成:
$ terraform init
接下来,通过执行 Terraform 的plan命令来预览 Terraform 文件中的更改,以查看拟议的基础设施更改:
$ terraform plan
总共有五个新的资源会被创建,包括databricks_notebook资源,它代表了包含 DLT 管道定义的笔记本、目标 Unity Catalog 的目录、目标 Unity Catalog 架构、databricks_pipeline资源,它代表我们的 DLT 管道,以及databricks_job资源,它代表将触发管道更新的工作流。
在验证计划之后,我们现在可以将我们的 DLT 管道部署到 Databricks 工作区。接下来,执行 Terraform 的apply命令,将新的基础设施更改部署到我们的工作区:
$ terraform apply
一旦所有资源更改应用完毕,您应该期待 Terraform 输出 Databricks 工作流的 URL。
将工作流 URL 复制并粘贴到浏览器窗口中,并确保该地址解析到目标工作区中新创建的工作流。您会注意到,新工作流包含一个用于更新 DLT 管道的任务。该工作流已暂停,如 Terraform 配置所示。您也可以点击蓝色的立即运行按钮来触发工作流的一个新的立即运行。
图 8.6 – Terraform 将输出用于更新 DLT 管道的工作流 URL
尽管将更改部署到目标 Databricks 工作区很简单,但撤销这些更改同样容易。执行以下命令以从目标 Databricks 工作区中删除所有资源更改:
$ terraform destroy
通过输入yes确认该决定。完全撤销所有资源从您的 Databricks 工作区中可能需要几分钟。
正如你所看到的,通过几个按键和几次点击,使用 Databricks Terraform 提供者在 Databricks 工作区中配置和去配置资源变得既快速又简便。我们没有指示 Terraform 如何将资源部署到目标 Databricks 工作区,而是专注于通过配置做出哪些更改,让 Terraform 工具为我们处理繁重的工作。
总结
在本章中,我们介绍了如何使用 Databricks Terraform 提供者实现 CI/CD 过程,以便在工作区之间部署数据管道。我们看到,如何轻松设置本地开发环境来处理 Terraform 配置文件,以及在将配置应用于目标环境之前,如何轻松测试我们的 Terraform 计划。我们还从 Terraform 注册中心安装了 Databricks Terraform 提供者,并将其导入到 Terraform 配置文件中。接下来,我们深入了解了 databricks_pipeline 资源,它由 Databricks Terraform 提供者用于将 DLT 管道部署到目标工作区。我们检查了资源规范中的每个参数,并了解了如何控制 DLT 管道的运行时配置、计算设置,甚至管道输出数据集的位置。最后,我们看到,通过将 Terraform 配置文件存储在 GitHub 等版本控制系统中并利用 Azure DevOps Pipelines 等构建工具自动化部署,自动化配置文件变得如此简单。我们通过一个实际操作示例来结束本章,展示了如何使用 Terraform 扩展和流行的代码编辑器 VS Code 从本地开发环境部署 DLT 管道。
然而,Terraform 并不适合所有人,可能对你的用例来说,它过于复杂或难以使用。在下一章中,我们将深入探讨Databricks 资源包(DABs),这是另一个 CI/CD 工具,可以简化将 Databricks 代码工件打包并部署到数据和机器学习工作负载的过程。
第九章:利用 Databricks 资产包简化数据管道部署
本章探讨了一种相对较新的 持续集成与持续部署 (CI/CD) 工具,称为 Databricks 资产包 (DABs),它可以用来简化数据分析项目在不同 Databricks 工作区中的开发和部署。在本章中,我们将深入了解 DABs 的核心概念。我们将通过几个实践操作示例来演示 DAB 的实际应用,帮助你熟悉作为 DAB 开发下一个数据分析项目。最后,我们将介绍如何通过版本控制系统(如 GitHub)使用 DAB 来促进跨团队协作,以及 DAB 如何简化即使是最复杂的数据分析部署。
在本章中,我们将覆盖以下主要内容:
-
Databricks 资产包简介
-
Databricks 资产包的应用
-
简化跨团队协作
-
版本控制与维护
技术要求
为了跟随本章中的示例,建议你拥有 Databricks 工作区的管理员权限,以便能够将 DAB 部署到目标工作区。你还需要下载并安装版本为 0.218.0 或更高版本的 Databricks CLI。所有的代码示例可以从本章的 GitHub 仓库中下载,地址为 github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter09
。在本章中,我们将部署多个新的工作流、DLT 管道、笔记本和集群。预计这将消耗大约 5-10 Databricks Units (DBUs)。
Databricks 资产包简介
DAB 提供了一种简单便捷的方式,可以与 YAML 元数据一起开发你的数据和 人工智能 (AI) 项目,用于声明相关的基础设施—就像一个捆绑包。DAB 为数据工程师提供了一种程序化验证、部署和测试 Databricks 资源到目标工作区的方法。这可能包括部署工作区资产,如 Delta Live Tables (DLT) 管道、工作流、笔记本等。DAB 还提供了一种便捷的方式来开发、打包和部署机器学习工作负载,使用可重用的模板(我们将在 使用模板初始化资产包 部分介绍 DAB 模板),这些模板称为 MLOps 堆栈。
DAB 是围绕表达基础设施即代码(IaC)的原则设计的,利用配置来驱动数据应用程序架构组件的部署。DAB 提供了一种将 IaC 配置与数据资产(如 Python 文件、笔记本和其他依赖项)一起管理的方式。如果你觉得 Terraform(在第八章中有介绍)对于你组织在 Databricks 数据智能平台中的需求过于复杂,DAB 也可以作为一种替代方案。
DAB 与 Terraform 有一些相似之处,两者都是 IaC 工具,能够让用户定义云资源并以云中立的方式部署这些资源。然而,它们也有许多不同之处。让我们比较一下 DAB 和 Terraform 之间的一些相似性和差异性,以便更好地了解在组织的需求下,何时选择哪个工具:
图 9.1 – DAB 和 Terraform 都是 IaC 工具,但它们满足的是非常不同的需求
在我们开始编写第一个 DAB 之前,先花点时间了解一下构成 DAB 配置文件的主要构建块。
DAB 配置文件的元素
DAB 的核心是一个 YAML 配置文件,名为databricks.yml。该配置文件为工程师提供了一个配置其项目资源部署的入口点。该文件由许多可组合的构建块组成,这些构建块告诉 Databricks 的命令行界面(CLI)将什么资源部署到目标 Databricks 工作区,并如何配置每个资源。每个构建块都接受不同的参数来配置该组件。
在本章稍后,我们将介绍如何将配置文件分解为多个 YAML 文件,但为了简单起见,我们从单个 YAML 文件开始。在这个 YAML 配置文件中,我们将声明我们的 Databricks 资源以及其他元数据。这些构建块,或映射,告诉 DAB 工具创建什么 Databricks 资源,更重要的是,告诉 DAB 工具操作哪个 Databricks REST API 来创建和配置 Databricks 资源。
这些映射可以是各种 Databricks 资源。例如,DAB 配置文件可以包含以下映射的任意组合:
映射名称 | 是否必需? | 描述 |
---|---|---|
bundle | 是 | 包含有关当前资产包的顶级信息,包括 Databricks CLI 版本、现有集群标识符和 git 设置。 |
variables | 否 | 包含在 DAB 部署执行过程中将动态填充的全局变量。 |
workspace | 否 | 用于指定非默认工作区位置,例如根存储、工件存储和文件路径。 |
permissions | 否 | 包含有关要授予已部署资源的权限的信息。 |
resources | 是 | 指定要部署的 Databricks 资源以及如何配置它们。 |
artifacts | 否 | 指定部署工件,如 Python .whl 文件,这些文件将在部署过程中生成。 |
include | 否 | 指定一个相对文件路径模式列表,以包括其他配置文件。这是将 DAB 配置文件分割成多个子配置文件的一个好方法。 |
sync | 否 | 指定在部署过程中要包含或排除的相对文件路径模式列表。 |
targets | 是 | 除了 Databricks 工作区外,指定有关工作流、管道和工件的上下文信息。 |
表 9.1 – databricks.yml 文件中的映射
让我们看一个简单的 DAB 配置文件,以便我们熟悉一些基本概念。以下示例将创建一个名为Hello, World!的新 Databricks 工作流,该工作流将运行一个打印简单而流行表达式 Hello, World! 的笔记本:
bundle:
name: hello_dab_world
resources:
jobs:
hello_dab_world_job:
name: hello_dab_world_job
tasks:
- task_key: notebook_task
existing_cluster_id: <cluster_id>
notebook_task:
notebook_path: ./src/hello_dab_world.py
targets:
dev:
default: true
workspace:
host: https://<workspace_name>.cloud.databricks.com
在这个简单的示例中,我们的 DAB 配置文件由三个主要部分组成:
-
bundle : 该部分包含有关当前 DAB 的高级信息——在此案例中是其名称。
-
resources : 这定义了一个新的 Databricks 工作流,包含一个单一的笔记本任务,应该在现有集群上运行。
-
targets : 这指定了有关目标 Databricks 工作区的信息,工作流和笔记本应该部署到该工作区。
现在我们已经对 DAB 配置文件的基础知识有了充分了解,接下来让我们看看如何在不同的部署场景下部署 Databricks 资源。
指定部署模式
DAB 配置文件中有一个可用的属性是部署模式,它允许我们在部署资源时指定操作模式。部署模式有两种类型:开发模式和生产模式。
在 开发 模式下,所有资源都以特殊前缀 [dev <用户名>] 标记,表示这些资源处于开发阶段。此外,所有可用资源在部署时都会带有 dev 元数据标签,进一步表明这些资源处于开发阶段。正如你可能从 第二章 回忆起的那样,DLT 也有一个开发模式可用。当 DLT 管道在开发模式下使用 DAB 部署时,所有已部署的 DLT 管道将在目标工作区启用开发模式的情况下进行部署。
在开发生命周期中,工程师通常需要尝试更改并快速迭代设计变更。因此,开发模式也会暂停所有 Databricks 工作流计划,并允许相同工作流的并行运行,使工程师能够直接从 Databricks CLI 以临时方式运行工作流。同样,开发模式也允许您指定一个现有的通用集群用于部署过程,您可以通过 -- compute-id <cluster_id> 参数从 Databricks CLI 指定集群 ID,或者将集群 ID 添加到 YAML 配置文件的顶级 bundle 映射中。
让我们来看看如何指定一个目标工作区,以便将其用作开发环境,并使用默认的现有通用集群覆盖所有集群:
targets:
dev:
default: true
mode: development
compute_id: <cluster_id>
workspace:
host: https://<workspace_name>.cloud.databricks.com
相反,您还可以指定生产模式。在 生产 模式下,资源不会添加特殊的命名前缀,也不会应用标签。然而,生产模式会在将资源部署到目标工作区之前验证这些资源。例如,它会确保所有 DLT 管道已设置为生产模式,并且指定云存储位置或工作区路径的资源不会指向用户特定的位置。
在下一节中,我们将卷起袖子,深入使用 Databricks CLI 来实验资源包,并查看它们的实际应用。
Databricks 资源包在实际应用中的效果
DAB 完全依赖于 Databricks CLI 工具(请参阅第八章获取安装说明)来从模板创建新的资源包,将资源包部署到目标工作区,甚至从工作区中删除先前部署的资源包。对于本节内容,您需要使用版本 0.218.0 或更高版本的 Databricks CLI。您可以通过传递 -- version 参数快速检查本地 Databricks CLI 的版本:
databricks –-version
您应该会得到类似于以下 图 9 .2 的输出:
图 9.2 - 检查已安装的 Databricks CLI 版本
一旦您成功安装了推荐版本的 Databricks CLI,您可以通过显示 bundle 命令的手册页来测试安装是否成功。输入以下命令,以从 CLI 显示可用的参数和说明:
$ databricks bundle --help
我们将看到如下的手册页:
图 9.3 – Databricks CLI 中 bundle 命令的手册页
在开始编写 DABs 并将资源部署到 Databricks 工作区之前,我们需要先进行 Databricks 工作区的认证,以便我们可以部署资源。DABs 使用 OAuth 令牌与 Databricks 工作区进行认证。DABs 可以使用两种类型的 OAuth 认证——用户与机器(U2M)认证和机器与机器(M2M)认证。
用户与机器认证
U2M 认证涉及一个人在循环中生成一个 OAuth 令牌,该令牌可以在将新资源部署到目标工作区时使用。这种认证类型涉及一个用户,当 CLI 工具提示时,用户将通过 web 浏览器登录。此认证类型适用于开发场景,用户希望在非关键的开发工作区中尝试 DAB 并部署资源。
U2M 是与 Databricks 工作区认证的最简单方法,可以直接通过 Databricks CLI 完成:
$ databricks auth login --host <workspace-url>
工作区的信息,如工作区的 URL、昵称和认证详情,都会保存在本地机器的用户目录下的隐藏文件中。例如,在 Mac 和 Linux 系统中,这些信息将被写入用户主目录下的本地 ~/.databrickscfg 文件:
图 9.4 – 多个 Databricks 配置文件保存到本地配置文件的示例
你可以通过使用 CLI 命令传递 --profile <profile_nickname> 参数来快速切换不同的 Databricks 工作区。例如,以下命令将把 DAB 应用到 TEST_ENV 配置文件下保存的工作区:
$ databricks bundle deploy –-profile TEST_ENV
U2M 认证严格设计用于开发目的。对于生产环境,不推荐使用这种认证类型,因为它无法自动化,且无法限制访问到最低必要权限。在这种情况下,推荐使用 M2M 认证。
让我们看看这种替代认证类型,特别是当你在生产环境中自动化 DAB 部署时。
机器与机器认证
M2M 认证本身并不涉及人类。这种认证类型是为完全自动化的 CI/CD 工作流设计的。此外,这种认证类型与版本控制系统如 GitHub、Bitbucket 和 Azure DevOps 配合良好。
M2M 需要使用服务主体来抽象化 OAuth 令牌的生成。此外,服务主体使自动化工具和脚本仅通过 API 访问 Databricks 资源,相较于使用用户或组,提供了更高的安全性。因此,服务主体是生产环境的理想选择。
M2M 需要 Databricks 账户管理员创建一个服务主体,并从 Databricks 账户控制台生成 OAuth 令牌。一旦 OAuth 令牌在服务主体的身份下生成,该令牌就可以用来填充环境变量,如DATABRICKS_HOST、DATABRICKS_CLIENT_ID 和 DATABRICKS_CLIENT_SECRET,这些环境变量用于 GitHub Actions 或 Azure DevOps 等自动化构建和部署工具。
使用模板初始化资产包
DAB 还提供项目模板,允许开发人员使用预定义设置快速创建新包。DAB 模板包含预定义的工件和常用的 Databricks 项目设置。例如,以下命令将初始化一个本地 DAB 项目:
$ databricks bundle init
从 CLI 进行操作时,用户将被提示选择一个 DAB 模板:
图 9.5 – 使用 Databricks CLI 从模板初始化一个新的 DAB 项目
在撰写本文时,DAB 提供四个模板供选择:default-python,default-sql,dbt-sql 和 mlops-stacks(docs.databricks.com/en/dev-tools/bundles/templates.html
)。不过,你也可以选择创建组织模板并生成可重用的项目包。
现在我们对 DAB 的基础知识有了清晰的了解,让我们将迄今为止学到的内容整合起来,并将一些资源部署到 Databricks 工作区。
实操练习 – 部署你的第一个 DAB
在这个实操练习中,我们将创建一个基于 Python 的资产包,并在目标工作区中部署一个简单的 Databricks 工作流,该工作流运行一个 DLT 管道。
我们从创建一个本地目录开始,稍后将在该目录中创建我们 DAB 项目的框架。例如,以下命令将在用户的主目录下创建一个新目录:
$ mkdir –p ~/chapter9/dabs/
接下来,导航到新创建的项目目录:
$ cd ~/chapter9/dabs
通过输入以下命令并在重定向到浏览器窗口时完成单点登录(SSO)登录,生成一个新的 OAuth 令牌,使用 U2M 认证:
$ databricks auth login
现在我们已经创建了目录,并且与目标工作区进行了身份验证,接下来让我们使用 Databricks CLI 初始化一个空的 DAB 项目。输入以下命令以弹出选择 DAB 模板的提示:
$ databricks bundle init
接下来,从模板选择提示中选择default-python。为你的项目输入一个有意义的名称,例如my_first_dab。当提示你选择一个笔记本模板时,选择No。当提示你是否包含一个示例 DLT 管道时,选择Yes。最后,当提示你是否添加示例 Python 库时,选择No。项目框架将被创建,此时你可以列出目录内容,以便查看生成的构件:
$ cd ./my_first_dab # a new dir will be created
$ ls -la # list the project artifacts
为了更方便地导航到新创建的项目文件,使用你喜欢的代码编辑器(如 VS Code)打开项目目录:
图 9.6——使用 default-python 模板生成的 DAB 项目框架,从 VS Code 查看
继续探索生成的 DAB 项目的子目录。你应该会注意到几个重要的目录和文件:
-
src:此目录包含作为笔记本文件的 DLT 管道定义。
-
resources:DAB 可以被分解为多个与单个资源或资源子集相关的 YAML 文件。此目录包含 DLT 管道的资源定义以及运行管道的工作流定义,包括调度、笔记本任务定义和作业集群属性。
-
databricks.yml:这是我们 DAB 的主要入口点和定义。它告诉 Databricks CLI 部署哪些资源以及如何部署它们,并指定目标工作空间信息。
-
README.md:这是项目的 README 文件,包含有关项目不同部分的有用信息,以及如何部署或撤销资源的说明。
打开src目录下包含的dlt_pipeline.ipynb笔记本。注意,这个笔记本定义了两个数据集——一个是读取来自 NYC Taxi 数据集的原始、未处理 JSON 文件的视图,另一个是基于fare_amount值小于 30 的行过滤视图的表。
接下来,打开databricks.yml文件。你会注意到该文件有三个主要部分:bundle、include和targets。
为了简化,在targets映射下,删除所有部分,保留dev部分。我们将在此练习中只部署到开发环境。
最后,确保dev目标指向正确的开发工作空间。你的databricks.yml文件应该类似于下面的内容:
bundle:
name: my_first_dab
include:
-resources/*.yml
targets:
mode: development
default: true
workspace:
host: https://<workspace_name>.cloud.databricks.com
保存对databricks.yml文件的更改,并返回到终端窗口。让我们通过从 Databricks CLI 执行validate命令来验证我们对 DAB 项目所做的更改:
$ databricks bundle validate
现在我们的项目已经根据我们的需求进行了修改,是时候将 bundle 部署到我们的开发工作空间了。从你的 Databricks CLI 中执行以下命令:
$ databricks bundle deploy
Databricks CLI 将解析我们的 DAB 定义,并将资源部署到我们的开发目标。登录到开发工作区并验证是否已创建一个名为[dev
恭喜!您刚刚创建了第一个 DAB 并将其部署到开发工作区。您已经在自动化部署数据管道和其他 Databricks 资源的路上迈出了重要一步。
让我们通过执行已部署工作流的新运行来测试部署是否成功。从同一个 Databricks CLI 中,输入以下命令。这将启动新创建的工作流的执行,并触发 DLT 管道的更新:
$ databricks bundle run
您可能会被提示选择要运行的资源。在这里,请选择my_first_dab_job。如果成功,您应该会看到 CLI 的确认消息,说明工作流正在运行。返回到您的 Databricks 工作区,验证确实已开始执行运行。
在某些情况下,您可能需要从目标工作区中撤销部署资源。要撤销先前创建的工作流和 DLT 管道定义,我们可以使用 Databricks CLI 中的destroy命令。输入以下命令以恢复在本次实践中所做的所有更改。您需要确认是否永久删除所有资源:
$ databricks bundle destroy
到目前为止,我们已经在目标 Databricks 工作区中创建了一个简单的工作流和 DLT 管道,并在源笔记本中定义了它们。我们使用本地代码编辑器编写了 DAB 项目,并将更改从本地计算机部署出去。然而,在生产环境中,您将与组织内的团队合作,共同编写数据管道和其他 Databricks 资源,这些资源协同工作,为您的组织生成数据产品。
在下一节中,我们将探讨如何在这个简单的练习基础上进行扩展,并与团队成员一起使用自动化工具部署 Databricks 资源,如工作流、笔记本或 DLT 管道。
实践练习 – 使用 GitHub Actions 简化跨团队协作
通常,您将与一个由数据工程师组成的团队一起工作,部署 Databricks 资产,如 DLT 管道、通用集群或工作流等。在这些情况下,您可能会使用版本控制系统(如 GitHub、Bitbucket 或 Azure DevOps)与团队成员协作。
DAB 可以轻松地集成到您的 CI/CD 管道中。让我们看看如何使用 GitHub Actions 自动部署代码库主分支所做的更改,并将资源更改自动部署到生产 Databricks 工作区。
GitHub Actions 是 GitHub 中的一项功能,允许用户直接从 GitHub 仓库实现 CI/CD 工作流,使得基于某些触发事件(例如将功能分支合并到主分支)声明要执行的工作流变得简单。结合 DABs,我们可以实现一个强大、完全自动化的 CI/CD 管道,将对 Databricks 代码库所做的更改自动部署。这使得我们的团队能够更加灵活,尽快部署可用的更改,从而加速迭代开发周期并快速测试更改。
设置环境
在这个动手实践中,我们将创建一个 GitHub Action,在我们的 GitHub 仓库中分支合并后,自动将更改部署到 Databricks 工作空间。让我们回到本章早些时候的示例。如果你还没有这样做,可以从本章的 GitHub 仓库克隆示例:github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter09
。
首先,让我们在仓库的根目录下创建一个新的私有文件夹——即 .github。在此文件夹中,让我们创建另一个名为 workflows 的子文件夹。这个嵌套目录结构是一个特殊的模式,其存在将被 GitHub 仓库自动识别并解析为 GitHub Actions 工作流。在这个文件夹中,我们将定义 GitHub Actions 工作流,工作流同样使用 YAML 配置文件声明 CI/CD 工作流。在 .github/workflows 文件夹中创建一个名为 dab_deployment_workflow.yml 的新 YAML 文件。
接下来,我们将在我们喜欢的代码编辑器中打开工作流文件,以便更方便地操作。
配置 GitHub Action
我们首先通过在 GitHub Actions 工作流文件中添加基本结构来开始。我们将在 YAML 文件中为 GitHub Actions 工作流指定一个用户友好的名称,例如 DABs in Action。在这个文件中,我们还将指定每当一个批准的拉取请求被合并到我们的代码仓库的主分支时,CI/CD 管道就应当运行。将以下内容复制并粘贴到新创建的文件 dab_deployment_workflow .yml 中:
name: "DABs in Action"
on:
push:
branches:
- main
接下来,让我们在 GitHub Actions 的 YAML 文件中定义一个任务,该任务将克隆 GitHub 仓库、下载 Databricks CLI,并将我们的 DAB 部署到目标 Databricks 工作空间。将以下任务定义添加到工作流文件中:
jobs:
bundle-and-deploy:
name: "DAB Deployment Job"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: databricks/setup-cli@main
- run: databricks bundle deploy --target prod
working-directory: ./dabs
env:
DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_SERVICE_PRINCIPAL_TOKEN }}
你还会注意到,我们在之前的示例中使用了相同的 Databricks CLI bundle 命令来部署我们的 Databricks 资源,使用本地安装来部署资源。此外,在 working-directory 参数下,我们指定了 DAB 配置文件将位于 GitHub 仓库根目录下的 dabs 文件夹中。我们还利用了 GitHub Secrets(docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository
)安全地存储了用于与目标 Databricks 工作空间进行身份验证的 API 令牌,并遵循了使用服务主体的最佳实践(请参阅 用户到机器身份验证 部分)来自动化资源的部署。
你可能还记得,服务主体只能调用部分 API,并遵循最小权限的最佳实践,而用户账户则会提供比必要的更多权限。此外,我们的用户可能会在组织中进出,这使得诸如用户注销等维护任务变得头疼。
测试工作流
现在我们已经定义了何时触发 CI/CD 流水线以及负责将 DAB 部署到目标工作空间的工作流作业,我们可以测试 GitHub Actions 工作流。
让我们在现有的 GitHub Actions 工作流文件中添加一个部分,该部分将触发我们在前一个示例中创建的 my_first_dab_job Databricks 工作流。你还会注意到,在 needs 参数下,我们声明了对 DAB Deployment Job 的依赖关系,必须先完成该作业,才能执行 Databricks 工作流的运行。换句话说,在部署更改之前,我们不能测试这些更改。将以下作业定义添加到工作流文件中的 bundle-and-deploy 作业下:
run-workflow:
name: "Test the deployed pipeline workflow"
runs-on: ubuntu-latest
needs:
- bundle-and-deploy
steps:
- uses: actions/checkout@v3
- uses: databricks/setup-cli@main
- run: databricks bundle run my_first_dab_job
working-directory: ./dabs
env:
DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_SERVICE_PRINCIPAL_TOKEN }}
保存 GitHub Actions 工作流文件。现在,通过在 GitHub 仓库中打开一个新的拉取请求并将其合并到主分支来测试更改。
首先,使用 git 创建一个新的功能分支:
$ git checkout –b increaseAutoScaling
接下来,在代码编辑器中打开 Databricks 工作流的 DAB 配置文件。将我们的作业集群的自动缩放大小从四个工作节点更新为五个。保存文件并提交更改到分支。最后,将更改推送到远程仓库。使用 Web 浏览器导航到 GitHub 仓库并创建一个新的拉取请求。批准更改并将分支合并到主分支。确保触发 GitHub Actions 工作流,并且代码更改已部署到目标 Databricks 工作空间。你还应该看到 my_first_dab_job Databricks 工作流的一个新运行已由 GitHub Actions 工作流执行。
现在我们已经看到了将我们的 DABs 融入 CI/CD 管道中有多么容易,让我们扩展这个例子,看看当我们希望将我们的代码库的不同版本部署到 Databricks 工作空间时,DABs 如何帮助我们。
版本控制和维护
DABs 可以简化迭代地将更改部署到不同的环境。可能会有场景,您希望尝试不同的更改,并记录这些更改来自存储库的特定版本。顶级bundle映射允许用户指定存储库 URL 和分支名称,以注释部署到目标 Databricks 工作空间的代码库的不同版本。这是一个很好的方式来记录 bundle 部署来自特定存储库和功能分支的情况。例如,以下代码注释说明了一个资产 bundle 使用了实验性功能分支作为项目来源:
bundle:
name: new-feature-dab
git:
origin_url: https://github.com/<username>/<repo_name>
branch: my_experimental_feature_br
以另一个例子来说,DABs 使得自动化和文档化常规维护活动变得简单,例如将 Databricks 运行时升级到最新版本。这是一个很好的方式来试验运行时的 beta 版本,并测试与现有 Databricks 工作流的兼容性。例如,如果工作流开始失败,DABs 可用于自动化手动部署和测试过程,甚至回滚更改。
概述
在本章中,我们介绍了如何使用 DABs 自动化部署您的 Databricks 资源。我们看到了 Databricks CLI 在从预配置模板创建新 bundle、将 CLI 工具与目标 Databricks 工作空间进行身份验证、触发 Databricks 工作流运行以及管理端到端 bundle 生命周期中的重要性。我们还看到了通过在 DAB 配置文件中使用开发模式来快速迭代设计和测试的方法。
在下一章中,我们将总结在生产环境中监控数据应用所需的技能。我们将涉及 Databricks 数据智能平台的关键特性,包括警报、查看管道事件日志以及使用 Lakehouse Monitoring 测量统计指标。
第十章:生产环境中的数据管道监控
在前几章中,我们学习了如何使用 Databricks 数据智能平台构建、配置和部署数据管道。为了完善湖仓的数据管道管理,本书的最后一章将深入探讨生产环境中数据管道监控的关键任务。我们将学习如何直接利用 Databricks 数据智能平台的全面监控技术,追踪管道健康状况、管道性能和数据质量等。我们还将通过实际操作练习实现一些现实世界的示例。最后,我们将讨论确保数据管道平稳运行的最佳实践,及时检测和解决问题,并确保为您的分析和业务需求提供可靠、准确的数据。
在本章中,我们将涵盖以下主要内容:
-
数据管道监控简介
-
管道健康和性能监控
-
数据质量监控
-
生产环境故障解决的最佳实践
-
实操练习 – 设置一个 Webhook 警报,当作业运行时间超过预期时触发
技术要求
为了跟随本章中的示例,您需要拥有 Databricks 工作空间权限,以创建和启动通用集群,便于导入和执行本章附带的笔记本。还建议将您的 Databricks 用户提升为工作空间管理员,这样您才能创建和编辑警报目标。所有代码示例可从本章的 GitHub 仓库下载:github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter10
。本章将创建并运行多个新笔记本,预计消耗约 10-15 Databricks Units (DBUs)。
数据管道监控简介
随着数据团队将数据管道部署到生产环境中,能够在发生处理错误、延迟或数据质量问题时立即检测到,这对于在问题扩展到下游系统和流程之前进行捕捉和纠正,能产生巨大的影响。因此,数据团队构建和部署管道的环境应该能够监控它们,并在出现问题时发出警报。
探索监控数据管道的方法
数据团队可以通过 Databricks 数据智能平台在生产环境中监控数据管道。比如,数据团队可以通过以下方式手动观察数据管道的更新:
-
从 Delta Live Tables (DLT) UI 查看管道状态
-
从 DLT 事件日志中查询管道信息
尽管这些手动方式提供了一种快速查看数据管道最新状态的方式,但它们显然不是一个可扩展的解决方案,特别是当你的数据团队增加越来越多的管道时。相反,组织会转向更自动化的机制。例如,许多组织选择利用 Databricks 数据智能平台内置的通知系统。通知在平台内的许多对象中都很常见。例如,数据管理员可以在以下场景中配置通知,提醒数据团队有关特定 Databricks 资源状态的变化:
-
DLT 管道(无论是更新还是流)
-
Databricks 工作流(在最上层的作业级别)
-
Databricks 工作流任务(比前述选项更细粒度的通知)
尽管这些通知有助于在数据处理过程中提醒团队有关事件或状态变化,数据团队还需要机制来提醒彼此有关进入企业湖仓的数据内容中的问题。
使用 DBSQL 警报通知数据有效性
Databricks 数据智能平台可以通过平台内的 DBSQL 部分创建警报通知,称为DBSQL 警报。DBSQL 警报是一个有用的工具,可以提醒数据团队关于数据进入他们的企业湖仓的信息。DBSQL 警报通过指定一个特定的查询结果条件来操作,只有当条件被满足时,数据才会被认为是有效的。然而,如果警报中的某个条件被违反,比如订单金额超过某个美元阈值,那么系统就会触发通知并发送到警报目标。下图描述了一个 DBSQL 警报,当销售订单超过特定金额时通过电子邮件通知收件人——在这个例子中,金额阈值是 10,000 美元。在此示例中,查询是一个最大值聚合,触发条件是当最大值聚合超过 10,000 美元时,警报目标是一个电子邮件地址。
图 10.1 – 配置 DBSQL 警报通过电子邮件通知收件人
此外,DBSQL 警报可以设置为按重复计划执行,例如每小时执行一次。这是一种极好的方式,通过 Databricks 数据智能平台内置的机制,自动化进行数据验证检查,确保数据集内容的准确性。下图是一个示例,展示了如何使用警报在重复的时间间隔内调度数据验证查询,并在特定条件或条件集被违反时通知数据团队。
图 10.2 – 警报触发条件的配置
监控生产环境中的数据管道的另一种机制是通过工作流通知。在 Databricks 数据智能平台中,通知消息可以发送到企业消息平台,如 Slack 或 Microsoft Teams,或发送到事件管理系统,如 PagerDuty。本章稍后我们将探讨如何实现基于 HTTP Webhook 的交付目标,这在 Web 服务架构环境中非常流行。
可以从特定工作流中发送两种类型的通知——作业状态和任务状态。作业状态通知是关于特定工作流整体成功或失败的高级状态。然而,您还可以配置通知在任务级别发送到监控目标,例如,如果您希望监控工作流中的任务何时被重试。
图 10.3 – 配置作业级别和任务级别的通知
虽然警报通知是自动通知团队成员处理问题的好方法,但数据团队还需要定期和临时地监控数据管道的健康状况。我们将在下一节讨论这一点。
管道健康状况和性能监控
Databricks 数据智能平台提供了一个供数据团队查询数据管道状态的地方,称为事件日志。事件日志包含与特定 DLT 管道相关的所有事件的历史记录。特别地,事件日志将包含一个事件流,其中列出包含以下录制元数据的事件对象:
-
发生的事件类型
-
事件的唯一标识符
-
事件发生的时间戳
-
事件的高级描述
-
关于事件的细粒度详情
-
事件级别指示(INFO、WARN、ERROR 或 METRICS)
-
事件的来源
与标量函数不同,标量函数返回单一值,表值函数(TVF)则是返回一个表作为结果的函数。对于发布到 Unity Catalog 中目录和模式的 DLT 管道,Databricks 数据智能平台提供了一种特殊的 TVF,名为 event_log(),用于查询有关特定 DLT 管道的全面信息。event_log() 函数可以接受两个参数之一作为输入:管道数据集的完全限定表名或管道 ID 作为参数。
图 10.4 – event_log() TVF 返回发生的事件列表
event_log() 函数将检索有关给定 DLT 管道的信息,包括以下内容:
-
数据质量检查(预期结果)的结果
-
审计信息
-
管道更新状态
-
数据血统信息
一种常见的方法是,数据管理员可以通过注册一个视图与特定管道的数据集一起,使查询特定 DLT 管道事件变得更加容易。这允许用户在后续查询中方便地引用事件日志结果。以下 SQL 数据定义语言 (DDL) 语句将创建一个视图,用于检索 ID 为 my_dlt_pipeline_id 的 DLT 管道的事件日志:
CREATE VIEW my_pipeline_event_log_vw AS
SELECT
*
FROM
event_log('<my_dlt_pipeline_id>');
有时,特定管道的事件日志可能会增长得太大,使得数据管理员很难快速总结最新的状态更新。相反,数据团队可以进一步缩小事件日志的范围,聚焦于 DLT 管道中的特定数据集。例如,数据团队可以在某个特定数据集上创建视图,通过 table() 函数捕获所有事件,并提供完全限定的表名作为函数的参数。以下 SQL DDL 语句将创建一个视图,用于检索名为 my_gold_table 的数据集的事件日志:
CREATE VIEW my_gold_table_event_log_vw AS
SELECT
*
FROM
event_log(table(my_catalog.my_schema.my_gold_table));
event_log() TVF 函数为数据团队提供了对特定 DLT 管道和数据集上执行的操作的良好可见性,使得实现端到端的可观察性和可审计性变得更加容易。
重要提示
当前,如果一个 DLT 管道被配置为将输出数据集发布到 Unity Catalog,那么只有特定 DLT 管道的所有者可以查询这些视图。为了共享事件日志的访问权限,管道所有者必须将事件日志馈送的副本保存到 Unity Catalog 中的另一个表中,并授予其他用户或组访问权限。
让我们来看一下如何利用 event_log() 函数查询特定 DLT 管道的数据质量事件。
实践练习 – 查询数据集的数据质量事件
重要提示
对于以下练习,您需要使用共享的通用集群或 Databricks SQL 仓库来查询事件日志。此外,事件日志仅适用于查询已配置为在 Unity Catalog 中存储数据集的 DLT 管道。对于已配置为在传统 Hive Metastore 中存储数据集的 DLT 管道,将找不到事件日志。
数据质量指标作为序列化的 JSON 字符串存储在事件日志中。我们需要将 JSON 字符串解析成不同的数据结构,以便能够方便地查询事件日志中的数据质量事件。我们将使用 from_json() SQL 函数解析序列化的 JSON 字符串,以满足我们的数据质量预期。我们需要指定一个 schema 作为参数,指示 Spark 如何将 JSON 字符串解析为反序列化的数据结构——具体来说,是一个包含期望名称、数据集名称、通过记录数和失败记录数的结构体数组。最后,我们将使用 explode() SQL 函数,将期望结构体数组转换为每个期望的新行。
我们可以利用之前定义的视图来监控我们 DLT 管道中数据集的持续数据质量。让我们创建另一个与 DLT 管道数据质量相关的视图:
CREATE OR REPLACE TEMPORARY VIEW taxi_trip_pipeline_data_quality_vw AS
SELECT
timestamp,
event_type,
message,
data_quality.dataset,
data_quality.name AS expectation_name,
data_quality.passed_records AS num_passed_records,
data_quality.failed_records AS num_failed_records
FROM
(
SELECT
event_type,
message,
timestamp,
explode(
from_json(
details :flow_progress.data_quality.expectations,
"ARRAY<
STRUCT<
name: STRING,
dataset: STRING,
passed_records: INT,
failed_records: INT
>
>"
)
) AS data_quality
FROM
my_table_event_log_vw
);
数据团队常常提出一些常见问题,比如:“处理了多少条记录?”,“有多少条记录未通过数据质量验证?”或者“通过记录与未通过记录的比例是多少?”。让我们进一步分析前面的例子,并总结一下我们管道中每个数据集的高层数据质量指标。我们来统计一下应用了预期值的行数,以及每个数据集中通过记录与未通过记录的百分比:
SELECT
timestamp,
dataset,
sum(num_passed_records + num_failed_records)
AS total_expectations_evaluated,
avg(
num_passed_records /
(num_passed_records + num_failed_records)
) * 100 AS avg_pass_rate,
avg(
num_failed_records /
(num_passed_records + num_failed_records)
) * 100 AS avg_fail_rate
FROM
taxi_trip_pipeline_data_quality_vw
GROUP BY
timestamp,
dataset;
我们得到以下输出:
图 10.5 – DLT 事件日志中捕获的事件
如你所见,event_log() 函数使数据团队能够轻松查询有关给定 DLT 管道的全面信息。数据团队不仅可以查询管道更新的状态,还可以查询质量数据是否已成功地导入到湖仓。然而,数据团队仍然需要一种方式,在运行时自动通知数据质量检查失败的情况,尤其是在下游报告的数据准确性对业务至关重要时。我们将在接下来的部分中更详细地探讨这个问题。
数据质量监控
持续监控你湖仓中数据集的数据质量,对于成功部署到生产环境的业务关键型数据应用至关重要。例如,假设某个关联列突然注入了空值,这可能会影响到依赖于上游数据集连接的下游报告。突然间,商业智能(BI)报告可能会刷新,但数据可能显得过时或不准确。通过在问题出现时自动检测数据质量问题,数据团队可以及时收到潜在问题的警报,并立即采取措施干预,修正可能的数据损坏,甚至数据丢失。
图 10.6 – 及早检测问题对确保下游流程的质量至关重要
引入湖仓监控
Lakehouse Monitoring,作为 Databricks 数据智能平台的一个新功能,赋予数据团队跟踪和监控湖仓中数据及其他资产数据质量的能力。数据团队可以自动测量列之间的数据统计分布、空值数量、最小值、最大值、中位数值以及其他统计属性。通过 Lakehouse Monitoring,数据团队能够自动检测数据集中的重大问题,如数据偏斜或缺失值,并提醒团队成员关注问题,以便他们采取适当措施。
湖仓监控在监控 Delta 表、视图、物化视图和流表的数据质量时最为有效。它甚至可以在机器学习(ML)管道中使用,测量数据集的统计摘要,并在检测到数据漂移时触发警报通知。此外,湖仓监控可以根据监控度量的粒度需求进行精细或粗粒度定制。
湖仓监控从创建一个监控对象开始,监控对象随后将附加到湖仓中的一个数据资产(例如 Delta 表)。在后台,监控对象将创建两个额外的表,用于捕捉对应 Delta 表或其他数据资产的统计度量。
然后,监控表将被用于驱动仪表板,数据团队和其他相关方可以使用该仪表板查看湖仓中数据质量的实时数据洞察。
图 10.7 – 一个湖仓监控器将为监控的数据资产创建两个度量表。
湖仓监控器可以配置为衡量数据资产的不同方面,这也被称为配置文件类型。可以创建三种监控配置文件类型:
-
快照:这是一个通用但强大的监控工具。它用于监控表的数据质量和其他度量指标。
-
时间序列:它适用于时间序列数据集。它用于监控数据在时间段窗口内的质量变化。
-
推断:将机器学习模型推断的质量与输入在一段时间内的变化进行比较是非常有用的。
本章仅覆盖时间序列和快照类型。推断的讨论超出了本书的范围,但我们鼓励你探索湖仓监控如何对机器学习用例有所帮助(docs.databricks.com/en/lakehouse-monitoring/fairness-bias.html
)。
也可以创建比较表的统计指标与基准表的监控器。例如,可以用于比较智能恒温器设备本周的相对湿度与上周的湿度,或者将某个数据集的录入销售数量与上月的销售报告进行比较。
让我们来看一个实际的例子,看看如何在湖仓中使用湖仓监控器。
实操练习 – 创建一个湖仓监控器
在这个动手练习中,我们将创建一个 lakehouse 监控工具,用于衡量目标 Delta 表的数据质量。虽然我们的 Delta 表确实包含时间戳信息,但我们将选择一个 快照配置文件 来监控我们 lakehouse 中目标 Delta 表的数据质量。回想一下,快照配置文件是一个通用的 lakehouse 监控工具,正如前面所提到的,它还具有相当大的灵活性。快照分析器将允许我们衡量数据集的标准总结性指标,或者围绕数据质量插入自定义的业务计算。
就像 Databricks 数据智能平台中的许多资源一样,您可以通过多种方式创建新的 lakehouse 监控工具。例如,您可以使用 Databricks UI、Databricks REST API、Databricks CLI(详见 第九章),或者像 Terraform 这样的自动化工具,等等。也许最简单的创建新监控工具的方式是通过 UI。在这个动手练习中,我们将使用 Databricks UI 来创建 lakehouse 监控工具。这是开始实验 Lakehouse 监控和不同数据质量度量的绝佳方法,用于评估数据集。然而,建议在生产环境中将您的 lakehouse 监控工具迁移到自动化构建工具,如 Databricks 资产包(DABs)(详见 第九章)或 Terraform(详见 第八章)。
如果您还没有这样做,可以在 github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter10
下载并克隆本章的相关代码资源。
第一步是生成一个目标 Delta 表,我们希望监控其数据质量。克隆或导入数据生成器笔记本,或创建一个新的笔记本并使用以下代码生成器源代码。
在笔记本的第一个单元格中,我们将利用 %pip 魔法命令来下载并安装 dbldatagen Python 库,用于生成样本数据:
%pip install dbldatagen==0.4.0
接下来,我们将定义一个辅助函数,用于生成一个包含智能恒温器读数的合成数据集,记录随时间变化的情况:
import dbldatagen as dg
from pyspark.sql.types import IntegerType, FloatType, TimestampType
def generate_smart_thermostat_readings():
"""Generates synthetics thermostat readings"""
ds = (
dg.DataGenerator(
spark,
name="smart_thermostat_dataset",
rows=10000,
partitions=4)
.withColumn("device_id", IntegerType(),
minValue=1000000, maxValue=2000000)
.withColumn("temperature", FloatType(),
minValue=10.0, maxValue=1000.0)
.withColumn("humidity", FloatType(),
minValue=0.1, maxValue=1000.0)
.withColumn("battery_level", FloatType(),
minValue=-50.0, maxValue=150.0)
.withColumn("reading_ts", TimestampType(), random=False)
)
return ds.build()
# Generate the data using dbldatagen
df = generate_smart_thermostat_readings()
df.display()
最后,我们将把新创建的数据集保存为 Unity Catalog 中的 Delta 表:
(df.write
.format("delta")
.mode("overwrite")
.saveAsTable(FULLY_QUALIFIED_TABLE_NAME))
既然我们的 Delta 表已经在 lakehouse 中创建完成,让我们在 Catalog Explorer 中使用 UI 来创建一个新的监控工具。
在左侧导航栏中,点击“目录浏览器”图标。接下来,通过展开目录列表或使用搜索字段过滤结果,导航到为本章创建的目录。点击为本章创建的架构。最后,点击之前由我们的数据生成笔记本创建的 Delta 表。点击标题为质量的数据质量标签。
图 10.8 – 可以直接从 Databricks 用户界面创建新的监控器
接下来,点击开始使用按钮,开始创建新的监控器。一个弹出对话框将打开,提示您选择监控器的配置文件类型,以及一些高级配置选项,如调度、通知传递和存储生成的仪表板的工作区目录。
点击下拉菜单选择配置文件类型,并选择生成快照配置文件的选项。
接下来,点击高级选项部分以展开对话框表单。用户界面将允许用户捕获数据集指标,可以选择手动执行或定义一个 cron 调度来定期执行指标计算。您会注意到,该对话框提供了使用传统 cron 语法定义调度的灵活性,或者通过选择对话框表单中的日期和时间下拉菜单来定义调度。对于本次实践,我们将选择前一种方式,并通过点击按钮手动刷新监控指标。
可选地,您可以选择将关于监控指标计算成功或失败的通知通过电子邮件发送给一组电子邮件接收者。您最多可以添加五个电子邮件地址,以便通知能够送达。确保您的用户电子邮件地址列在通知部分,并且勾选复选框以接收关于指标收集失败的通知。
如果你还记得之前提到的,湖仓监控器将创建两个指标表。我们需要在 Unity Catalog 中提供存储这些指标表的位置。在指标部分,添加为本章实践创建的目录和架构名称。例如,输入chp10.monitor_demo。
我们需要指定的最后一项是存储生成的湖仓监控仪表板的工作区位置。默认情况下,生成的资源将存储在用户的主目录下,例如/Users/<user_email_address>/databricks_lakehouse_monitoring。在本次实践中,我们将接受默认位置。
我们准备好创建监控器了!点击创建按钮,以为我们的 Delta 表创建湖仓监控器。
由于我们尚未为湖仓监控配置调度,因此我们需要手动执行度量收集。在 Catalog Explorer 中,在我们 Delta 表的质量标签下,点击刷新度量按钮以手动触发度量收集。
图 10.9 – 监控度量可以通过 Catalog Explorer UI 手动触发
表格度量的更新将会被触发并执行,可能需要几分钟才能完成。更新完成后,点击查看仪表板按钮查看捕获的度量数据。恭喜你!你已经创建了第一个湖仓监控,并且你正在稳步推进,为数据团队实现强大且自动化的数据质量可观察性。
现在我们知道了如何在生产问题出现时提醒我们的团队成员,让我们关注一些解决生产部署中失败问题的方法。
生产故障解决的最佳实践
DLT 框架是针对故障解决设计的。例如,DLT 会自动响应三种常见的管道故障类型:
-
Databricks Runtime 回归(详见 第二章)
-
更新处理失败
-
数据事务失败
让我们更详细地看一下更新失败和数据事务失败。
处理管道更新失败
DLT 框架是针对强大的错误处理设计的。在管道更新期间,框架会尝试将最新的更新应用到数据流图中定义的表。如果发生处理错误,框架会将错误分类为可重试错误或不可重试错误。可重试错误意味着框架将运行时错误分类为可能由当前条件集引起的问题。例如,系统错误不会被认为是可重试错误,因为它与运行时环境有关,而执行重试无法解决。然而,网络超时会被视为可重试错误,因为它可能受到临时网络环境条件的影响。默认情况下,如果检测到可重试错误,DLT 框架会对管道更新进行两次重试。
从表事务失败中恢复
由于 Delta Lake 事务日志的特性,数据集的更改是原子性的,这意味着它们只能在表事务(如数据操作语言(DML)语句)提交到事务日志时发生。因此,如果一个事务在执行过程中失败,则整个事务会被放弃,从而防止数据集进入需要数据团队介入并手动撤销数据更改的非确定性状态。
现在我们已经理解了如何处理生产环境中的管道故障,让我们通过一个真实世界的示例来巩固本章的主题。
实践练习 – 设置 Webhook 警报,当作业运行时间超过预期时进行通知
在这个实践练习中,我们将创建一个自定义 HTTP Webhook,当 Databricks 中的定时作业超时时,会向 HTTP 端点发送通知。
Webhook 警报是 Databricks 数据智能平台中的一种通知机制,它使数据团队能够通过自动发布特定作业执行结果,来监控其数据管道。例如,您可以收到关于作业成功运行、执行状态和运行失败的通知。
为什么我们使用工作流而不是直接使用 DLT 管道?
实际上,DLT 管道通常只是一个完整数据产品中的许多依赖项之一。Databricks 工作流是一个流行的编排工具,能够准备依赖项、运行一个或多个 DLT 管道,并执行下游任务。在本次练习中,我们将配置来自 Databricks 工作流的通知,而不是直接从 DLT 管道中获取通知,以模拟典型的生产场景。
让我们首先进入您的 Databricks 工作区并登录。接下来,创建一个新的工作流。我们可以通过点击工作区导航栏左侧的工作流图标,进入工作流 UI。为工作流命名时,可以选择一个有意义的名称,例如生产 监控演示。
如果您还没有下载,可以在github.com/PacktPublishing/Building-Modern-Data-Applications-Using-Databricks-Lakehouse/tree/main/chapter10
下载本章练习的示例笔记本。我们将使用名为04a-IoT 设备数据生成器.py的物联网设备数据生成器笔记本和名为04b-IoT 设备数据管道.py的物联网设备 DLT 管道定义笔记本。
在工作流 UI 中,创建一个包含两个任务的新工作流。第一个任务将使用04a-IoT 设备数据生成器.py笔记本准备输入数据集;第二个任务将使用04b-IoT 设备数据管道.py笔记本执行读取生成数据的 DLT 管道。
图 10.10 – 工作流将生成物联网设备数据并执行 DLT 管道更新
现在我们的工作流已经创建完成,假设我们的管道执行时间比预期的要长。若出现潜在的处理延迟,是否能及时收到通知,以便您的数据团队立即进行调查,或者防止由于处理错误导致长时间运行的任务产生巨额云计算费用,这不就非常有帮助吗?
幸运的是,Databricks 数据智能平台使得配置这种类型的通知变得简单。让我们为工作流创建一个超时阈值。这将自动通知我们的 HTTP webhook 端点,告知我们的工作流执行时间超过了预期。一旦工作流超过了这个超时阈值,当前的执行将被停止,并且该执行被标记为失败。我们希望在这种失败场景发生时收到通知。
在工作流 UI 中,点击新创建的工作流生产监控演示,以查看详细信息。在作业通知部分,点击添加度量阈值按钮,添加一个新的运行时长阈值。让我们将最大时长设置为 120 分钟,然后点击保存按钮。接下来,点击+ 添加通知按钮,添加一个新的通知。展开目标下拉菜单,选择+ 添加新系统目标。一个新的浏览器标签页将打开,显示 Databricks 工作区的工作区管理设置。在通知部分,点击管理按钮。点击添加目标按钮。选择Webhook作为目标类型,为目标提供一个有意义的名称,输入通知应发送的端点 URL,并在端点使用基本 HTTP 认证时输入用户名和密码信息。点击创建按钮以创建 Webhook 目标。
图 10.11 – 创建一个新的 Webhook 目标
最后,点击保存按钮以最终确定度量阈值通知。
图 10.12 – 可以为工作流的任务设置执行时长阈值
现在我们已经设定了运行时长阈值,每当我们的工作流运行超过 120 分钟时,工作流将会停止,并且会向我们的 HTTP webhook 目标发送一个状态为超时的通知消息。
恭喜!现在你已经自动化了生产环境中数据管道的监控,当出现故障条件时,团队将会自动收到通知。这意味着你的团队可以在问题发生时立即介入并修正数据处理问题,从而最小化潜在的停机时间。
总结
在本章中,我们介绍了几种实现管道和数据质量可观测性的技术,以便数据团队能够在问题出现时迅速做出反应,避免下游大规模的中断。成为一个成功的数据团队的关键之一就是能够快速应对问题。我们看到了 Databricks 数据智能平台在许多方面内置了警报通知,并且我们可以配置不同类型的警报目标,以在条件不满足时发送通知。
我们介绍了 Databricks 平台内置的监控功能,例如管道事件日志,它使得管道拥有者可以轻松查询数据管道的健康状况、可审计性、性能,以及实时的数据质量。我们还看到,湖仓监控是一个强大且多功能的功能,它允许数据团队自动监控数据集的统计指标,并在阈值被突破时通知团队成员。我们还介绍了评估数据质量的技术,以确保整个管道中没有下游错误和不准确的情况。
最后,我们通过一个实际案例来结束这一章,演示了如何在遇到一个现实且非常常见的问题时,自动提醒数据团队——当一个计划任务运行超时。
恭喜你完成了本书的阅读!感谢你与我一起通过每一章进行这段旅程。我们已经涵盖了许多话题,但到目前为止,你应该为自己的成就感到骄傲。到现在为止,你应该已经建立了一个扎实的湖仓基础,接下来你可以继续在其上构建。实际上,我希望这本书能给你带来灵感,继续你的湖仓之旅,并构建出具有重大意义的现代数据应用。祝你好运,鼓励你保持学习!