如何借助Cloud Run和Redis将p95延迟从10秒降低至2秒

如何借助Cloud Run和Redis将p95延迟从10秒降低至2秒

想象一下,在旅游网站上搜索航班,结果加载需要等待 10 秒。感觉像一个世纪那么长,对吧?现代旅游搜索平台即使在高负载下也必须几乎即时返回结果。然而,不久前,我们旅游搜索引擎的 API 的 p95 延迟徘徊在 10 秒左右。这意味着 5% 的用户搜索(通常是在高峰流量期间)需要 10 秒或更长时间。结果——用户失望、跳出率高,更糟的是——销售额损失。因此,在这种情况下,降低延迟是不可妥协的。

本文是一个真实案例研究,介绍了我们如何改进云基础架构以消除延迟问题。通过利用 Google Cloud Run 进行可扩展计算并使用 Redis 进行智能缓存,我们将搜索 API 的 p95 延迟从约 10 秒缩短至约 2 秒。在这里,我们将介绍降低延迟的整个过程。其中包括性能瓶颈、优化以及它们带来的显著改进。

延迟瓶颈

极高的延迟是一个严重的问题。深入研究后,我们发现多个因素拖累了我们的响应时间。所有这些因素都有一个共同点——它们使我们的搜索 API 在每个请求上都承担了大量的繁重工作。在实现整体延迟降低之前,我们必须解决以下问题:

延迟瓶颈

  • 多次后端调用:对于每个用户查询,该服务都会按顺序联系多个下游服务(机票价格提供商、辅助信息数据库等)。例如,搜索航班有时会同时调用三个不同的 API,每个 API 大约需要 2 到 3 秒。某些搜索的总延迟时间累积接近 10 秒。
  • 无缓存层:由于没有内存或 Redis 缓存来快速返回最新结果,网站上的每个请求都从头开始。即使是相同的搜索也会在几分钟内重复。这意味着即使是热门航线或静态数据(如机场详情),每次都需要从数据库或第三方获取。
  • Cloud Run 冷启动:我们的服务在 Cloud Run(一个无服务器容器平台)上运行。在默认设置(最小实例数为 0)下,当流量空闲且有新请求传入时,Cloud Run 必须启动一个容器实例。这些冷启动会增加明显的延迟(通常会增加 1-2 秒的开销)。这种“启动税”严重损害了我们的尾部延迟。
  • 单个请求:最初,我们将每个 Cloud Run 容器配置为一次仅处理一个请求(并发数 = 1)。这简化了请求处理,但也意味着 10 个并发搜索的突发情况会立即启动 10 个独立实例。由于冷启动和每个实例的 CPU 资源有限,我们的系统难以高效处理峰值请求。

所有这些因素共同导致了 p95 延迟过慢。从根本上讲,我们的架构并未针对速度进行优化。查询执行了冗余工作,而我们的基础架构并未针对延迟敏感的工作负载进行调整。好消息是?每个瓶颈都是降低延迟的机会。

我们为降低延迟所做的改进

我们主要从两个方面来降低延迟:缓存以避免重复工作;Cloud Run 优化以最大限度地减少冷启动和处理开销。后端的改进过程如下:

引入Redis缓存层

我们部署了 Redis 缓存,以缩短热路径上高开销操作的执行时间。其理念非常简单:存储频繁或近期查询的结果,并直接将其提供给后续请求。例如,当用户搜索特定日期从纽约飞往伦敦的航班时,我们的 API 会获取并编译一次结果。然后,它会将该“票价响应”缓存在 Redis 中一小段时间。

如果另一个用户(或同一个用户)随后进行了相同的搜索,后端可以在几毫秒内返回缓存的票价数据,从而避免重复调用外部 API 和数据库查询。通过避免在缓存命中时进行高开销的上游调用,我​​们显著降低了热查询的延迟。

我们也对其他数据应用了缓存,例如静态或缓慢变化的参考数据。例如机场代码、城市元数据、货币汇率,现在都使用缓存。服务现在不再每次请求都访问数据库查找机场信息,而是从 Redis 中检索(在启动或首次使用时加载)。这减少了许多细微的查找操作,这些查找操作会不时增加几毫秒的时间(在负载下这些时间会累积起来)。

缓存必备

根据经验,我们决定“缓存热门数据”。热门航线、最近获取的价格以及机场信息等静态参考数据都保存在内存中,方便随时访问。为了保持缓存数据的新鲜度(在价格变化时至关重要),我们设置了合理的 TTL(生存时间)到期时间和失效规则。例如,票价搜索结果最多缓存几分钟。

之后,它们就会过期,以便新的搜索能够获得最新的价格。对于高度波动的数据,我们甚至可以在检测到更改时主动使缓存条目失效。正如 Redis 文档所述,机票价格通常“每隔几个小时”才会更新一次。因此,较短的 TTL 与基于事件的失效机制相结合,可以在新鲜度和速度之间取得平衡。

结果如何?缓存命中后,每个查询的响应时间从几秒缩短到几百毫秒甚至更短。这全都归功于 Redis,它能够通过内存以极快的速度提供数据。事实上,行业报告显示,使用内存中的“票价缓存”可以将需要数秒的航班查询转化为仅需几十毫秒的响应。虽然我们的结果并非完全即时,但这个缓存层带来了巨大的提升。显著降低了延迟,尤其是在重复搜索和热门查询方面。

优化Cloud Run设置以降低延迟

