2023年5月1日

加速无服务器应用的最佳实践

应用的快速性能对于提供出色的用户体验至关重要!在本文中,我们将探讨优化无服务器应用程序中冷启动和处理程序性能的陷阱和最佳实践。

Best Practices To Speed Up Your Serverless Applications

目录

引言

通过函数即服务(FaaS)的无服务器部署模式使开发者能够以可扩展且经济高效的方式轻松部署其应用程序。然而,这种便利性和灵活性也伴随着一系列需要注意的复杂性。

在过去使用长时间运行服务器的部署模型中,只要您的服务器启动并运行,执行环境就始终可用。这使得您的应用程序能够立即响应传入的请求。

新的无服务器范式要求我们开发者找到方法,确保您的函数尽快可用并响应请求。

无服务器函数中的性能陷阱

在无服务器环境中,您的函数可以扩展到零。这使得您可以将运营成本降至最低,但也伴随着技术成本。当没有函数的实例可用于响应请求时,必须实例化一个新的实例。这被称为冷启动

注意:有关冷启动的详细解释以及我们如何在使用 Prisma ORM 时努力使其尽可能短,请阅读我们的最新文章:我们如何将 Prisma 的无服务器冷启动速度提升9倍

缓慢的冷启动会导致用户体验非常糟糕,并最终降低他们对您产品的体验。这是问题一。

除了冷启动问题,您的实际处理程序函数的性能也非常重要。无服务器应用程序通常由许多通过 HTTP、事件总线、队列等协议相互交互的小型、隔离的函数组成...

个体函数之间的这种相互通信在每个请求上创建了一个依赖链。如果其中一个函数非常慢,它将影响链中的其余部分。因此,处理程序性能是问题二。

优化 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 账户费用。

这使得您可以保持指定数量的可用执行环境,这些环境无需冷启动即可响应请求。

虽然这听起来很棒,但有几件重要的事情需要记住:

  • 使用预置并发需要额外付费
  • 您的应用程序永远不会缩减到零

这些都是重要的考虑因素,因为额外增加的成本可能不值得您特定场景的投入。在采取此措施之前,我们建议您审视它为您的应用程序带来的价值,并考虑增加的成本是否合理。

结论

在本文中,我们探讨了为使用 Prisma ORM 构建和部署无服务器函数的开发者推荐的一些最佳实践。本文提到的增强功能和最佳实践并非详尽无遗。

快速回顾一下,我们建议您:

  • 将数据库托管在尽可能靠近已部署函数的位置
  • 在处理程序外部运行尽可能多的代码
  • 尽可能缓存可重复使用的值和计算结果
  • 让您的函数尽可能简单
  • 如果您愿意承担财务权衡,可以考虑使用预置并发

感谢您的阅读,希望这些信息对您有所帮助!

不要错过下一篇文章!

订阅 Prisma 邮件列表