你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
本文提供改进 无服务器 函数应用的性能和可靠性的指导。 有关更常规的 Azure Functions 最佳做法集,请参阅 Azure Functions 最佳做法。
以下是使用 Azure Functions 生成和构建无服务器解决方案的最佳做法。
避免使用长时间运行的函数
大型且长时间运行的函数可能会导致意想不到的超时问题。 若要了解有关给定托管计划的超时的详细信息,请参阅 函数应用超时持续时间。
由于许多 Node.js 依赖项,函数可能会变得很大。 导入依赖项还可能导致加载时间增加,从而导致意外超时。 依赖项是显式和隐式加载的。 代码加载的单个模块可能会加载自己的附加模块。
尽可能将大型函数重构为较小的函数集,以便协同工作并快速返回响应。 例如,Webhook 或 HTTP 触发器函数可能需要在特定时间限制内进行确认响应;Webhook 通常需要立即响应。 可以将 HTTP 触发器有效负载传递到队列中,以供队列触发器函数处理。 此方法允许延迟实际工作并返回即时响应。
确保后台任务完成
当函数启动任何任务、回调、线程、进程时,它们必须在函数代码返回之前完成。 由于 Functions 不会跟踪这些后台线程,因此无论后台线程状态如何,都可能发生站点关闭,这可能会导致函数出现意外行为。
例如,如果函数启动后台任务并在任务完成之前返回成功的响应,则 Functions 运行时会将执行视为成功完成,而不考虑后台任务的结果。 如果此后台任务正在执行基本工作,则站点关闭可能会抢占该任务,使该工作处于未知状态。
跨函数通信
Durable Functions 和 Azure 逻辑应用 旨在管理多个函数之间的状态转换和通信。
如果不使用 Durable Functions 或 Logic Apps 来与多个函数集成,最好使用存储队列进行跨函数通信。 主要原因是存储队列比其他存储选项更便宜、更易于预配。
存储队列中的单个消息的大小限制为 64 KB。 如果需要在函数之间传递较大的消息,可以使用 Azure 服务总线队列支持标准层中最多 256 KB 的消息大小,在高级层中最多支持 100 MB 的消息。
如果需要在处理之前筛选消息,则服务总线主题非常有用。
事件中心可用于支持大量通信。
将函数编写为无状态
如果可能,函数应为无状态且幂等。 将任何必需的状态信息与数据相关联。 例如,正在处理的排序可能具有关联的 state
成员。 当函数本身保持无状态时,函数可以基于该状态处理顺序。
建议将幂等函数与计时器触发器一起使用。 例如,如果有一些绝对必须每天运行一次的内容,请编写它,以便它可以在一天中随时运行,结果相同。 当特定日期没有工作时,该函数可以退出。 此外,如果上一次运行未能完成,则下一次运行应从其离开的位置开始。 对于在失败时重试的基于消息的绑定,这一点尤其重要。 有关详细信息,请参阅 为相同的输入设计 Azure 函数。
编写防御函数
假设函数随时可能遇到异常。 设计您的函数,使其能够在下次执行时从以前的失败点继续。 请考虑一种需要执行以下操作的情景:
- 查询数据库中的 10,000 行。
- 为这些行中的每一行创建一个队列消息,以便进一步处理。
根据您的系统的复杂程度,您可能会遇到:下游服务表现异常、断网情况,或达到或超过配额限制等。这些情况都可能随时影响您的功能。 需设计函数,使其做好该准备。
将其中 5,000 个项插入队列以进行处理后,如果发生故障,代码会如何反应? 跟踪已完成的一组中的项。 否则,下次可以再次插入它们。 这种双重插入可能会对工作流产生严重影响,因此 使函数具有幂等性。
如果已处理队列项,则允许函数不执行任何操作。
利用已为 Azure Functions 平台中使用的组件提供的防御措施。 有关示例,请参阅 Azure 存储队列触发器和绑定文档中的处理有害队列消息。
对于基于 HTTP 的函数,请考虑使用 Azure API 管理的 API 版本控制策略 。 例如,如果必须更新基于 HTTP 的函数应用,请将新更新部署到单独的函数应用,并使用 API 管理修订或版本将客户端定向到新版本或修订版。 一旦所有客户端都使用版本或修订,并且上一个函数应用上不会再执行任何执行,则可以取消预配以前的函数应用。
函数组织最佳做法
作为解决方案的一部分,可以开发和发布多个函数。 这些函数通常组合到单个函数应用中,但也可以分别在多个函数应用中运行。 在高级和专用(应用服务)托管计划中,多个函数应用还可以在同一计划中运行来共享相同的资源。 函数和函数应用的分组方式会影响整个解决方案的性能、缩放、配置、部署和安全性等方面。 没有适用于每个方案的规则,因此在规划和开发函数时,请考虑本节中的信息。
组织函数以改善性能和缩放情况
你创建的每个函数都有内存占用。 虽然此占用空间通常很小,但函数应用中的函数过多可能会导致新实例上应用启动速度较慢。 这也意味着函数应用的总体内存使用率可能更高。 很难说单个应用中应该有多少个函数,这取决于你的特定工作负荷。 但是,如果函数在内存中存储大量数据,请考虑在单个应用中使用更少的函数。
如果在单个高级计划或专用(应用服务)计划中运行多个函数应用,这些应用将共享分配给该计划的相同资源。 如果有一个函数应用具有比其他函数应用更高的内存需求,则会在部署应用的每个实例上使用不成比例的内存资源。 由于这可能会为每个实例上的其他应用留出更少的内存,因此你可能希望在自己的单独的托管计划中运行使用高内存的函数应用。
请考虑是否要将具有不同负载特征的函数进行分组。 例如,如果你有一个处理数千条队列消息的函数,而另一个函数只偶尔调用,但内存需求较高,则你可能希望在单独的函数应用中部署它们,以便它们获取自己的资源集,并且彼此独立缩放。
组织函数以进行配置和部署
函数应用有一个 host.json
文件,用于配置函数触发器和 Azure Functions 运行时的高级行为。 对 host.json
文件的更改将应用于应用中的所有函数。 如果你有一些需要自定义配置的函数,请考虑将它们移动到自己的函数应用中。
本地项目中的所有函数作为一组文件一起部署到 Azure 中的函数应用。 可能需要单独部署单个函数,或者对某些函数使用 部署槽 等功能,而不是使用其他功能。 在这种情况下,应将这些函数(在单独的代码项目中)部署到不同的函数应用。
按权限组织函数
应用程序设置中存储的连接字符串和其他凭据为函数应用中的所有函数提供关联资源中相同的权限集。 请考虑将具有特定凭据访问权限的函数数量降至最少,具体方法是将不使用这些凭据的函数移动到单独的函数应用中。 你始终可以使用诸如函数链之类的技术在不同函数应用中的函数之间传递数据。
可伸缩性最佳做法
有许多因素会影响函数应用实例的缩放方式。 文档中提供了 函数缩放 的详细信息。 以下是确保函数应用的最佳可伸缩性的一些最佳做法。
共享和管理连接
尽可能重复使用与外部资源的连接。 了解如何 管理 Azure Functions 中的连接。
避免共享存储帐户
创建函数应用时,必须将它与存储帐户相关联。 存储帐户连接在 AzureWebJobsStorage 应用程序设置中维护。
若要最大程度地提高性能,请对每个函数应用使用单独的存储帐户。 当涉及到 Durable Functions 或 Event Hubs 触发的函数时,由于这两者都会生成大量存储事务,因此该方法尤其重要。 当应用程序逻辑与 Azure 存储交互时,无论是直接(使用存储 SDK)交互还是通过某个存储绑定进行交互,都应使用专用存储帐户。 例如,如果有事件中心触发的函数将一些数据写入 Blob 存储,请使用两个存储帐户:一个用于函数应用,另一个用于函数存储的 blob。
请勿在同一函数应用中混合测试和生产代码
函数应用内的函数共享资源。 例如,共享内存。 如果在生产环境中使用函数应用,请不要向其添加与测试相关的函数和资源。 在生产代码执行期间,这可能会导致意外的开销。
请注意在生产函数应用中加载的内容。 内存是应用中每个函数的平均值。
如果在多个 .NET 函数中引用了共享程序集,请将其放在一个公共共享文件夹中。 否则,您可能会意外部署同一二进制文件的多个版本,这些版本在不同函数之间表现不一致。
请勿在生产代码中使用详细日志记录,这会对性能产生负面影响。
使用异步代码,但避免阻止调用
建议使用异步编程,尤其是在涉及阻塞 I/O 操作时。
在 C# 中,始终避免在实例上引用Result
属性或调用Wait
Task
方法。 此方法可能会导致线程耗尽。
小窍门
如果计划使用 HTTP 或 WebHook 绑定,请制定计划来避免因实例化 HttpClient
不当导致的端口耗尽现象。 有关详细信息,请参阅如何在 Azure Functions 中管理连接。
使用多个工作进程
默认情况下,Functions 的任何主机实例都使用单个工作进程。 为了提高性能,尤其是使用 Python 等单线程运行时,请使用 FUNCTIONS_WORKER_PROCESS_COUNT 来增加每个主机的工作进程数(最多 10 个)。 然后,Azure Functions 尝试在这些辅助角色之间均匀分配同时发出的函数调用。
FUNCTIONS_WORKER_PROCESS_COUNT 适用于 Functions 在横向扩展应用程序来满足需求时创建的每一个主机。
尽量批量接收消息
某些触发器(如事件中心)支持在单个调用上接收一批消息。 批处理消息的性能要好得多。 可以在host.json
文件中配置最大批大小,具体信息请参见host.json 参考文档。
对于 C# 函数,可以将类型更改为强类型数组。 例如,方法签名可以是 EventData[] sensorEvent
,而不是 EventData sensorEvent
。 对于其他语言,需要将function.json
到many
之间的基数属性显式设置,以便启用批处理,如下所示。
配置主机行为以更好地处理并发性
host.json
函数应用中的文件允许配置主机运行时和触发器行为。 除了批处理行为之外,还可以管理多个触发器的并发。 调整这些选项中的值往往有助于每个实例根据被调用函数的需求适当缩放。
host.json 文件中的设置应用于应用内单个 实例 内的所有函数。 例如,如果函数应用具有两个 HTTP 函数,并且 maxConcurrentRequests
请求设置为 25,则对任一 HTTP 触发器的请求将计入共享的 25 个并发请求。 当函数应用扩展到 10 个实例时,十个函数实际上允许 250 个并发请求(每个实例 10 个实例 * 25 个并发请求)。
host.json 配置文章中提供了其他主机配置选项。
后续步骤
有关详细信息,请参阅以下资源: