你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

无服务器事件处理

Azure Cosmos DB
Azure Functions
Azure Monitor
Azure Pipelines
Azure 存储

本参考体系结构演示一个可以引入数据流、处理数据,并将结果写入后端数据库的事件驱动式无服务器体系结构。

体系结构

关系图显示使用 Azure Functions 进行的无服务器事件处理的参考体系结构。

工作流

  • 事件到达 Azure 事件中心。
  • 触发函数应用来处理事件。
  • 该事件将存储在 Azure Cosmos DB 数据库中。
  • 如果函数应用未能成功存储事件,事件将保存到存储队列,以供之后处理。

组件

  • 事件中心引入数据流。 事件中心面向高吞吐量数据流场景。

    注意

    对于物联网 (IoT) 场景,建议使用 Azure IoT 中心。 IoT 中心包含与 Azure 事件中心 API 兼容的内置终结点,因此,无需对后端处理进行重大更改,就能在本体系结构中使用任一服务。 有关详细信息,请参阅将 IoT 设备连接到 Azure:IoT 中心和事件中心

  • 函数应用Azure Functions 是一个无服务器计算选项。 它使用事件驱动的模型,其中的一段代码(“函数”)由触发器调用。 在本体系结构中,当事件抵达事件中心时,会触发一个函数,该函数处理事件并将结果写入存储。

    函数应用适合用于处理来自事件中心的单个记录。 对于更复杂的流处理场景,请考虑结合 Azure Databricks 或 Azure 流分析使用 Apache Spark。

  • Azure Cosmos DBAzure Cosmos DB 是一个多模型数据库服务,可在基于消耗的无服务器模式下使用。 对于此方案,事件处理函数将使用 Azure Cosmos DB for NoSQL 存储 JSON 记录。

  • 队列存储队列存储用于将消息加入死信队列。 如果处理事件时出错,函数会将事件数据存储到死信队列供稍后处理。 有关详细信息,请参阅本文后面的复原能力部分

  • Azure MonitorMonitor 收集解决方案中部署的 Azure 服务的性能指标。 在仪表板中可视化这些指标可以洞察解决方案的运行状况。

  • Azure PipelinesPipelines 是用于生成、测试和部署应用程序的持续集成 (CI) 和持续交付 (CD) 服务。

注意事项

这些注意事项实施 Azure 架构良好的框架的支柱原则,即一套可用于改善工作负载质量的指导原则。 有关详细信息,请参阅 Microsoft Azure 架构良好的框架

可用性

下面所示的部署驻留在单个 Azure 区域中。 更具弹性的灾难恢复方法利用不同服务中的异地分发功能:

  • 事件中心。 创建两个事件中心命名空间:主要(主动)命名空间和次要(被动)命名空间。 除非故障转移到次要命名空间,否则消息将自动路由到主动命名空间。 有关详细信息,请参阅 Azure 事件中心异地灾难恢复
  • 函数应用。 部署另一个函数应用,用于等待从次要事件中心命名空间读取数据。 此函数将数据写入死信队列的辅助存储帐户。
  • Azure Cosmos DB。 Azure Cosmos DB 支持多个写入区域,因此可以向添加到 Azure Cosmos DB 帐户中的任何区域写入数据。 如果未启用多写入,仍可以故障转移主要写入区域。 Azure Cosmos DB 客户端 SDK 和 Azure 函数绑定会自动处理故障转移,因此你无需更新任何应用程序配置设置。
  • Azure 存储。RA-GRS 存储用于死信队列。 这会在另一个区域中创建只读副本。 如果主要区域不可用,可以读取当前位于队列中的项。 此外,在次要区域中预配另一个存储帐户,在故障转移后,函数可将数据写入该存储帐户。

可伸缩性

事件中心

事件中心的吞吐量容量以吞吐量单位来度量。 可以通过启用自动扩充来自动缩放事件中心。自动扩充可以根据流量,最高将吞吐量单位自动扩展到配置的上限。

