现代无服务器和边缘运行时使得部署快速、可扩展的应用程序比以往任何时候都更容易。但随着应用程序变得更加分布式,性能问题往往从代码转移到基础设施。
本文探讨了全球分布式应用程序中常见的后端瓶颈:长时间的数据库往返、连接搅动、冷启动和低效查询。它还介绍了连接池、缓存、区域感知部署和更智能监控等实用解决方案,以确保您的应用程序无论用户身在何处都能保持快速。
边缘和无服务器的真正含义
在我们深入探讨之前,先快速定义一下背景。
无服务器意味着您编写一个函数,部署它,然后您的云提供商按需运行它。您无需考虑基础设施。它会自动进行扩展和缩减。
边缘意味着这些函数在靠近用户的地方运行。您的代码可能为日本用户在东京运行,为德国用户在法兰克福运行。这减少了物理距离,从而降低了延迟。
这对于前端响应能力和轻量级API来说非常棒——但它在幕后也引入了新的挑战。
为什么无状态函数会使事情复杂化
其中一个挑战是无服务器和边缘函数是无状态的。它们在请求之间不保留状态。因此,每次请求到来时,可能会启动一个新实例,并且没有与数据库的持久连接。
这导致了一个称为连接搅动的问题:数以百计的新连接被快速打开和关闭。
如果1000个用户同时访问您的函数,那么在短时间内会产生1000个数据库连接。大多数数据库都不是为此而设计的。您会达到连接限制,您的数据库开始限流,一切都变得缓慢。
冷启动加剧了这个问题。如果一个函数最近没有被使用,第一个请求会因为运行时启动并建立新连接而变慢。
使用连接池
解决方案?连接池。
连接池允许多个函数调用共享一组小的、持久的连接。它充当您数据库前的一个队列。每个函数不再打开新连接,而是从池中获取一个连接。
如果您使用的是像Prisma Postgres这样的数据库,连接池会在幕后自动处理——跨函数自动进行连接池和查询优化。其他工具,如PgBouncer或Supavisor(来自Supabase),也可能很有用。
单独为您的数据库使用连接池器就可以稳定高流量边缘环境中的性能。
至此,我们已经解决了如何管理过多连接的问题。但导致边缘性能缓慢的幕后还有另一个隐藏的罪魁祸首。
您的边缘应用程序不慢,是到数据库的往返时间慢
想象一下,您将边缘函数部署到东京。它运行速度极快——直到它调用弗吉尼亚州的数据库。突然间,您的响应时间增加了500毫秒。
这不是您的代码问题。这是地理位置问题。
边缘运行时速度很快,但如果您的函数必须跨洋查询数据库,每个请求都会增加数百毫秒的往返延迟。将其乘以多个查询,用户体验就会受到影响。
让我们探索如何解决这个问题。
缓存不需要实时的数据
减少不必要的数据库调用的最简单方法之一是缓存不经常更改的数据。
思考一下:产品列表、网站设置或功能标志。这些值不需要每次都重新获取。
您可以使用以下方式缓存数据库查询:
- 使用适当的
Cache-Control
头实现CDN级别缓存 - 边缘键值数据库(如Vercel KV、Cloudflare Workers KV)
- 在“热”无服务器函数内部进行内存缓存
- 使用具有内置缓存的数据库提供商,例如Prisma Postgres
缓存可以减轻数据库的负载,并显著缩短重复请求的往返时间。
将您的函数和数据库共置
另一种加速API的方法是将您的代码和数据库运行在同一个区域。
假设您的数据库托管在us-east-1
(弗吉尼亚州)。但您的边缘函数是从东京调用的。如果函数运行在靠近用户的地方(例如在ap-northeast-1
),但数据库位于太平洋彼岸的美国,那么每个查询都必须进行长途网络往返——多次。
延迟就是在这里迅速累积的。
函数可能看起来像这样:
如果函数靠近用户(在东京),但远离数据库(在弗吉尼亚州),每个数据库查询都需要时间——由于跨太平洋网络延迟、TLS握手和DNS解析,每次往返大约需要300毫秒。
此处理程序按顺序运行三个依赖查询
- 3个查询 × 300毫秒 = 约900毫秒总延迟
所以,即使您的函数还没有做任何实际工作,也几乎有一秒钟的时间只是在等待数据。
现在将它们共置
通过将函数运行在与数据库相同的区域(弗吉尼亚州),这些查询不再需要跨越海洋。它们保持在本地——通常每个查询在10-30毫秒内完成。
这意味着即使请求来自东京,整个响应也能在90毫秒内返回。用户仍然会因为距离产生一些延迟,但您的后端保持快速和一致。
区域锁定使其可行
像Vercel、AWS Lambda等平台允许您将函数锁定到特定区域——在本例中是us-east-1
。
对于Vercel中的边缘部署,region
配置可以使您锁定一个区域
这种设置在以下情况最为理想:
- 您按顺序运行多个查询
- 您想避免编写复杂的客户端缓存
- 您关心稳定、低延迟的API
与将后端分布在全球各地不同,将计算与数据共置可以避免数百毫秒的开销——只需一行配置。
何时考虑多区域数据库
如果您的大多数用户都在读取数据并且分布在全球各地,多区域数据库会有所帮助。
这会将您的数据复制到不同区域,因此欧洲、亚洲或澳大利亚的用户可以从离他们最近的副本读取数据。这可以改善延迟并减少单个数据库节点的负载。
像AWS这样的云提供商提供了多区域功能,如DynamoDB Global Tables和Aurora Global,但像CockroachDB这样专门构建的数据库也使得跨区域复制数据以获得更好性能变得容易。
分布式数据库在以下情况是很好的选择:
- 读取量远超写入量
- 轻微的数据不一致(最终一致性)可接受
- 您想减少全球往返次数
但如果出现以下情况则暂缓使用:
- 您的应用程序需要严格一致性(例如金融交易)
- 您在多个区域有频繁的写入操作
- 您需要精确控制版本冲突
关注您的查询
即使您使用了缓存并进行了共置,糟糕的查询仍然会成为性能瓶颈。
尽早设置监控以跟踪百分位延迟
- p50 = 中位数查询时间
- p75 = 较慢的查询,通常在轻负载下
- p99 = 最坏情况下的查询,性能问题通常隐藏在此处
例如,p50为30毫秒很棒——但如果您的p99为700毫秒,一些用户仍然会遇到痛苦的延迟。
在识别性能瓶颈时,还要注意:
- N+1查询模式
- 过滤字段上缺少索引
- 过度获取嵌套数据
仅仅优化几个繁重的查询就能将您的总体延迟减少一半。像Prisma Optimize这样的工具通过识别边缘和无服务器函数中最慢的查询、查明根本原因并提出可行的修复建议,从而使这一过程变得更容易。
快速回顾
以下是问题及解决方案的快速一览。
总结
部署到边缘很容易。但要让您的应用程序感觉快速——尤其是在全球范围内——则需要更多的思考。
好消息是?您无需重建您的技术栈。一些小的改变——缓存、共置、连接池和监控——就能带来巨大的不同。
下次当您的边缘函数开始感觉缓慢时,很少是计算问题。十有八九,是您的数据库问题。这通常是速度下降的起点,也是速度提升的关键所在。
让我们继续交流
如果这篇文章对您有所帮助,我们很乐意听取您的反馈。在X上标记我们并分享您正在构建的内容。或者如果您想聊天、解决问题或深入探讨数据库和性能,可以加入我们的Discord。
我们还会定期在YouTube上发布深度视频。如果您对此感兴趣,请点击订阅。更多示例、更多性能技巧,也许还有一些惊喜发布。我们到时见。
不要错过下一篇文章!
订阅Prisma新闻邮件