缓存有助于处理重复性工作,但我们还需要优化首次查询和扩展的性能。因此,我们对 Cloud Run 服务进行了微调,以实现低延迟。

始终只有一个热实例

我们为 Cloud Run 服务启用了最小实例数 = 1。这确保即使在空闲期间也至少有一个容器处于启动状态并准备好接收请求。第一个用户请求不再会受到冷启动惩罚。Google 工程师指出,保持最小实例数可以显著提高对延迟敏感的应用的性能,因为它消除了从零到一的启动延迟。

在我们的案例中,将最小实例数设置为 1(高峰时段甚至可以设置为 2 或 3),意味着用户无需等待容器启动。仅凭这一项优化,p95 延迟就显著下降。

提高每个容器的并发数

我们重新审视了并发设置。在确保代码能够安全地处理并行请求后,我们将 Cloud Run 的并发数从 1 提升到更高的数字。我们尝试了 5、10 等值,最终将我们的工作负载设置为 5。这意味着每个容器在需要启动新实例之前最多可以同时处理 5 个搜索请求。

结果——流量高峰期间生成的新实例更少。这反过来又意味着更少的冷启动和更低的开销。本质上,我们让每个容器并行执行更多工作,直到 CPU 使用率仍然保持健康。我们密切监控 CPU 和内存——我们的目标是高效利用每个实例,而不会使其过载。

这种调整有助于平滑突发流量下的延迟:如果同时有 10 个请求,而不是 10 个冷启动(并发度 = 1),我们会使用 2 个热实例,每个实例处理 5 个请求,从而保持快速响应。

更快的启动和处理速度

我们还进行了一些应用级别的调整,以加快 Cloud Run 的启动和运行速度。此外,我们还启用了 Cloud Run 的启动 CPU 提升功能,该功能可在启动期间为新实例提供 CPU 的爆发式增长。我们还使用了精简的基础容器镜像,并在启动时仅加载必要的模块。

某些初始化步骤(例如加载大型配置文件或预热某些缓存)也被移至容器启动​​阶段,而不是在请求时执行。得益于最小实例数,这种启动过程的运行频率很低。实际上,当请求到达时,实例已经完成引导(数据库连接打开、配置加载等),因此它可以立即开始处理查询。

我们实际上只支付了一次启动成本,然后在多个请求中重复使用它,而不是在每个请求上都支付少量成本。

实施这些优化后,效果立竿见影。我们监控了 API 的性能,对比了优化前后的对比。p95 延迟从大约 10 秒骤降至 2 秒左右。对于我们的用户来说,这带来了令人惊叹的 5 倍加载速度提升。平均延迟也得到了改善(对于缓存命中查询,延迟通常小于 500 毫秒)。

更重要的是,响应变得一致且可靠。用户不再需要忍受痛苦且令人恐惧的 10 秒等待。系统能够从容应对流量高峰:Cloud Run 可以根据需要扩展至更多实例。凭借热容器和更高的并发性,系统能够轻松应对,避免冷启动带来的拥堵。

同时,Redis 缓存吸收了重复查询,并降低了下游 API 和数据库的负载。这还通过防止这些系统成为瓶颈,间接降低了延迟。

最终结果是,搜索 API 更加敏捷、更具可扩展性,满足了客户对快速响应和流畅体验的期望。

关键要点

在我们为降低延迟而进行的一系列优化中,以下是一些关键要点和值得您考虑的要点。

  • 测量并控制尾部延迟:关注 p95 延迟(及以上)至关重要。它能凸显真实用户感受到的最坏延迟情况。将 95 百分位延迟从 10 秒降低到 2 秒,使我们最糟糕的体验提升了 5 倍,速度也更快。这对用户满意度来说是一个巨大的提升!因此,请始终监控这些高百分位指标,而不仅仅是平均值。
  • 使用缓存避免冗余工作:事实证明,引入 Redis 缓存对我们来说至关重要。通过从内存提供结果,缓存频繁请求的数据可以显著缩短响应时间。内存速度与周到的失效机制(使用 TTL 和更新)相结合,可以减轻后端昂贵的计算负担。
  • 优化无服务器以提高速度:Cloud Run 让我们能够轻松扩展,但为了真正实现低延​​迟,我们利用了其显著的功能——保持最小实例预热。这消除了冷启动延迟,并调整了并发性和资源,使实例能够高效利用,避免不堪重负。少量的前期成本(始终在线的实例)可以非常值得,因为它能带来稳定的性能。
  • 尽可能并行化和精简:我们重新审视了请求流程,以消除不必要的序列化和延迟。通过并行化外部调用和在启动期间进行一次性设置(并非针对每个请求),我们将关键路径缩短了数秒。每个微优化(非阻塞 I/O、更快的代码、预加载数据)都会在大规模分布式系统中累积起来。
  • 持续分析和迭代:最后,需要注意的是,这是一个迭代的过程。我们使用监控和分析来查找最大的瓶颈,逐一解决它们,并衡量其影响。性能调整很少是一次性完成的。它关乎数据驱动的改进,有时还会进行创造性的修复,以达到您的延迟目标。

小结

虽然这些降低延迟的策略似乎难以一次性全部实现,但在实践中,系统地逐一检查每个策略都非常顺利。整个测试过程中,我们最大的亮点在于,我们将旅行搜索 API 从迟缓的体验提升到了即时的体验。在用户期望“昨天”就能得到答案的时代,将 P95 延迟从 10 秒缩短到 2 秒,对于提供流畅的旅行搜索体验至关重要。

评论留言