函数应用中的事件中心触发器根据事件中心内的分区数进行缩放。 每次为每个分区分配一个函数实例。 若要最大程度地提高吞吐量,请分批接收事件,而不要逐个接收。

Azure Cosmos DB

Azure Cosmos DB 提供两种不同的容量模式:

  • 无服务器:用于具有间歇性或不可预测流量且平均峰值流量比低的工作负载。
  • 预配吞吐量,用于具有持续流量的工作负载,需要可预测的性能。

若要确保工作负载可缩放,请务必在创建 Azure Cosmos DB 容器时选择适当的分区键。 下面是适当分区键的一些特征:

  • 键值空间较大。
  • 为每个键值平均分配读取/写入,避免出现热键。
  • 为任何一个键值存储的数据上限不超过最大物理分区大小 (20 GB)。
  • 文档的分区键不会更改。 无法更新现有文档中的分区键。

在本参考体系结构的方案中,函数针对发送数据的每个设备正好存储一个文档。 函数使用 upsert 操作持续以最新的设备状态更新文档。 设备 ID 非常适合在此场景中用作分区键,因为写入操作将在不同的键之间平均分配,并且每个分区的大小严格受限,因为每个键值对应单个文档。 有关分区键的详细信息,请参阅 Azure Cosmos DB 中的分区和缩放

复原

配合 Functions 使用事件中心触发器时,可以捕获处理循环中的异常。 如果发生未经处理的异常,Functions 运行时不会重试消息。 如果无法处理某个消息,会将该消息放入死信队列。 使用带外进程来检查消息并确定纠正措施。

以下代码演示引入函数如何捕获异常,并将未经处理的消息放入死信队列。

[FunctionName("RawTelemetryFunction")]
[StorageAccount("DeadLetterStorage")]
public static async Task RunAsync(
    [EventHubTrigger("%EventHubName%", Connection = "EventHubConnection", ConsumerGroup ="%EventHubConsumerGroup%")]EventData[] messages,
    [Queue("deadletterqueue")] IAsyncCollector<DeadLetterMessage> deadLetterMessages,
    ILogger logger)
{
    foreach (var message in messages)
    {
        DeviceState deviceState = null;

        try
        {
            deviceState = telemetryProcessor.Deserialize(message.Body.Array, logger);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Error deserializing message", message.SystemProperties.PartitionKey, message.SystemProperties.SequenceNumber);
            await deadLetterMessages.AddAsync(new DeadLetterMessage { Issue = ex.Message, EventData = message });
        }

        try
        {
            await stateChangeProcessor.UpdateState(deviceState, logger);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Error updating status document", deviceState);
            await deadLetterMessages.AddAsync(new DeadLetterMessage { Issue = ex.Message, EventData = message, DeviceState = deviceState });
        }
    }
}

请注意,函数使用队列存储输出绑定将项放入队列。

所示代码还会将异常记录到 Application Insights。 可以使用分区键和序号将死信消息与日志中的异常相关联。

死信队列中的消息应该包含足够的信息,使你能够了解错误的上下文。 在此示例中,DeadLetterMessage 类包含异常消息、原始事件数据,以及反序列化的事件消息(如果有)。

public class DeadLetterMessage
{
    public string Issue { get; set; }
    public EventData EventData { get; set; }
    public DeviceState DeviceState { get; set; }
}

使用 Azure Monitor 监视事件中心。 如果显示了输入但未显示输出,则表示消息尚未处理。 在此情况下,请转到 Log Analytics 并查找异常或其他错误。

DevOps

尽可能使用基础结构作为代码 (IaC)。 IaC 使用声明性方法(如 Azure 资源管理器)管理基础结构、应用程序和存储资源。 这将有助于使用 DevOps 作为持续集成和持续交付 (CI/CD) 解决方案来自动化部署。 模板应进行版本控制并作为发布管道的一部分包含在内。

