教程:实现数据湖捕获模式以更新 Databricks Delta 表

本教程介绍如何处理使用分层命名空间的存储帐户中的事件。

你创建了一个小型解决方案,通过上传一个包含销售订单信息的逗号分隔值 (CSV) 文件,将数据写入 Databricks Delta 表。 你通过连接事件网格订阅、一个 Azure 函数和 Azure Databricks 中的一个作业来构建这个解决方案。

在本教程中,你将了解:

  • 创建一个事件网格订阅用于调用 Azure 函数。
  • 创建一个 Azure 函数,用于接收事件中的通知,然后运行 Azure Databricks 中的作业。
  • 创建一个 Databricks 作业,用于将客户订单插入到存储帐户中的 Databricks Delta 表。

你按相反顺序构建这个解决方案,从Azure Databricks工作区开始。

先决条件

创建销售订单

首先,创建一个描述销售订单的CSV文件,然后将该文件上传到存储账户。 之后,你用这个文件中的数据填充Databricks Delta表的第一行。

  1. 转到Azure门户中的新存储帐户。

  2. 选择 存储浏览器>Blob 容器>添加容器 并创建一个名为 data 的新容器。

    在 Azure 存储 浏览器中创建容器的截图。

  3. 在 data 容器中,创建名为 input 的目录。

  4. 在文本编辑器中粘贴以下文本:

    InvoiceNo,StockCode,Description,Quantity,InvoiceDate,UnitPrice,CustomerID,Country
    536365,85123A,WHITE HANGING HEART T-LIGHT HOLDER,6,12/1/2010 8:26,2.55,17850,United Kingdom
    
  5. 将此文件保存到本地电脑,并将其命名为 data.csv

  6. 在存储浏览器中,将此文件上传到 输入 文件夹。

在 Azure Databricks 中创建作业

在本部分中,你将执行以下任务:

  • 创建 Azure Databricks 工作区。
  • 创建笔记本。
  • 创建并填充 Databricks Delta 表。
  • 添加用于将行插入 Databricks Delta 表的代码。
  • 创建作业。

创建 Azure Databricks 工作区

在本节中,你可以通过使用 Azure 门户创建 Azure Databricks 工作区。

  1. 创建 Azure Databricks 工作区。 给工作区 contoso-orders命名。 请参阅创建 Azure Databricks 工作区

  2. 创建群集。 将群集命名为 customer-order-cluster。 参阅创建群集

  3. 创建笔记本。 将笔记本命名为 configure-customer-table,并选择“Python”作为笔记本的默认语言。 请参阅创建笔记本

创建并填充 Databricks Delta 表

  1. 在创建的笔记本中,将以下代码块复制并粘贴到第一个单元中,但暂时不要运行此代码。

    将此代码块中的 appIdpasswordtenant 占位符值替换为你在完成本教程先决条件时收集的值。

    dbutils.widgets.text('source_file', "", "Source File")
    
    spark.conf.set("fs.azure.account.auth.type", "OAuth")
    spark.conf.set("fs.azure.account.oauth.provider.type", "org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider")
    spark.conf.set("fs.azure.account.oauth2.client.id", "<appId>")
    spark.conf.set("fs.azure.account.oauth2.client.secret", "<password>")
    spark.conf.set("fs.azure.account.oauth2.client.endpoint", "https://login.partner.microsoftonline.cn/<tenant>/oauth2/token")
    
    adlsPath = 'abfss://data@contosoorders.dfs.core.chinacloudapi.cn/'
    inputPath = adlsPath + dbutils.widgets.get('source_file')
    customerTablePath = adlsPath + 'delta-tables/customers'
    

    此代码将创建名为 source_file 的小组件。 稍后你将创建一个 Azure 函数,用于调用此代码并将文件路径传递到该小组件。 这段代码还会用存储账户认证你的服务主体,并生成一些变量,用于其他单元格。

    注意

    在生产设置中,请考虑将身份验证密钥存储在 Azure Databricks 中。 然后,在代码块中添加查找密钥,而不是认证密钥。

    例如,不使用这行代码: spark.conf.set("fs.azure.account.oauth2.client.secret", "<password>"),改用以下代码行: spark.conf.set("fs.azure.account.oauth2.client.secret", dbutils.secrets.get(scope = "<scope-name>", key = "<key-name-for-service-credential>"))

    完成本教程后,请查看Azure Databricks网站上的Azure Data Lake Storage文章,了解该方法的示例。

  2. SHIFT + Enter 即可在该区块中运行代码。

  3. 将以下代码块复制粘贴到另一个单元格,然后按 SHIFT + ENTER 在该单元中运行代码。

    from pyspark.sql.types import StructType, StructField, DoubleType, IntegerType, StringType
    
    inputSchema = StructType([
    StructField("InvoiceNo", IntegerType(), True),
    StructField("StockCode", StringType(), True),
    StructField("Description", StringType(), True),
    StructField("Quantity", IntegerType(), True),
    StructField("InvoiceDate", StringType(), True),
    StructField("UnitPrice", DoubleType(), True),
    StructField("CustomerID", IntegerType(), True),
    StructField("Country", StringType(), True)
    ])
    
    rawDataDF = (spark.read
     .option("header", "true")
     .schema(inputSchema)
     .csv(adlsPath + 'input')
    )
    
    (rawDataDF.write
      .mode("overwrite")
      .format("delta")
      .saveAsTable("customer_data", path=customerTablePath))
    

    这段代码会在你的存储账户中创建Databricks Delta表,然后从你之前上传的CSV文件加载一些初始数据。

  4. 当该代码块成功运行后,从笔记本中移除该代码块。

