应用程序的快速性能对于提供卓越的用户体验至关重要!在本文中,我们将探讨无服务器应用程序中优化冷启动和处理程序性能的陷阱和最佳实践。
目录
引言
通过函数即服务 (FaaS) 实现的无服务器部署模式使开发人员能够以可扩展且经济高效的方式轻松部署其应用程序。然而,这种便利性和灵活性带来了一系列需要注意的复杂性。
在早期使用长期运行的服务器的部署模型中,只要您的服务器启动并运行,您的执行环境始终可用。这使您的应用程序能够立即响应传入的请求。
新的无服务器范例要求我们作为开发人员找到确保您的函数可用并尽快响应请求的方法。
无服务器函数中的性能陷阱
在无服务器环境中,您的函数可以缩减为零。这使您能够将运营成本保持在最低水平,但确实会带来技术成本。当没有可用的函数实例来响应请求时,必须实例化一个新实例。这被称为冷启动。
注意:有关冷启动是什么以及我们在使用 Prisma ORM 时如何努力尽可能缩短冷启动的详细说明,请阅读我们最近的文章:我们如何通过 Prisma 将无服务器冷启动速度提高 9 倍。
缓慢的冷启动会导致用户体验非常差,并最终降低他们对您的产品的体验。这是问题 #1。
除了冷启动问题之外,您的实际处理程序函数的性能也极其重要。无服务器应用程序通常由许多小的、隔离的函数组成,这些函数通过 HTTP、事件总线、队列等协议相互交互。
单个函数之间的这种相互通信会在每个请求上创建依赖链。如果其中一个函数速度非常慢,它将影响链的其余部分。因此,处理程序性能是问题 #2。
优化 FaaS 性能的最佳实践
在 Prisma,我们在过去几个月里深入研究了无服务器环境,并优化了 Prisma 在其中的行为方式。在此过程中,我们发现了许多最佳实践,您可以将这些实践应用于您自己的应用程序,以尽可能保持高性能。
在本文的其余部分中,我们将介绍我们发现的一些最佳实践。
将您的函数托管在与数据库相同的区域
任何时候您托管需要访问传统关系数据库的应用程序或函数时,您都需要启动与该数据库的连接。这需要时间和延迟。对于您执行的任何查询也是如此。
您的目标是尽可能将该时间和延迟保持在绝对最低限度。目前最好的方法是确保您的应用程序或函数部署在与数据库服务器相同的地理区域中。
您的请求到达数据库服务器的距离越短,建立连接的速度就越快。在部署无服务器应用程序时,这是一个非常重要的事情,因为不这样做产生的负面影响可能是巨大的。
不这样做会影响以下时间:
- 完成 TLS 握手
- 与数据库建立安全连接
- 执行您的查询
所有这些因素都会在冷启动期间激活,因此会导致使用 Prisma 数据库对应用程序的冷启动产生影响。
在研究这种情况对冷启动的影响时,我们尴尬地注意到,我们在 AWS Lambda 的eu-central-1
中使用无服务器函数进行了前几次测试,而 RDS PostgreSQL 实例托管在us-east-1
中。我们很快就修复了这个问题,“之后”的测量结果清楚地显示了这会对您的数据库延迟产生巨大的影响,无论是连接的创建还是执行的任何查询
之前
之后
使用离您的函数不够近的数据库会直接增加冷启动的持续时间,并且在稍后处理热请求期间执行查询时也会产生相同的成本。
在处理程序之外运行尽可能多的代码
考虑以下无服务器函数
在某些情况下,AWS Lambda 在函数执行环境的初始启动期间会为虚拟环境分配更多的内存和 CPU。之后,在您的热函数调用期间,您的函数可用的内存和 CPU 实际上保证是您在函数配置中配置的值 — 这可能会低于函数外部的值。
注意:如果您好奇,以下是一些解释上述资源分配差异的资源
通过将代码移到处理程序的范围之外,可以利用此知识来提高函数的性能。这可确保在环境有更多可用资源时执行处理程序之外的代码。
例如,您可能会在您的无服务器函数中执行以下操作
上面的处理程序函数计算斐波那契数列中的第 40 个数字。计算完成后,您的函数将继续处理请求并最终返回响应。
将其移到处理程序的外部允许在环境有更多可用资源时进行计算,并导致它只运行一次,而不是在每次调用时运行。
更新后的代码如下所示
需要记住的另一件事是 AWS Lambda 支持顶层 await,这允许您在处理程序之外运行异步代码。
我们发现,在处理程序外部显式运行 Prisma Client 的$connect
函数对您的函数性能有积极影响
保持您的函数尽可能简单
无服务器函数的设计目标是非常小巧、隔离的代码片段。如果你的函数的 JavaScript 代码和依赖树庞大复杂,或者分散在多个文件中,运行时读取和解释它们会花费更长的时间。
以下是一些可以提高启动性能的方法:
- 只包含你的函数实际需要执行其工作的代码
- 不要使用加载大量你不需要的内容的库和框架
这里的一般原则是:需要解释的代码越少,依赖树越简单,请求的处理速度就越快。
不要做超出需要的工作
任何可能在函数每次调用时重复使用的值计算或昂贵的操作,都应该缓存在处理程序范围之外的变量中。这样做可以避免每次调用函数时都执行这些昂贵的操作。
考虑这样一种情况:从数据库中获取一个不经常变化的值,例如可配置的重定向
虽然这段代码可以工作,但每次调用该函数时都会运行查找重定向的查询。这并不理想,因为它需要访问数据库来查找你之前调用时已经找到的值。
更好的编写方法是首先检查处理程序之外的缓存值。如果未找到,则运行查询并存储结果以备下次使用
现在,查询仅在你的函数第一次调用时运行。任何后续调用都将使用缓存的值。
预置并发
最后要考虑的是使用预置并发,如果你正在使用 AWS Lambda,可以保持你的 Lambda 处于“温暖”状态。
根据 AWS 文档
注意:预置并发会初始化请求数量的执行环境,以便它们准备好立即响应你的函数调用。请注意,配置预置并发会产生 AWS 账户费用。
这使你可以维护指定数量的可用执行环境,这些环境可以响应请求,而无需冷启动。
虽然这听起来很棒,但有几件重要的事情需要记住
- 使用预置并发需要支付额外的费用
- 你的应用程序永远不会缩减到 0
这些都是重要的考虑因素,因为额外的成本可能不适合你的特定场景。在采用此措施之前,我们建议你查看它为你的应用程序带来的价值,并考虑额外的成本是否合理。
结论
在本文中,我们介绍了为使用 Prisma ORM 构建和部署无服务器函数的开发人员提出的一些最佳实践。本文中提到的增强功能和最佳实践并非详尽无遗。
为了快速回顾,我们建议你:
- 将数据库托管在尽可能靠近已部署函数的位置
- 在处理程序之外运行尽可能多的代码
- 尽可能缓存可重用的值和计算结果
- 尽可能保持函数简单
- 如果你愿意处理财务上的权衡,请考虑使用预置并发
感谢您的阅读,我们希望这些信息对您有所帮助!
不要错过下一篇文章!
注册 Prisma 新闻简报