创建模板时,请对资源进行分组,以便按工作负载整理和隔离资源。 考虑工作负载的常见方法是单个无服务器应用程序或虚拟网络。 隔离工作负载的目的是将资源关联到团队,使 DevOps 团队能够独立管理这些资源的所有方面并执行 CI/CD。

此体系结构包括使用具有 YAML 和 Azure Functions 槽的 Azure 管道配置无人机状态函数应用的步骤。

部署服务时,需要对其进行监视。 请考虑使用 Application Insights 来让开发人员监视性能和检测问题。

有关详细信息,请参阅 DevOps 清单

灾难恢复

下面所示的部署驻留在单个 Azure 区域中。 更具弹性的灾难恢复方法利用不同服务中的异地分发功能:

  • 事件中心。 创建两个事件中心命名空间:主要(主动)命名空间和次要(被动)命名空间。 除非故障转移到次要命名空间,否则消息将自动路由到主动命名空间。 有关详细信息,请参阅 Azure 事件中心异地灾难恢复

  • 函数应用。 部署另一个函数应用,用于等待从次要事件中心命名空间读取数据。 此函数将数据写入死信队列的辅助存储帐户。

  • Azure Cosmos DB。 Azure Cosmos DB 支持多个写入区域,因此可以向添加到 Azure Cosmos DB 帐户中的任何区域写入数据。 如果未启用多写入,仍可以故障转移主要写入区域。 Azure Cosmos DB 客户端 SDK 和 Azure 函数绑定会自动处理故障转移,因此你无需更新任何应用程序配置设置。

  • Azure 存储。 将 RA-GRS 存储用于死信队列。 这会在另一个区域中创建只读副本。 如果主要区域不可用,可以读取当前位于队列中的项。 此外,在次要区域中预配另一个存储帐户,在故障转移后,函数可将数据写入该存储帐户。

成本优化

成本优化是关于寻找减少不必要的费用和提高运营效率的方法。 有关详细信息,请参阅成本优化支柱概述

使用 Azure 定价计算器估算成本。 下面是有关 Azure Functions 和 Azure Cosmos DB 的一些其他注意事项。

Azure Functions

Azure Functions 支持两种托管模型:

  • 消耗计划。 在运行代码时自动分配计算能力。
  • 应用服务计划。 一组虚拟机 (VM) 会分配给代码。 应用服务计划定义 VM 数目和 VM 大小。

在此体系结构中,到达事件中心的每个事件都会触发一个处理该事件的函数。 从成本角度来看,建议使用消耗计划,因为只需为所使用的计算资源付费。

Azure Cosmos DB

使用 Azure Cosmos DB,你需要为你针对数据库执行的操作以及你的数据使用的存储付费。

  • 数据库操作。 数据库操作的收费方式取决于你使用的 Azure Cosmos DB 帐户的类型。
    • 无服务器模式下,在 Azure Cosmos DB 帐户中创建资源时无需预配任何吞吐量。 在计费周期结束时,会针对你的数据库操作消耗的请求单位量计费。
    • 预配吞吐量模式下,指定每秒请求单位量 (RU/s) 所需的吞吐量,并按小时计费给定小时的最大预配吞吐量。 注意:由于预配的吞吐量模型会将资源提供给容器或数据库,因此即使不运行任何工作负载,你也要为已预配的吞吐量付费。
  • 存储。 给定小时内数据和索引所消耗的存储总量(以 GB 为单位)按统一费率计费。

在本参考体系结构中,函数针对发送数据的每个设备正好存储一个文档。 该函数使用更新插入操作,以最新设备状态持续更新文档,这在使用的存储方面比较经济高效。 有关详细信息,请参阅 Azure Cosmos DB 定价模型

使用 Azure Cosmos DB 容量计算器快速估算工作负载成本。

部署此方案

GitHub 徽标GitHub 中提供了本体系结构的参考实现。

后续步骤