排查 App Engine 应用延迟时间过长的问题

在许多情况下,应用中增加的延迟时间最终会导致 5xx 服务器错误。由于错误和延迟峰值的根本原因可能相同,因此请应用以下策略来排查延迟问题:

  1. 确定延迟问题的范围
  2. 确定原因
  3. 问题排查

确定延迟问题的范围

通过以下问题定义问题的范围:

  • 哪些应用、服务和版本会受到此问题的影响?
  • 此问题会影响服务的哪些特定端点?
  • 这是否影响了全球的所有客户端,或只影响部分客户端?
  • 突发事件的开始时间和结束时间是什么?考虑指定时区。
  • 具体错误是什么?
  • 观察到的延迟时间增量(通常指定为特定百分位的增加)是多少?例如,第 90 百分位的延迟时间增加了 2 秒。
  • 如何测量延迟时间?具体而言,您是在客户端测量,还是在 Cloud Logging 或 App Engine 服务基础架构提供的 Cloud Monitoring 延迟时间数据中可见?
  • 您的服务的依赖项有哪些?其中的任何依赖项会遇到突发事件?
  • 您是否执行了任何可能触发此问题的代码、配置或工作负载更改?

服务可能拥有自己的自定义监控和日志记录,您可以使用这些监控和日志记录进一步缩小问题的范围。定义问题的范围将引导您找到可能的根本原因,并确定接下来的问题排查步骤。

找出原因

确定请求路径中的哪个组件最有可能导致延迟或错误。请求路径中的主要组件如下所示:

客户端 --> 互联网 --> Google Front End (GFE) --> App Engine 服务基础架构 --> 服务实例

如果上述信息未指明故障来源,请在查看服务实例的运行状况和性能时,采用以下策略:

  1. 监控 App Engine 请求日志。如果您在这些日志中看到 HTTP 状态代码错误或延迟时间增加,则问题可能出在运行您的服务的实例中。

  2. 如果服务实例数量未扩容到与流量水平相匹配,您的实例可能会过载,导致错误和延迟时间增加。

  3. 如果您在 Cloud Monitoring 中看到错误或延迟增加,问题可能出在负载均衡器上游,该负载均衡器用于记录 App Engine 指标。在大多数情况下,这表明服务实例存在问题。

  4. 如果您在监控指标中看到延迟时间增加或错误,但未看到请求日志,则表示负载均衡器出现故障,或者实例出现严重故障,导致负载均衡器无法路由请求。如需区分这些情况,请查看突发事件开始之前的请求日志。如果请求日志在故障前显示延迟时间增加,则表示应用实例在负载均衡器停止将请求路由到它们之前开始出现故障。

问题排查

本部分介绍了针对请求路径中以下组件导致的延迟时间过长问题的问题排查策略:

  1. 互联网
  2. Google 前端 (GFE)
  3. App Engine 服务基础架构
  4. 应用实例
  5. 应用依赖项

互联网

由于连接不佳或带宽较低,您的应用可能会遇到延迟问题。

互联网连接状况欠佳

如需确定问题是否是互联网连接状况欠佳,请在客户端上运行以下命令:

$ curl -s -o /dev/null -w '%{time_connect}\n' <hostname>

time_connect 的值表示客户端与最近的 Google Front End 的连接的延迟时间。对于连接速度缓慢的情况,请使用 traceroute 进一步排查问题,以确定网络上的哪个跃点导致延迟。

从不同地理位置的客户端运行测试。App Engine 会自动将请求路由到最近的 Google 数据中心,该数据中心因客户端的位置而异。

带宽低

应用可能会快速响应;但是,网络瓶颈会延迟 App Engine 服务基础架构通过网络快速发送数据包,从而减慢响应速度。

Google Front End (GFE)

由于路由不正确、从 HTTP/2 客户端发送的并发请求或 SSL 连接终止,您的应用可能会遇到延迟问题。

将客户端 IP 映射到地理区域

Google 会根据 DNS 查找中使用的客户端 IP 地址将 App Engine 应用的主机名解析为离客户端最近的 GFE。如果客户端的 DNS 解析器未使用 EDNS0 协议,Google 可能不会将客户端请求路由到最近的 GFE。

HTTP/2 队头阻塞

由于 GFE 处的队头阻塞,并行发送多个请求的 HTTP/2 客户端可能会遇到延迟时间增加。如需解决此问题,客户端必须使用 QUIC 协议

自定义网域的 SSL 终止服务

GFE 可能会终止 SSL 连接。如果您使用的是自定义网域,而不是 appspot.com 网域,则需要额外的跃点来终止 SSL。这可能会增加在某些区域运行的应用的延迟时间。如需了解详情,请参阅映射自定义网域

App Engine 服务基础架构

由于服务级问题或自动扩缩,您的应用可能会出现延迟时间延长的情况。

服务范围的突发事件

Google 会在服务运行状况信息中心内发布服务级严重问题的详细信息。不过,Google 会逐步发布,因此服务范围的突发事件不太可能一次影响所有实例。

自动扩缩

