大厂实践:Airbnb 使用 Impulse 进行负载测试

翻译 2025-06-27 17:56:02

导读:本文分享负载测试主题,这是研发和运维都应该关注的方向,上线之前做负载测试,后面才能做容量管理(Capacity Management 是 SRE 重点方向之一),尤其是大促之前,必须要提前知道系统容量上限。下面是正文。

系统级负载测试对于可靠性和效率至关重要。它可以识别瓶颈、评估峰值流量的容量、建立性能基准并检测错误。在像 Airbnb 这样规模和复杂性的公司中,我们了解到负载测试需要稳健、灵活和分散。这需要一套合适的工具,使工程团队能够执行与 CI 无缝集成的自助式负载测试。

Impulse 是我们内部的 load-testing-as-a-service 框架之一。它提供的工具可以生成合成负载、模拟依赖项以及从生产环境收集流量数据。在这篇博文中,我们将分享 Impulse 的架构如何最大限度地减少手动工作,与我们的可观测性堆栈无缝集成,并使团队能够主动解决潜在问题。

架构

Impulse 是一个全面的负载测试框架,允许服务所有者执行上下文感知负载测试、模拟依赖项和收集流量数据,以确保系统在各种条件下的性能。它包括以下组件:

  • Load generator 动态生成上下文感知请求,用于使用合成或收集的流量测试不同的场景。
  • Dependency mocker 来模拟具有延迟的下游响应,以便对受测服务(SUT)进行负载测试时不需要涉及某些依赖服务。当依赖项是不支持负载测试的供应商服务时,或者如果团队希望在日常部署期间对其服务进行回归负载测试而不影响下游,这一点尤其重要。
  • Traffic collector 从生产环境中收集上行和下行流量,然后将生成的数据应用于测试环境。
  • Testing API generator,将异步工作流包装到同步 API 调用中,以进行负载测试。

这四个工具中的每一个都是独立的,使服务所有者能够灵活地选择一个或多个组件来满足其负载测试需求。

Load generator 负载生成器

Context aware 上下文感知

在进行负载测试时,对 SUT 发出的请求通常需要来自上一个响应的一些信息,或者需要按特定顺序发送。例如,如果更新 API 需要提供要更新的 entity_id,则必须确保该实体已存在于测试环境上下文中。

我们的负载生成器工具允许用户用 Java 或 Kotlin 编写任意测试逻辑,并启动容器以针对 SUT 大规模运行这些测试。为什么要编写代码而不是 DSL/配置逻辑?

  • 灵活性:编程语言比 DSL 更具表现力,可以更好地支持复杂的上下文场景。
  • 可重用性:相同的测试代码可用于其他测试,例如集成测试。
  • 开发人员熟练程度:入门学习曲线低/无,无需学习如何编写测试逻辑。
  • 开发人员体验:IDE 支持、测试、调试等。

下面是一个合成上下文感知测试用例的示例:

class HelloWorldLoadGenerator : LoadGenerator {
   override suspend fun run() {
       val createdEntity = sutApiClient.create(CreateRequest(name="foo", ...)).data

       // request with id from previous response (context)
       val updateResponse = sutApiClient.update(UpdateRequest(id=createdEntity.id, name="bar"))
       
       // ... other operations
       
       // clean up
       sutApiClient.delete(DeleteRequest(id=createdEntity.id))
   }
}

Decentralized 分散

负载生成器是去中心化和容器化的,这意味着每次触发负载测试时,都会创建一组新容器来运行测试。此设计具有以下几个优点:

  • 隔离:不同服务之间的负载测试运行彼此隔离,消除了任何干扰。
  • 可扩展性:容器数量可以根据流量需求进行扩容或缩容。
  • 成本效益:容器的生命周期很短,因为它们仅在负载测试运行期间存在。

更重要的是,由于我们的服务是基于云的,因此一个微妙的点是 Impulse 框架将在我们所有的数据中心之间平均分配工作线程,并且负载将从所有工作线程均匀地发出。Impulse 的负载生成器确保整体每秒触发 (TPS) 与配置一致。基于此,我们可以更好地利用负载均衡器中的 locality 设置,从而更好地模拟生产中的真实流量分配。

Execution 执行

负载生成器设计为在 CI/CD 管道中执行,这意味着我们可以自动触发负载测试。开发人员可以分多个阶段配置测试规范,例如,预热阶段、稳态阶段、峰值阶段等。每个阶段都可以配置:

  • 要运行的测试用例
  • 每个测试用例的 TPS(每秒触发数)
  • 测试持续时间

Dependency mocker 依赖项模拟器

