虽然负载测试变得越来越容易,但配置能够忠实再现生产条件的负载测试可能很困难。一个好的负载测试必须使用一组代表生产流量的 URL,并实现模拟真实用户的请求速率。即使执行分布式负载测试也需要维护大量的服务器。
ShadowReader 旨在解决这些问题。它直接从生产日志中收集 URL 和请求速率,并使用 AWS Lambda 重放它们。由于是无服务器的,因此它比传统的分布式负载测试更具成本效益和性能;在实践中,它已扩展到每分钟 50,000 多个请求。
在 Edmunds,我们已经能够利用这些功能来解决诸如 Node.js 内存泄漏等问题,这些问题仅在生产环境中发生,方法是在我们的 QA 环境中重现相同的条件。我们每天还在使用它为预生产 Canary 部署生成负载。
我们在 Node.js 应用程序中遇到的内存泄漏问题使我们的工程团队感到困惑;因为它仅在我们的生产环境中发生;在我们引入 ShadowReader 将生产流量重放到 QA 之前,我们无法在 QA 中重现它。
事件
在 2017 年圣诞节前夕,我们遭遇了一起事件,其中响应时间全面跳跃,错误率增加了两倍,影响了我们网站的许多用户。


事件期间的监控帮助我们快速识别并解决问题,但我们仍然需要了解根本原因。
在 Edmunds,我们利用强大的持续交付 (CD) 管道,每天多次将新更新发布到生产环境。我们还动态扩展我们的应用程序以适应高峰流量,并缩小规模以节省成本。不幸的是,这产生了掩盖内存泄漏的副作用。
在我们的调查中,我们看到内存泄漏已经存在了数周,自 12 月初以来。内存使用率将攀升至 60%,同时第 99 个百分位数的响应时间缓慢增加。
在我们的 CD 管道和自动扩展事件之间,长期运行的容器经常被关闭并被较新的容器替换。这无意中掩盖了内存泄漏,直到 12 月,我们决定停止发布软件以确保假期期间的稳定性。

我们的 CD 管道
乍一看,Edmunds 的 CD 管道看起来像这样
- 单元测试
- 为应用程序构建 Docker 镜像
- 集成测试
- 负载测试/性能测试
- Canary 发布
该解决方案是完全自动化的,无需手动切换。最后一步是直接 Canary 部署到实时网站,使我们能够每天多次发布。
对于我们的负载测试,我们利用构建在 JMeter 之上的自定义工具。它从生产 URL 中随机抽取样本,并且可以模拟各种百分比的流量。然而,不幸的是,我们的负载测试无法在我们的任何预生产环境中重现内存泄漏。
解决内存泄漏
在查看 QA 中的内存模式时,我们注意到存在非常健康的模式。我们的初步假设是,我们在 QA 中的 JMeter 负载测试无法以允许我们预测应用程序性能的方式模拟生产流量。
虽然负载测试从生产 URL 中抽取样本,但它无法精确模拟客户使用的 URL 以及调用的确切频率(即突发速率)。
我们的第一步是在 QA 中重现问题。我们使用了一个名为 ShadowReader 的新工具,该项目是从我们的黑客马拉松中发展而来的。虽然我们考虑的许多项目都以产品为中心,但这是唯一一个以运营为中心的项目。它是一个负载测试工具,在 AWS Lambda 上运行,可以针对我们的 QA 环境重放生产流量和使用模式。
它返回的结果是立竿见影的

知道我们可以在 QA 中重现问题后,我们采取了额外的步骤,将 ShadowReader 指向我们的本地环境,因为这使我们能够触发 Node.js 堆转储。在分析转储内容后,很明显内存泄漏来自两个仅包含字符串的过大对象。在转储快照时,这些对象包含 373MB 和 63MB 的字符串!

我们发现这两个对象都是临时查找缓存,其中包含要在客户端使用的元数据。这些缓存都从未打算持久保存在服务器端。用户的浏览器仅缓存了自己的元数据,但在服务器端,它缓存了所有用户的元数据。这就是为什么我们无法使用综合测试重现泄漏的原因。综合测试总是导致服务器端缓存中相同的固定元数据集。仅当我们有来自各种用户生成的足够数量的唯一元数据时,泄漏才会浮出水面。
一旦我们确定了问题,我们就能够删除我们在堆转储中观察到的大型缓存。从那时起,我们对应用程序进行了检测,以开始收集可以帮助更快地检测此类问题的指标。

