2020 年开源负载测试工具评测
本文是一篇客座文章,由Ragnar Lönn撰写。
比较 2017 年以来最好的开源负载测试工具!
距离我们首次发布那些广受欢迎的比较和基准测试文章已经过去了将近三年。由于一些工具在过去几年中发生了很大变化,我们觉得更新似乎有些晚了。为了这次更新,我们决定将所有内容整合到一篇篇幅较长的文章中,使其更像一篇指南,供那些正在尝试选择工具的人参考。
首先声明:作为作者,我力求保持客观公正,但鉴于我参与了评测中的一款工具(k6)的开发,我难免会对这款工具抱有偏见。请您自行揣摩字里行间的含义,并对我所写的任何关于 k6 的正面评价保持怀疑 ;)
关于评论
我们考察的工具列表变化不大。我们将Grinder排除在评测之外,因为尽管它是一款我们喜欢的优秀工具,但它似乎不再积极开发,安装起来更加麻烦(需要旧版 Java),而且用户似乎也不多。一位与 k6 合作的同事建议我们添加一款用 Rust 构建的工具,他认为Drill是个不错的选择,所以我们把它也添加到了评测中。以下是测试工具的完整列表以及我们测试过的版本:
- Apachebench 2.3
- 炮兵 1.6.0
- Drill 0.5.0(新)
- 加特林 3.3.1
- 嘿 0.1.2
- Jmeter 5.2.1
- k6 0.26.0
- 蝗虫0.13.5
- 围攻 4.0.4
- Tsung 1.7.0
- 贝吉塔 12.7.0
- 工作 4.1.0
那么我们测试了什么?
基本上,这篇评论围绕两件事:
1. 工具性能:
该工具生成流量的效率如何?其测量结果的准确性如何?
2. 开发人员用户体验:
对于像我这样的开发人员来说,该工具的使用体验是否简单便捷?
自动化负载测试越来越受到进行负载测试的开发人员的关注,虽然没有时间将每个工具正确集成到 CI 测试套件中,但作者试图通过从命令行和通过脚本执行下载、安装和运行每个工具来确定工具是否适合自动化测试。
该评论不仅包含工具性能等硬性数据,还包含作者对工具各个方面或行为的许多非常主观的意见。
都清楚了吗?开始吧!本文的其余部分将以第一人称形式撰写,希望能让它更具吸引力(或者至少当你不同意某个观点时,你知道该怪谁)。
章节
第一章:历史与现状
这些工具从何而来?哪些工具正在积极开发/维护?
第二章:可用性评估
它们有哪些功能?对于开发人员来说,它们使用起来有多方便?
顶级的非脚本化工具
顶级的脚本化工具
其余工具
第三章:性能评估和基准测试
工具生成流量的效率如何?其测量结果的准确性如何?
最大流量生成能力
内存使用情况
测量精度
第一章 历史与现状
工具概述
下表列出了有关评论中所用工具的一些基本信息。
工具 | Apachebench | 炮兵 | 钻头 | 加特林 |
---|---|---|---|---|
创建者 | Apache基金会 | 肖尔迪奇行动有限公司 | 费兰·巴索拉 | 加特林公司 |
执照 | Apache 2.0 | MPL2 | GPL3 | Apache 2.0 |
写于 | 碳 | NodeJS | 锈 | Scala |
可编写脚本 | 不 | 是的:JS | 不 | 是的:Scala |
多线程 | 不 | 不 | 是的 | 是的 |
分布式负载生成 | 不 | 否(高级) | 不 | 否(高级) |
网站 | httpd.apache.org | artillery.io | github.com/fcsonline | gatling.io |
源代码 | svn.apache.org | github.com/artilleryio | github.com/fcsonline | github.com/gatling |
工具 | 嘿 | JMeter | k6 | 刺槐 |
---|---|---|---|---|
创建者 | 贾娜·B·多根 | Apache基金会 | 负载冲击 | 乔纳森·海曼 |
执照 | Apache 2.0 | Apache 2.0 | AGPL3 | 麻省理工学院 |
写于 | 去 | Java | 去 | Python |
可编写脚本 | 不 | 有限(XML) | 是的:JS | 是的:Python |
多线程 | 是的 | 是的 | 是的 | 不 |
分布式负载生成 | 不 | 是的 | 否(高级) | 是的 |
网站 | github.com/rakyll | jmeter.apache.org | k6.io | locust.io |
源代码 | github.com/rakyll | github.com/apache | loadimpact@github | locustio@github |
工具 | 围城 | 宗 | 贝吉塔 | 工作 |
---|---|---|---|---|
创建者 | 杰夫·富尔默 | 尼古拉斯·尼克劳斯 | 托马斯·塞纳特 | 威尔·格洛泽 |
执照 | GPL3 | GPL2 | 麻省理工学院 | Apache 2.0 修改版 |
写于 | 碳 | Erlang | 去 | 碳 |
可编写脚本 | 不 | 有限(XML) | 不 | 是的:Lua |
多线程 | 是的 | 是的 | 是的 | 是的 |
分布式负载生成 | 不 | 是的 | 有限的 | 不 |
网站 | joedog.org | erland-projects.org | tsenart@github | wg@github |
源代码 | JoeDog@github | processone@github | tsenart@github | wg@github |
开发现状
好的,那么,2020 年初,哪些工具正在积极开发中?
我查看了不同工具的软件仓库,并统计了自 2017 年末上次工具评测以来的提交和发布情况。Apachebench 没有自己的仓库,但它是 Apache httpd 的一部分,所以我在这里跳过了它,因为 Apachebench 的开发进度已经相当糟糕了。
看到几个项目进展迅速,真是令人欣慰!Jmeter 或许可以更频繁地发布?Locust 在过去一年似乎有所加快,2018 年它只有 100 次提交和 1 个版本,但 2019 年它有 300 次提交和 10 个版本。而从提交数量来看,Gatling、Jmeter 和 k6 似乎发展得非常快。
看看 Artillery,我觉得开源版本比付费版本少了很多关注。阅读 Artillery Pro 的更新日志(开源版 Artillery 似乎没有更新日志)发现,Artillery Pro 在过去两年里似乎新增了很多功能,但在查看开源 Artillery 的 Github 仓库中的提交信息时,我发现大部分更新都像是偶尔的 bug 修复。
Apachebench
这款老牌工具是Apache httpd网络服务器工具套件的一部分。它诞生于 90 年代末,显然是 Zeus Technology 开发的一款类似工具的衍生品,用于测试 Zeus 网络服务器(Apache 和微软网络服务器的老对手)。Apachebench 目前在开发方面进展不大,但由于所有安装了 Apache httpd 工具套件的用户都可以使用它,因此它非常容易上手,许多人都用它来对新安装的 HTTP 服务器等进行快速而粗略的性能测试。它也可能被用在不少自动化测试套件中。
炮兵
伦敦的 Shoreditch Ops LTD 创建了 Artillery。这些人名不见经传,但我记得他们是一家初创公司,在 Artillery 流行起来之前或之后就转向了负载测试。当然,我还记得一些从未发生过的事情,所以谁知道呢。总之,这个项目似乎在 2015 年左右启动,在采用现在的名字之前曾被命名为“Minigun”。
Artillery 是用 Javascript 编写的,并使用NodeJS作为引擎。
钻头
Drill 是这群人中最新的一个。它于 2018 年问世,是唯一用Rust编写的工具。显然,作者 Ferran Basora 是为了学习 Rust 而编写的。
加特林
Gatling 最初于 2012 年由一群前咨询师在法国巴黎发布,他们希望构建一款更适合测试自动化的负载测试工具。2015 年,Gatling 公司成立,次年发布了高端 SaaS 产品“Gatling Frontline”。Gatling 公司在其网站上表示,迄今为止下载量已超过 300 万次——我估计这是 OSS 版本的下载量。
Gatling 是用Scala编写的,当然这有点奇怪,但无论如何它似乎运行得很好。
嘿
Hey 之前的名字是 Boom,取自一个同名的Python 负载测试工具。但作者显然厌倦了由此带来的困惑,所以改了名字。新名字总是让我想起“马食”,所以我还是有点搞不懂,不过这个工具还不错。它用Go语言编写,功能上与 Apachebench 相当接近。作者表示,她编写这个工具的一个目标是取代 Apachebench。
Jmeter
这是这群人中的老牌巨头。它也来自 Apache 软件基金会,是一个大型、古老的 Java 应用程序,具有大量功能,而且仍在积极开发中。在过去的两年里,它的代码库提交数量比评测中的任何其他工具都要多。我怀疑 Jmeter 的市场份额正在慢慢被 Gatling 等新工具抢走,但考虑到它已经存在了很长时间,而且仍然保持着很大的发展势头,可以肯定它会在这里存在很长时间。Jmeter 有如此多的集成、附加组件等,以及建立在它之上的整个 SaaS 服务(如Blazemeter),再加上人们花费了大量时间学习如何使用它,它还将继续强劲发展很多年。
k6
超级棒的工具!嗯,就像我之前写的,我在这里有点偏见。但客观事实是这样的:k6 于 2017 年发布,所以相当新。它是用 Go 编写的,而且我刚刚发现一件有趣的事情,那就是 Go 和 C 语言并驾齐驱——评测中的三个工具是用 C 编写的,另外三个是用 Go 编写的。我最喜欢的两种语言——这是巧合还是一种规律?!
k6 最初由Load Impact (一家 SaaS 负载测试服务公司)开发并维护。Load Impact 有几位全职员工致力于 k6 的开发,再加上社区的积极贡献,意味着开发工作非常活跃。这款工具为什么叫“k6”鲜为人知,但我很乐意在此透露:在经历了一场漫长的内部命名之争并最终陷入僵局之后,我们最初使用了一个以“k”开头的 7 个字母的名称,但大多数人都不喜欢,所以我们将其缩短为“k6”,这似乎解决了这个问题。你一定会喜欢第一世界的问题!
刺槐
Locust 是一款非常流行的负载测试工具,从其发布历史来看,至少从 2011 年就出现了。它用Python编写,Python 就像编程语言中的可爱小狗——每个人都喜欢它!这种喜爱让 Python 变得非常流行,而 Locust 也变得非常流行,因为目前还没有其他基于 Python 的出色负载测试工具(而且 Locust 也可以用 Python 编写脚本!)。
Locust 是由一群瑞典人创建的,他们自己也需要这个工具。它仍然由主要作者 Jonathan Heyman 维护,但现在也有很多外部贡献者。与 Artillery、Gatling 和 k6 等工具不同,Locust 的开发没有商业机构主导——据我所知,它完全是社区努力的成果。Locust 的开发一直处于非常活跃和不太活跃之间——我猜这主要取决于 Jonathan 的参与程度。在 2018 年的一段低谷之后,该项目在过去 18 个月左右的时间里经历了相当多的提交和发布。
围城
Siege 也已经存在很久了——大概从 2000 年代初就开始了。我不确定它的使用频率,但网上很多地方都提到了它。它由 Jeff Fulmer 编写,至今仍由他维护。开发仍在进行中,但新版本发布间隔可能很长。
宗
我们唯一的 Erlang 竞争对手!Tsung 由 Nicolas Niclausse 编写,基于一款名为 IDX-Tsunami 的旧工具。它也比较老了——大约是 2000 年代初的,和 Siege 一样,虽然仍在开发,但进展缓慢。
贝吉塔
贝吉塔好像是漫画里的超级英雄之类的。该死,现在大家总算明白我有多老了。说真的,这些用于负载测试软件的、听起来很激进的名字和措辞,难道不是很蠢吗?就像你vegeta attack ...
启动负载测试时那样。更别提“炮兵”、“围城”、“加特林”之类的了。我们是在给五岁小孩留下深刻印象吗?“蝗虫”至少好一点(虽然它不停地“孵化”和“蜂拥”的动作很俗气)。
看到了吧?我跑题了。脑子进水了!好吧,又扯远了。Vegeta 好像 2014 年就出来了,也是用 Go 写的,而且好像很流行(Github 上快 1.4 万颗星!相比之下,很流行的 Locust 大概有 1.2 万颗星)。Vegeta 的作者是 Tomás Senart,开发似乎也挺活跃的。
工作
Wrk 由 Will Glozer 用 C 语言编写。它自 2012 年就已存在,所以不算新,但我一直把它当作性能参考点,因为它速度快得惊人,效率高,总体来说是一款非常可靠的软件。它在 Github 上也有超过 2.3 万颗星,所以它的用户群可能相当庞大,尽管它不像许多其他工具那样易于访问(你需要编译它)。遗憾的是,Wrk 的开发并不活跃,很少发布新版本。
我觉得应该有人给Wrk设计个logo。它值得一个。
第二章:可用性评审
我是一名开发者,通常不喜欢点击式的应用程序。我喜欢用命令行。我还喜欢用脚本来自动化操作。我缺乏耐心,只想着把事情做好。我有点老了,这意味着我经常对新技术有点不信任,更喜欢久经考验的东西。你可能和你不一样,所以试着弄清楚哪些东西你能接受而我不能接受,反之亦然。这样,你或许能从我对这些工具的看法中有所收获。
我的做法是在命令行上手动运行所有工具,并将结果解释打印到标准输出或保存到文件中。然后,我创建了一些 Shell 脚本来自动提取和整理结果。
使用这些工具让我对每个工具都有了一些了解,并了解了它们在我的特定用例中的优缺点。我想我所寻找的东西与您在设置自动化负载测试时所寻找的东西类似,但我可能不会考虑到所有方面,因为我还没有真正将每个工具集成到某个 CI 测试套件中(这可能是下一篇文章要写的)。只是一个免责声明。
另外,请注意,工具的性能会影响可用性评估——如果我觉得很难产生我想要的流量,或者我无法信任工具的测量结果,那么可用性评估就会反映这一点。不过,如果您想了解性能详情,则需要向下滚动到性能基准页面。
远程电源
您将在本篇博客文章中看到RPS 这个术语的广泛使用。该首字母缩略词代表“每秒请求数”,用于衡量负载测试工具产生的流量。
VU
这是另一个经常使用的术语。它是(负载)测试的缩写,是“虚拟用户”的缩写。虚拟用户是模拟的人类/浏览器。在负载测试中,VU 通常表示一个并发执行线程/上下文,它会独立发送 HTTP 请求,从而允许你在负载测试中模拟多个同时用户。
可编写脚本的工具 vs 不可编写脚本的工具
我决定列出我最喜欢的支持脚本和不支持脚本的工具。这样做的原因是,是否需要脚本很大程度上取决于你的用例,而且有一些非常好的工具不支持脚本,值得在这里提一下。
可编写脚本的工具和不可编写脚本的工具之间有什么区别?
脚本化工具支持您用来编写测试用例的脚本语言,例如 Python、JavaScript、Scala 或 Lua。这意味着您在设计测试时可以获得最大的灵活性和强大的功能——您可以使用高级逻辑来决定测试中发生的情况,可以引入库来获得额外的功能,通常还可以将代码拆分成多个文件等等。这才是真正的“开发者之道”。
另一方面,非脚本化工具通常更容易上手,因为它们不需要学习任何特定的脚本 API。它们通常也比脚本化工具消耗更少的资源,因为它们不需要脚本语言运行时和脚本线程的执行上下文。因此,它们速度更快,内存占用更少(一般来说,并非在所有情况下都是如此)。缺点是它们的功能更有限。
好的,让我们进入主观工具评论!
顶级非脚本工具
以下是我最喜欢的非脚本工具,按字母顺序排列。
嘿
Hey 是一款用 Go 编写的简单工具,性能出色,具备运行简单静态 URL 测试所需的所有常用功能。它无需任何脚本,但对于简单的负载测试来说,可以作为 Apachebench 或 Wrk 等工具的良好替代方案。Hey 支持 HTTP/2,而 Wrk 和 Apachebench 均不支持。虽然我在 2017 年并不认为 HTTP/2 支持是什么大事,但如今我们看到 HTTP/2 的普及率比当时高得多,所以我认为现在对 Hey 来说,这更是一个优势。
使用 Hey 而不是 Apachebench 的另一个潜在原因是 Hey 是多线程的,而 Apachebench 不是。Apachebench 速度非常快,因此通常不需要多个 CPU 核心来生成足够的流量,但如果需要,那么使用 Hey 会更合适,因为它的负载生成能力将与机器上的 CPU 核心数量几乎呈线性增长。
Hey 具有速率限制,可用于运行固定速率测试。
嘿,帮助输出
嘿嘿总结
Hey 很简单,但它的功能却非常出色。它很稳定,是评测中性能较高的工具之一,并且输出结果非常漂亮,包括响应时间直方图、百分位数等等。它还具有速率限制功能,而这是许多工具所缺乏的。
贝吉塔
Vegeta 有很多很酷的功能,比如它的默认模式是以恒定速率发送请求,并会调整并发性以尝试达到这个速率。这对于回归/自动化测试非常有用,因为在回归/自动化测试中,你通常希望运行尽可能相同的测试,因为这样可以更准确地判断任何偏差结果是否是由新提交的代码的回归导致的。
Vegeta 是用 Go(耶)编写的,性能非常好,支持 HTTP/2,有几种输出格式,报告灵活,并且可以生成图形响应时间图。
如果您查看上面的运行时屏幕截图,您会发现 Vegeta 显然是设计为在命令行上运行的;它从stdin
要生成的 HTTP 事务列表中读取,并将结果以二进制格式发送到 stdout,您应该在其中重定向到文件或将它们直接通过管道传输到另一个 Vegeta 进程,然后根据数据生成报告。
这种设计提供了极大的灵活性,并支持新的用例,例如,通过在不同的主机上远程执行 Vegeta 的 shell 来实现基本的负载分配,然后将每个 Vegeta“从属”进程的二进制输出复制并传输到一个生成报告的 Vegeta 进程中。你还可以通过标准输入将要访问的 URL 列表“喂给”Vegeta,这意味着你可以让一个软件执行复杂的逻辑来生成这个 URL 列表(尽管该程序无法访问事务的结果,所以我猜这种设置的实际用处值得怀疑)。
稍微不利的一面是,如果您使用过其他负载测试工具,那么命令行 UX 可能不是您所习惯的,如果您只是想运行快速命令行测试来访问具有一些流量的单个 URL,那么它也不是最简单的。
贝吉塔帮助输出
贝吉塔概要
总的来说,Vegeta 是一款非常强大的工具,它能够满足那些想要测试简单静态 URL(例如 API 端点)但又希望获得更多功能的用户的需求。或者,对于那些想要构建自己的负载测试解决方案,并需要一个灵活的负载生成器组件以用于不同用途的用户来说,Vegeta 也同样适用。如果您想创建自己的负载测试工具,Vegeta 甚至可以用作 Golang 库/包。
最大的缺陷(当我是用户时)是缺乏可编程性/脚本,这使得它不太以开发人员为中心。
我肯定会使用 Vegeta 进行简单的自动化测试。
工作
Wrk 可能有点过时了,而且最近也没有太多新功能,但它确实是一段非常扎实的代码。它总是能按照你的预期运行,并且在速度/效率方面远远超过其他所有工具。在相同硬件条件下,使用 Wrk 可以产生比使用 k6 5 倍的流量。如果你觉得这让 k6 听起来很糟糕,那就再想想吧,因为并不是 k6 很慢,而是 Wrk 的速度实在是太快了。与其他工具相比,Wrk 比 Gatling 快 10 倍,比 Locust 快 15-20 倍,比 Artillery 快 100 多倍。
这种比较有点不公平,因为有几款工具允许其 VU 线程运行比 Wrk 允许的复杂得多的脚本代码,但仍然如此。你可能会认为 Wrk 根本没有提供任何脚本功能,但它实际上允许你在 VU 线程中执行 Lua 代码,理论上,你可以创建相当复杂的测试代码。然而,实际上,Wrk 脚本 API 是基于回调的,不太适合编写复杂的测试逻辑。但它的速度也非常快。这次测试 Wrk 时,我没有执行 Lua 代码——而是使用了单 URL 测试模式,但之前的测试表明,执行 Lua 代码对 Wrk 的性能影响很小。
然而,Wrk 的功能仅限于快速和正确测量。它不支持 HTTP/2,没有固定的请求速率模式,没有输出选项,也没有在 CI 设置中生成通过/失败结果的简单方法等等。简而言之,它的功能相当稀疏。
Wrk帮助输出
工作总结
Wrk 之所以被列为顶级非脚本化工具之一,是因为如果你的目标仅仅是为网站生成大量的简单流量,那么没有比它更高效的工具了。它还能精准测量事务响应时间,而这一点是许多其他工具在被迫生成大量流量时无法做到的。
顶级脚本工具
对我来说,这是最有趣的类别,因为在这里您可以找到可以按照您想要的任何奇怪方式进行编程的工具!
或者,用更无聊的方式来说,这里有一些工具可以让你以纯代码的形式编写测试用例,就像你作为开发人员所习惯的那样。
请注意,我列出的工具是按字母顺序排列的——我不会给它们排名,因为列表很傻。阅读这些信息,然后想想你应该用哪个工具。
加特林
Gatling 实际上并不是我的最爱,因为它是一个 Java 应用程序,而我不喜欢 Java 应用程序。对于整天在 Java 环境中工作的人来说,Java 应用程序可能很容易使用,但对于其他人来说,它们绝对不是用户友好的。每当用其他语言编写的应用程序中出现故障时,您都会收到一条错误消息,该消息通常可以帮助您找出问题所在。如果 Java 应用程序失败,您将收到 1,000 行堆栈跟踪和重复的通用错误消息,这绝对没有任何帮助。此外,运行 Java 应用程序通常需要手动调整 JVM 运行时参数。也许 Java 非常适合大型企业后端软件,但不适合负载测试工具之类的命令行应用程序,因此在我看来,作为 Java 应用程序是一个明显的缺点。
如果你看上面的截图,你会注意到你必须在“JAVA_OPTS”环境变量中添加测试参数,然后从你的 Gatling Scala 脚本中读取该变量。你无法为 Gatling 添加任何参数来影响并发性/VU、持续时间或类似参数,这些参数必须来自 Scala 代码本身。当你只以自动化方式运行某些东西时,这种方式很好,但如果你想在命令行上运行一些手动测试,那就有点麻烦了。
尽管以 Java 为中心(或者说是“Java 中心主义”?),我不得不说 Gatling 是一款相当不错的负载测试工具。它的性能不算出色,但对大多数人来说可能已经足够了。它有一个基于 Scala 的不错的脚本环境。再说一次,Scala 不是我的菜,但如果你喜欢 Scala 或 Java,用 Gatling 编写测试用例脚本应该会很方便。
这是一个非常简单的 Gatling 脚本:
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class GatlingSimulation extends Simulation {
val vus = Integer.getInteger("vus", 20)
val duration = Integer.getInteger("duration", 10)
val scn = scenario("Scenario Name") // A scenario is a chain of requests and pauses
.during (duration) {
exec(http("request_1").get("http://192.168.0.121:8080/"))
}
setUp(scn.inject(atOnceUsers(vus)))
}
脚本 API 似乎功能强大,它可以根据用户定义的条件生成通过/失败的结果。
我不喜欢启动 Gatling 时默认的文本菜单系统。好在,可以使用正确的命令行参数跳过这个菜单。如果你稍微深入研究一下,就会发现 Gatling 从命令行运行起来非常简单。
Gatling 的文档非常好,这对于任何工具来说都是一个很大的优点。
Gatling 有一个看起来不错的记录工具,不过我自己还没试过,因为我更感兴趣的是编写脚本场景来测试单个 API 端点,而不是记录网站上的“用户旅程”。不过我想,很多运行模拟最终用户行为的复杂负载测试场景的人都会很高兴有这个记录器。
Gatling 默认会将结果报告到标准输出 (stdout),并在测试完成后生成漂亮的 HTML 报告(使用我最喜欢的图表库Highcharts)。很高兴看到它最近也支持将结果输出到 Graphite/InfluxDB 并使用 Grafana 进行可视化。
Gatling 帮助输出
加特林总结
总的来说,Gatling 是一款非常优秀的工具,并且得到了积极的维护和开发。如果您现在正在使用 Jmeter,那么一定要了解一下 Gatling,看看您缺少了什么(提示:可用性!)。
k6
由于我参与了 k6 的开发,因此我欣赏该项目的决策也就不足为奇了。k6 的初衷是为现代开发者打造一款高质量的负载测试工具,它允许开发者以纯代码的方式编写测试,拥有简洁一致的命令行用户体验、实用的结果输出选项以及足够好的性能。我认为所有这些目标都基本实现了,这使得 k6 成为一个非常有吸引力的负载测试工具选择,尤其对于像我这样的开发者而言。
k6 可以用纯 JavaScript 编写脚本,并且拥有我认为是我测试过的所有工具中最好的脚本 API。这个 API 可以轻松执行常见操作,测试程序是否按预期运行,以及控制自动化测试的通过/失败行为。一个非常简单的 k6 脚本可能如下所示:
import http from 'k6/http';
import { check } from 'k6';
export default function() {
var res = http.get('http://192.168.0.121:8080/');
check(res, {
'is status 200': r => r.status === 200
});
}
上述脚本将使每个 VU 生成一个 HTTP 事务,然后检查响应代码是否为 200。此类检查的状态会打印在标准输出上,您可以设置阈值,当检查失败的比例足够大时,测试将失败。在我看来,k6 脚本 API 让编写自动化性能测试成为一种非常愉快的体验。
k6 的命令行界面简洁、直观且一致,给人一种现代感。k6 是本次评测中速度较快的工具之一,它支持所有基本协议(HTTP 1/2/Websocket),并提供多种输出选项(文本、JSON、InfluxDB、StatsD、Datadog、Kafka)。由于 k6 可以将 HAR 文件转换为 k6 脚本,并且主流浏览器可以记录会话并将其保存为 HAR 文件,因此从浏览器记录流量非常容易。此外,它还提供将 Postman 集合等转换为 k6 脚本代码的选项。对了,它的文档总体来说非常出色(虽然我刚刚与一位文档编写人员进行了交流,他对文档目前的状况表示不满意,但我认为这很好。当产品开发人员感到满意时,产品就会停滞不前)。
那么,k6 缺少什么呢?嗯,它不包含负载分布生成功能,所以如果你想运行真正大规模的测试,你必须购买高级 SaaS 版本(该版本具有分布式负载生成功能)。另一方面,它的性能意味着你不太可能在单台物理机上耗尽负载生成能力。如果你喜欢 Web UI,它没有任何 Web UI。我不喜欢。
人们可能期待但 k6 却没有的一件事是 NodeJS 兼容性。许多(甚至可能是大多数?)NodeJS 库无法在k6 脚本中使用。如果您需要使用 NodeJS 库,Artillery 可能是您唯一安全的选择(哦不!)。
除此之外,我唯一不喜欢 k6 的地方就是我必须用 Javascript 编写测试脚本!JS 不是我最喜欢的语言,我个人更喜欢使用 Python 或 Lua——后者是 Load Impact 多年来一直用来编写负载测试脚本的脚本语言,而且非常节省资源。但就市场渗透率而言,Lua 就像个小白鼠,而 JS 则像头大象,所以选择 JS 而不是 Lua 是明智之举。说实话,只要脚本不是用 XML(或 Java)编写的,我就很满意。
如前所述,k6 的开源版本正在积极开发中,新功能层出不穷。请务必查看发行说明/变更日志,顺便说一句,它们是我见过的最好的记录之一(感谢维护者 @na--,他在这方面非常出色)。
k6帮助输出
k6 的内置帮助功能也值得一提,比本次评测中的其他工具都要好用得多。它有一个类似 docker 的多级k6 help
命令,你可以输入参数来显示特定命令的帮助信息。例如,它k6 help run
会提供详尽的帮助文本,展示如何使用该run
命令。
k6总结
我并非有偏见,但我认为从开发者的整体体验来看,k6 远远领先于其他工具。市面上有速度更快的工具,但没有哪个速度更快的工具还能支持复杂的脚本编写。市面上有支持更多协议的工具,但 k6 支持最重要的协议。市面上有输出选项更多的工具,但 k6 拥有的选项最多。在几乎所有方面,k6 都处于平均水平或更胜一筹。在某些方面(文档、脚本 API、命令行用户体验),它的表现非常出色。
刺槐
Locust 的脚本编写体验非常棒。Locust 脚本 API 虽然功能比较基础,但相当不错,并且缺少其他 API 所具备的一些实用功能,例如自定义指标或内置函数,以便在 CI 环境中运行负载测试时生成通过/失败结果。
不过,Locust 脚本最大的优势在于——你可以用Python来编写脚本!具体情况可能因人而异,但如果我可以选择任何脚本语言进行负载测试,我可能会选择 Python。Locust 脚本如下所示:
from locust import TaskSet, task, constant
from locust.contrib.fasthttp import FastHttpLocust
class UserBehavior(TaskSet):
@task
def bench_task(self):
while True:
self.client.get("/")
class WebsiteUser(FastHttpLocust):
task_set = UserBehavior
wait_time = constant(0)
不错吧?可以说比 k6 脚本的外观还要好看,但 API 确实缺少一些功能,比如内置的通过/失败结果支持(Blazemeter有一篇文章介绍如何为 Locust 实现自己的断言,这需要生成 Python 异常并获取堆栈跟踪——这听起来有点棘手)。此外,新的 FastHttpLocust 类(详情请见下文)的功能似乎有些受限(例如,不确定它是否支持 HTTP/2?)。
Locust拥有一个美观的命令控制 Web 用户界面,可以显示测试的实时状态更新,以及停止测试或重置统计信息的位置。此外,它还内置了易于使用的负载生成分布功能。使用 Locust 启动分布式负载测试非常简单,只需使用交换机启动一个 Locust 进程--master
,然后使用交换机启动多个进程--slave
,并将它们指向主服务器所在的机器即可。
这是运行分布式测试时的 UI 截图。如你所见,我遇到了一些 UI 问题(使用 Chrome 79.0.3945.130),导致实时状态数据打印在导航菜单栏的顶部(可能是字符串host
太长了?)。不过除此之外,这个 Web UI 简洁实用。虽然我在其他地方写过我不喜欢 Web UI,但当你试图控制多个从属负载生成器并随时掌握最新情况时,它们有时还是相当不错的。
Python实际上既是 Locust 最大的优点,也是最大的缺点。缺点在于 Locust 是用 Python 编写的。Python 代码运行速度较慢,这会影响 Locust 生成流量和提供可靠测量的能力。
我第一次对 Locust 进行基准测试是在 2017 年,当时它的性能非常糟糕。Locust 生成一个 HTTP 请求所消耗的 CPU 时间比我测试过的任何其他工具都要多。更糟糕的是,Locust 是单线程的,所以如果不运行多个 Locust 进程,它只能使用一个 CPU 核心,根本无法产生太多流量。幸运的是,Locust 当时就支持分布式负载生成,这使得它在单台物理机器的流量生成能力方面从最差的变成了倒数第二。当时 Locust 的另一个缺点是,它往往会给响应时间测量带来巨大的延迟,使其非常不可靠。
更酷的是,自那时起,Locust 的开发人员做出了一些改进,并真正提升了 Locust 的速度。这一点非常独特,因为过去两年里,所有其他工具的性能都停滞不前或有所倒退。Locust 引入了一个名为 FastHttpLocust 的新 Python 类/库,它比旧的 HttpLocust 类(基于 Requests 库构建)快得多。在我的测试中,我看到原始请求生成能力的速度提高了 4-5 倍,这也与 Locust 作者在文档中描述的一致。这意味着,一个典型的、拥有 4-8 个 CPU 核心的现代服务器在分布式模式下运行 Locust 时,应该能够生成 5-10,000 RPS 的数据。需要注意的是,由于 Locust 仍然是单线程的,因此分布式执行通常仍然是必要的。在基准测试中,我还注意到,在分布式模式下运行 Locust 时,随着工作负载的增加,测量精度的下降幅度会更加平缓。
然而,这些改进的好处在于,现在很多人可能会发现,在运行 Locust 时,单台物理服务器就能满足他们的负载测试需求。他们可以覆盖大多数内部暂存系统,甚至生产系统(或其副本)。Locust 仍然是评测中性能较差的工具之一,但现在感觉性能不再成为它无法使用的原因了。
就我个人而言,我对 Locust 有点儿神经质。我喜欢它能用 Python 编写脚本(还能用上百万个 Python 库!)。这对我来说是迄今为止最大的卖点。我喜欢它内置的负载生成分布,但并不相信它能够扩展到真正大规模的测试(我怀疑单个--master
进程很快就会成为瓶颈——测试起来会很有趣)。我喜欢它的脚本 API,不过如果它能更好地支持通过/失败结果,而且 FastHttpLocust 的 HTTP 支持似乎比较基础,那就更好了。我喜欢它内置的 Web UI。我不喜欢它整体性能低下,即使在单机上运行 Locust 也可能会被迫以分布式模式运行——必须配置多个 Locust 实例,这对我来说是额外的麻烦,我并不想这样做,尤其是在自动化测试中。我不太喜欢它的命令行用户体验。
关于单机分布式执行——我不知道让 Locust--master
默认以模式启动,然后让它自动启动多个--slave
子进程(每个检测到的 CPU 核心一个)有多难?(当然,如果用户想控制,一切都是可配置的)让它更像 Nginx 之类的。在我看来,这样可以带来更好的用户体验,并且配置过程也更简单——至少在单机上运行它时是这样。
Locust 帮助输出
Locust 概要
如果不是因为 k6,Locust 会是我的首选。如果你真的喜欢 Python,绝对应该先了解一下 Locust,看看它是否适合你。我建议你先确保它的脚本 API 能让你以简单的方式完成你想做的事情,并且性能足够好,然后再决定是否全盘采用。
其余工具
以下是我对其余工具的评论。
Apachebench
Apachebench 的开发并不活跃,而且有点老旧了。我之所以把它纳入其中,主要是因为它非常常见,是 Apache httpd 捆绑实用程序的一部分。Apachebench 速度很快,但单线程。它不支持 HTTP/2,也没有脚本功能。
Apachebench 总结
Apachebench 非常适合对单个 URL 进行简单的性能测试。它在该用例中唯一的竞争对手是 Hey(它是多线程的,并且支持 HTTP/2)。
炮兵
Artillery 是一款速度极慢、资源消耗巨大且开发进度可能不快的开源负载测试工具。我想这或许不太好,但请继续阅读。
它用 JavaScript 编写,并使用 NodeJS 作为引擎。基于 NodeJS 构建的优点在于其与 NodeJS 的兼容性:Artillery 可以用 JavaScript 编写脚本,并且可以使用常规的 NodeJS 库,而 k6 等平台则无法做到这一点,尽管 k6 也可以用常规 JavaScript 编写脚本。然而,作为 NodeJS 应用的缺点在于性能:在 2017 年的基准测试中,Artillery 的表现排名倒数第二,仅次于 Locust。它占用了大量的 CPU 和内存,却生成了相当不尽如人意的 RPS 数字和响应时间测量结果,而且这些结果根本不准确。
我很遗憾地说,自 2017 年以来,这里的情况并没有太大变化。如果说有什么变化的话,那就是今天的炮兵似乎慢了一点。
2017 年, Artillery 在单个 CPU 核心上运行可以产生两倍于 Locust 的流量。如今,当这两种工具同样受限于单个 CPU 核心时,Artillery 只能产生 Locust 三分之一的流量。部分原因是 Locust 的性能有所提升,但变化比预期的要大,所以我很确定 Artillery 的性能也下降了。另一个支持该理论的数据点是 Artillery 与 Tsung 的对比。2017 年,Tsung 比 Artillery 快 10 倍。如今,Tsung 快了 30 倍。我认为 Tsung 的性能根本没有变化,这意味着 Artillery 比以前慢得多(当时它的速度也不算快)。
Artillery 的性能肯定是一个问题,更令人恼火的因素是开源 Artillery 仍然没有任何类型的分布式负载生成支持,因此除非您购买高级 SaaS 产品,否则您只能使用性能非常低的解决方案。
artillery.io网站并没有明确说明 Artillery 开源版和 Artillery Pro 版之间的区别,但似乎只有 Artillery Pro 版有更新日志。查看Github 仓库,Artillery 开源版的版本号是 1.6.0,而根据更新日志,Pro 版的版本号是 2.2.0。浏览开源 Artillery 版的提交信息,似乎主要都是一些 bug 修复,在两年多的时间里并没有太多的提交。
Artillery 团队应该更好地记录 Artillery 开源版和高级产品 Artillery Pro 之间的区别,并写一些关于他们对这款开源产品的意图。它正在慢慢停产吗?看起来确实如此。
关于性能,还有一点需要注意:现在,每当 CPU 使用率超过 80%(单核)时,Artillery 都会打印“高 CPU”警告,建议不要超过这个数值,以免“降低性能”。我发现,如果我将 CPU 使用率保持在 80% 左右以避免这些警告,Artillery 产生的流量会少很多——大约是 Locust 每秒处理请求数的 1/8。如果我忽略警告信息,让 Artillery 完全使用一个核心,那么 RPS 会提升到 Locust 的 1/3。但这会造成相当大的测量误差。
抛开性能问题不谈,Artillery 也有一些优点。它非常适合持续集成/自动化,因为它易于在命令行中使用,拥有简洁明了的基于 YAML 的配置格式、用于生成通过/失败结果的插件、以 JSON 格式输出结果等等。而且如前所述,它可以使用常规的 NodeJS 库,这些库提供了大量易于导入的功能。但当一个工具的性能达到 Artillery 的水平时,所有这些对我来说都无关紧要。我唯一会考虑使用 Artillery 的情况是,我的测试用例必须依赖一些 k6 无法使用但 Artillery 可以使用的 NodeJS 库。
炮兵概要
只有当您已经将自己的灵魂卖给了 NodeJS(即您必须使用 NodeJS 库)时才使用它。
钻头
Drill 是用Rust编写的。我一直避免使用 Rust,因为我担心自己可能会喜欢它,而且我不想让任何东西妨碍我使用 Golang。但这可能不在本文的讨论范围内。我的意思是,Rust 应该很快,所以我假设用 Rust 编写的负载测试工具也应该很快。
然而,运行一些基准测试后,很快就发现这个工具慢得惊人!或许我不应该这么快就把 Drill 纳入评测,因为它既新又尚未被广泛使用。或许它并非旨在认真开发一款新工具?(毕竟作者声称 Drill 的开发是因为他想学习 Rust)。另一方面,它确实有很多实用的功能,比如基于 YAML 的强大配置文件格式、通过/失败结果的阈值等等,所以在我看来,它确实像是一次半正式的尝试。
所以 - 这个工具看起来相当可靠,虽然简单(无需脚本)。但是,当我在测试环境中运行它时,它充分利用了四个 CPU 核心,却只产生了令人难以置信的约 180 个请求/秒。我使用了许多不同的参数运行了许多测试,这是我能从 Drill 中榨出的最佳数据。CPU 的周期消耗得飞快,就像没有明天一样,但这个工具发出的 HTTP 事务非常少,我几乎可以用纸笔来回复它们。这就像在每个 HTTP 请求之间挖一个比特币!将它与在相同环境下每秒执行超过 50,000 次 RPS 的 Wrk(用 C 编写)进行比较,你就会明白我的意思了。Drill 并非“Rust 比 C 更快”这一说法的典型代表。
如果你的应用性能毫无提升,那么使用 Rust 这样的编译语言还有什么意义呢?那你还不如用 Python。或者说,基于 Python 的 Locust 比这快得多。如果我的目标是在特定的测试设置上达到 200 RPS 左右,我可能会用 Perl!或者,说不定,用个 Shell 脚本也行?
我一定要试试。看看这个卷发棒
所以,这个 Bash 脚本实际上比 Drill 强多了,因为它curl
在命令行上多次并发执行。它甚至会计算错误。curl-basher.sh
在我的测试设置中,我勉强维持了 147 RPS(不得不说,这是一个非常稳定的 147 RPS),而 Drill 的 RPS 为 175-176 RPS,所以它只快了 20%。这让我不禁怀疑,Drill 代码究竟在做什么,竟然消耗了这么多 CPU 时间。它无疑创下了 HTTP 请求生成效率的新低——如果你担心全球变暖,就不要使用 Drill!
演习总结
对于微小、短时间的负载测试,可能值得考虑使用 Drill,或者如果房间有点冷。
Jmeter
这才是真正的“巨兽”。与大多数其他工具相比,Jmeter 简直就是一头庞然大物。它历史悠久,功能集更丰富,集成、附加组件、插件等等,比本次评测中的任何其他工具都多。长期以来,它一直是开源负载测试工具中的“王者”,或许现在依然如此。
过去,人们要么花大价钱购买 HP Loadrunner 许可证,要么花大价钱购买某个模仿 Loadrunner 的专有工具的许可证,要么干脆免费使用 Jmeter。当然,也有人选择使用 Apachebench、OpenSTA 或其他一些早已被遗忘的免费解决方案,但如果你想进行严肃的负载测试,Jmeter 确实是唯一可用且免费的替代方案。
因此,Jmeter 的用户群不断壮大,Jmeter 的开发也随之蓬勃发展。如今,大约 15 年过去了,Jmeter 已经拥有一个庞大的社区,其活跃开发时间比任何其他负载测试工具都要长,因此它拥有比其他工具更多的功能也就不足为奇了。
由于它最初是作为 15-20 年前旧的专有负载测试软件的替代品而构建的,因此它的设计目的是为了迎合与那些应用程序相同的受众。即,它被设计为供负载测试专家使用,运行复杂、大规模的集成负载测试,这些测试需要花费很长时间来规划、执行和分析结果。这些测试需要大量的手动工作和非常具体的负载测试领域知识。这意味着 Jmeter 从一开始就不是为自动化测试和开发人员使用而构建的,这一点在今天使用它时可以清楚地感受到。它是专业测试人员的工具,而不是开发人员的工具。它不适合自动化测试,因为它的命令行使用不方便,默认结果输出选项有限,它占用大量资源,并且没有真正的脚本功能,只支持在 XML 配置中插入一些逻辑。
这可能就是 Jmeter 的市场份额被 Gatling 等新工具蚕食的原因。Gatling 与 Jmeter 有很多共同之处,因此对于那些希望使用更现代化、脚本和自动化支持更佳的工具,但又希望保留 Java 工具的组织来说,它提供了一条颇具吸引力的升级路径。无论如何,Jmeter 确实比 Gatling 等工具有一些优势。这主要归功于其生态系统的规模——包括我提到的所有集成、插件等等。性能方面,它们相当相似。Jmeter 曾是本次评测中性能最强的工具之一,但性能有所下降,现在处于平均水平,与 Gatling 相当接近(甚至略快)。
Jmeter总结
如果您刚刚开始进行负载测试,那么选择 Jmeter 的最大原因是:
- 需要测试许多只有 Jmeter 支持的不同协议/应用程序,或者
- 你是一个以 Java 为中心的组织,并且想要使用最常见的基于 Java 的负载测试工具,或者
- 您需要一个 GUI 负载测试工具,通过指向和单击即可执行操作
如果以上都不适用,我认为 Gatling(在很多方面与 Jmeter 非常接近)、k6 或 Locust 更适合你。或者,如果你不太在意可编程性/脚本编写(将测试写成代码),可以看看 Vegeta。
围城
Siege 是一个简单的工具,类似于 Apachebench,因为它没有脚本,主要用于重复访问单个静态 URL。它具有而 Apachebench 所缺乏的最大功能是能够读取 URL 列表并在测试期间访问所有 URL。但是,如果您不需要此功能,那么我的建议是直接使用 Apachebench(或者更好 - Hey)。因为如果您尝试使用 Siege 做任何稍微高级的事情,它都一定会让您头疼 - 例如弄清楚当您用流量访问目标站点时目标站点的响应速度有多快,或者产生足够的流量来减慢目标系统的速度,或诸如此类的事情。实际上,仅使用正确的配置或命令行选项运行它(尽管它们不是太多)就会感觉像是某种神秘的益智游戏。
Siege 不可靠,而且不止一个方面。首先,它崩溃得相当频繁。其次,它更频繁地卡死(主要是在退出时,我已经记不清遇到过多少次了kill -9
)。如果你尝试启用 HTTP 保持连接,它有 25% 的时间会崩溃或卡死。我不明白 HTTP 保持连接怎么会在这么老的工具上进行实验!HTTP 保持连接本身非常古老,是 HTTP/1.1 的一部分,而 HTTP/1.1 是 20 年前标准化的!它在今天也非常非常普遍地使用,并且对性能有巨大的影响。大多数 HTTP 库都支持它。HTTP 保持连接会在请求之间保持连接打开,因此连接可以重用。如果你无法保持连接打开,则意味着每个 HTTP 请求都会导致新的 TCP 握手和新的连接。这可能会给您误导性的响应时间结果(因为每个请求都涉及 TCP 握手,而 TCP 握手很慢),也可能导致目标系统上的 TCP 端口饥饿,这意味着测试将在一段时间后停止工作,因为所有可用的 TCP 端口都处于 CLOSE_WAIT 状态并且不能重新用于新连接。
不过,嘿,你不必启用像 HTTP keep-alive 这样怪异、奇特、实验性、前沿的东西来让 Siege 崩溃。你只需让它多启动一两个线程,它就会很快崩溃或挂起。为了避免提及这个事实,它使用了障眼法——它新增了一条limit
配置指令,限制了-c
命令行参数(并发)的最大数量——这个参数决定了 Siege 会启动多少个线程。该值默认设置为 255,原因是 Apache httpd 默认只能处理 255 个并发连接,超过这个数量会“弄得一团糟”。这简直就是牧场里一堆可疑的棕色东西。在我的测试中,当你将并发级别设置为 3-400 之间时,Siege 似乎会变得不稳定。超过 500 就会经常崩溃或挂起。更诚实的做法是在文档中写“抱歉,我们似乎无法创建超过 X 个线程,否则 Siege 会崩溃。我们正在努力解决这个问题。”
Siege 的选项/参数杂乱无章,缺乏直观性,而且帮助信息有时还会欺骗用户。我至今仍无法使用该-l
选项(据说可以用来指定日志文件位置),不过长格式的帮助信息--log=x
似乎能像宣传的那样正常工作(并且能完成一些-l
无法完成的操作)。
Siege 的性能现在与 Locust 相当(当 Locust 在分布式模式下运行时),这对于 C 应用程序来说并不算太好。Wrk 比 Siege 快 25 倍,提供的功能集几乎相同,测量精度更高,而且不会崩溃。Apachebench 和 Hey 也快得多。我现在觉得使用 Siege 的理由很少。
我唯一能说的真正好的地方是,Siege 实现了一个非常巧妙的功能,而大多数工具都缺乏这个功能——一个命令行开关 ( -C
),它可以读取所有配置数据(以及命令行参数),然后打印出运行负载测试时将使用的完整配置。这是一个非常棒的功能,其他工具应该也具备。尤其是在配置方式多种多样的情况下——例如命令行选项、配置文件、环境变量——要确切知道你实际使用的配置是什么,可能很棘手。
围攻概要
向其他方向快速奔跑。
宗
Tsung 是我们唯一基于 Erlang 的工具,已经推出一段时间了。它看起来非常稳定,文档完善,速度也相当快,并且拥有一系列优秀的功能,包括支持分布式负载生成,以及能够测试多种不同的协议。
它是一款非常强大的工具,但在我看来,它的主要缺点在于类似 Jmeter 的基于 XML 的配置,以及缺乏脚本编写能力。和 Jmeter 一样,你可以在 XML 配置中定义循环、使用条件语句等,因此实际上你可以编写测试脚本,但与使用 k6 或 Locust 等真实语言相比,它的用户体验非常糟糕。
Tsung 仍在开发中,但进展非常缓慢。总而言之,我认为如果你需要测试它支持的某个额外协议(例如 LDAP、PostgreSQL、MySQL、XMPP/Jabber),Tsung 是一个不错的选择,因为你可能只能在 Jmeter 和 Tsung 之间做出选择(而在这两者中,我更喜欢 Tsung)。
Tsung 总结
如果您是 Erlang 粉丝,请尝试一下。
卷曲攻击者
嗯,嗯。
第三章:绩效评估和基准
负载测试可能很棘手,因为在负载生成方面遇到一些性能问题很常见,这意味着你衡量的是系统生成流量的能力,而不是目标系统处理流量的能力。即使是经验丰富的负载测试专业人员也经常会陷入这个陷阱。
如果负载生成能力不足,您可能会发现负载测试无法超过每秒一定数量的请求,或者您可能会发现响应时间测量变得完全不可靠。通常这两种情况都会发生,但您可能不知道原因,并错误地将糟糕和/或不稳定的性能归咎于糟糕的目标系统。这时,您可能会尝试优化已经优化的代码(当然,因为您的代码运行速度很快),或者您会对某个可怜的同事大喊大叫,因为他在热路径中没有一行代码,但这却是您在整个代码库中能找到的唯一性能低下的代码。然后,这位同事会感到愤慨,偷走您的鼠标垫来报复,这在办公室引发了一场战争,不知不觉中,整个公司就倒闭了,您不得不去 Oracle 找一份新工作。这真是太浪费了,而您只需要确保您的负载生成系统能够胜任它的任务!
这就是为什么我认为了解负载测试工具的性能非常有趣。我认为每个使用负载测试工具的人都应该对其性能方面的优势和劣势有一些基本的了解,并且偶尔也要确保他们的负载测试设置能够生成正确加载目标系统所需的流量,并保持健康的裕度。
测试测试工具
测试工具的一个好方法是,不要直接在自己的代码上测试,而是在一些性能肯定非常高的第三方工具上测试。我通常会启动一个 Nginx 服务器,然后通过抓取默认的“欢迎使用 Nginx”页面进行负载测试。不过,top
在测试过程中,使用类似工具来跟踪 Nginx 的 CPU 使用率非常重要。如果你只看到一个进程,并且它的 CPU 使用率接近 100%,则意味着目标端可能存在 CPU 瓶颈。这时,你需要重新配置 Nginx,使用更多工作线程。
如果你看到多个 Nginx 进程,但只有一个占用大量 CPU,则意味着你的负载测试工具只与该特定的工作进程通信。那么你需要弄清楚如何让工具打开多个 TCP 连接,并在这些连接上并行发出请求。
网络延迟也是一个需要考虑的重要因素,因为它限制了每秒可以处理的请求数量。如果服务器 A(运行负载测试工具的地方)和服务器 B(Nginx 服务器所在的位置)之间的网络往返时间为 1 毫秒,并且您只使用一个 TCP 连接发送请求,那么理论上您每秒可以实现的最大请求数是 1/0.001 = 1,000 个。在大多数情况下,这意味着您需要负载测试工具使用多个 TCP 连接。
每当达到限制,似乎无法再处理每秒的请求时,您就会尝试找出耗尽的资源。使用类似 的工具来监控负载生成端和目标端的 CPU 和内存使用情况top
。
如果双方的 CPU 都没问题,请试验并发网络连接的数量,看看更多的连接是否有助于提高 RPS 吞吐量。
另一个容易被忽视的事情是网络带宽。假设 Nginx 默认页面需要传输 250 字节才能加载,这意味着如果服务器通过 100 Mbit/s 的链路连接,那么理论上的最大 RPS 速率大约为 100,000,000 除以 8(比特/字节)除以 250 => 100M/2000 = 50,000 RPS。虽然这是一个非常乐观的计算 - 协议开销会使实际数字低很多,所以在上面的例子中,如果我看到我可以推动最大 30,000 RPS 或类似的速度,我就会开始担心带宽是一个问题。当然,如果您碰巧正在加载一些更大的资源,例如图像文件,这个理论上的最大 RPS 数字可能会低很多。如果您有疑问,请尝试更改正在加载的文件的大小,看看它是否会改变结果。如果将大小加倍,得到的 RPS 却只有一半,则说明带宽有限。
最后,服务器内存也可能是一个问题。通常,内存耗尽时会非常明显,因为大多数程序会停止工作,而操作系统会疯狂地尝试将辅助存储用作 RAM(即交换或抖动),从而破坏它。
经过一些实验,您将确切地知道如何从负载测试工具中获得最高的 RPS 值,并且您将知道它在当前硬件上的最大流量生成能力。了解这些之后,您就可以开始测试您想要测试的真实系统,并且确信,每当您看到例如 API 端点每秒无法处理超过 X 个请求的情况时,您都会立即知道这是由于目标端的问题,而不是负载生成器端的问题。
这些基准
上述步骤大致是我测试这些工具时所经历的。我使用一台小型、无风扇、四核赛扬服务器,运行 Ubuntu 18.04 系统,8GB 内存,作为负载生成器。我想要一台多核但性能不太强大的服务器。重要的是,目标/接收器系统能够处理比负载生成器所能生成的流量更大的流量(否则我就不会对负载生成端——也就是工具——进行基准测试了)。
我的目标测试环境是一台 4Ghz i7 iMac,内存 16G。我的确用了和工作机一样的机器,在上面运行了一些终端窗口,并在浏览器中打开了一个 Google 电子表格,但确保测试期间没有任何高负载运行。由于这台机器有 4 个非常快的内核,并且支持超线程(能够并行运行 8 个任务),所以应该还有剩余容量。但为了安全起见,我在不同时间点多次重复了所有测试,只是为了验证结果是否比较稳定。
这些机器通过千兆以太网连接到同一个物理 LAN 交换机。
实际测试表明,目标系统足够强大,足以测试所有工具,但可能只有一种工具无法测试。Wrk 的 RPS 达到了 50,000 以上,这使得目标系统上的 8 个 Nginx 工作线程消耗了大约 600% 的 CPU 资源。Nginx 的 CPU 资源或许无法超过这个上限(考虑到 800% 的使用率应该是 4 核 i7 超线程处理器的绝对理论最大值),但我认为这无关紧要,因为 Wrk 在流量生成方面独树一帜。我们实际上不必探究 Wrk 是比 Artillery 快 200 倍,还是仅仅快 150 倍。重要的是要证明目标系统能够处理大多数工具无法达到的极高 RPS 值,因为这样我们就知道我们实际上测试的是负载生成端,而不是目标系统。
原始数据
有一个电子表格,其中包含所有测试的原始数据和文本注释。不过,实际运行的测试比电子表格中列出的要多——例如,我运行了大量测试,以找出“最大 RPS”测试的理想参数。目标是尽可能地从每个工具中挤出尽可能多的 RPS,为此需要进行一些探索性测试。此外,每当我觉得需要确保结果稳定时,我都会再次运行一组测试,并与之前记录的结果进行比较。我很高兴地说,结果通常波动很小。有一次我遇到一个问题,所有测试的性能数据突然明显低于之前。无论使用哪种工具,这种情况都会发生,最终我重启了负载生成器,问题才得以解决。
测试的原始数据可以在这里找到
我试图找到答案
最大流量生成能力
在这个实验设置中,每个工具每秒能生成多少个请求?我尝试了大多数可用参数,但主要还是并发性(工具使用了多少个线程,多少个 TCP 连接),以及启用 HTTP 保持连接、禁用工具中耗费大量 CPU 的操作(比如 HTML 解析)等等。目标是不惜一切代价,让每个工具每秒都能生成尽可能多的请求!
这个想法是为每种工具获取某种基线,以显示该工具在原始流量生成方面的效率。
每个 VU 的内存使用量
有些工具非常耗内存,而且有时内存使用量还取决于测试的规模,以虚拟用户数 (VU) 为单位。每个 VU 的高内存使用量可能会阻碍人们使用该工具运行大规模测试,所以我认为这是一个非常值得衡量的性能指标。
每个请求的内存使用量
一些工具会在负载测试过程中收集大量统计信息。尤其是在 HTTP 请求发起时,通常会存储各种事务时间指标。根据存储的具体内容和存储方式,这可能会消耗大量内存,对于密集型和/或长时间运行的测试来说,可能会造成问题。
测量精度
所有工具都会在负载测试期间测量并报告事务响应时间。由于多种原因,这些测量结果总会存在一定程度的误差,尤其是在负载生成器本身进行一些工作时,通常会看到响应时间测量结果中出现相当多的额外延迟。了解何时可以信任负载测试工具报告的响应时间测量结果,以及何时不能信任这些测量结果,这很有用,我已经尝试针对每种不同的工具找出答案。
最大流量生成能力
下面的图表显示了当我真正全力以赴时可以从每个工具中获得的最大 RPS 数及其内存使用情况:
显而易见的是,Wrk 在这方面没有真正的竞争对手。它在流量生成方面非常强大,所以如果你只需要大量的 HTTP 请求,那就下载(并编译)Wrk 吧。你不会失望的!
虽然 Wrk 是一款出色的请求生成器,但它并非完美适用于所有用途(参见评测),因此看看其他工具的表现会很有趣。让我们将 Wrk 从图表中移除,以获得更清晰的尺度:
在讨论这些结果之前,我想提一下,为了生成尽可能高的 RPS 数字,我们在非默认模式下运行了三种工具:
炮兵
Artillery 运行时的并发设置过高,导致其占用了整个 CPU 核心,这并非 Artillery 开发者所推荐的做法,也会导致Artillery 发出CPU 过高警告。我发现,占用一个 CPU 核心会大幅提高请求率,从 CPU 使用率约为 80% 时的每秒略高于 100 RPS,上升到 CPU 使用率达到 100% 时的每秒 300 RPS。当然,每秒的 RPS 数值仍然非常低,正如 Artillery 常见问题解答中所述,以及我们在响应时间精度测试中看到的那样,当 Artillery 占用一个 CPU 核心时,响应时间测量结果很可能完全无法使用。
k6
k6 运行时使用了--compatibility-mode=base
命令行选项,该选项会禁用较新的 JavaScript 功能,让你只能使用旧版 ES5 编写脚本。这会导致内存使用量减少约 50%,总体速度提升约 10%,这意味着最大 RPS 速率从约 10k 提升到约 11k。不过差别并不大,而且我认为,除非你遇到内存问题,否则在运行 k6 时不建议使用此模式。
刺槐
Locust 以分布式模式运行,这意味着启动了 5 个 Locust 实例:一个主实例和四个从实例(每个 CPU 核心一个从实例)。Locust 是单线程的,因此无法使用多个 CPU 核心,这意味着您必须将负载生成分散到多个进程上,才能充分利用多 CPU 服务器上的所有 CPU 资源(他们应该将主/从模式集成到应用程序本身,以便它能够自动检测一台机器何时拥有多个 CPU 并默认启动多个进程)。如果我只在一个实例中运行 Locust,它只能生成大约 900 RPS 的数据。
我们还使用了新的 FastHttpLocust 库进行 Locust 测试。该库比旧版 HttpLocust 库快 3-5 倍。然而,使用它意味着你会失去一些 HttpLocust 拥有而 FastHttpLocust 不具备的功能。
自 2017 年以来发生了什么变化?
不得不说,这些结果一开始让我有点困惑,因为我在 2017 年测试过这些工具中的大多数,并且预计现在的性能应该差不多。当然,这些 RPS 的绝对值与我之前的测试结果无法比较,因为当时我使用了不同的测试设置。但我预计这些工具之间的关系应该大致保持不变:例如,我当时认为 Jmeter 仍然是最快的工具之一,而 Artillery 在单核 CPU 上运行时仍然会比 Locust 更快。
Jmeter比较慢!
嗯,正如您所见,Jmeter 的性能现在看起来相当一般。从我的测试来看,Jmeter 的性能在 2.3 版本和我现在测试的 5.2.1 版本之间下降了大约 50%。这可能是 JVM 的问题。我使用 OpenJDK 11.0.5 和 Oracle Java 13.0.1 进行了测试,两者的性能几乎相同,所以不太可能是由于 JVM 速度较慢造成的。我还尝试过提高决定 JVM 可分配内存量的-Xms
和-Xmx
参数,但这也没有影响性能。
炮兵现在速度极其缓慢,而蝗虫几乎已经很不错了!
至于 Artillery,它现在的速度似乎也比两年前慢了大约 50%,这意味着它现在的速度和两年前的 Locust 一样慢,当时我还在不停地抱怨它有多慢。那么 Locust 呢?它是自 2017 年以来唯一一款性能大幅提升的工具。得益于其新的 FastHttpLocust HTTP 库,它现在的速度比当时快了大约 3 倍。这确实意味着会失去旧 HttpLocust 库(该库基于非常用户友好的 Python Requests 库)提供的一些功能,但我认为性能提升对 Locust 来说确实很划算。
攻城速度更慢!
两年前,Siege 并不是一个非常快的工具,尽管它是用 C 语言编写的,但不知何故,它的性能似乎在 4.0.3 和 4.0.4 版本之间进一步下降,因此现在当后者以分布式模式运行并且可以使用单台机器上的所有 CPU 内核时,它比基于 Python 的 Locust 更慢。
钻得非常非常慢
Drill 是用 Rust 编写的,所以速度应该很快,而且它充分利用了所有 CPU 核心,这些核心在测试期间一直处于繁忙状态。然而,没有人知道这些核心在做什么,因为 Drill 每秒只能产生 176 RPS!这和 Artillery 差不多,但 Artillery 只使用一个 CPU 核心,而 Drill 使用了四个!我想看看一个 Shell 脚本是否能像 Drill 一样产生如此大的流量。答案是“是的,差不多”。你可以自己试试:curl-basher
贝吉塔终于可以上基准了,而且还不错!
Vegeta 过去没有提供并发控制功能,这使得它很难与其他工具进行比较,所以在 2017 年我没有将它纳入基准测试。不过,现在它新增了一个-max-workers
可以限制并发的开关,再加上-rate=0
(不限速率)功能,我可以用与其他工具相同的并发级别进行测试。我们可以看到,Vegeta 的性能非常出色——它既能生成大量流量,又能占用很少的内存。
总结流量生成能力
其余工具的性能与 2017 年大致相同。
我想说,如果您需要生成大量流量,图表左侧的工具可能更适合您,因为它们效率更高。但大多数情况下,每秒生成几千个请求就足够了,Gatling 或 Siege 可以做到这一点,或者分布式 Locust 也可以。但是,除非您是受虐狂或想要额外的挑战,否则我不建议使用 Artillery 或 Drill。使用这些工具生成足够的流量会很棘手,而且当测量结果出现偏差时,解释结果(至少是 Artillery 的结果)也很棘手,因为您必须耗尽负载生成器上的每一分 CPU 资源。
内存使用情况
那么内存使用情况如何呢?让我们再看一下那张图表:
占用大量内存的程序包括 Tsung、Jmeter、Gatling 和 Locust。尤其是我们常用的 Java 应用程序——Jmeter 和 Gatling——它们非常珍惜内存,需要大量内存。如果 Locust 不需要在多进程中运行(因为它是单线程的),内存占用会更高,那么它的表现就不会那么糟糕。多线程应用程序可以在线程之间共享内存,但多个进程必须保存相同的大量进程数据集。
这些数字虽然能反映出这些工具的内存占用情况,但并不能反映全部真相。毕竟,在今天,500MB 算什么?几乎所有服务器都配备几 GB 的内存,所以 500MB 应该不成问题。然而,问题在于,如果在扩大测试规模时内存使用量也随之增加。以下两种情况通常会导致内存使用量增加:
- 长时间运行的测试,收集大量结果数据
- 增加 VU/执行线程的数量
为了调查这些情况,我运行了两套测试,分别测量“每个 VU 的内存占用”和“每个请求的内存占用”。我并没有真正计算每个 VU 或请求的确切内存占用,而是通过增加请求和 VU 的数量来运行测试,并记录内存占用情况。
每个 VU 级别的内存使用情况
这里我们可以看到随着虚拟用户 (VU) 数量的增加会发生什么。请注意,显示的数字是在一个非常短的测试(10 秒)内的平均内存使用情况。测试期间每秒都会进行采样,因此通常有 9-10 个样本。这项测试实际上应该使用更多的 VU,例如从 1VU 到 200VU 左右,并且 VU 的作用范围不要太大,以免得到太多结果数据。这样,当您尝试模拟更多用户时,就能真正看到工具的“扩展”能力。
但我们可以从中发现一些问题。当我们更改 VU 数量时,一些工具似乎并未受到影响,这表明它们要么没有在每个 VU 上使用太多额外内存,要么是以块的形式分配内存,而我们在此测试中配置的 VU 数量不足以强制它们分配比初始值更多的内存。您还可以看到,使用 Jmeter 之类的工具,当您尝试扩展测试规模时,内存很可能成为问题。如果您尝试从这些非常低的 VU 级别大幅提升,Tsung 和 Artillery 似乎也可能会使用大量内存。
每个请求量的内存使用量
在本次测试中,我使用相同的并发参数,但不同的测试时长运行了所有工具。目的是让这些工具收集大量的结果数据,并观察内存使用量随时间的变化。该图显示了从存储 2 万个事务结果到存储 100 万个结果时,每个工具的内存使用量的变化情况。
可以看出,Wrk 实际上几乎不占用任何内存。当然,它也不会存储太多结果数据。Siege 似乎也相当节省内存,但我们未能测试 100 万笔交易,因为 Siege 在达到 100 万笔交易之前就中止了测试。这并不完全出乎意料,因为 Siege 在每个 TCP 套接字上只发送一个请求,然后它会关闭套接字,并为下一个请求打开一个新的套接字。这会导致系统缺乏可用的本地 TCP 端口。如果您使用 Siege,任何更大或更长时间的测试都可能失败。
随着测试的进行,Tsung 和 Artillery 的内存使用量似乎有所增加,但速度并不快。k6 和 Hey 的曲线更加陡峭,对于运行时间非常长的测试,最终可能会遇到麻烦。
再次强调,占用大量内存的是 Java 应用:Jmeter 和 Gatling。Jmeter 在执行 100 万次请求后,内存占用会从 160MB 飙升至 660MB。需要注意的是,这是整个测试的平均内存使用量。测试结束时的实际内存使用量可能是这个数字的两倍。当然,也可能是 JVM 根本不进行垃圾回收,直到它觉得有必要才进行——我不确定这是怎么回事。如果真是这样,那么如果 JVM 在测试过程中的某个时刻真的需要进行大规模的垃圾回收,性能会受到怎样的影响,这将会非常有趣。这值得进一步研究。
哦,Drill 被排除在这些测试之外了。用 Drill 生成 100 万笔交易实在太耗时了。测试进行的时候,我的孩子都长大了。
测量精度
有时,当你运行负载测试并将目标系统暴露给大量流量时,目标系统会开始产生错误。事务将失败,并且目标系统原本应该提供的服务将不再对部分(或所有)用户可用。
然而,这通常不是首先发生的情况。当系统承受高负载时,通常发生的第一件坏事就是系统速度变慢。或者更准确地说,事务会排队,对用户的服务速度会变慢。交易将无法像以前那样快速完成。这通常会导致更差的用户体验,即使服务仍在运行。如果性能下降幅度不大,用户对服务的满意度会略有下降,这意味着更多的用户会跳出、流失,或者干脆不使用所提供的服务。如果性能下降严重,其影响可能是电子商务网站等机构的收入或多或少全部损失。
这意味着测量事务响应时间非常有趣。您需要确保它们在预期的流量水平下处于可接受的范围内,并持续跟踪它们,以免随着新代码添加到系统而出现性能下降。
所有负载测试工具都会尝试在负载测试期间测量事务响应时间,并为您提供相关统计数据。然而,测量结果总会存在误差。误差通常会出现在实际客户端实际体验到的响应时间上。换句话说,负载测试工具报告的响应时间通常会比实际客户端实际体验到的响应时间更差。
这个误差究竟有多大,因人而异。它取决于负载生成器端的资源利用率——例如,如果你的负载生成器机器的 CPU 占用率达到 100%,那么响应时间的测量结果肯定非常不稳定。而且,不同工具之间的误差也存在很大差异——一个工具的整体测量误差可能比另一个工具低得多。
作为用户,我希望错误尽可能小,因为如果错误很大,可能会掩盖我正在寻找的响应时间回归问题,使它们更难发现。而且,它可能会误导我,让我误以为我的系统响应速度不够快,无法满足用户的需求。
下面的图表显示了不同工具在不同 VU/并发级别报告的响应时间:
我们可以看到,一个名字听起来像军事用途的工具,把图表的比例拉得太大,以至于很难与其他工具进行比较。所以我把这个“罪魁祸首”移除了,因为我已经在本文的其他地方彻底批评过了。现在我们得到:
好的,这样好多了。首先,我想先介绍一下这次测试的具体内容。我们以设定的并发级别运行每个工具,尽可能快地生成请求。也就是说,请求之间没有延迟。请求速率会有所不同——从 150 RPS 到 45,000 RPS 不等,具体取决于所使用的工具和并发级别。
如果我们先从最无聊的工具 Wrk 开始看,我们会发现,随着 VU 级别从 10 增加到 100,其响应时间中值(所有响应时间均为中位数,即 50 分位)从约 0.25 毫秒上升到 1.79 毫秒。这意味着,在并发级别 100(100 个并发连接发出请求)和 45,000 RPS(Wrk 在本次测试中达到的 RPS)下,实际服务器响应时间低于 1.79 毫秒。因此,在这个级别上,任何工具报告的超过 1.79 毫秒的延迟,几乎可以肯定是负载测试工具本身造成的延迟,而不是目标系统造成的。
你可能会问,为什么是中位数响应时间?为什么不是更高的百分位数呢?这通常更有趣。原因很简单,因为除了“最大响应时间”之外,这是我能从所有工具中得到的唯一指标。一个工具可能报告 90 和 95 百分位数,而另一个工具可能报告 75 和 99 百分位数。甚至并非所有工具都报告平均响应时间(我知道这是一个糟糕的指标,但它非常常见)。
这里处于中间位置的工具报告 100 VU 级别的平均响应时间为 7-8 毫秒,比 Wrk 报告的 1.79 毫秒高出约 5-6 毫秒。因此,可以合理地假设,在此并发级别上,普通工具会使报告的响应时间增加约 5 毫秒。当然,某些工具(例如 Apachebench 或 Hey)能够生成大量 HTTP 请求,而不会过多地增加响应时间。其他工具(例如 Artillery)只能生成非常少量的 HTTP 请求,但这样做仍然会增加非常大的测量误差。让我们看一下显示 RPS 数与中位响应时间测量值的图表。请记住,这里的服务器端可能或多或少总是能够提供小于 1.79 毫秒的中位响应时间。
让我们看看每个工具生成的响应时间与每秒请求数 (RPS),因为这可以让我们了解该工具可以执行多少工作并且仍然提供可靠的测量。
再次,Artillery 远远落后于其他工具,测量误差高达大约 +150 毫秒,而每秒只能发出不到 300 个请求。相比之下,Wrk 的输出流量是 Artillery 的 150 倍,但测量误差仅为 Artillery 的 1/100,由此可见,性能最佳和最差的工具之间的差距有多么巨大。
我知道用过 Artillery 的人会说:“但这仅仅是因为他把 CPU 用光了,尽管 Artillery 会打印 CPU 过高的警告。” 好吧,我还做了一个测试,降低了 Artillery 的速度,所以那些警告从未出现过。我仍然使用了 100 个并发访客/用户,但他们各自运行了内置休眠的脚本,这意味着 CPU 使用率保持在 80% 左右,并且没有打印任何警告。当然,RPS 的速率最终要差得多——只有 63 RPS。响应时间测量结果呢?43.4 毫秒。误差超过 +40 毫秒。
因此,即使 Artillery 运行“正确”,并产生了惊人的 63 RPS,它产生的测量误差仍然比 Wrk 大 20 倍,而 Wrk 产生的流量是 Artillery 的近 1000 倍。我没有测试过,但如果curl-basher在这方面的表现优于 Artillery,我也不会感到惊讶。
让我们再次从图表中删除炮兵:
有趣的是,测量误差最高的四个工具(不包括 Artillery)在这里的表现相当相似:Siege、Gatling、Jmeter 和 Locust。在我的设置上,当模拟 100 VU 时,它们的 RPS 都略低于 3,000,而且它们似乎都增加了类似的测量误差:在 20 到 30 毫秒之间。
Jmeter 曾经是这些基准测试中性能较好的工具之一,但在过去两三年里,它的表现似乎有所下滑。Siege 的表现也下降了不少,现在它的性能已经完全看不出它是一个用 C 语言编写的工具。相反,基于 Python 的 Locust 却一路飙升,与其他工具并驾齐驱,虽然在流量生成方面略逊一筹,但在流量测量方面却丝毫没有逊色于 Siege。
Tsung 再次令人印象深刻。虽然它是一款老旧且维护不怎么活跃的工具,但它的负载生成能力相当不错,测量结果也仅次于 Wrk。
钻头很奇怪。
Vegeta、Apachebench、k6 和 Hey 似乎都擅长生成流量,同时将测量误差保持在合理的较低水平。这里再次发出偏差警告,但我很高兴看到 k6 在所有这些基准测试中都处于中间位置,因为它执行的是复杂的脚本逻辑,而性能优于它的工具却没有。
结束摘要
k6 规则!
或者,呃,确实如此,但这些工具大多各有优势。它们只是在不同情况下表现出色而已。
- 我不太喜欢Gatling,但理解为什么其他人在“我需要更现代的 Jmeter”用例中喜欢它。
- 我喜欢Hey中的“我需要一个简单的命令行工具来访问具有一些流量的单个 URL”用例。
- 我喜欢Vegeta的“我需要一个更高级的命令行工具来访问一些有流量的 URL”用例。
- 我根本不太喜欢Jmeter ,但猜测非开发人员可能会在“我们真的想要一个可以做所有事情的基于 Java 的工具/GUI 工具”用例中喜欢它。
- 我喜欢k6(显然)在“开发人员自动化测试”用例中的作用。
- 我喜欢Locust中的“我真的很想用 Python 编写我的测试用例”用例。
- 我喜欢Wrk的“用大量请求淹没服务器!”用例。
然后我们有几个似乎最好避免使用的工具:
- Siege已经过时、奇怪且不稳定,这个项目似乎已经走向死亡。
- Artillery速度超慢,测量不正确,而且开源版本似乎没有太大进展。
- 钻探正在加速全球变暖。
祝你好运!
文章来源:https://dev.to/k6/open-source-load-testing-tool-review-2020-5466