Jordan Sitkin 和 Luke Demi 讨论了 Coinbase 应对 2017 年加密货币的激增的方式,以及工程师们是如何利用从这些经历中获得的经验教训来创建用于容量规划的新工具和技术、为加密货币热潮的未来浪潮做准备的。Luke Demi 是 Coinbase 可靠性团队(Reliability Team)的软件工程师。Jordan Sitkin 是 Coinbase 的软件工程师。本文整理自他们在 QCon 的演讲《Capacity Planning for Crypto Mania》。
但不管怎么说,冷静。没错,这个现象很糟糕,原因有很多。但主要是因为,如果当时你是 Coinbase 的客户,那么你一定会产生担心的情绪——你会担心自己的钱不见了。Coinbase 刚刚被黑了,发生了什么事?我们开始看到一些可怕的说法,比如这种:“Coinbase 随着比特币和以太网而没落了。”好吧,这么说很对。但是,很伤人。这些年我们一直低调行事,尝试建立这个生态系统,并试图领导这场变革。这些 Reddit 上的评论,有点幽默,但抓住了那天的情绪。我再读一条评论:“有趣的是,Coinbase 应该使用了 AWS,因此容量不该是个问题。除非他们不使用自动扩展或者太便宜而无法购买更多的 AWS 资源,或者只是懒。”
这是某天我们在一个房间里的写照。那天非常有趣,因为我们一整天都在发愁。大概有 8 小时,我们在一个有沙发的房间里就这么坐着,想找出方法来让网站回复原状。那天的《纽约时报》刊登了一篇文章,称 Coinbase 宕机了,无法处理网络负载。对我们公司来说,这是一个重要的转折点。我们意识到,如果我们不能团结一致,那么,Coinbase 就无法生存。
Siktin:我们把这个访谈叫做加密狂热的容量规划。该访谈的内容是我们如何找回最佳状态。也就是说,我们如何从 Luke 刚介绍的、去年那个黑暗的日子开始把这个故事反转。我们准备介绍我们在那段时间学到的东西。在那段短短几个月的时间里,Coinbase 的流量增加了 20 倍。我们犯了一些错,得到了一些教训,而且我们有幸在工作的同时进行了一些有趣的挑战。当然,在此期间,我们的工具和系统不得不快速成熟。我们将介绍 Coinbase 从那天开始的故事,并会随时提出一些见解和经验。然后,我们计划讲讲我们现在在做的事,为未来做更好的准备。
先简单做个介绍。我是 Jordan,这位是 Luke。我们是 Coinbase 可靠性团队成员。需要说明的是,在去年那场疯狂的热潮中,我们只是涉及其中的两位。我们也是现在和未来负责维护系统可靠性的人。
买卖数字货币
正如你们中的很多人所指出的,大家已经很熟悉 Coinbase 了。对于 Coinbase,大多数人都知道是个应用程序。我们希望它成为最简单、最值得信任的数字货币交易及管理场所。但是现在,Coinbase 实际上远不止是个应用程序。我们是围绕着 Coinbase 这一名称的一个其他品牌和服务的小集合。为了更全面地概述我们的技术栈,今天我们在这里主要讨论的是一个独立 Rails 应用程序。它由 MongoDB 数据库支持,其基础设施在 AWS 上部署和管理。
在今天的演讲中,我们希望告诉大家两件重要的事情。首先,今天演讲的题目是跟容量规划有关的,但是,我们实际上会用大多数时间来谈负载测试。原因是,我们觉得负载测试(或者叫容量测试、压力测试、批量测试)是我们在容量规划工作中最重要的工具。我的意思是,通过容量测试(或同样术语的不同其他叫法),实际上能够模拟和研究生产中可能发生的真实故障。这样说应该比较容易理解吧。
其次,我们希望告诉大家,在负载测试环境中,与生产完全对等不是从负载测试系统中获得良好结果的必须要求。我们准备解决它的方法之一是,引入我们称之为容量周期的概念。这将是我们在整个演讲中都要回归的主题,因此,我希望现在就把它介绍给大家。我们准备详细介绍它的含义,但我们也打算通过这种方式分享我们的一些经验。
后端 RPM
Demi:好,我们稍微回头看看,再讲一点关于这个故事的背景。其实只是展示一下我们的流量模式在事情变糟前的样子。我要说的是,它们很随意。这是我们到 Rails 服务的后端流量。我们可以看到,流量一会儿增加一会儿下降,但其波动处于一个相当狭窄的范围内。这里画了一条红线,没其他特别的原因,只是表示上面的情况很糟糕,但我们离那里还很远。
在这个时间点,红线就意味着人们真正开始对加密货币、盈利能力这类东西感兴趣了,它们不是我们担心的事情类型。我们不认为我们会碰到这条红线。然而,这条红线最终成为一场灾难。这是我们的流量图。稍微提一下,该图截止到 2017 年 7 月前后。这张图展示了我们的流量变化,以及我们穿过这条红线的方式。很显然,这背后是以太坊和比特币的热潮。
我们在这条红线上方待了几天。有几天我们从太平洋时间凌晨 3 点开始一直到半夜,每分钟都有 10 万多个请求,因为人们只是不断尝试登录以购买产品。我们的系统崩了,但是没关系,人们只是想继续回来。对这一现象最恰当的描述就是“爆炸“,人们到处飞。这里再展示一下《纽约时报》的那篇文章。大家可以看出那段时间事情进展得有多不顺利。
网络服务时间崩溃
事情进展不顺利的原因是,虽然事情进展的方式正常,但进展速度太快了。这是 New Relic,非常简单,它显示了我们的的 Ruby 时间和 MongoDB 时间。我们可以看到,Ruby 时间显然占了我们应用程序内部所做的一大块,MongoDB 时间只有一点点。这是我们访问网站时,通常看到的样子。80% 的时间是 Ruby 的,一点点是 MongoDB 的,还有其他一些服务我们没有算在这里。但是,当我们经历这些问题时,也就是当网站宕机时,它看起来是这样的。出于一大堆不同的原因,这让人感到困惑。首先,Ruby 和 MongoDB 紧密相连。我们可以看到,由于某些原因,它们是同步的。很显然,它们上升了很多。我的意思是,大家看一下左侧的图表,这是 4 秒的响应时间,只是为了获取 Ruby 和 Monogo。我们可以想像在那个时间点,没人能通过。
这是个让人非常困惑的图表。但是,更让人困惑的是,在那个时间点,这是我们拥有的唯一图表。我们当时盯着这图表看,坐在那里讨论说:“好吧,显然 Mongo 很慢,但是 Ruby 也很慢。”“还会有其他情况吗?”“伙计,图表就是这样显示的”。我们没有使用工具深入研究正在发生的事情。实际上,这张图表是错误的。但是,它让我们在这段时间陷入了很难搞的事情。我们花了好多天做荒谬的事。我们当时最靠谱的做法应该是:“如果我们调整内核参数会怎样?”那在当时是有道理的。是的,也许内核就是问题所在。难搞的事情有一大堆。
惊醒梦中人
为了解释 Jordan 提出的周期,我来描述一下当时发生的事情。我们会在每天早上,或用一整天的时间,进行一次细致的一致负载测试。当我们进而分析它时,我们完全搞错了方向。对于工具和周期的改进部分,我们应该做的是添加工具以帮助我们理解在负载测试过程中发生的事。在那个时间点,我们没有那么做。我们只是再次盯着同样的工具,并思考古怪的想法。
为了解决这一问题及其中一些扩展问题,我们做了每个人都最爱做的事——把责任推到数据库和升级版本上。我们升级了 MongoDB 的每一个版本,以让自己保持最新的版本。如果你熟悉 MongoDB 就会知道,我们一开始使用的版本非常糟糕,因此升级版本会有一些相当大的改进。我们还做了另外一些事,比如把我们的集群分开。举个例子说,我们有一个用户集和一个事务集被托管在同一个主机上,我们就把它们分开。这样它们各自就会有更多的增长空间,这让我们摆脱了当时的困境。然而,我们意识到,如果我们希望能够承受更多的负载,还需要更好的方法。因为在这个时间点上,我还是感觉流量一直在增加,也就是事情仍然在变坏。情况确实如此。
然而,我们在这里得到了一个重大的教训。这比较明显,但是把它说清楚真的非常重要。好的工具会让问题显现出来。但是,糟糕的工具会让问题变得模糊,让人感到困惑,总有一天会让我们看上去像个白痴。
Sitkin:回到我们的时间线,假设我们现在处于 2017 年中期。回到这张我们一直在看的流量图表。我们可以看到,在这次突破之后,我们被迫经历了一些慌乱、快速改进了系统,存活下来了。流量增加而又相对稳定了。我们的流量到达了新基线水平。我们基本上在这个时间点处理这个问题。我们可以看到,虽然有些波动,但我们基本上处于一种新的流量平衡中。
此时,那些来自用户的负载测试再也没有使我们的站点以相同的方式宕机了。我们没有跳进这个周期,这个周期是我们后来才明白的。结果,我们对我们的工具做了很大变换,优化了我们已有的工具。但是,我们没有足够的创造力来强迫我们的系统创建下一个故障模式。因此,我们仍有点不安,担心未来。为了从周期的角度来说明这一点,最终我们将其亲切地称为 YOLO 负载测试阶段,可以在这里看到我们的负载测试步骤的一周版本。
我们意识到,我们需要人为地返回这个负载测试,以便回到快速改进周期。于是,我们只做了我们可以想到的最简单的事。我们基本上只是采用了一些现成的工具,运行了一些合成负载测试。我们再次根据开发环境进行了测试。我们不是很确定从哪里开始最好,于是,我们就这么开始工作了。
实际负载测试的支柱
在继续详细介绍我们所做的工作之前,现在是介绍我们称之为实际负载测试支柱概念的好时机。这是一种在三个类别中分解负载测试策略的方法,是评估实际可行性的一种更好的方法。其中一个重点是,这三个类别中的每一个都可以单独解决,同时提高了我们负载测试的真实性。我们不一定需要把它们一起提高,或者它们彼此之间甚至不必同样重要。
第一个我们称为数据;现在展示的是我们数据库中数据的形状。类似于:在我们的测试环境中,每种类型有几行?跟在生产中所有的相比,这些记录之间的关系是否真实?接下来的是流量。这是从负载测试系统中出来的流量形状。我们可以问问自己,出现了多少请求?速率是多少?这些请求到达哪些端点?这些请求的分布是否匹配我们在生产环境中实际看到的流量类型?第三类是三者中最简单的,那就是构成我们负载测试环境的物理系统是否匹配我们在生产中实际运行的东西?我们在负载测试环境中运行的这类服务器有多少?它们之间是如何关联的?网络层看起来都一样吗?
在这种情况下,回到我们在这个时间点真正在做的。我们从一个名为 Locust 的现成开源工具开始。我们觉得,对我们来说,这是开始实施分布式负载测试最容易的方法。我们知道,我们可以从中得到相当大的吞吐量。大家如果不熟悉 Locust,我就简单介绍一下。这是一个工具,允许我们用 Python 编写模拟的用户流,然后在多个节点上回放它们,以实施分布式负载测试。这其中,我们拥有的一个主控件是调试我们的模拟用户池中的用户数量,然后添加到这个池中的速度快慢。
再多讲一些细节。这是 Locust 的基本架构图。我们有一个主节点,我们与它交互来控制测试,然后,它控制一群从属节点。这些从属节点中的每一个负责维护一个小用户池,这些用户正运行在我们用 Python 编写的模拟用户流中。
在这个时间点上,这是我们的第一次尝试。最终,这个测试对我们来说就是个玩具。它没有真正提供我们所需的结果类型。但是,我们没有真正理解它没有提供的原因,也没有真正相信它提供给我们的结果。但是,尽管存在这些问题,它确实有助于我们更好地理解一个良好的负载测试系统应有的样子。
从我们刚刚介绍的这些支柱的角度来看,我们知道,数据和系统与生产不匹配。这是一种设计。我们知道,我们刚刚的测试只是用一种幼稚的先通过方式来实验。但真正的问题是,我们不了解系统输出流量的方式在哪些方面是不现实的。我们还没有建立一个提高现实性的流程,甚至没有创建这个初始用户流。我们并没有根据基本原理来做这件事。因此,该系统永远不会成为我们工具箱中的重要工具,因为我们不信任它。巧合的是,这也很快变得无关紧要。
圣杯(the holy grail)
Demi:因为在 2017 年稍晚的时候,也就是 2017 年 10 月,加密货币真正成为主流。现在,我们把我们工作的公司称为“妈妈都知道“的公司了。我们的应用程序在应用程序商店下载排行榜上排名第一,新闻报道开始的风格变成这样了:“比特币狂热”,“加密货币狂热”,“我们应该买点加密货币吗?”,“我们是否应该把钱都投资到加密货币上?”
我们的流量现在呈现这种趋势。这条红线是我为了扩展而随意画的。并且对更大规模,流量会有更大的跃升,这也就是我们之前宕机的原因。而在这段时间里,情况没有那么糟糕。我们肯定会在某些地方出故障,这取决于我们跟得有多近,信不信由你们。但是,我们能够生存下来的原因是,我们这里有个东西,我们把它称为“容量周期的圣杯“。为了解决这个问题,也就是我们认识到的基本问题,我们进入了这个周期的节奏。比如,我们会在凌晨 4:30 醒来,这时候东海岸的人们会在想:“我应该买比特币吗?”他们就开始对我们的网站进行狂轰滥炸。我们花了一个早上只是为了保持网站在线。午餐时,我们会分析结果:今天宕机的原因是什么?明天我们又会因为什么宕机?
例如,假设我们在 Mongo 上的用户集合现在已经被分解成一个新的集群,我们没有更多空间进行垂直扩展。那么我们能做些什么?于是,我们花上一个晚上试图改进。我们添加检测以增加清晰度,也就是可以通过添加新功能来改进。然后,第 2 天,我们再次测试负载。这个周期会继续下去。每天我们醒来,喝杯咖啡,“好,现在来个负载测试,”然后,下午来分析发生的事,并改进工具。
这是在这段时间,我们为该用户集群所做的其中一些重要事情。在一个周末,我们利用了我们称之为 Memcached 的标识缓存添加了一个完整的缓存层。这使用户集群的负载下降了 90%。这给我们带来了足够的余量,至少够用几周。它允许我们坚持到第二天并解决下一个问题。这个流程让我们解决了问题。它也告诉了我们一个重要的道理:当我们在解决问题时,反馈越快,进展也越快。每次我们经历这个周期,就是一个改进的机会。我们经历的次数越多,能够做出的改进次数也越多。
Sitkin:让我们回到我们的时间线。现在是 2018 年,我们刚刚经历过这个让人痛苦、非常累人的周期。在这个周期中,我们得到了快速反馈周期和快速改进,并我们的系统上取得很多进展。我们进入了一个平稳期,不是每天都能创下历史新高。我们开始不那么频繁地创历史新高了。你可能会认为,我们会因为体验到这一点而感到宽慰。是的,相信我,在这几天能多睡点真是太好了。
但是,每天早上我们都会有一种潜在的焦虑感,因为我们不再进行这个奇妙的负载测试了。在增加我们的容量方面,我们没有这种真正的催化想法,即我们要做的最重要的事情是什么。就周期而言,基本上,这是当我们的负载测试逐步脱离我们控制的时候。有一点点害怕,因为我们知道仍然存在一些挥之不去的性能问题。很显然,仍然要添加新功能,因为新情况还在继续出现。
事实上,现在我们非常关注推出新货币。每次我们进行这项工作时,都可以看到略微不同的有趣的流量峰值,我们希望为这些情况做好准备的不同流量模式。因此,我们在这个时间点觉得有必要回到现实,恢复这个负载测试步骤。我们需要能够在测试环境中看到实际负载。
生产流量的捕获及回放
基于我们在 YOLO 合成负载测试方法上的经验,我们对来自合成系统的流量并不十分自信。在我们处理这个问题的过程中,这个挥之不去的问题出现了好几次,那就是,我们为什么不能在负载测试中使用实际的生产数据。这看起来是个很自然的问题,非常有趣。这样,在我们的负载测试中创建宕机的方式就会自然地与我们在生产中看到的宕机类型相匹配。其原因就是,我们实际上在利用实际的用户行为。
此外,我们非常确定 MongoDB 是目前堆栈中最敏感的部分。它是共享最多的资源,这也就是之前我们宕机的原因。但是,一般来说,单独测试某些东西不是好主意,我希望在这里使用另一个概念,以证明我们为什么觉得在像数据库之类的东西中进行单独测试是有用的。Neil Gunther 有一个通用可扩展性定律(Universal Scalability Law)的概念。这是众多描述系统拓扑扩展的不同方法的其中之一。在这里的这张简单图表上,我们已经有了两条线;虚线代表没有共享资源的系统可以如何扩展。随着吞吐量的增长,负载和它有完美的线性关系,因此它可以进行完美的线性扩展。这两者之间的关系没有理由随着负载的增加而变化,在该系统中没有共享资源。
但是,与此相反,我们有这条红线,它代表有共享资源的系统可以如何扩展,因为要争夺共享资源(如数据库)。随着负载的增加,吞吐量根据围绕着共享资源的争夺情况会下降。这个曲线的形状会根据系统的实际设计变化。但是,这里的关键是,随着系统的增长,我们在这些资源上的收益递减。然后,随着负载更多的增长,我们甚至开始后退了一点点。这是一种说法,在数据库上的负载几乎总是让我们失败。正是这个原因,它才是共享资源。大多数时候,我们对像应用程序节点的东西有个相当清晰的扩展描述。这些节点是无状态的,本身并不是共享资源,但它们通常会加载到有问题的数据库上。
这是一个巨大的胜利。当我们在讨论单独测试数据库时,这对我们来说非常好。我们使用它可以很好地完成一些事情,如充满信心地正确调整我们的集群规模。因为我们知道我们可以根据数据库的更改参数调整不同的可调参数,并确切知道它在现实世界中的响应方式。又因为,我们在应用程序实际生成的数据库上回放相同的负载。我们能够在我们的测试环境中创建这些非常现实的故障。当然,接下来我们要考虑的是,如果这招真的好用,那么我们可以单独测试数据库。但是,用同样的策略测试我们系统的其余部分会怎样呢?
Demi:正如我们能看到的,很显然,我们能够单独测试 MongoDB。因此,从字面上看,我们能够在 Rails 和 Mongo 之间捕获流量并回放之。这是我们能够获得的最接近的结果。然而,我们意识到,在我们的系统中有很多边界,我们不得不想出一种方法来测试,该方法能够与我们的 Mongoreplay 故事的成功相匹配。这一切努力是为了确保我们不会在适当的时机宕机。如果我们不测试负载测试中的关系和边界,那么,它可以告诉我们很多关于单独系统的事情。但是,如果在我们的环境中稍微不那么突出的系统或其他共享资源之间发生新的回归,那怎么办?因此,我们知道我们需要尝试找到不同的方法。
流量、数据及系统
让我们从流量开始,回顾现实主义的三层或三个支柱。这些是我们以有效性为标准对它们进行的排序。例如,最底层的、最不切实际或基本的测试方法是使用这种简单的单用户流。因此,这意味着只测试当用户点击三或四个页面时所发生的事,就像我们在运行 A/B 测试或类似的东西一样。
第二种,也许是更现实的方法,可以根据真实的用户流做些像合成流量生成这类的事情。比如,我们可能会有个工具可以综合地生成流量,但却需要查看我们的日志数据,以找出如何在新环境中进行回放。最后,Holy Grail 是这种捕获回放的想法。我们用的是完全真实的生产流量,我们只是把它指向其他内容,并看看它的响应情况。
顺便提一下,捕获回放听起来不错。我们碰到的主要问题是,特别对流量来说,记录 post body 是极其困难的。我们在 post body 中存储了很多敏感信息,针对非生产环境或者在生产环境中重放这些信息时,会存在很多问题。此外,很多用于后续请求的 ID 并没有被确定地生成。因此,当我们尝试重放流量时,很难匹配那些请求。结果,我们需要做的是以某种方式重写它们,使其工作,这有损于现实性。
然后,回过来谈谈数据。最简单的方法就是,用 siege 测试开发环境的方法来测试数据库,以便创建它们。只需要创建一些基本用户,把它们插入负载测试框架中就可以了。下一个最佳选择是,综合生成数据,但用更实际的方法来实现。实际查看用户的布局、拥有很多账号的用户是否有很多以及这些用户之间的关系,然后在我们的环境中综合地创建这些东西。
最后,这里有排在前两位的清除数据的方法。因此,我们可以使用生产数据库,把它放到较少的生产环境中或另一个生产环境中。但是,重要的是,要明确地删除那些我们要保护的重要客户信息。很显然,获取一个真实负载测试的最佳可能方法是在生产环境中对数据进行实际的测试。
最后,来讲讲系统,这是有道理的。但是,如果我们在一个简单的开发环境中进行测试,那得不到实际的结果。如果我们的集群大小有问题,那么就不会产生实际的结果。然后,是创建生产平价环境。也许我们想做一个编译框架,该框架可以创建我们的所有生产资源,只要把它指向另一个 AWS 账号并运行就可以。这很现实。但是,很显然,在生产中测试是最终的策略。我们得到了确切的生产环境,它具有所有的问题和节点等等东西。
然而,我们开始思考这个问题时,意识到捕获回放方法很难,合成方法也很难。但是,我们是区块链公司。我们为什么不能用区块链解决方案来解决这个问题?于是,我们决定采用大胆的新方法。因此,今天,我们很高兴地宣布,在 Coinbase 发布 Load Testing Coin。Load Testing Coin 是兼容 ERC20 的 coin,允许你告诉你的用户来加载测试你的负载。我在开玩笑。我们不希望把大家搞得太兴奋。我们不得不这样做,没有随便搞个 Load Testing Coin,那是假的。但是,事实上,那会很酷,对吗?如果你只需要告诉用户来,… ,不是 coin,这个 coin 是愚蠢的。说真的,你会买它吗?
Sitkin:是的,我也许会考虑买。
Demi:我们会加油的。抱歉,这个讲话是要转录的,对吗?可以掐掉这段吗?所以这里的关键在于,理想的解决方案是告诉实际用户到网站来做真实的事情。就像发生在我们身上的一样,我们希望根据需要创建密码狂热,但是,如果我们能激励人们加入,那就好了。不幸的是,这个想法被否决了。但是,事实是,我们决定要做的是,回到我们跟 Locust 的最初策略上。我们决定看看,根据这些层,我们可以对这个策略做多少改进?我们如何沿着这些层往上走,并把这个策略改进为我们日常工作中可以用的实际东西?
实践中的容量周期
Sitkin:因此,正如 Luke 所说的,我们回到我们之前被称为 YOLO 的测试策略。但是,我们从这个新起点开始,更好地了解我们需要负载测试系统做什么。因此,在这一节里,我将逐步介绍在我们的实际日常工作中应用这个容量周期可能的感觉。就采取负载系统而言,负载系统处于一个非常基本的状态,然后,使用我们的周期来增加其真实性。最终,找到我们系统的一些有趣的扩展问题。
这个示例的设置在这里,我们打算尽快开始。我们将其范围缩小到刚够用规范工厂创建种子数据。我们可以通过数据获得现实基线水平。然后,对于流量,我们刚打算重新创建脚本,只是一个单用户流的单个简化版本,只是那些最基本的可能工作。然后,系统变成基于我们已经在运行的开发环境,即位于负载平衡器背后的少数节点,没有后台工作人员。然后,我们的三个主要后台数据库每个只有一个实例。因此很直接,很简单,很精简。
我们开始吧。我们进行第一个负载测试,每秒最多 400 个请求。首先,Redis 上的 CPU 得到了充分利用。我们到达了上限。因此,很自然地,我们会深入查看系统是如何布局的。很明显,这里的问题是,我们通过运行几个不同的 Redis 集群来隔离生产中的工作负载,然后在我们的测试环境中,我们只运行这个服务一切的集群。因此,这是一个快速的循环周期。很明显,我们在这里能做的最简单的事情是,让我们的系统更加真实。于是,我们打算通过打破 Redis 集群和测试环境来改进层,以匹配我们在生产环境中所运行的东西。
好了,那么,让我们再次经历这个周期。结果,这一次,我们在同一个时间点宕机了。看看我们 Redis 集群 CPU 的统计数据,我们又一次看到其中之一满负荷运作。事实证明,我们实际上只是忘了禁用一个跟踪工具,该工具消耗了大量计算资源,而在生产中我们不使用它。因此,我们又有了一个好机会来提高我们的负载测试环境中的系统类别的真实性,即只需要禁用该工具。因此,这是又一个快速反馈周期。
好,我们再来测试一下。这次,我们走得更远一些,每秒有 850 个请求。再我们的系统中查看这些指标,这次发生了不同的事情。我们查看 Redis 状态,看上去都没问题。我们查看 Mongo 统计数据,看上去也没问题。所以我们缩小了查看范围,我们只查看了在这个端点上我们实际看到的生产中的请求,还有我们在负载测试系统中看到的请求的端到端跟踪,并且所有内容看起来几乎相同。 但显然,出现问题比我们预期的时间要早。 但事实证明,我们注意到我们的应用程序节点上的 CPU 满负荷了。 基本上,Ruby 时间是问题的根源。
我们在这里有另一个机会来提高系统中的真实性,可以通过匹配应用程序节点和我们在生产中实际运行的支持服务之间的比例来实现。最简单的方法是增加在测试环境中运行的应用程序节点数量,因为这让我们与生产系统更为接近。因此,我们可以很快地做出改变,并再次完成该周期。
第四次的时候,我们走得更远,每秒有 1500 个请求。这一次,出现的第一件事是,我们 MongoDB 集群上的 CPU 负载太高了。如果你们经常使用 MongoDB,应该知道 mloginfo 这个出色的工具。我们使用了这个工具。这确实是个非常好的工具,可以用于解析 MongoDB 服务日志,并提供特定排序细分,特别是那些查询形状在系统中是又昂贵又慢的。并且,它给我们指出,有一个单独的、从未出现过的昂贵的查询在这里排名很高。这种情况向我们指出一个事实,即在负载测试系统和生产中的数据形状之间可能有重大的差异。因此,这里我们意识到,在每次负载测试运行之间,我们没有正确地重置我们的数据库状态。这给我们指出了一个事实,我们需要增加数据类别的真实性,例如修复此重置脚本。
现在,我们使用改进的负载测试环境进行另一次测试运行。我们在每秒 1400 次请求时出错,非常类似的范围。但是这一次,在 MongoDB 上的 CPU 统计数据又一次没问题。但是,通过查看跟踪,我们看到,事实上来自 Memcached 的响应很慢。那绝对是我们不习惯看到的。因此,我们对这个问题进行了一点研究,查看硬件级的统计数据。第一件事情是,我们尝试针对单个 T2 micro 运行生产级的负载。T2 micro 是一个运行 Memcached 的很小实例。因此,很显然,很快就崩溃了。
同样,这里有个简单的增加现实性的方法,就是提高 MongoDB 节点上实例的大小。这给我们另一个机会来完成一个周期。这一次,我们事实上做得相当好;在这个用户流上每秒有 1700 个请求。从资源的角度看,这实际上是特别昂贵的流。通常,我们在生产中不经常碰到这种负载。在测试环境中达到每秒 1750 个请求是非常令人鼓舞的;这是个好结果。
Mongo 上的 CPU 统计数据看起来相当不错,就像我们的跨堆栈硬件统计数据一样,看起来相当健康。这让我们稍作停顿,因为,假如我们这里的用户流事实上以非常逼真的方法来运行的话,那么,我们自然不会期望能够提供这类负载。因此,这让我们意识到,我们在生产环境中所看到的和我们在负载测试环境中所看到的之间,可能在流量类别上有差异。再深入一点,我们注意到,这绝对是事实。为了在我们的应用程序中重现这个用户行为,我们只是运行了一小部分真正需要点击的端点。通过更好地调查和编写更好的脚本,以更好地重建用户在实际执行该流程时在日志中所看到的内容,那么,我们能够提高流量类别的真实性。
最后一次运行这个周期,这次,我们每秒最多得到 700 个请求。根据我们可能期望看到的情况,这可能更为现实。分析表明,我们大部分堆栈的硬件统计数据,特别是 Mongo 没有问题。我们习惯于看到 Mongo 中的故障,因此,这常常是我们在数据库层首先查看硬件统计数据的地方。然而,我们 web 节点上的 CPU 也满负荷。上次我们遇到这个问题时,我们觉得测试环境中的应用程序节点增加了一倍,因为,这让我们到了更接近生产环境的地方。但是,这次我们不打算这么做,因为在负载测试中运行的节点数量与在生产环境中的节点数量的比例是准确的。因此,这么做就不对,会因为添加更多节点而破坏我们的真实性。
但是,当我们真正挖掘一些分析以查看我们的应用程序在 CPU 方面做了什么事、实际上占用了什么的时候,我们发现了一些有趣的事情。这是一个我们已经意识到的问题。但是,我们注意到,这个特定的性能问题是造成我们这次测试失败的瓶颈。我们有一些事件跟踪的处理十分低效。因此,这里,这是我们希望得到的结果。我们实际上发现了一个令人信服的现实瓶颈。当我们达到这个负载水平时,这可能会让我们在生产中宕机。实际上,这里要做的正确的事情是应用修复。因此,我们改进了系统。现在我们可以跳回到循环周期中,并获得更多的见解。
我把这个例子留在这里。但是,我希望,通过讲解这个真实世界的例子,我已经让你知道,这个快速反馈周期有多么的强大。它可以在测试下改进系统,也可以改进测试系统本身。循环周期的每次迭代都是一个改进这些东西其中之一的机会。不久之后你就会发现,事实上最重要的问题在哪里。
关于这些问题,还要注意一件重要的事情,即我们也许意识到我们的系统有很多潜在的性能问题。但是,真正的价值在于,它迫使这些问题中的其中一个变为最突出的、最可能让我们失败的问题。于是,这让我们的工作计划变得非常容易。这是最重要的事,它实际会把能力提升到一个新的水平。
现在,我们在这里,对未来有一些更大的计划。但是,我希望在本节留下一个经验,即我们都在负载测试环境中追求真实性。但是,关键在于,随着我们每一步都在增加真实性,这个过程就和最终目标一样重要,因为我们一路上要学很多东西。
未来
Demi:我们今天就是为此事而来的。我们现在在用的这个负载是为新货币的发行做准备的。现在,这更像是 Luke 和 Jordan 的工具。我们能够用它来找出和确认每次我们发行新货币时会引发流量激增的任何事情。我们可以测试那些已知的在这些过程中将会变得突出的流,并确保没有在它们发生前就存在,而我们却还没有发现的瓶颈。
我们目前正在增加这些测试的真实性。构建自动化方法来了解最新的流量模式,并把它们应用到我们实际在运行的测试方法中,这是我们目前的首要任务。同时,我们要继续改进我们使用数据的方法。我们希望很快得到的结论是,我们的流量基于完全真实的流量,并且,我们的数据基于真实的清洗用户数据。理想情况下,我们的系统也是一样的。
这个周期将继续指导我们。我们对这个容量周期感到非常兴奋。我们在内部对每个人宣传此周期的有效性;有些人相信也有些人不信。但是,我们内部真正要做的就是坚持这个想法。我们不希望这只是个 Luke 和 Jordan 的工具。因为 Luke 和 Jordan 的工具无法长久存活。当我们在做其他事情时分心了,怎么办?如果这个工具开始分离崩析,怎么办?它会变得毫无用途。
我们正在尝试做的是,创建一个可重复的持续元素以实现把我们的代码交付到生产中。我们希望为大家创建这个本质上是快速反馈的循环周期。因此,当有人推动新的生产变革时,他们应该知道它会不会影响生产工作中的绩效。因此,我们处理这个问题的一些方法可能成为压缩测试环境中构建过程的一部分,比如一个非常一致的环境。我们能够处理多少请求来掌控最重要的流?这些是我们现在要关注的事情。
教训
但是,在我们离开之前,我想再快速回顾一下我们的教训。首先,良好的工具会让问题浮现出来,而糟糕的工具会使问题变得模糊不清,让我们感到困惑。我认为,这适用于几乎任何事情,特别是,如果我们是那些经常在生产中挖掘问题的人。如果我们没有合适的工具,那么就添加合适的工具。如果我们不了解我们的工具,那么要保持好奇,去研究它。不要以为我们看到的就是实际存在的。
更快的反馈意味着更快的进展。这适用于一切,但是,特别适用于我们的生存能力,以及在这些疯狂流量增长时期有所发展。这只是因为,我们能够使用这个快速反馈来指导我们采取的行动。最终,我们使用简化的负载测试环境获得出色的负载测试结果,并增加了真实性。因此,当我们进行负载测试时,过程和目标一样有价值。不要害怕深入,立即开始以取得成果。我们过去曾经遇到问题,我们已经经历了无数次宕机。我们一开始就想到真实性,并试图让一切都和生产环境中的一样,但是,在这个过程中,我们失去了一路上所学到的东西。
演讲到此结束。最终,我们学到了关于容量规划的很多真正有价值的经验教训。我们当然希望这个演讲能帮助你避免犯我们已经犯过的错。这是我们的推特号。但是,我们真的很想知道你们是如何在现场进行负载测试的。希望这能激发一些有趣的对话。当然,也可以在会议的其他时间通过推特联系我们。
问与答
参与者 1:谢谢你们的演讲。很有见解,真实地反映了我们团队现在正在进行的一些活动。我很想从你们的经验中学到东西,并把它应用到我的团队中。我的一个问题是,你们是从哪里触发这些任务的?如果负载测试从生产负载中减去 5 倍、10 倍、50 倍,那么,你们会延伸到什么程度?你们是否会满足于“好吧,目前的设计就这样了,它能够支持什么”?
Sitkin:Locust 提供了一个 web UI,让我们真正开始测试。因此,这是使用这个开源工具的好处之一。为了回答你关于我们如何决定将测试提升到什么程度的问题,到目前为止,我们基本上把它设置在一个荒谬的程度上,我们在寻找性能开始下降、服务崩溃的时间点。因此,在这个时间点上,我们真正在做压力测试。我们在寻找系统停止工作的时间,然后只是测量这个程度是什么,不断地将它增加。我们对想要达到的目标有个粗略的想法,这是上次在解决中让我们宕机原因的一个粗略倍数。但是,其中很大一部分只是意识到特定用户流从哪里开始消失。
参与者 1:还有一个问题。由于依赖服务不受你们控制,你们是如何使用依赖服务的?如果这些是瓶颈,你们有什么建议来解决它们,甚至和这些团队合作来解决问题?
Sitkin:是的,这就是我说的负载测试中有点艺术性的一部分。我们在开始时非常简单的一个好处是,如果很难开始,那么就放一边。然后,当你对一小部分流的真实性有信心时,就可以开始逐个把这些依赖项添加进去。它们中的每一个都会有自己的特性,关于删除或模拟的最好方法。
例如,作为加密货币公司,我们最终触及的很多事情都是区块链。当然,我们必须创造性地剔除这些东西。我们用这样的东西只能实现一定程度的真实性。因此,我发现很难对这类问题给出一个普遍的答案。但是,我认为,回到流程,一切都是关于逐一解决它们的问题。获得快速反馈周期,并一次只添加一个。不是一下子就追求完全的真实性。因为通过添加一个更改获得的观察结果可能非常有用。
参与者 2:谢谢你的精彩演讲。我的问题是,作为一家快速发展的公司,当你们飞快地发布新产品和新功能时,你们是否在生产中看到,数据的形状和请求的模式经常变化?如果是这样,你们如何让你们的负载测试环境跟得上这些改变?
Demi:是的,特别是在流量方面,这是使用捕获回放的主要好处之一。你可以保证捕获并再次运行,你可以保证流量是最新的。但是。我们现在的处理方法是。通过一个脚本运行于数据的各个部分,然后我们提取其中存在的流。因此,存在一定的有状态流。因此,你不能只是按顺序回放请求,需要把它们拉出来。
我们能够做的是,生成数据形状,然后把它们应用到 Locust 上。这个部分事实上更容易。数据的形状实际上极其困难。这是我们完全没解决的问题。我们知道数据的形状是什么样的,我们实际上能够使用我们数据团队在用的分析工具。它们能够在我们拥有的最大用户上指导我们,比方说,10000 个不同的交易,对吗?因此,我们可以在那里构建一个分布,但是要保持它是最新的,我们根本没解决这个问题。这很难。但是,理想情况下,我们可以做的是,与这些工具进行交互,并在我们清除和更新测试时进行调整。
参与者 3:很棒的演讲。我想知道,因为生成负载测试是相当昂贵的,基本上像攻击,因为我们试图让网站宕机。我想知道,你是否找到一个方法可以计算实际情况下的理论极限值?是否可以根据一些例子,据此推断并找到下一个瓶颈在哪里?
Sitkin:我接触过一点通用扩展理论的概念。当我们实施这个有点巧妙的方法进行负载测试时,这些是我们要注意的事情。这也有助于我们形成关于什么可能出错的假设。但是,我认为,我们发现进入循环周期的好事是,我们很快就能测试这些假设。这也有点让我们的性能理论方法变得更好,看到这些东西与现实世界的真实关系。但是,为了更直接地回答你的问题,我们还没有做类似学术研究的方法。我们一直在实践中关注那些我们系统中产生的启发。你有什么不同意见吗?
Demi:代价不菲。现在我们要做的是,构建一些真正复杂的技术,以让我们不因为财务问题而失败。这就涉及到如何进行扩展。幸运的是,AWS 使这个变得非常简单。数据库扩展的方式,应用程序服务扩展的方式,我们有一个脚本来启动和关闭。这是有帮助的。
但是,我们喜欢做的是,当我们接受这种挤压测试的想法时,每一个进入我们主要项目的拉取请求或提交,这就是我们打算做的事。我认为,我们还没有真正讨论作为一个开始,我们可以做什么,但是,使用我们在挤压测试环境中目前所有的相同数据,并在完整的生产环境中也这么做,来看看它们能处理多少。然后,我们可以进行推断,那将是个相当不错的处理方法。你对这个想法有什么看法?
Sitkin:好主意。Luke。
Demi:一起做个演讲不容易。我们必须对每个想法进行交流。我很高兴你喜欢这样。
阅读英文原文:Capacity Planning for Crypto Mania;https://www.infoq.com/presentations/coinbase-cryptocurrency
来源:区块链前哨
译者 | 姚佳灵