一站式SaaS模式的电商服务平台Shopify,拥有庞大且复杂的活动部件,每个组件都需要进行精细调整,为大型销售活动做好准备。本文为Shopify工程团队针对大型促销活动的规模测试的实践总结,包括容量规划、弹性测试、单一应用程序性能测试和全面负载测试等等。
以下为作者观点:
在Shopify,Black Friday Cyber Monday(黑色星期五网络星期一 ) (编者注:Black Friday Cyber Monday,简称BFCM,“Cyber Monday”指的是“黑色星期五”之后的第一个星期一,是美国一年当中最火爆的购物日之一。) 对于我们和我们的商家来说是一年中最关键的时刻。正是在这些时候,我们每年都达到了前所未有的业绩水平,并达到了最高的规模水平。
为此,为了做好准备,我们利用不同类型的演练,包括容量规划、弹性测试、单一应用程序性能测试和全面负载测试,或者我们称之为 BFCM 规模测试。
接下来介绍了解一下 BFCM 规模测试的方法,探索如何确保我们的电子商务平台能够应对一年中最繁忙的周末。
什么是 BFCM Scale 测试?
最简单地说,BFCM 规模测试就是测试平台,确保其能够处理 BFCM 级别的流量。在高层次上讲,包括:
-
将我们的核心平台(和选择的子系统)扩展到 BFCM 配置文件。
-
对平台进行负载测试,模拟 BFCM 的峰值流量水平
-
将核心平台缩小到常规基线
-
分析结果并讨论我们的发现
-
不断改进性能,并在全年多次重复这一过程
Shopify 架构由混合模型组成,中心有一个整体 ruby 应用程序(Shopify Core),该模型由各种服务支持,包括 Storefront Renderer(负责向浏览器提供 HTML 和一些店面 API 流量)、我们的分析管道、欺诈分析管道、支付处理基础设施、网络钩子发送服务等等。
可以想象,当买家浏览店面、将商品添加到购物车、输入送货和付款信息并最终通过结账完成购买时,涉及许多不同的系统来接收流量。当我们针对平台生成 BFCM 级别负载时,我们需要了解哪些组件需要扩展(以及扩展到多高)才能支持该负载。
扩大规模
当时,我们决定在2022年进行比2021年更多的全面测试,目标是尽可能让这些测试成为平常工作的一部分。通过分析 2021年BFCM的数据,包括 Shopify Core 各组件和其他相关应用程序的运行实例数量,以及修改2021年规模测试的部分结果,我们在2月份开始计划2022年的首次测试。
在制定了扩大规模的计划后,我们的下一步工作是与云供应商合作,确保在首次规模测试日期获得所需的虚拟机数量。在运营大规模云基础设施时遇到的挑战之一是,云提供商在任何时候可用的资源数量也是有限的,因此,将已经很大的占用空间扩大 200% 的请求需要特别注意。
我们的扩展策略是通过多年观察整个平台如何大规模运行以及哪些组件需要扩展以进行负载测试而演变而来的。例如,位于核心平台下游的 nginx 请求路由层必须在 BFCM 规模测试之前进行扩展,以适应我们生成的高流量。同样,Shopify 核心的上游组件(例如我们的 ProxySQL 层和我们的全局键/值存储 Key/Value)需要在任何大规模测试之前进行扩展。
协调这些 BFCM 规模测试时,需要多个团队的努力。一些团队在测试前一天扩展其相关组件,因为他们可能需要更多时间,而其他组件仅在测试日扩展以帮助降低成本。
在每次 BFCM 规模测试当天,我们将其余组件放大到其全规模配置文件,并在当天的测试过程中(通常持续数小时)保持这种配置。在我们完成全面升级的过程中(或之后不久),我们偶尔会遇到一些问题,这些问题是在系统没有任何额外负载的情况下,以这种升级配置运行时产生的。现在(而不是在 BFCM 期间)发现这些问题,是我们尽早并经常进行这些演练的关键原因。这些问题包括以下主题:
-
由于运行到上游应用程序的 Storefront Renderer 实例数量增加而导致的连接数量增加,即使在平台负载空闲的情况下也会使上游应用程序过载。
-
由于连接数量增加,Shopify Core 副本数量的增加给我们的流媒体基础设施带来了额外的压力。
在我们遇到这些基于连接计数的问题的情况下,我们有时能够扩展依赖服务以应对额外的连接。其他时候,我们可以通过重构部分架构并引入中间软件来复用这些连接来避免争用源。
运行负载测试
在完成全面扩展并且平台稳定后,我们开始初始化负载测试。今年,我们使用了一套包含五个主要测试的测试,每个测试都针对平台模拟不同类别的负载:
-
浏览和购买流程:此流程模拟用户浏览店面,将产品添加到购物车,并通过模拟支付网关使用测试信用卡详细信息进行结账。
-
管理流程:此流程模拟商家在其商店的管理门户上加载和进行更改,例如检查库存和更新产品价格。
-
限时抢购流程:我们有一个模拟限时抢购的专用流程,该流程会在模拟用户购买单个产品时导致店面活动爆发。
-
店面API流程:随着Shopify Hydrogen的日益普及,我们希望确保为这项新技术提供支持的 API 已准备好应对增加的流量。此流程模拟用户在无头商店浏览和结账时无头 Shopify 商店进行的 API 调用。
-
氢气和氧气流(Hydrogen and Oxygen flow):此流程模拟用户通过由 Shopify 托管的氢气和氧气产品提供支持的真实无头店面进行浏览和结帐。
每个流程代表一个用户的 HTTP 调用过程,然后由我们的自定义负载测试工具 Cronograma 执行多次,一个流程可能会进行多达 25 到 50 次 HTTP 调用,因为它会模拟用户浏览店面产品页面、在购物车中添加和删除一些商品,然后最终结账。
我们将这些不同的流量配置为以不同的速率运行,以便控制每分钟从每类负载中对平台产生的所需目标请求。我们还会随着时间的推移缓慢增加负载,并按顺序将测试层层叠加。这是一种保持控制的额外方法,因为我们会在扩展测试期间持续监控平台的健康状况。如果我们在扩展测试期间的任何时候发现平台性能下降的迹象,我们会在 Cronograma 工具中集成一个快速 "中止开关",用于立即停止所有测试。
将不同的负载测试流程增量地堆叠在一起的示例,每个流程都会增加额外的负载。请注意,并非所有流程都可以在整个测试期间运行。
确定我们让平台走向多远
对于2022年的前几次 BFCM 规模测试,我们根据 BFCM 2021预测和实际数据得出了这些早期测试的拟议目标。我们通常根据每分钟实现的请求数 (RPM) 以及每分钟完成的结帐 (CCPM) 来衡量这些大型测试。
之前,我们收到了内部数据科学团队的见解,这些团队为我们提供了2022年 BFCM 周末的官方预测,从而进一步调整我们希望在大规模测试中实现的 RPM 和 CCPM 目标。我们无法准确知道实际数据,但我们希望在测试时负载足够高,这样我们就能在不对平台造成过大压力的情况下轻松满足预测要求。
在选定的规模测试中,我们还选择测试在一个关键云区域宕机的情况下如何处理这种级别的负载。在这种情况下,我们将所有请求路由到另一个姊妹地区,从而有效地关闭了美国的一个云地区。该测试的目的是验证我们在单个区域的容量是否足以在另一个区域发生灾难性故障时仍能为该工作负载提供服务。
我们的目标不是通过这些 BFCM 规模测试来破坏平台,而是获得信心,相信我们能够在不降低系统性能的情况下为目标负载提供服务。作为此过程的一部分,需要识别平台中需要进一步优化的部分,并且我们不断迭代以消除发现的扩展问题。
缩小规模
当我们完成练习的测试运行后,就该开始缩小规模了。在缩小规模期间,重要的是要确保它以与扩大规模相反的顺序发生,以继续保护 Shopify Core(例如 ProxySQL)等系统上游的依赖项。如果我们要在缩小 Shopify Core 之前缩小 ProxySQL,则可能会面临 ProxySQL 过载的风险,因为较大的 Shopify Core 配置文件仍在建立大量连接。
分析结果
规模缩小完成后,参与规模测试练习的团队需要时间从其特定学科的系统收集数据,并得出是否有需要作为行动项目解决的问题。在花了一天左右的时间进行数据收集和分析后,我们作为一个小组聚集在一起,对规模测试练习的进行情况进行无可指责的回顾,不仅特别关注出现的任何需要后续采取补救措施的问题,而且还考虑到系统的部分接近过载,但在规模测试期间没有表现出故障。
迭代计划
在我们确定需要调整的项目后,我们确保对这些项目进行适当的跟踪,拥有适当的所有者,并制定行动计划,以便在下一次规模测试之前推进缓解措施。迭代的最后一步是开始计划下一个规模测试!
为什么要进行全面测试?
在设计分布式系统的负载测试时,(至少)可以采用两种策略。第一种方法是使用生产环境的缩放模型,其中测试将针对少量服务器而不是整个生产队列进行。测试期间针对服务器生成的流量不是最终预期的生产负载,而是根据为工作负载提供服务的服务器数量缩放的流量样本。测试完成并分析结果后,工程师可以推断数据并比较实际生产流量估计,然后根据缩放模型测试水平扩展工作负载。
这种缩放模型策略具有成本效益,并且更易于推理和管理,但也存在一些缺点,包括:
-
将数据库和其他垂直缩放的组件转换为缩放模型很困难
-
它假设工作负载可以以可预测的方式水平扩展
与比例模型方法相比,我们选择采用全面方法进行 BFCM 规模测试,其中我们将系统扩展到其预期的 BFCM 配置,并在负载测试期间模拟预计的全部流量。
有哪些考虑因素和权衡?
与使用扩展模型进行负载测试相比,将工作负载扩展到大量以进行全面测试会增加大量额外费用,尤其是在基于使用量计费的云环境模型中。在 Shopify,我们承认 BFCM 是商家一年中最重要的周末,因此我们必须竭尽全力确保我们的平台为这些巨大的流量浪潮做好准备,这一点非常重要。
当大幅扩展大型系统时,通常会出现在系统没有增加任何负载的情况下出现问题的情况。它纯粹是由于在基础设施的更高基线级别上运行,即使系统上没有额外的负载。在按数量级扩展系统时经常出现的一个常见问题是,由于 TCP 连接增多而导致系统崩溃。系统中的 TCP 连接数量越多,通常意味着代理和数据库等事物消耗更多的内存,甚至可能表现为最大连接数超出错误等问题。能够适应在如此高的规模下运行系统,可以增强基础设施的所有部分都已准备好支持高负载场景的信心。
我们还希望识别复杂架构中意外的交互。在不断变化的环境中,期望任何一个人能够在单个时间点保留所有起作用的依赖项的思维导图是不切实际的。通过系统推送高水平的实际流量有助于我们找到这些依赖关系,这些依赖关系可能会因平台负载的副作用而出现问题。
当我们通过几个核心业务流模拟大量流量时,该路径中的所有系统都会“免费”进行负载测试。为代理等中间件组件设计适当的负载测试可能很困难。您应该模拟多少负载?什么类型的?什么是现实的?通过确保这些中间件组件能够满足全面的负载测试工作负载,我们可以合理地预期它们的大小适合处理大规模流量。
系统的某些部分超出了我们的控制范围,例如我们使用但不拥有或运营的内容交付网络 (CDN)。在 BFCM 期间,真实的用户流量来自世界各地,用户的 HTTP 请求穿过距离他们最近的 CDN 存在点 (PoP)。我们的 BFCM 规模测试从有限数量的位置生成 HTTP 请求负载,这并不是流量源的真实表示。结果是,当我们尝试从少量负载生成集群每分钟生成数百万个请求时,所有这些负载都集中在少数 CDN PoP 上,这可能会出现问题并使它们不堪重负。为了解决这个问题,我们的负载测试脚本被配置为绕过 CDN 获取部分流量,将一些请求直接发送到我们的源,同时仍然通过 CDN 发送一部分。
我们负载测试的系统中有多层缓存,我们需要意识到这会如何人为地扭曲结果。我们运行这些测试的一些应用程序利用不同层的内存缓存和键/值存储缓存。为了解决这个问题,我们在负载测试脚本中指定了一部分请求来“破坏”缓存。支持此机制的应用程序正在监视特制的 HTTP 请求,如果观察到请求,它们就会绕过给定请求的缓存层。
当使用此类合成工作负载进行测试时,应该强调的是,仅仅因为系统在合成负载下通过了特定量级的负载测试,并不意味着它已准备好应对相同量级的生产流量。在负载测试中很难重现准确的生产流量模式,我们承认生产流量的唯一替代品是真实的生产流量。我们将这些练习视为一个迭代过程,而不是成功的保证。尽管我们的负载生成并不模仿生产流量,但在这些测试运行时,我们仍然会密切关注来自系统的最细微的负面反馈。任何延迟或错误率的增加都可能是即将出现更大问题的早期预警信号。
我们学到了什么
在2022年BFCM规模测试的整个过程中,我们克服了几个关键挑战,否则在像 BFCM 这样的高流量活动中可能会出现问题。
年中出现的问题之一是我们意识到,在重构架构以运行更少数量的 Shopify Core Kubernetes 集群后,由于ingress-nginx 中尚未解决的问题。这意味着,实际上只有前 1000 个 pod 会收到流量,而不是扩展到我们所需的 Shopify 核心 Kubernetes Pod 数量(每个集群大约 1200 个)。我们在这里讨论了几种修复方法,包括自己修补 ingress-nginx 以及为每个集群运行额外的部署。最终,我们决定更改 Kubernetes Pod 的配置文件,以便每个 Pod 拥有更多的 CPU、内存和 Web Worker 容量,从而能够为每个 Pod 提供更多请求。在此配置中,我们能够将每个集群的 Pod 数量保持在 1000 个以下,并保持足够的容量来服务这些 BFCM 规模测试以及 BFCM 周末本身。
在我们将所有请求路由到单个区域的测试中(即,模拟同级区域的区域中断),我们注意到,当我们开始增加负载时,我们遇到了一些延迟,并且错误不断增加。经过进一步分析,我们发现 Shopify Core 集群内的一些入口 nginx Kubernetes Pod 的负载远高于其他 Pod,并且表现出过载症状,导致响应时间延长和偶尔超时,从而导致 HTTP 504。经过与我们的云供应商进行大量调试和故障排除后,我们确认云负载均衡器并未在负载均衡器后端之间均匀分配请求。这些 nginx pod 与其他工作负载调度在相同的 Kubernetes 节点池上,这意味着负载均衡器后端列表中存在的一些 Kubernetes 节点上有一个 nginx pod,有些节点上有多个。除了这个平衡问题之外,我们的负载测试从有限数量的源 IP 生成流量,这意味着用于在云负载均衡器上选择后端的熵有限,这进一步导致请求在后端。为了缓解这个问题,我们为 nginx 构建了专用的 Kubernetes 节点池,以确保所有 Kubernetes 节点始终运行等量的 Kubernetes Pod。这有助于避免 nginx pod 过载的这些热点。
我们的下一步?
展望未来,我们的 BFCM 规模测试之旅的下一步可能会涉及采用更多的负载生成地点,以进一步配合 Shopify 的全球扩张。为了不断提高合成负载测试的真实度,我们认识到有必要继续迭代负载测试流程,使其更符合生产流量模式。
最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:【文末自行领取】
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!