添加用于将行插入 Databricks Delta 表的代码

  1. 将以下代码块复制并粘贴到另一个单元中,但不要运行此单元。

    upsertDataDF = (spark
      .read
      .option("header", "true")
      .csv(inputPath)
    )
    upsertDataDF.createOrReplaceTempView("customer_data_to_upsert")
    

    该代码通过使用CSV文件中的数据插入临时表视图。 该CSV文件的路径来自你之前步骤创建的输入小部件。

  2. 将以下代码块复制并粘贴到另一个单元格中。 此代码将临时表视图的内容与 Databricks Delta 表合并。

    %sql
    MERGE INTO customer_data cd
    USING customer_data_to_upsert cu
    ON cd.CustomerID = cu.CustomerID
    WHEN MATCHED THEN
      UPDATE SET
        cd.StockCode = cu.StockCode,
        cd.Description = cu.Description,
        cd.InvoiceNo = cu.InvoiceNo,
        cd.Quantity = cu.Quantity,
        cd.InvoiceDate = cu.InvoiceDate,
        cd.UnitPrice = cu.UnitPrice,
        cd.Country = cu.Country
    WHEN NOT MATCHED
      THEN INSERT (InvoiceNo, StockCode, Description, Quantity, InvoiceDate, UnitPrice, CustomerID, Country)
      VALUES (
        cu.InvoiceNo,
        cu.StockCode,
        cu.Description,
        cu.Quantity,
        cu.InvoiceDate,
        cu.UnitPrice,
        cu.CustomerID,
        cu.Country)
    

创建职位

创建一个能运行你之前创建的笔记本的工作。 之后,你创建一个 Azure 函数,在事件触发时执行该作业。

  1. 选择 >工作

  2. 给工作起个名字,选择你创建的笔记本,然后选择一个集群。 然后选择“创建”以创建作业。

    新作业会显示在 Jobs 列表中,并带有你所选择的笔记本和集群。

创建 Azure 函数

创建一个 Azure 函数来执行该作业。

  1. 在你的 Azure Databricks 工作区中,选择顶部栏的 Azure Databricks 用户名。 从下拉菜单中选择 用户设置

  2. 在“访问令牌”选项卡上,选择“生成新令牌”。

  3. 复制出现的标记,然后选择 完成

  4. 在 Databricks 工作区的上角选择人像图标,然后选择“用户设置”。

    用户设置菜单的截图,用于生成Databricks访问令牌。

  5. 依次选择“生成新令牌”按钮、“生成”按钮。

    一定要把令牌复制到安全的地方。 你的 Azure 函数需要这个令牌来与 Databricks 进行认证,这样它才能运行作业。

  6. 在 Azure 门户菜单上或在门户主页中,选择“创建资源”。

  7. “新建” 页面,选择 “计算”>“函数应用”

  8. 在“创建函数应用”页的“基本信息”选项卡中选择一个资源组,然后更改或验证以下设置:

    设置
    Function App 名称 contosoorder
    运行时堆栈 .NET
    发布 Code
    操作系统 Windows
    计划类型 消耗(无服务器)
  9. 选择“查看 + 创建”,然后选择“创建”。

    部署完成后,选择“转到资源”打开函数应用的概述页。

  10. 在“设置”组中,选择“配置”。

  11. 在“应用程序设置”页中,选择“新建应用程序设置”按钮以添加每个设置。

    在功能应用配置中添加新应用设置的截图。

    添加以下设置:

    设置名称
    DBX_INSTANCE Databricks 工作区的区域。 例如: chinaeast2.databricks.azure.cn
    DBX_PAT 前面生成的个人访问令牌。
    DBX_JOB_ID 正在运行的作业的标识符。
  12. 选择“保存”以提交这些设置。

  13. 在“函数”组中选择“函数”,然后选择“创建”。

  14. 选择“Azure 事件网格触发器”。

    根据提示安装 Microsoft.Azure.WebJobs.Extensions.EventGrid 扩展。 如果需要安装,请再次选择 Azure 事件网格 Trigger 来创建该函数。

    此时将显示“新建函数”窗格。

  15. 新建函数中输入 UpsertOrder 函数名称,然后选择 创建

  16. 将代码文件内容替换为以下代码,然后选择 保存

      #r "Azure.Messaging.EventGrid"
      #r "System.Memory.Data"
      #r "Newtonsoft.Json"
      #r "System.Text.Json"
      using Azure.Messaging.EventGrid;
      using Azure.Messaging.EventGrid.SystemEvents;
      using Newtonsoft.Json;
      using Newtonsoft.Json.Linq;
    
      private static HttpClient httpClient = new HttpClient();
    
      public static async Task Run(EventGridEvent eventGridEvent, ILogger log)
      {
         log.LogInformation("Event Subject: " + eventGridEvent.Subject);
         log.LogInformation("Event Topic: " + eventGridEvent.Topic);
         log.LogInformation("Event Type: " + eventGridEvent.EventType);
         log.LogInformation(eventGridEvent.Data.ToString());
    
         if (eventGridEvent.EventType == "Microsoft.Storage.BlobCreated" || eventGridEvent.EventType == "Microsoft.Storage.FileRenamed") {
            StorageBlobCreatedEventData fileData = eventGridEvent.Data.ToObjectFromJson<StorageBlobCreatedEventData>();
            if (fileData.Api == "FlushWithClose") {
                  log.LogInformation("Triggering Databricks Job for file: " + fileData.Url);
                  var fileUrl = new Uri(fileData.Url);
                  var httpRequestMessage = new HttpRequestMessage {
                     Method = HttpMethod.Post,
                     RequestUri = new Uri(String.Format("https://{0}/api/2.0/jobs/run-now", System.Environment.GetEnvironmentVariable("DBX_INSTANCE", EnvironmentVariableTarget.Process))),
                     Headers = { 
                        { System.Net.HttpRequestHeader.Authorization.ToString(), "Bearer " + System.Environment.GetEnvironmentVariable("DBX_PAT", EnvironmentVariableTarget.Process)},
                        { System.Net.HttpRequestHeader.ContentType.ToString(), "application/json" }
                     },
                     Content = new StringContent(JsonConvert.SerializeObject(new {
                        job_id = System.Environment.GetEnvironmentVariable("DBX_JOB_ID", EnvironmentVariableTarget.Process),
                        notebook_params = new {
                              source_file = String.Join("", fileUrl.Segments.Skip(2))
                        }
                     }))
                  };
                  var response = await httpClient.SendAsync(httpRequestMessage);
                  response.EnsureSuccessStatusCode();
            }
         }
      }
    

    该代码解析被提出的存储事件信息,然后生成带有触发事件文件URL的请求消息。 在该消息中,该函数会将一个值传递给前面创建的 source_file 小组件。 函数代码会将消息发送到Databricks作业,并使用你之前获得的令牌作为认证。

