Azure Functions 中的并发

本文介绍 Azure Functions 中事件驱动的触发器的并发行为。 它还比较了静态和动态并发模型。

在 Functions 中,可以在单个计算实例上并发运行给定函数的多个执行进程。 例如,假设函数应用中有三个不同的函数,该应用已横向扩展到多个实例以处理增加的负载。 在这种情况下,每个函数都在执行以响应所有三个实例中的单个调用,而给定实例可以处理同一类型的多个调用。 请记住,单个实例上的函数执行共享相同的内存、CPU 和连接资源。 由于每个实例上可以并发运行多个函数执行,因此每个函数都需要有管理并发执行数的方法。

当应用托管在动态缩放计划(“消耗”、“Flex 消耗”或“高级”)中时,主机会根据传入事件的数量纵向扩展或缩减函数应用实例的数量。 有关详细信息,请参阅事件驱动的缩放。 将函数托管在专用(应用服务)计划中时,必须手动配置实例或设置自动缩放方案

这些缩放决策也直接受到给定实例上的并发执行数的影响。 当动态缩放计划中的应用达到并发限制时,可能需要进行缩放以满足传入的需求。

Functions 提供了两种主要方法来管理并发:

  • 静态并发可以配置主机级的并发限制,这些限制特定于单个触发器。 这是 Functions 的默认并发行为。

  • 动态并发对于某些触发器类型,Functions 主机可以自动确定应用中该触发器的最佳并发级别。 必须选择加入此并发模型

静态并发

默认情况下,大多数触发器都支持主机级静态配置模型。 在此模型中,每个触发器类型都有每实例并发限制。 但是,对于大多数触发器,也可以为该触发器类型请求特定每实例并发数。 例如,服务总线触发器host.json 文件中提供 MaxConcurrentCallsMaxConcurrentSessions 设置。 这些设置共同控制每个函数在每个实例上并发处理的最大消息数。 其他触发器类型具有用于跨实例对调用进行负载均衡的内置机制。 例如,事件中心和 Azure Cosmos DB 都使用基于分区的方案。

对于支持并发配置的触发器类型,选择的设置将应用于所有正在运行的实例。 这样,就可以控制每个实例上函数的最大并发数。 例如,当函数是 CPU 密集型或资源密集型函数时,可以选择限制并发,使实例保持正常运行,并依赖于缩放来处理增加的负载。 同样,如果你的函数向受到限制的下游服务发出请求,你也应该考虑限制并发,以避免下游服务过载。

HTTP 触发器并发

仅适用于 Flex 消耗计划(预览版)

Flex 消耗计划将所有 HTTP 触发器函数作为一个组一起进行缩放。 有关详细信息,请参阅按函数缩放。 下表根据配置的实例内存大小,指示给定实例上 HTTP 触发器的默认并发设置。

实例大小 (MB) 默认并发数*
2048 16
4096 32

*对于 Python 应用,所有实例大小的默认 HTTP 触发器并发数为 1

这些默认值应该非常适合大多数情况,你可以在开始时使用这些默认值。 请注意,在 HTTP 请求数一定的情况下,增加 HTTP 并发值可减少处理 HTTP 请求所需的实例数。 同样,减少 HTTP 并发值则需要更多实例来处理相同的负载。

如果需要微调 HTTP 并发数,可以使用 Azure CLI 来实现。 有关详细信息,请参阅设置 HTTP 并发限制

仅当尚未设置自己的 HTTP 并发设置时,上表中的默认并发值才适用。 如果尚未显式设置 HTTP 并发设置,则在更改实例大小时,默认并发数会如表中所示增加。 在专门设置 HTTP 并发值后,即使实例大小发生更改,该值也会保持不变。

确定最佳静态并发

虽然通过静态并发配置可以控制特定的触发器行为(例如限制函数),但可能很难确定这些设置的最佳值。 通常,必须通过负载测试的迭代过程才能得出可接受的值。 即使在你确定了一组适用于特定的负载配置文件的值后,来自连接服务的事件数量也可能每天都有变化。 这种可变性意味着应用可能经常使用次优值运行。 例如,函数应用可能会在一周的最后一天处理要求特别高的消息有效负载,这就需要降低并发。 但是,在该周的剩余时间,消息有效负载更简单,这意味着你可以在这些时间使用更高的并发级别。

理想情况下,我们希望系统允许实例处理尽可能多的工作,同时使每个实例保持正常状态和较低的延迟,而动态并发正是为此目的而设计的。

动态并发

Functions 现在提供一个动态并发模型,可以简化同一计划中运行的所有函数应用的并发配置。

注意

