排查导致应用延迟时间变长的问题

在许多情况下,应用中增加的延迟时间最终会导致 5xx 服务器错误。因此,考虑到每个原因可能相同,遵循一组类似的问题排查步骤来缩小错误和延迟峰值的根本原因是有意义的。

确定问题范围

首先,通过收集相关信息,尽可能缩小问题的范围。以下是一些可能相关的信息建议。

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

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

确定失败的原因

接下来,确定请求路径中的哪个组件最有可能导致延迟或错误。请求路径中的主要组件包括:

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

如果步骤 1 中收集的信息未指向故障来源,则通常应先查看应用实例的运行状况和性能。

确定问题是否出在应用实例中的一种方法是查看 App Engine 请求日志:如果您在这些日志中看到 HTTP 状态代码错误或延迟时间增加,这通常意味着问题位于运行您的应用的实例中。

在以下情况下,请求日志中的错误增加和延迟时间增加可能不是由应用实例本身引起的:如果应用的实例数未扩充以匹配流量水平,则实例可能过载,从而导致错误和延迟时间增加。

如果您在 Cloud Monitoring 中看到错误或延迟增加,通常可以认为问题在于负载均衡器,用于记录 App Engine 指标。在大多数情况下,这表明应用实例存在问题。

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

可能导致突发事件的场景

以下是用户遇到的一些场景。

客户端

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

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

互联网

互联网连接状况欠佳

在您的客户端上运行以下命令,以确定问题是否是互联网连接状况欠佳。

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

time_connect 的值通常表示客户端与最近的 Google Front End 的连接的延迟时间。如果此连接速度较慢,您可以使用 traceroute 进一步排除故障,以确定网络上的哪个跃点导致延迟。

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

带宽较低的客户端

应用可能会快速响应,但响应可能会因为网络瓶颈而减慢速度,从而导致 App Engine 服务基础架构无法尽可能快地通过网络发送数据包。

Google Front End (GFE)

HTTP/2 队头阻塞

由于 GFE 处的队头阻塞,并行发送多个请求的 HTTP/2 客户端可能会遇到延迟时间增加。最好的解决方案是客户端升级成使用 QUIC 协议。

自定义网域的 SSL 终止服务

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

App Engine 服务基础架构

服务范围的突发事件

Google 将在 https://status.cloud.google.com/ 上发布有关严重服务范围的详细信息。请注意,Google 会逐步发布,因此服务范围的突发事件不太可能一次影响所有实例。

自动扩缩

流量纵向扩容太快

App Engine 自动扩缩功能可能无法在流量增加时快速扩缩实例,从而导致临时过载。通常,当流量并非由最终用户自然生成,而由计算机程序生成时,就会发生这种情况。解决此问题的最佳方法是系统限制生成流量。

流量高峰

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

自动扩缩器设置

您可以根据应用的扩缩特性配置自动扩缩器。在某些情况下,这些扩缩参数可能会变得非最佳。

App Engine 柔性环境应用根据 CPU 利用率进行扩缩。但是,在突发事件发生期间,应用可能会受 I/O 限制,导致具有大量请求的实例过载,因为不会发生基于 CPU 的扩缩。

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

如果您的应用处理最终用户流量,请勿使用 App Engine 标准环境手动扩缩。手动扩缩更适合任务队列等工作负载。即使您预配了足够的实例,也会看到手动扩缩的待处理时间增加。

请勿将 App Engine 标准环境基本扩缩用于对延迟敏感的应用。这种扩缩类型旨在最大限度地减少费用,但代价是延迟。

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

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

Deployment

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

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

应用实例

应用代码

应用代码中的问题可能很难调试,尤其是在问题是间歇性的或无法轻松重现的情况下。为帮助诊断问题,我们建议您使用日志记录监控跟踪进行插桩。您可以尝试使用 Cloud Profiler 诊断问题。请参阅此诊断加载请求延迟示例,使用 Cloud Trace 上传每个请求的其他时间信息。

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

如果您在 App Engine 柔性环境中运行,则可以通过 SSH 连接到实例并进行线程转储,以查看应用的当前状态。您可以尝试在负载测试中或在本地运行应用来重现问题。您可以增加实例大小,看看这是否可以解决问题。例如,对于由于垃圾回收而遇到延迟的应用,增加 RAM 可以解决问题。

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

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

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

工作负载变化

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

健康检查失败

App Engine 柔性环境负载均衡器会停止将请求路由到导致健康检查失败的实例。这可能会增加其他实例的负载,从而可能导致级联故障。App Engine 柔性环境 Nginx 日志显示未通过健康检查的实例。分析日志和监控以确定实例健康状况不佳的原因,或将健康检查配置为对瞬时故障不太敏感。请注意,在负载均衡器停止将流量路由到健康状况不佳的实例之前,会出现短暂延迟。如果负载均衡器无法重试请求,则此延迟可能会导致错误峰值。

App Engine 标准环境不使用健康检查。

内存压力

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

资源泄露

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

代码优化

优化 App Engine 代码以缩短延迟时间的一些方法:

  • 离线工作:使用 Cloud Tasks,这样用户请求就不会阻止等待工作(例如发送邮件)完成。

  • 异步 API 调用:确保您的代码在等待 API 调用完成时不会被阻塞。ndb 等库提供内置的支持。

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

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

依赖项

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

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

非扩容依赖项

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

从中进行恢复的一种方法是:

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

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

缓存层故障

加快请求速度的好方法是使用多个缓存层:

  • 边缘缓存
  • Memcache
  • 实例内存

延迟时间突然增加可能是由其中一个缓存层故障所导致。例如,Memcache 刷新可能会导致更多请求转到较慢的 Datastore。