以下自动扩缩场景可能会导致延迟时间延长或错误增加:

  • 流量纵向扩容太快:App Engine 自动扩缩功能可能无法在流量增加时快速扩缩实例,从而导致临时过载。通常,当流量由计算机程序而非最终用户生成时,就会发生过载。如需解决此问题,请限制生成流量的系统。

  • 流量激增:如果自动缩放的服务需要在不影响延迟的情况下更快地扩展,流量激增可能会导致延迟增加。最终用户流量通常不会导致频繁的流量峰值。如果您发现流量出现峰值,则应调查原因。如果批处理系统按时间间隔运行,您可以平滑流量或使用不同的伸缩设置。

  • 自动伸缩器设置:您可以根据服务的伸缩特性配置自动伸缩器。在以下情况下,伸缩参数可能会变得非最佳:

    • 如果设置过于激进,App Engine 标准环境伸缩设置可能会导致延迟。如果您在日志中看到状态代码为 500 且消息为“Request was aborted after waiting too long to attempt to service your request”(请求在等待太长时间后被中止,无法尝试处理您的请求)的服务器响应,则表示在等待空闲实例的待处理队列中,请求超时。

    • 即使您预配了足够的实例,也会看到手动伸缩的待处理时间增加。如果您的应用处理最终用户流量,我们建议您不要使用手动伸缩。手动伸缩更适合任务队列等工作负载。

    • 基本伸缩会以延长延迟时间为代价来最大限度地降低费用。我们建议您不要对对延迟时间敏感的服务使用基本伸缩

    • App Engine 的默认伸缩设置可为大多数服务提供最佳延迟时间。如果您仍看到等待时间较长的请求,请指定实例数下限。如果您通过最大限度地减少空闲实例来调节伸缩设置以降低费用,则在负载突然增加时,可能会出现延迟时间急剧增加的风险。

我们建议您使用默认伸缩设置对性能进行基准测试,然后在每次更改这些设置后运行新的基准测试。

部署

部署后不久会出现延迟时间增加,表示您在迁移流量之前尚未充分扩容。较新的实例可能不会预热本地缓存,因此传送速度可能比较旧的实例慢。

为避免延迟时间急剧增加,请勿使用与现有服务版本相同的版本名称部署 App Engine 服务。如果您重复使用现有版本名称,则无法缓慢将流量迁移到新版本。由于 App Engine 会在短时间内重启每个实例,因此请求速度可能会变慢。如果要还原到先前版本,也必须重新部署。

应用实例

本部分介绍了可应用于应用实例和源代码的常见策略,以优化性能并缩短延迟时间。

应用代码

应用代码中的问题可能很难调试,尤其是在问题是间歇性的或无法重现的情况下。

如需解决问题,请执行以下操作:

  • 如需诊断问题,我们建议您使用日志记录监控跟踪进行插桩。您还可以使用 Cloud Profiler

  • 尝试在本地开发环境中重现问题,从而允许您运行可能无法在 App Engine 中运行的特定语言的调试工具。

  • 为了更好地了解应用故障的方式以及存在的瓶颈,请对应用进行负载测试,直到失败。设置实例数上限,然后逐步增加负载,直到应用失败。

  • 如果延迟时间问题与新版本的应用代码的部署相关,请回滚以确定新版本是否导致了突发事件。但是,如果您连续部署,则部署频率可能足够高,很难根据部署时间来确定部署是否导致了突发事件。

  • 您的应用可能会将配置设置存储在 Datastore 或其他位置内。创建配置更改的时间线,以确定其中任何一项是否与延迟增加的开始一致。

工作负载变化

工作负载变化可能会导致延迟时间增加。一些可能指示工作负载变化的监控指标包括 qps、API 使用情况和延迟时间。还应检查请求和响应大小的变化。

内存压力

如果监控显示内存用量呈锯齿状,或者与部署相关的内存用量下降,则性能问题可能是由内存泄漏引起的。内存泄漏也可能会导致频繁进行垃圾回收,从而导致延迟时间增加。如果您无法将此问题追溯到代码中的问题,请尝试预配具有更多内存的较大实例。

资源泄露

如果应用实例的延迟时间不断增加与实例存在时间相关,则可能会出现资源泄露,导致性能问题。部署完成后,延迟时间会缩短。例如,由于 CPU 使用率较高,数据结构随着时间的推移而变慢,可能会导致任何受 CPU 限制的工作负载变慢。

代码优化

如需缩短 App Engine 上的延迟时间,请使用以下方法优化代码:

  • 离线工作:使用 Cloud Tasks 可防止用户请求阻止应用等待工作(例如发送邮件)完成。

  • 异步 API 调用:确保您的代码在等待 API 调用完成时不会被阻塞。

  • 批量 API 调用:批量版本 API 调用通常比发送单个调用更快。

  • 对数据进行反规范化:通过对数据进行反规范化,缩短对数据持久层的调用的延迟时间。

应用依赖项

监控应用的依赖项,以检测延迟时间峰值是否与依赖项故障相关。

工作负载变化和流量增加可能会导致依赖项的延迟时间增加。

非扩容依赖项

如果应用的依赖项未随着 App Engine 实例数量的增加而扩容,那么当流量增加时,依赖项可能会过载。一个无法扩缩的依赖项示例是 SQL 数据库。应用实例数量越多,数据库连接数量就越多,可能会导致数据库无法启动,造成级联故障。如需解决此问题,请执行以下操作:

  1. 部署未连接到数据库的新默认版本。
  2. 关停以前的默认版本。
  3. 部署连接到数据库的新非默认版本。
  4. 将流量缓慢迁移到新版本。

作为预防措施,请设计应用以使用自适应限制来丢弃对依赖项的请求。

缓存层故障

如需加快请求速度,请使用多个缓存层,例如边缘缓存、Memcache 和实例内存。其中一个缓存层故障可能会导致延迟时间突然增加。例如,Memcache 刷新可能会导致更多请求转到较慢的 Datastore。