创建事件网格订阅

在本节中,你创建一个事件网格订阅,当文件上传到存储账户时调用 Azure 函数。

  1. 选择“集成”。集成 页面,选择 事件网格触发器

  2. 在“编辑触发器”窗格中,将事件命名为 ,然后选择“创建事件订阅”。

    注意

    该名称eventGridEvent与 Azure 函数接收的参数名称相匹配。

  3. 在“创建事件订阅”页的“基本信息”选项卡中,更改或验证以下设置:

    设置
    名称 contoso-order-event-subscription
    主题类型 存储帐户
    源资源 contosoorders
    系统主题名称 <create any name>
    筛选事件类型 已创建 Blob、已删除 Blob
  4. 选择“创建”

测试事件网格订阅

  1. 创建名为 customer-order.csv 的文件,将以下信息粘贴到该文件中,并将其保存到本地计算机。

    InvoiceNo,StockCode,Description,Quantity,InvoiceDate,UnitPrice,CustomerID,Country
    536371,99999,EverGlow Single,228,1/1/2018 9:01,33.85,20993,Sierra Leone
    
  2. 在存储浏览器中,将此文件上传到你存储账户的 输入 文件夹。

    上传文件时,会触发 Microsoft.Storage.BlobCreated 事件。 事件网格会通知所有订阅者已发生该事件。 在这种情况下,Azure Function是唯一的订阅者。 Azure 函数将分析事件参数以确定发生了哪个事件。 然后它将文件的 URL 传递给 Databricks 作业。 Databricks 作业读取文件,并在你的存储账户中的 Databricks Delta 表中添加一行。

  3. 要检查任务是否成功,请查看该任务的运行记录。 你会看到完成情况。 有关如何查看作业运行的更多信息,请参见 “查看作业运行”。

  4. 在新的工作簿单元格中,运行此查询以查看更新后的delta表。

    %sql select * from customer_data
    

    返回的表将显示最新记录。

    Databricks Delta 表查询的截图显示了最新的记录。

  5. 若要更新此记录,请创建名为 customer-order-update.csv 的文件,将以下信息粘贴到该文件中,然后将其保存到本地计算机。

    InvoiceNo,StockCode,Description,Quantity,InvoiceDate,UnitPrice,CustomerID,Country
    536371,99999,EverGlow Single,22,1/1/2018 9:01,33.85,20993,Sierra Leone
    

    这个CSV文件几乎与之前的相同,只是订单数量从改 22822

  6. 在存储浏览器中,将此文件上传到你存储账户的 输入 文件夹。

  7. 再次运行 select 查询以查看更新后的增量表。

    %sql select * from customer_data
    

    返回的表将显示更新后的记录。

    Databricks Delta表查询截图,显示更新后的记录。

清理资源

不再需要资源时,请删除资源组和所有相关资源。 要删除资源组,选择存储账户的资源组并选择 删除

后续步骤