在本章中,我们将直接探讨 Delta Live Tables (DLT) 如何使从各种输入源摄取数据变得简单而直接,无论是将文件存储到云存储中,还是连接到外部存储系统,例如关系数据库管理系统 (RDBMS)。接着,我们将看看如何高效且准确地将输入数据源中的变更应用到下游数据集,使用 APPLY CHANGES 命令。最后,我们将深入探讨高级数据管道设置,结束本章内容。
总结一下,本章我们将涵盖以下主要内容:
- 从输入源摄取数据
- 将变更应用到下游表
- 发布数据集到 Unity Catalog
- 数据管道设置
- 实践操作 -- 应用 SCD 类型 2 的变更
技术要求
为了能够跟随本章的内容,建议具有 Databricks 工作区权限来创建通用集群和 DLT 管道,使用集群策略。同时,建议具备 Unity Catalog 权限,以便创建和使用目录、模式和表。所有代码示例可以从本章的 GitHub 仓库下载,地址为 github.com/PacktPublis...。本章将创建并运行几个新的笔记本和一个 DLT 管道,使用的是核心产品版本。因此,估计这些管道将消耗大约 10-15 个 Databricks 单位 (DBUs)。
从输入源摄取数据
DLT 使从各种输入源摄取数据变得简单。例如,DLT 可以高效地处理全天新到达的文件,将结构化数据通过连接外部存储系统(如关系数据库)摄取,或读取可以缓存到内存中的静态参考表。接下来,让我们看看如何使用 DLT 增量摄取新到达的云存储位置中的数据。
使用 Databricks Auto Loader 摄取数据
Databricks 数据智能平台的一个关键特性是称为 Auto Loader 的功能,它是一种简单而强大的摄取机制,用于高效地从云存储读取输入文件。Auto Loader 可以通过使用 cloudFiles 数据源在 DataFrame 定义中引用。例如,以下代码片段将使用 Databricks Auto Loader 功能从存储容器摄取新到达的 JSON 文件:
less
df = (spark.readStream
.format("cloudFiles")
.option("cloudFiles.format", "json")
.option("cloudFiles.schemaLocation", schema_path)
.load(raw_data_path))
Auto Loader 可以扩展到高效地处理云存储中的数十亿个文件。Databricks Auto Loader 支持摄取存储在 CSV、JSON、XML、Apache Parquet、Apache Avro 和 Apache Orc 文件格式中的文件,以及文本和二进制文件。此外,在上述代码片段中,您可能会注意到没有为输入流指定模式定义,而是指定了目标模式位置。这是因为 Auto Loader 会自动推断数据源的模式,并在一个单独的存储位置跟踪模式定义的变化。在后台,Auto Loader 会采样最多前 1,000 个云文件对象,以推断云文件源的模式结构。对于像 JSON 这样的半结构化格式,模式可能会随着时间变化,这可以减轻数据工程团队的巨大负担,因为他们不需要维护最新模式定义的最新版本。
结构化流处理中的可扩展性挑战
传统上,使用 Spark Structured Streaming 的数据管道在摄取新文件时(文件被附加到云存储位置),当数据量增长到 GB 或甚至 TB 时,往往难以扩展。随着新文件写入云存储容器,Structured Streaming 会执行目录列表操作。对于大型数据集(即由数百万个文件或更多组成的数据集),仅目录列出的过程就需要相当长的时间。此外,云提供商会对这些目录列表调用评估 API 费用,增加了总体的云提供商费用。对于已经处理过的文件,这个目录列表过程既昂贵又低效。
Databricks Auto Loader 支持两种类型的云文件检测模式------通知模式和传统目录列表模式。在通知模式下,Auto Loader 完全绕过了这个昂贵的目录列表过程,通过自动部署一个更可扩展的架构来实现。在几行 Python 代码的帮助下,Databricks 预先配置了后台云服务,这些服务将自动跟踪已经到达云存储的新文件,以及已经处理过的文件。
让我们通过一个示例来演示如何在通知模式下配置的 Auto Loader 功能高效地处理新到达的云文件对象:
该过程从 Databricks Auto Loader 开始监听特定云存储路径上的新文件对象创建事件(也称为 PUT 事件,因其使用的 HTTP 动词命名)。
当一个新文件对象被创建时,关于该新文件的元数据会被持久化到一个键值存储中,如果发生系统故障,这个存储将作为检查点位置。
接下来,关于文件对象的信息将发布到一个事件流中,cloudFiles 数据源会从中读取数据。
在从事件流中读取数据后,Databricks 中的 Auto Loader 进程将仅获取与那些新的、未处理的云存储中文件对象相关的数据。
最后,Auto Loader 进程将更新键值存储,标记新文件为已处理。
这种基于通知的文件处理实现避免了昂贵且低效的目录列表过程,确保该过程能够从故障中恢复,并且文件只会被处理一次。
使用 Auto Loader 与 DLT
Databricks Auto Loader 可以用于在 DLT 管道中创建流式表。现在我们了解了后台发生的过程,构建一个健壮且可扩展的流式表,能够扩展到数十亿个文件,可以通过几行 Python 代码完成。事实上,对于将新文件附加到云存储的数据源,建议始终使用 Auto Loader 来摄取数据。让我们从前一节的流式 DataFrame 定义开始,并将其与 DLT 数据集注释结合,定义管道中的新数据流:
less
@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 命令,允许通过匹配特定条件将变更数据捕获(CDC)合并到目标表中。然而,如果新数据恰好是乱序的,合并的变更将导致不正确的结果,进而产生不准确和误导的输出。为了解决这个问题,数据工程团队需要构建复杂的对账过程来处理乱序数据,这又给数据管道增加了一层管理和维护的负担。
APPLY CHANGES 命令
DLT 提供了一个新的 API,能够自动将变更应用到下游表,甚至基于一个或多个序列列处理乱序数据。慢变化维度(SCD)是传统数据仓库中的维度,允许在时间上跟踪数据的当前和历史快照。DLT 允许数据工程团队使用上游数据源中的变更来更新数据管道中的下游数据集。例如,DLT 允许用户捕获 SCD 类型 1(不保留先前行历史)和 SCD 类型 2(保留行的历史版本)。
DLT 提供了一个 Python API 以及 SQL 语法来应用变更数据捕获:
- APPLY CHANGES --- 用于使用 SQL 语法编写的管道
- apply_changes() --- 用于使用 Python 编写的管道
假设我们有一个表,用于记录全天发布的智能温控器的房间温度,并且需要保留温度更新的历史。以下代码片段将使用 apply_changes()
API 将 SCD 类型 2 变更应用到数据管道中的输出表:
ini
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 会捕获关于应用变更的数据变更的高层操作指标。例如,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
参数指定的列或列的序列来排序每一行的表变更,选择最新的行变更来计算视图的结果集。
正如您所看到的,DLT 使得将下游数据源与源中的数据变化保持一致变得极其简单。通过简单的参数更改,您可以使用强大的 apply_changes()
API 应用 SCD 数据。
现在我们了解了如何利用 DLT 框架定义数据转换并将变更应用到下游表,接下来让我们关注如何在我们的管道数据集上添加强大的数据治理。
将数据集发布到 Unity Catalog
DLT 提供了两种方法来存储数据集到 Databricks 数据智能平台------传统的 Hive Metastore 和 Unity Catalog。
如第 1 章所述,Unity Catalog 是一个集中式治理存储,跨越您在特定全球区域内的所有 Databricks 工作区。因此,数据访问策略可以在一个集中位置定义,并在您的组织中一致地应用。
然而,在 DLT 管道的上下文中,这两种存储输出数据集的方法是互斥的------也就是说,特定的 DLT 管道不能将一些数据集存储在 Unity Catalog 中,其他数据集存储在 Hive Metastore 中。您必须为整个数据管道输出选择一个单一的元存储位置。
为什么要将数据集存储到 Unity Catalog?
Unity Catalog 是存储数据和查询数据集的全新最佳实践方法。您可能会选择将数据管道中的着陆数据存储到 Unity Catalog 中,而不是 Hive Metastore,原因包括以下几点:
- 数据默认是安全的。
- 在各组和用户之间有一致的访问策略定义,而不是为每个单独的工作区定义数据访问策略。
- 开源技术,无供应商锁定风险。
此外,Unity Catalog 提供了与 Hive 兼容的 API,允许第三方工具像与 Hive Metastore 一样与 Unity Catalog 元存储集成。
创建新目录
Unity Catalog 与 Hive Metastore 之间的一个主要区别是,前者在定义表时引入了三层命名空间。父命名空间将指向目录对象。目录是一个逻辑容器,将包含一个或多个模式(或数据库)。
构建新 DLT 管道的第一步是定义一个集中位置来存储输出数据集。在 Unity Catalog 中创建新目录非常简单,可以通过多种方式完成,例如通过 Catalog Explorer UI、在笔记本中执行 SQL 语句,或者使用 Databricks REST API。
我们将使用 Databricks Catalog Explorer UI 来创建新目录:
- 首先,通过点击导航侧边栏中的 Catalog Explorer 标签,导航到 Catalog Explorer。
- 接下来,点击 Create Catalog 按钮。
- 为目录指定一个有意义的名称。
- 选择 Standard 作为目录类型。
- 最后,点击 Create 按钮创建新目录。
分配目录权限
如前所述,使用 Unity Catalog 的一个好处是数据默认是安全的。换句话说,除非明确授权,否则无法访问存储在 Unity Catalog 中的数据。为了在新创建的目录中创建新表,我们需要授予创建和操作新表的权限。
重要提示
如果您是目标目录和模式对象的创建者和所有者,并且也是 DLT 管道的创建者和所有者,则无需执行以下 GRANT 语句。GRANT 语句旨在演示在典型的 Unity Catalog 元存储中跨多个组和用户共享数据资产所需的权限类型。
首先,让我们授予使用目录的权限。在新笔记本中,执行以下 SQL 语句,授予 my_user
用户使用新创建的目录 chp2_transforming_data
的权限:
swift
GRANT USE CATALOG, CREATE SCHEMA ON CATALOG `chp2_transforming_data` TO `my_user`;
接下来,我们需要创建一个模式来保存 DLT 管道的输出数据集。在同一笔记本中,执行以下 SQL 语句来创建新模式:
swift
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: Core 是基础产品版本。这个产品版本适用于仅将新数据附加到流式表的流式工作负载。数据质量强制执行(将在下一章讨论)和应用变更数据捕获的工具在此版本中不可用。
- Pro: Pro 产品版本是比 Core 更高的版本。该版本设计用于将新数据附加到流式表并使用 APPLY CHANGES 命令应用更新和删除的流式工作负载。然而,数据质量期望在此版本中不可用。
- Advanced: Advanced 产品版本是功能最全的版本。该版本支持数据质量期望,并支持将新数据附加到流式表,并应用来自上游数据源的插入、更新和删除。
有时,您的需求可能会随着时间变化。例如,您可能需要严格的数据质量执行,以防止第三方商业智能(BI)报告工具中的下游故障。在这种情况下,您可以随时更新现有 DLT 管道的产品版本,允许您的数据管道适应功能需求和预算的变化。
管道执行模式
DLT 提供了一种方式,可以告知系统数据管道的更改是实验性的。这个功能称为数据管道环境模式。共有两种环境模式可供选择------开发模式和生产模式。主要的区别在于计算资源的行为。
-
开发环境模式: 在开发环境模式下,如果遇到故障,数据流任务不会自动重试。这使得数据工程师可以在临时开发周期中介入并修正任何程序错误。
此外,在开发模式下发生故障时,执行数据管道更新的集群将保持开启状态。这样,数据工程师可以查看集群的驱动日志和集群指标,并且避免每次管道执行时进行长时间的集群重配置和重新初始化,具体时间取决于云提供商,可能需要 10 到 15 分钟才能完成。预计开发和测试周期较短且具有迭代性,这有助于数据工程师在其开发生命周期中保持集群运行。
数据管道环境模式可以通过 DLT UI 设置,通过点击 DLT UI 中数据管道的顶部导航栏上的环境模式切换按钮来切换模式。
或者,环境模式也可以使用 Databricks REST API 设置。在以下代码片段中,我们将使用 Python 的 requests
库向 Databricks DLT 管道 REST API 发送 PUT 请求,以设置 DLT 管道的开发模式。请注意,端点 URL 会根据您的 Databricks 工作区部署而有所不同,下面的代码片段仅为示例:
python
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 会自动升级数据管道集群,以使用最新的稳定版运行时。运行时升级非常重要,因为它们引入了 bug 修复、新的性能特性和其他增强功能。这意味着您的数据管道将执行得更快,从而减少在湖仓中转换最新数据所花费的时间和成本。
您甚至可能迫不及待地想测试最新的性能特性。每个 DLT 管道都有一个频道设置,允许数据工程师选择两种频道选项之一------Current 和 Preview 。Preview 频道允许数据工程师配置数据管道,使用包含新性能特性和其他增强功能的最新实验性运行时执行。然而,由于这是一个实验性运行时,不建议在生产中运行的数据管道使用 Databricks 运行时的 Preview 频道。相反,建议使用 Current 选项,它选择了最新的稳定版本的 Databricks 运行时。
此外,DLT 系统会主动捕获部署在生产模式中的数据管道的运行时异常。例如,如果一个新的运行时版本引入了运行时错误(也称为运行时回归)或库版本冲突,DLT 会尝试将集群降级到一个已知能够成功执行数据管道的较低版本运行时,并重试执行管道更新。
以下图示展示了自动运行时升级异常处理过程。
管道集群类型
每个数据管道将有两个相关联的集群------一个用于执行数据集更新,另一个用于执行表维护任务。
这两种类型集群的设置通过管道的管道设置表达,使用 JSON 集群配置定义。可以在管道设置中表达三种类型的集群配置------更新集群配置、维护集群配置,以及一个作为默认集群配置的第三种选择,它将通用设置应用于更新集群和维护集群。这个 JSON 配置的架构紧跟 Databricks Clusters REST API 的架构。
除了配置集群的物理属性(如工作节点数和虚拟机实例类型)外,集群配置还可以包含高级 Spark 配置。让我们一起来看一个示例集群配置。
以下示例包含两个独立的集群配置------一个默认集群配置,将应用于更新和维护的 DLT 集群,以及另一个仅应用于更新 DLT 集群的集群配置。
在第一个集群配置中,我们将指定集群配置为默认集群配置,使用 label
属性。这意味着该集群配置将应用于用于更新数据集的 DLT 集群和用于运行表维护任务的集群。然后,我们将为 DLT 集群启用自动扩展,指定所有集群将开始使用一个虚拟机实例进行集群配置,但可以根据处理需求增加至最多五个虚拟机。我们还将指定应使用集群自动扩展算法的增强版本。
在第二组集群配置中,我们将再次使用 label
属性,指定集群配置仅应用于 DLT 更新集群。接着,我们将指定为更新集群的驱动程序和工作节点配置哪些实例类型。对于协调任务的驱动节点,我们将指定使用 i4i.2xlarge
EC2 实例类型,而所有工作节点将使用 i4i.xlarge
EC2 实例类型。最后,我们还将启用 Databricks 运行时的性能特性,称为自动优化 Shuffle(AOS)。AOS 会在运行时自动调整 Spark shuffle 分区的数量,这可以在执行大范围的 Spark 转换(如联接、聚合和合并操作)时提升性能。
重要提示
在以下示例中,我们选择使用 AWS 云的虚拟机实例来说明集群配置设置。然而,如果您的工作区在其他云提供商上,我们建议使用类似大小的 Delta 缓存加速虚拟机实例------驱动节点为 8 个核心,工作节点为 4 个核心(详情链接):
css
{
"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.shuffle.partitions": "auto"}
}]
}
如您所见,集群配置是一个强大的工具,允许数据工程师应用通用的集群设置,或针对特定集群设置,或者同时结合使用这两者。这是一种非常好的方式来针对特定工作负载调整集群,进一步提升 DLT 管道的性能。
传统计算与无服务器计算
数据管道可以使用配置了传统计算或无服务器计算的集群执行。
传统计算为用户提供了对计算资源的最大控制权。然而,使用传统计算时,用户需要管理底层集群的多个方面。例如,数据工程团队需要配置集群属性,如自动扩展行为,管道是否应使用 Photon 引擎或传统的 Catalyst 引擎在 Spark 中执行,以及可选的集群标签。此外,传统计算允许用户完全控制为集群的驱动节点和工作节点选择的虚拟机实例类型。如我们在前一节中所见,虚拟机实例类型可以在管道设置中通过在 JSON 配置中列出具体的实例类型来指定。例如,以下集群配置为 DLT 管道中的所有更新集群指定了 i4i.xlarge
和 i4i.2xlarge
EC2 实例类型:
css
{
"clusters": [{
"label": "updates",
"node_type_id": "i4i.xlarge",
"driver_node_type_id": "i4i.2xlarge"
}]
}
然而,配置为使用无服务器计算的 DLT 管道将抽象掉所有底层集群设置,例如集群虚拟机实例类型、工作节点的数量和自动扩展设置。正如 "无服务器计算" 这一名称所示,计算资源将在 Databricks 云提供商帐户中由 Databricks 提供和管理。在后台,Databricks 会维护一池预配置的计算资源,从而确保集群的快速配置。只要触发数据管道更新,DLT 系统就会在 Databricks 云提供商帐户中创建一个逻辑网络,并初始化集群来执行管道的数据流图。Databricks 会自动选择虚拟机实例类型、Photon 执行引擎以及自动扩展行为。
作为额外的安全层,逻辑网络之间或与外部互联网之间不允许通信,并且计算资源不会在无服务器工作负载之间重复使用。当数据管道处理完成并且集群终止时,计算资源将被释放回云提供商并销毁。
您可能会选择无服务器计算,以去除维护和更新多个集群策略的基础设施开销,同时也能在处理需求激增时利用快速集群配置的优势。此外,无服务器执行还启用了其他平台特性,例如在连续处理模式下更新物化视图(处理模式将在下一节讨论)。
加载外部依赖
数据管道可能需要加载外部依赖项,例如辅助工具或第三方库。因此,DLT 提供了三种方式来为数据管道安装运行时依赖项:
- 使用
%pip
魔法命令在笔记本单元格中(详情链接) - 从工作区文件或 Databricks 仓库加载模块
- 使用集群初始化脚本
流行的 Python 包管理工具 pip 可以通过 %pip
Databricks 魔法命令从数据管道的源代码中的任何笔记本中安装 Python 模块。%pip
是安装数据管道中库依赖项的最简单方法。在运行时,DLT 系统将检测所有包含 %pip
魔法命令的笔记本单元格,并首先执行这些单元格,然后再执行任何管道更新。此外,声明的数据管道的源代码中的所有笔记本将共享一个虚拟环境,因此库依赖项将一起安装在一个隔离的环境中,并且对数据管道源代码中的所有笔记本都可全局访问。反过来,数据管道的笔记本不能安装同一个 Python 库的不同版本。例如,以下代码示例将使用 pip 包管理器安装流行的库 numpy
、pandas
和 scikit-learn
,以及从 Databricks Volumes 位置加载的自定义 Python 库:
shell
%pip install numpy pandas scikit-learn /Volumes/tradingutils/tech-analysis-utils-v1.whl
作为最佳实践,这些依赖项安装语句应放在笔记本的顶部,以便更快速地引用管道依赖项。
另外,库依赖项也可以作为 Python 模块安装。在这种情况下,可以将库安装并加载到 DLT 管道中,作为工作区文件或从 Databricks Repo 加载,如果该模块是通过 Git 提供商(如 GitHub 或 Bitbucket)进行版本控制的。
最后,集群初始化脚本也可以用于安装外部依赖项。这些脚本在集群配置虚拟机并安装 Databricks 运行时后执行,但在数据管道开始执行之前。例如,在需要在所有数据工程平台上持续安装公司范围的库时,这种类型的依赖项安装可能是适用的。
重要提示
您可能已经注意到,前面提到的选项仅涵盖了安装 Python 依赖项。DLT 不支持安装 JVM 库,因为它只提供 Python 和 SQL 编程接口。
数据管道处理模式
数据管道的处理模式决定了管道内的表和物化视图更新的频率。DLT 提供了两种类型的管道处理模式------触发处理模式和连续处理模式。
触发处理模式将在管道中更新数据集一次,然后立即终止为运行管道而配置的集群,将计算资源释放回云提供商,从而终止额外的云费用。如其名称所示,触发处理模式可以以临时方式运行,并将在触发事件发生时立即执行,例如用户在 DLT UI 中点击按钮,或调用 Databricks REST API。
触发处理模式也可以通过 cron 调度触发运行,可以通过 UI 或 REST API 配置。如图 2.6 所示,可以通过点击 DLT UI 中的 Schedule 下拉按钮,点击 Add schedule 按钮,最后选择触发管道更新的时间来创建一个重复的调度。每天,管道中的数据集将在预定时间刷新。
相反,连续处理模式将在数据管道中为数据集刷新配置计算资源,但会继续无限执行,处理数据并在数据从源端到达时刷新表格和物化视图。连续处理管道将保持计算资源运行,并继续产生云计算成本,权衡是最小的数据陈旧性。该管道模式应在数据延迟优先于云计算成本时选择,特别适用于某些数据管道。
幸运的是,数据管道处理模式及其他管道设置可以在数据管道的生命周期内进行更新,这使得管道在处理延迟和计算成本之间具有灵活性。例如,经济衰退可能迫使组织将节省成本放在优先位置,但稍后可能会再次强调延迟。
让我们一起利用本章所学,构建一个应用SCD类型2变更的DLT管道,用于下游数据集的更新。
实践操作 -- 应用SCD类型2变更
在本次实践操作中,我们将使用Databricks Auto Loader增量加载写入云存储账户的原始着陆区中的JSON文件。接下来,我们将转换下游列并连接从外部Postgres数据库中获取的数据。
重要说明 从远程Postgres数据库读取数据是可选的。此步骤旨在展示Databricks数据智能平台的灵活性,向您展示如何轻松地从远程关系型数据库管理系统(RDBMS)读取结构化数据,并与半结构化数据结合。如果没有Postgres数据库,我们为您提供了一个包含出租车司机信息的静态DataFrame。
如果尚未执行,您需要从本章的GitHub仓库克隆相关的笔记本,位于 github.com/PacktPublis... 。
我们从导入数据生成器笔记本开始,命名为"Generate Mock Taxi Trip Data"。此笔记本将生成一个包含出租车旅行虚拟信息的模拟数据集。一旦生成了模拟数据集,笔记本会将出租车旅行数据集作为多个JSON文件存储在云存储账户中,稍后将由DLT管道进行摄取。将出租车旅行数据生成器笔记本附加到全能集群并执行所有单元格,以生成模拟数据。
接下来,我们创建DLT管道定义。在左侧边栏点击工作区表格,点击"添加"下拉菜单,选择"笔记本"。将笔记本重命名为有意义的名称,比如"Taxi Trips DLT Pipeline"。我们将在此笔记本中声明DLT管道的数据集和转换。
然后,导入DLT Python模块,以便访问DLT函数装饰器来定义数据集和依赖关系,并导入PySpark函数模块:
javascript
import dlt
import pyspark.sql.functions as F
我们需要创建一个流式表来摄取写入云存储着陆区的出租车旅行JSON数据。我们首先定义一个新的流式表,使用cloudFiles数据源监听原始着陆区的新文件事件:
python
# 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变更:
bash
# Define a new streaming table to apply SCD Type 2 changes
dlt.create_streaming_table("taxi_trip_data_merged")
接下来,我们将利用之前介绍的apply_changes()
函数,指示DLT系统如何应用变更,哪些列应从下游表格中省略,以及使用哪种SCD类型。将以下函数调用添加到笔记本中:
ini
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数据库的访问权限,可以导入名为"Generate Postgres Table"的笔记本并执行单元格,生成一个用于测试的表格。我们最终的流式表,将丰富数据并连接最新的出租车司机参考数据,类似于以下内容:
python
@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))
less
@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页面。给管道起一个有意义的名字,比如"Taxi Trip Data Pipeline"。由于我们使用了apply_changes()
函数,我们需要选择高级产品版本。确保选中了"触发式处理模式"单选按钮。为了查看由apply_changes()
函数创建的后端表格,选择Hive Metastore作为存储位置,并提供一个目标模式来存储管道数据集。接受其余的默认值,然后点击"创建"按钮以创建管道。
最后,通过点击DLT UI中的"启动"按钮来运行新创建的管道。很快,您将看到一个数据流图,展示了从原始JSON数据中摄取变更、丰富下游列并连接来自Postgres数据库的远程结构化数据的过程。
随着模拟出租车旅行数据源中的数据发生变化,所有DML变更的完整历史记录将被提取并应用到目标taxi_trip_data_merged
表中。我们数据管道的输出将是一个策划过的流式表,包含关于出租车乘车信息、出租车司机信息以及出租车车辆信息。最棒的是,通过几行代码,我们部署了一个完全可扩展且成本高效的数据管道,能够轻松处理数十亿个文件。
总结
在本章中,我们探讨了DLT如何通过抽象处理Spark中的许多底层细节来简化我们的数据管道。我们看到Databricks Auto Loader如何解决从云存储中流处理文件的可扩展性问题。通过几行代码,我们部署了一个可扩展的后台系统,以高效地读取一旦出现在云存储位置的新文件。在将数据变更应用到管道中的下游数据集时,DLT框架再次简化了数据的对账过程,尤其是在数据事件延迟或乱序发布时。我们还看到了如何仅通过在apply_changes()
API中更改一些参数来应用慢变维(SCD)。最后,我们揭示了数据管道设置的细节,优化了管道计算,以满足我们在数据管道中所需的计算要求和DLT功能集。我们还看到了DLT如何自动处理管道失败,并主动采取行动尝试修复某些运行时异常。
在下一章中,我们将探讨如何在DLT中使用期望(expectations)在数据管道的每个环节上强制执行数据质量规则,并在数据质量规则被违反时采取行动。