Impulse 是一个去中心化的框架,其中每个服务都有自己的依赖项模拟器。这样可以消除服务之间的干扰,降低通信成本。每个依赖项模拟程序都是进程外服务,这意味着 SUT 的行为与在生产中的行为相同。我们在单独的实例中运行模拟程序,以避免对 SUT 的性能产生任何影响。模拟服务器都是短暂的 — 它们仅在测试运行之前启动,并在测试运行之后关闭,以节省成本和维护工作。响应延迟和异常是可配置的,并且可以按需调整 mocker 实例的数量以支持大量流量。

其他值得注意的功能:

  • 您可以有选择地存根某些依赖项。目前,HTTP JSON、Airbnb Thrift 和 Airbnb GraphQL 依赖项支持存根。
  • 依赖项模拟程序支持负载测试以外的使用案例。例如,集成测试通常依赖于其他服务或第三方 API 调用,这可能无法保证稳定的测试环境,或者可能仅支持理想的场景。依赖项模拟程序可以通过提供预定义的响应或异常来完全测试这些流来解决此问题。

Impulse 支持两个生成 mock 响应的选项:

  1. Synthetic response:响应由用户逻辑生成,如集成测试中所示;区别在于响应来自具有模拟延迟的远程(进程外)服务器。
  • 与负载生成器类似,该逻辑是用 Java/Kotlin 代码编写的,包含请求匹配和响应生成。
  • 可以使用 p95/p99 指标模拟延迟。
  1. 重放响应:响应从生产下游记录重放,由流量收集器组件支持。

以下是 Kotlin 中具有延迟的合成响应示例:

downstreamsMocking.every(
      thriftRequest<FooRequest>().having { it.message == "hello" }
    ).returns { request ->
      ThriftDownstream.Response.thriftEncoded(
        HttpStatus.OK,
        FooResponse.builder.reply("${request.message} world").build()
      )
    }.with {
      delay = latencyFromP95(p95=500.miliseconds, min=200.miliseconds, max=2000.miliseconds)
    }

Traffic collector 流量收集器

流量收集器组件旨在捕获上游和下游流量以及它们之间的关系。这种方法允许 Impulse 在负载测试期间准确地重放生产流量,避免下游数据或行为不一致。通过依赖项模拟程序复制下游响应(包括类似生产的延迟和错误),系统可确保高保真负载测试。因此,测试环境中的服务与生产环境中的服务行为相同,从而能够进行更真实、更可靠的性能评估。

Testing API generator 测试 API 生成器

我们严重依赖事件驱动的异步工作流,这对我们的业务运营至关重要。这些任务包括处理来自消息队列 (MQ) 的事件和执行延迟的作业。大多数 MQ 事件/作业都是从同步流(例如 API 调用)发出的,因此理论上它们可以被 API 负载测试覆盖。然而,现实世界要复杂得多。这些异步流通常涉及来自各种来源的长链事件和作业排放,因此很难仅使用基于 API 的方法准确复制和测试它们。

为了解决这个问题,测试 API 生成器组件在 CI 阶段根据事件或作业架构创建 HTTP API。这些 API 充当底层异步流的包装器,并以独占方式在测试环境中注册。此设置使负载测试工具(例如负载生成器)能够将流量发送到这些合成 API,从而允许异步流像同步一样执行。因此,可以在 asynchronous logic 上执行有针对性的、真实的负载测试,否则很难模拟。

测试 API 生成器的目标是帮助开发人员识别其异步流实施中以及高流量条件下的性能瓶颈和潜在问题。它通过启用异步流的直接负载测试来实现这一点,而不涉及 MQ 等中间件组件。其基本原理是,开发人员通常旨在评估他们自己的 logic 的行为,而不是中间件的行为,后者通常已经经过了充分的测试。通过绕过这些组件,这种方法简化了负载测试过程,并使开发人员能够独立管理和执行自己的测试。

与其他测试框架集成

Airbnb 强调产品质量,利用多功能测试框架,涵盖跨开发、暂存和生产环境的集成和 API 测试,并顺利集成到 CI/CD 管道中。Impulse 的模块化设计有助于其与这些框架的集成,从而提供系统的服务测试。

总结

在这篇博文中,我们分享了 Impulse 及其四个核心组件如何帮助开发者在 Airbnb 上执行自助负载测试。在撰写本文时,Impulse 已经在多个客户支持后端服务中实现,目前正在与公司内计划利用 Impulse 进行负载测试的不同团队进行探讨。

在此过程中,我们收到了很多好评。例如:“Impulse 帮助我们识别和解决服务中的潜在问题。在测试过程中,检测到线程池压力导致的 ApiClientThreadToolExhaustionException。此外,它还提醒我们服务部署期间客户端 API 调用中偶尔出现超时错误。Impulse 帮助我们确定了主服务容器中的高内存使用率,使我们能够微调内存分配并优化服务的资源使用情况。强烈建议将 Impulse 用作开发和测试过程不可或缺的一部分。

本文由 快猫团队 翻译,原文来自 Airbnb Tech Blog

标签: 大厂实践
快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云
OpenSource
开源版
Flashcat
Flashcat