在 QA 中进行修复后,我们看到内存使用率是恒定的,并且泄漏已得到修复。

什么是 ShadowReader?
ShadowReader 是一个由 AWS Lambda 和 S3 驱动的无服务器负载测试框架,用于重放生产流量。它通过以与实时网站相同的速率重放生产环境中的 URL 来模拟真实用户流量。我们很高兴地宣布,经过数月的内部使用,我们已将其开源!
功能特点
- ShadowReader 通过重放用户请求 (URL) 来模拟真实用户流量。它还可以重放某些标头,例如 True-Client-IP 和 User-Agent,以及 URL。
- 与在大量服务器上运行的传统分布式负载测试相比,它在成本和性能方面都更高效。管理用于分布式负载测试的大量服务器每月可能花费 1,000 美元或更多;使用无服务器堆栈,通过按需配置计算资源,它可以降至每月 100 美元。
- 我们已将其扩展到每分钟 50,000 个请求,但它应该能够处理每分钟超过 100,000 个请求。
- 新的负载测试可以立即启动和停止,这与传统的负载测试工具不同,后者可能需要几分钟才能生成测试计划并将测试数据分发到负载测试服务器。
- 它可以按百分比值向上或向下调整流量,以充当更传统的负载测试。
- 它的插件系统使您能够切换插件以更改其行为。例如,您可以从过去重放(即重放过去的请求)切换到实时重放(即重放传入的请求)。
- 目前,它可以重放来自 Application Load Balancer 和 Classic Load Balancer 弹性负载均衡器 (ELB) 的日志,并且即将支持其他负载均衡器。
工作原理
ShadowReader 由四个不同的 Lambda 组成:Parser、Orchestrator、Master 和 Worker。

当用户访问网站时,负载均衡器(在本例中为 ELB)通常会路由请求。当 ELB 路由请求时,它将记录事件并将其发送到 S3。
接下来,ShadowReader 通过 CloudWatch 事件每分钟触发一个 Parser Lambda,该 Lambda 解析 S3 上该分钟的最新访问(ELB)日志,然后将解析后的 URL 发送到另一个 S3 存储桶。
在系统的另一侧,ShadowReader 还每分钟触发一个 Orchestrator lambda。此 Lambda 保存系统的配置和状态。
然后 Orchestrator 调用 Master Lambda 函数。Master 从 Orchestrator 接收有关要重放哪个时间片的信息,并从已解析 URL 的 S3 存储桶(由 Parser 存放在那里)下载相应的数据。
Master Lambda 将负载测试 URL 分成更小的批次,然后调用每个批次并将其传递给 Worker Lambda。如果必须发送 800 个请求,则将调用八个 Worker Lambda,每个 Lambda 处理 100 个 URL。
最后,Worker 接收从 Master 传递的 URL 并开始对选定的测试环境进行负载测试。
更大的图景
随着我们从稳态应用程序大小调整转向按需模型,在负载测试无服务器基础设施中重现性的挑战变得越来越重要。虽然 ShadowReader 的设计和使用考虑了 Edmunds 的基础设施,但任何利用 ELB 的应用程序都可以充分利用它。很快,它将支持重放任何生成流量日志的服务的流量。
随着项目的推进,我们希望看到它发展为与下一代无服务器运行时(如 Knative)兼容。我们也希望看到其他开源社区为其基础设施构建类似的工具链,因为无服务器变得越来越流行。
开始使用
如果您想试用 ShadowReader,请查看 GitHub 仓库。README 包含操作指南和一个包含所有功能的 演示,它将部署所有必要的资源以在您的 AWS 账户中试用实时重放。
我们很乐意听取您的意见并欢迎贡献。请参阅 贡献指南 以开始使用!
本文基于 "我们如何通过使用 ShadowReader 将生产流量重放到 QA 来修复 Node.js 内存泄漏",发布在 Edmunds Tech Blog 上,并在 Carlos Macasaet、Sharath Gowda 和 Joey Davis 的帮助下完成。 Yuki Sawa 也在 ShadowReader—用于重放生产流量的无服务器负载测试 在 (SCaLE 17x) 3 月 7-10 日在加利福尼亚州帕萨迪纳市进行了演讲。
2 条评论