目前只有 Azure Blob 触发器、Azure 队列触发器和服务总线触发器支持动态并发,并且要求你使用下文扩展支持一节中列出的版本。

优点

使用动态并发提供以下优点:

  • 简化的配置:不再需要手动确定每个触发器的并发设置。 系统会不断地获知工作负载的最佳值。
  • 动态调整:可以实时动态调高或调低并发,使系统能够适应不断变化的负载模式。
  • 实例运行状况保护:运行时会将并发限制为函数应用实例可以轻松处理的级别。 这可以防止应用承担的工作量超过其应有的工作量,从而避免应用过载。
  • 提高了吞吐量:由于各个实例接受的工作不会超过它们可以快速处理的工作,因此总体吞吐量将得到提高。 这使得工作可以在实例之间更有效地进行负载均衡。 对于可以处理较高负载的函数,可以通过将并发数增加到高于默认配置的值来获取更高的吞吐量。

动态并发配置

可以在主机级别(在 host.json 文件中)启用动态并发。 启用后,将根据需要自动调整任何支持此功能的绑定扩展的并发级别。 在这些情况下,动态并发设置会替代任何手动配置的并发设置。

默认已禁用动态并发。 启用动态并发后,每个函数的并发从 1 开始,并且会调整为主机确定的最佳值。

可以通过在 host.json 文件中添加以下设置,在函数应用中启用动态并发:

    { 
        "version": "2.0", 
        "concurrency": { 
            "dynamicConcurrencyEnabled": true, 
            "snapshotPersistenceEnabled": true 
        } 
    } 

SnapshotPersistenceEnabledtrue(默认值)时,获知的并发值会定期保存到存储中,因此新实例将从这些值而不是从 1 开始,并且必须重新获知并发值。

并发管理器

在幕后,如果已启用动态并发,将有一个并发管理器进程在后台运行。 此管理器持续监视实例运行状况指标(例如 CPU 和线程利用率),并根据需要更改限制。 启用一个或多个限制时,函数并发将会调低,直到主机再次正常。 禁用限制时,可以提高并发。 将根据这些限制,使用各种试探法以智能方式按需调高或调低并发。 一段时间后,每个函数的并发将稳定在特定的级别。

每个函数的并发级别会受到管理。 因此,系统可在需要较低并发级别的资源密集型函数与可以处理较高并发的更轻型函数之间进行均衡。 均衡每个函数的并发有助于保持函数应用实例的总体正常状态。

启用动态并发后,你会在日志中看到动态并发决策。 例如,在启用各种限制后,每当调高或调低每个函数的并发,你都会看到日志。 这些日志会写入在跟踪表中的“Host.Concurrency”日志类别下。

扩展支持

动态并发是在主机级别为函数应用启用的,任何支持动态并发的扩展将在该模式下运行。 动态并发需要主机和单个触发器扩展之间的协作。 只有列出的以下扩展版本支持动态并发。

扩展 版本 说明
队列存储 版本 5.x(存储扩展) Azure 队列存储触发器具有自己的消息轮询循环。 使用静态配置时,并发由 BatchSize/NewBatchThreshold 配置选项控制。 使用动态并发时,将忽略这些配置值。 动态并发集成到消息循环中,因此会动态调整每次迭代提取的消息数。 启用限制(主机过载)后,消息处理将暂停,直到禁用限制为止。 禁用限制后,并发将提高。
Blob 存储 版本 5.x(存储扩展) 在内部,Azure Blob 存储触发器使用与 Azure 队列触发器相同的基础结构。 需要处理新的/更新的 Blob 时,消息将写入到平台管理的控制队列,并使用与队列触发器相同的逻辑来处理该队列。 启用动态并发后,将动态管理对该控制队列的处理并发。
服务总线 版本 5.x 服务总线触发器目前支持三种执行模型。 动态并发对这些执行模型的影响如下:

� 单一调度主题/队列处理:每次调用函数只会处理一条消息。 使用静态配置时,并发由 MaxConcurrentCalls 配置选项控制。 使用动态并发时,将忽略该配置值,并动态调整并发。
� 基于会话的单一调度主题/队列处理:每次调用函数只会处理一条消息。 每个实例根据主题/队列的活动会话数租用一个或多个会话。 每个会话中的消息将连续进行处理,以保证会话中的顺序。 不使用动态并发时,并发由 MaxConcurrentSessions 设置控制。 启用动态并发时,将忽略 MaxConcurrentSessions,每个实例处理的会话数将动态调整。
� 批处理:每次调用函数会处理一批消息,由 MaxMessageCount 设置控制。 由于批处理调用是连续性的,因此批处理触发的函数的并发始终为 1,且动态并发不适用。

后续步骤

有关详细信息,请参阅以下资源: