在本章中,我们将探讨数据行业在过去几十年中的演变。我们还将讨论为什么实时数据处理与企业如何对最新数据信号作出反应有着密切的关系。我们将解释为什么从零开始构建自己的流处理解决方案可能无法持续,并且为什么这种维护方式难以随着时间的推移扩展。到本章结束时,你应该完全理解 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/PacktPublis…。本章将创建并运行一个新的DLT 管道,使用的是 Core 产品版本。因此,该管道预计将消耗约 5-10 个 Databricks 单位(DBUs)。
湖仓的出现
在1980年代初,数据仓库是处理结构化数据的优秀工具。结合合适的索引方法,数据仓库使我们能够以惊人的速度提供业务智能(BI)报告。然而,进入21世纪后,数据仓库无法跟上新型数据格式(如 JSON)以及新的数据类型(如音频和视频)的发展。简而言之,数据仓库在处理大多数企业使用的半结构化和非结构化数据时遇到了困难。此外,数据仓库也难以扩展到数百万或数十亿行数据,这在2000年代初的信息时代已经变得非常普遍。转瞬之间,批处理数据处理作业很快与预定在早晨业务时段刷新报告的BI报告发生了冲突。
与此同时,云计算成为了许多组织的流行选择,因为它为企业提供了弹性计算能力,可以根据当前的计算需求快速扩展或缩减,而无需处理采购和安装额外硬件的前期成本。
现代的提取、转换和加载(ETL)处理引擎,如 Apache Hadoop 和 Apache Spark™,解决了处理大数据ETL管道的性能问题,开启了数据湖的概念。相反,数据湖在提供BI报告方面表现糟糕,且通常会在多个并发用户会话中提供性能下降的体验。此外,数据湖的管理也很差,容易出现混乱的数据处理模式,导致许多昂贵的数据集副本,这些副本常常与真实数据源发生偏差。因此,这些数据湖很快被戏称为“数据沼泽”。大数据行业需要改变,湖仓模式正是这种改变,旨在结合两者的优点——快速的BI报告和结构化、半结构化和非结构化数据的快速ETL处理,并且这一切都发生在云中。
Lambda架构模式
在2010年代初,数据流处理在数据行业中站稳了脚跟,许多企业需要一种方法来同时支持批量ETL处理和追加数据流。此外,具有多个并发ETL处理流程的数据架构需要同时读取和更改底层数据。组织在许多早期的数据架构中经常遇到相互冲突的写入失败,这些失败导致数据损坏甚至丢失。因此,在许多早期的数据架构中,构建了一个双重Lambda架构,用于为这些过程提供隔离层。
使用 Lambda 架构,下游过程如 BI 报告或机器学习(ML)模型训练可以在数据快照上执行计算,而流处理过程可以在隔离的环境中应用接近实时的数据变化。然而,这些 Lambda 架构为了支持并发的批处理和流处理工作负载,重复存储了数据,导致了不一致的数据变化,这些变化需要在每个工作日结束时进行对账。
引入勋章架构
为了清理数据湖并防止不良数据处理实践,数据湖架构师需要一种能够满足现代ETL处理高需求的数据处理模式。此外,组织需要一种简化的架构,能够处理批量和流处理工作负载,支持数据回滚、良好的数据审计和强大的数据隔离,并且能够扩展到每天处理数TB甚至PB的数据。
因此,在湖仓架构中出现了一种设计模式,通常称为勋章架构(medallion architecture)。这种数据处理模式通过在连续的数据跳跃(也称为数据层)中应用业务级别的转换,物理上隔离了数据处理,并通过提高数据质量来改进数据处理。
在湖仓中组织数据的典型设计模式(如图 1.2 所示)包括三个不同的数据层——青铜层、白银层和黄金层:
- 青铜层 作为原始、未处理数据的着陆区。
- 白银层 存储经过筛选、清理和增强的数据,这些数据具有定义的结构和强制执行的架构。
- 最后,黄金层 将提供精炼的、业务级别的聚合数据,准备供下游的 BI 和 ML 系统使用。
此外,这种简化的数据架构通过将数据集存储为支持并发批处理和流处理数据操作的大数据格式,实现了批处理和流处理工作负载的统一。
Databricks Lakehouse
Databricks Lakehouse 将一种新的高性能处理引擎——Photon 引擎的处理能力与 Apache Spark 的增强功能结合在一起。结合开放的数据存储格式,并支持广泛的数据类型,包括结构化、半结构化和非结构化数据,Photon 引擎可以使用便宜且弹性的云存储中的数据单一一致快照,处理各种工作负载。此外,Databricks Lakehouse 通过统一批处理和流处理,使用单一 API——Spark DataFrame API,简化了数据架构。最后,Databricks Lakehouse 在设计时考虑了数据治理和数据安全,允许组织集中定义数据访问模式,并在整个业务中一致地应用这些模式。
在本书中,我们将介绍 Databricks Lakehouse 的三个主要特性:
- Delta Lake 格式
- Photon 引擎
- 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 框架在每一步的增量数据处理中都 heavily 依赖于 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 时使用(例如,执行数据质量检查)。 |
管道
DLT 管道是一个由一个或多个流表、物化视图或视图组成的逻辑数据处理图。DLT 框架将通过 Python API 或 SQL API 接受数据集声明,并推断每个数据集之间的依赖关系。一旦管道更新运行,DLT 框架将使用依赖图(称为数据流图)按正确的顺序更新数据集。
管道触发器
管道将根据某些触发事件执行。DLT 提供三种类型的触发器——手动触发、计划触发和连续触发。一旦触发,管道将初始化并执行数据流图,更新每个数据集的状态。
工作流
Databricks 工作流是 Databricks 数据智能平台的一个托管编排功能,允许数据工程师将一个或多个依赖的数据处理任务串联起来。对于更复杂的数据处理用例,可能需要使用多个嵌套的 DLT 管道来构建数据管道。对于这些用例,Databricks 工作流可以简化这些数据处理任务的编排。
Databricks 计算类型
Databricks 数据智能平台为用户提供了四种类型的计算资源。
- 作业计算
作业计算是一个临时的虚拟机集合,安装了 Databricks Runtime(DBR),在计划的作业期间动态分配。作业完成后,虚拟机将立即释放回云提供商。由于作业集群不使用 Databricks 数据智能平台的 UI 组件(如笔记本和查询编辑器),作业集群的 DBU 费用较低。 - 通用计算
通用计算是一个临时的虚拟机集合,安装了 DBR,用户通过 Databricks UI 或 Databricks REST API 动态分配,并且在用户或自动终止定时器终止集群之前保持运行。终止时,虚拟机将返回云提供商,Databricks 停止收取额外的 DBU。 - 实例池
实例池是 Databricks 的一项功能,有助于减少配置额外虚拟机和安装 DBR 的时间。实例池会从云提供商预先配置虚拟机,并将其保存在一个逻辑容器中,类似于停车场中的代客泊车服务。实例池提高了效率,尤其是在多个作业计划紧密安排或时间重叠时。实例池还具备自动扩缩容功能,根据需求自动增加或减少池的大小。 - Databricks SQL 仓库
Databricks SQL 仓库(DBSQL)旨在运行 SQL 工作负载,如查询、报告和仪表板。DBSQL 仓库是预配置的计算资源,旨在优化临时数据探索和查询执行的配置。DBSQL 仓库采用最新的 DBR,利用 Databricks Photon 引擎,预配置了优化性能的 Spark 配置设置,具有结果缓存和磁盘缓存等附加性能特性,能够通过将数据靠近执行查询的硬件来加速工作负载。
Databricks Runtime
Databricks Runtime 是在集群初始化时预安装在驱动程序和工作节点上的一组库。这些库包括流行的 Java、R 和 Python 库,以帮助最终用户进行临时数据处理或其他开发任务。DBR 包括支持 Databricks 后端服务的核心组件,以支持丰富的平台功能,如协作笔记本、工作流和集群指标。此外,DBR 还包括性能特性,如数据文件缓存(磁盘缓存)、Databricks Photon 引擎加速的 Spark 处理等。DBR 分为标准版和 ML 版,分别针对不同的工作负载进行优化,例如 ML 版的 DBR 会预安装 TensorFlow 和 scikit-learn 等流行的 Python 库,以帮助最终用户进行 ML 模型训练、特征工程等任务。
Unity Catalog
如其名所示,Unity Catalog 是一个集中治理存储,旨在跨多个 Databricks 工作区使用。Unity Catalog 允许数据管理员在一个集中位置定义访问策略,从而避免在每个 Databricks 工作区中反复定义用户和组的权限。Unity Catalog 作为数据治理的单一真实来源。此外,Unity Catalog 还提供数据审计、数据溯源、数据发现和数据共享功能,这将在第 5、6 和 7 章中讨论。Unity Catalog 与 Databricks lakehouse 紧密集成,使得在考虑数据安全性的同时,使用 Delta Lake 等开放湖仓存储格式轻松构建接近实时的数据管道。
快速了解 Delta Lake
Delta Lake 是一种大数据文件协议,围绕多版本事务日志构建,提供诸如 ACID 事务、架构强制、时间旅行、数据文件管理和其他性能特性,所有这些都可以在湖仓中现有的数据文件之上使用。
最初,大数据架构中有许多并发的进程,这些进程既读取数据又修改数据,导致了数据损坏甚至数据丢失。如前所述,创建了一个双重 Lambda 架构,为应用流更新到数据的过程和需要一致数据快照的下游过程之间提供了隔离层,例如生成每日报告或刷新仪表板的 BI 工作负载。然而,这些 Lambda 架构为了支持批处理和流处理工作负载,重复存储了数据,导致了不一致的数据变化,这些变化需要在每个工作日结束时进行对账。
幸运的是,Delta Lake 格式为湖仓提供了一个共同的存储层,支持不同的工作负载,并统一了批处理和流处理工作负载。因此,Delta 表是 Delta Live Table 的基础。在后台,Delta Live Table 是由一个 Delta 表支持的,该表被添加到数据流图中,并且每当 DLT 管道更新执行时,其状态都会由 DLT 系统进行更新。
Delta 表的架构
Delta 事务日志是这一大数据格式架构中的关键部分。每个 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 之间的并发写入冲突:
假设这两个表写入者修改了相同的数据文件,并尝试提交彼此冲突的数据更改。让我们来看 Delta Lake 如何处理这种场景。
写入者 A 将首先记录它尝试提交到事务日志的事务的起始版本标识符。
然后,写入者 A 将写入它希望提交的所有数据文件。
接下来,写入者 A 将尝试将事务提交到 Delta 事务日志。
与此同时,写入者 B 已经使用相同的表版本标识符提交了他们的事务。
写入者 A 检测到写入者 B 的提交,并重放提交信息,以确定底层数据文件是否发生了变化(例如,数据是否已更新)。
如果数据没有变化(例如,写入者 A 和写入者 B 都将仅追加操作提交到事务日志),则写入者 A 会将版本标识符加 1,并尝试重新提交该事务到事务日志。
如果数据发生了变化,则写入者 A 需要从头开始重新计算事务,增加版本标识符,并尝试重新提交该事务。
墓碑数据文件
当对 Delta 表应用更新时,如果文件中的数据需要更新,将使用 AddFile 操作创建一个新文件。类似地,包含过时数据的文件将通过 RemoveFile 操作被逻辑删除。然后,这两个操作将被提交到事务日志。
默认情况下,Delta Lake 会保留表的元数据(事务日志数据)30天,然后自动从云存储中删除。当某个数据文件从 Delta 事务日志中移除时,通常被称为墓碑文件(tombstoned file)。
为了帮助控制云存储成本,这些墓碑文件,或者不再构成最新 Delta 表状态并且不再在事务日志中引用的文件,可以完全从云存储中移除。一个名为 Vacuum 的单独 Delta Lake 文件管理工具可以作为单独的进程运行,识别所有墓碑数据文件并将其移除。
此外,Vacuum 命令是可配置的,可以指定移除表文件的时间长度作为可选输入参数。例如,以下代码片段将在 Delta 表 yellow_taxi 上执行 Vacuum 命令,并移除过去 14 天内的表历史数据文件:
%sql
-- 对 Delta 表 `yellow_taxi` 执行 Vacuum 操作
-- 并保留 14 天的表历史
VACUUM yellow_taxi RETAIN 336 HOURS
正如我们将在即将到来的章节中看到的,这个过程会为 DLT 管道自动运行和管理。
计算 Delta 表状态
如前一节所提到的,Delta Lake 会自动压缩事务日志中的元数据。正如你能想象的那样,在大数据工作负载中,每天处理成千上万甚至数百万个事务时,Delta 表的大小会迅速增长。类似地,事务日志中的提交信息也会相应增长。
对于每第 10 次提交,Delta Lake 将创建一个检查点文件,使用 Apache Parquet 格式,包含最新的表状态信息。
在底层,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 的时间旅行功能是为纠正最近的数据问题而设计的,因此不应用于长期的数据存储需求,比如实现一个跨越多年的审计系统。
使用更改数据馈送(CDF)跟踪表的变化
Delta Lake 的更改数据馈送(CDF)功能跟踪对 Delta 表所做的行级更改,以及关于这些更改的元数据。例如,CDF 会捕获操作类型的信息、确认更改发生的时间戳以及其他溯源信息,如集群标识和用户信息。
对于更新操作,CDF 将捕获更新前的行快照,以及更新应用后的行快照。
此功能默认情况下未启用,但可以通过更新 Delta 表的属性进行配置。例如,可以通过使用 SQL ALTER 语句修改表来启用 CDF:
%sql
ALTER TABLE yellow_taxi
SET TBLPROPERTIES (delta.enableChangeDataFeed = true)
同样,创建表时也可以启用 CDF,通过在 CREATE TABLE 语句中包含表属性:
%sql
CREATE TABLE IF NOT EXISTS yellow_taxi
TBLPROPERTIES (delta.enableChangeDataFeed = true)
正如我们将在下一章看到的,CDF 功能在 DLT 如何高效地将源表中的更改应用到下游数据集并实现慢变维(SCDs)方面非常重要。
实战示例——创建你的第一个 Delta Live Tables 管道
在本节中,我们将使用 NYC 出租车示例数据集,通过 DLT 框架声明一个数据管道,并应用一个基本的转换来增强数据。
重要提示
为了最大化从本节中获得的价值,建议拥有 Databricks 工作区权限,以创建一个通用集群和一个 DLT 管道,并使用集群策略。在本节中,你将把一个笔记本附加到集群,执行笔记本单元,以及创建和运行一个新的 DLT 管道。
让我们从创建一个新的通用集群开始。通过从左侧边栏导航中选择“Compute”按钮,进入 Databricks 计算 UI。
点击右上角的“Create compute”按钮。接下来,为集群提供一个名称。在本练习中,集群可以是一个小型的单节点集群。选择“Single node”单选按钮作为集群类型。在运行时下拉菜单中选择最新的 DBR(Databricks Runtime)。接受默认设置后,再次点击“Create compute”按钮。
现在我们已经启动并运行了集群,可以开始开发我们的第一个数据管道。
首先,在工作区的主页目录下创建一个新的 Databricks 笔记本。通过点击左侧边栏的“Workspace”按钮,点击“Add”下拉菜单,并选择“Notebook”来创建一个新的笔记本。为笔记本命名一个有意义的名字,例如“My First DLT Pipeline”。这个新的笔记本将用于声明构成我们 Delta Live Table 管道的数据集和依赖关系。
所有 Databricks 工作区都附带了一组示例数据集,这些数据集位于 Databricks 文件系统中的 /databricks-datasets 目录。你可以使用 Databricks 文件系统工具列出目录内容来浏览可用数据集:
%py
display(
dbutils.fs.ls('/databricks-datasets')
)
接下来,我们需要导入 dlt Python 模块。dlt 模块包含函数装饰器,这些装饰器将指示 DLT 系统如何构建我们的数据管道、依赖关系,以及一个内部数据处理图(称为数据流图)。
在新笔记本单元中添加以下代码行:
import dlt
DLT 构建在 PySpark 之上,因此我们可以利用 Spark DataFrame 来定义如何从云存储中摄取数据以及如何应用数据转换。让我们首先定义一个函数,使用 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 读取存储在 yellow taxi 数据集中的原始数据,并将其作为流式 DataFrame 返回。
现在,我们需要告诉 DLT 框架,我们应该将这个声明的函数作为数据管道的一部分使用。我们可以通过添加 @dlt.table() 函数装饰器来实现。这个装饰器将从函数创建一个 Delta Live Table,并将其添加到管道的数据流图中。我们还可以向这个函数装饰器的可选注释参数添加一些描述性文本:
@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 管道。
点击“Create Pipeline”按钮以生成一个新的 DLT 管道。为数据管道命名一个有意义的名称,例如“Yellow Taxi Cab Pipeline”。选择“Core”作为产品版本,选择“Triggered”作为管道执行模式。
接下来,在目标位置设置下,选择 Unity Catalog 单选按钮,并指定你希望存储数据集的目标目录和架构。在计算设置下,将最小工作节点数(Min workers)设置为 1,最大工作节点数(Max workers)设置为 1。然后,通过点击“Create”按钮接受默认设置。最后,点击“Start”按钮以执行数据管道。你将被带到数据流图的可视化表示页面。
正如你所看到的,我们的数据流图由一个单一的流表组成,这是一个新的数据集,将从 Databricks 文件系统中的 /databricks-datasets/ 位置摄取原始的 NYC 出租车旅行数据。虽然这是一个简单的示例,但它展示了 DLT 框架的声明式特性,以及我们如何利用熟悉的 PySpark API 快速声明数据管道。此外,你现在应该对如何从 DLT UI 中监控和查看我们数据管道的最新状态有了一个初步的了解。
总结
在本章中,我们探讨了数据行业为何选择湖仓架构,这种架构旨在将 ETL 处理的可扩展性和 BI 工作负载的数据仓库速度合并在一个统一的架构下。我们了解到,实时数据处理对于在数据到达后立即挖掘其价值至关重要,但随着时间的推移,实时数据管道可能会使数据工程团队的生产力受到阻碍,因为复杂性会逐渐增加。最后,我们学习了 Delta Live Tables 框架的核心概念,以及如何通过几行 PySpark 代码和函数装饰器,快速声明一个能够以高吞吐量和低延迟增量处理数据的实时数据管道。
在下一章中,我们将深入探讨 Delta Live Tables 管道的高级设置,以及框架如何为我们优化底层数据集。然后,我们将使用一个实际的用例,开发一个数据管道,进一步研究更复杂的数据转换。