9

我从性能测试中学到的那些经验与教训

 4 years ago
source link: https://www.infoq.cn/article/DT97gmQ0dMLPAdzQJh2s
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文要点:

  • 要做好性能测试很难,但花一些额外的时间做好性能测试是值得的,这样可以避免一些反复出现的问题;
  • 要注意区分延迟测试和吞吐量测试,因为它们是系统的两个不同的方面;
  • 在进行延迟测试时,要把吞吐量固定在一个水平;
  • 在进行聚合时不要丢弃任何一个与性能有关的信息,要尽可能多地把它们记录下来;
  • 只要多加小心,再加以自动化,就可以最大程度地避免出现性能回退。

对于像 Hazelcast 这样的内存计算平台来说,性能意味着一切。因此,我们在性能方面投入了大量精力,并在这个方面积累了多年的经验。

在这篇文章中,我将介绍一些常见的问题,并分享如何做好常规的性能测试。

性能测试中反复出现的问题

首先,测试环境差异是最常见的一个问题。如果一个系统最终要被部署到多台具有超强处理能力的机器上,那就不能够只根据在本地开发机上进行的性能测试做出合理的假设,因为在将系统部署到真实的环境中,结果会完全不一样。有趣的是,人们比他们自己认为的更容易犯这种错误。在我看来,其中的主要原因是懒惰(在本地测试很容易,但搭建额外的测试机器需要时间),意识不到这会成为一个问题,有时候是因为缺乏资源。

另一个反复出现的问题是测试场景不真实,例如,负载不够高、并发线程太少,或者完全不一样的操作比率。通常来说,在测试时要尽可能接近真实场景。也就是说,在开始测试之前,我们要尽可能多地收集与测试、用例、测试目标、环境和场景有关的信息,尽可能做到与真实环境相似。

下一个问题是不区分吞吐量测试和延迟测试,有时候甚至不确定是不是真的关心这两种测试。从我的经验来看,客户通常会更关心应用程序的吞吐量,于是他们就尽可能压榨系统的吞吐量,并基于这些结果做出决策。但如果你深挖下去,你会发现,在 99% 的场景中,他们的负载都只是介于每秒多少个操作之间。在这个时候,可以把系统的吞吐量固定在这个水平,然后观察延迟,并进行延迟测试,而不是吞吐量测试。

最后一个问题是只关心聚合指标,比如均值和中值。这些指标把很多信息隐藏掉了,而这些被隐藏的信息很可能有助于做出更好的决策。本文的“评测性能”一节将更详细地介绍应该要收集哪些信息。

延迟测试和吞吐量测试之间的区别

吞吐量基本上是指某段时间内完成的操作次数(通常是每秒多少次操作)。延迟,也就是响应时间,是指从执行一个操作开始到接收到结果之间的时间。

这两个最基本的指标之间通常是相互关联的。在非并行系统中,延迟与吞吐量是成反向关系的,反过来也是一样。简单地说,如果每秒可以完成 10 个操作,那么每个操作(平均)需要十分之一秒。如果每秒种完成的操作越多,那么每个操作需要的时间就越少。

但是,在并行系统中,这种关系很容易被打破。以在 Web 服务器中增加线程为例,这样做不会缩短每个操作的响应时间,也就是说延迟不变,但吞吐量却提升了。

因此,吞吐量和延迟实际上是系统的两个不同的指标,我们要单独测试它们。更确切地说,在进行延迟测试时,我们要把吞吐量固定在一个水平,例如,“在每秒钟 10 万个操作的前提下测试延迟”。如果不这么做,在不同的吞吐量下系统延迟会发生变化,也就失去了可比性。

我们得到的经验教训是:在进行延迟测试时要使用固定的吞吐量。

收集和比较性能测试结果

另一个问题是如何收集、记录和分析性能测试结果。我觉得可以分为两个部分:一个是不要丢弃信息,一个是把分散的点连接起来。

我经常看到性能基准测试报告里会显示每秒平均操作数、中值或者延迟的某种百分位。的确,这样的报告看起来很简洁,也很容易发布出去。但是,如果你真的关心性能问题,就不要这么简单粗暴,你需要看到“所有”的数据。

在进行吞吐量测试时,一个常见的做法是讲总的操作数除以时间,这样做很容易就把重要信息丢掉了。如果你想要看到整体的情况,就需要显示吞吐量的变化过程。

eArqyyq.png!web

从这张图可以很容易地找到问题所在。根据简单的“每秒平均 X 次操作”,你可以快速地得出结论:绿线是最好的。但是,这张图看起来又有点奇怪。虽然绿线的吞吐量是最大的,但是也是最不稳定的。换句话说,绿线抖动得很厉害,一点也不平滑。我们基于这条线发现了线程调度问题。

总的来说,这张图显示了吞吐量的变化过程,但如果只是从聚合结果来看,是不可能发现问题的。我们可能会因为绿线的值最高而欢呼雀跃,但却忽略了一个重要的性能问题。

在进行延迟测试时,另一个常见的问题是只显示平均延迟,或者好一点的话会显示某种百分位。在理想情况下,你可以创建一个百分位直方图,让你可以看到整体的情况。你还可以做得更好,就是查看百分位随时间的变化情况。

作为例子,下图显示了 50 百分位随着时间发生变化的情况。

IBfmMrm.png!web

从图中我们可以清楚地看到延迟随着时间的变化而增长。如果我们只发布平均值或者是一个完整的直方图,就很难发现这些信息。我们知道,系统发生了内存泄露。

Gil Tene 的 HdrHistogram 是一个非常好的用来生成百分位和延迟图表的工具。

然后是同时查看所有的图表。如果你在其中的一张图中发现了问题,要在另一张图中进行确认。如果有性能问题,通常会在多个地方体现出来。

我们再以内存泄露为例。如果一个 Java 程序发生内存泄露,我们会看到内存使用量上升,这个时候吞吐量应该会下降,因为垃圾回收会占用更多的时间。与此相关联的是,延迟也可能会增加。我们也可以看一下线程的 CPU 时间,它们的 CPU 时间也会减少,因为垃圾回收占用了更多的 CPU 时间。信息越多,你就会得到更好的结论,就越容易找到解决问题的方案。

找出性能瓶颈

找出性能问题的根源通常是一个漫长而痛苦的过程,取决于具体的代码、测试人员的经验,有时候甚至是运气。但我们仍然是有迹可循的。

首先是逐步诊断,每次只诊断一个步骤。在进行性能调优时,我们通常会说“这个选项很好用,这样设置垃圾回收对性能有帮助,打开这个开关可以获得更好的结果,我们都试试吧”。最终,这种叠加效果导致我们搞不清楚哪个才是有效的。因此,每一次我们只走一步,只使用一个开关,只修改一个地方。

具体来说,我们可以利用所有可利用的工具,特别是在手机数据的时候。你掌握的信息越多,就越是能够更好地了解系统行为,更快地找到痛点。因此,我们需要尽可能收集所有信息:系统息息(CPU、内存、磁盘 IO、上下文切换)、网络信息(对于分布式系统来说尤为重要)、垃圾回收日志、Java Flight Recorder(JFR)的分析数据,等等。我们还开发了专有的诊断工具,更细粒度地直接收集内部信息——操作时间取样、内部线程池的大小和时间、内部管道和缓冲区的统计信息,等等。

A7VziqV.png!web

上图比较了三种调优选项的不同点,显示了每秒钟系统上下文切换次数。从结果看,绿线的切换次数最少,说明系统做的有用的事情更多,这个可以从吞吐量那张图中得到验证。

防止性能回退

进行常规(每天)自动化性能测试是很有必要的。在引入变更时需要尽可能快地获取必要的数据。在发现性能回退后,我们可以很容易将其隔离出来,极大减少用于修复回退所需的时间。

一个是执行测试,一个是保存和分析结果。在 Hazecast,我们使用 PerfRepo ,一个开源的 Web 应用程序,用于保存和分析性能测试结果。它会更新每一个最新的性能测试结果,所以很容易发现性能回退——你会在图中看到线往下走。我在 Red Hat 工作期间积极维护这个项目,因为时间原因,现在的开发进度慢了一些,但仍然是个完全可用的项目。

但不管怎么样,限制还是有的。我们无法面面俱到,因为各自情况的组合几乎是无限的。我们倾向于选择“最重要”的那些,但具体怎么做仍然值得我们讨论。

结论

要做好性能测试很难,很多方面都会出问题。关键在于要多注意细节,了解系统的行为,避免出现一些可笑的结果。当然,这需要时间。在 Hazelcast,我们也很注意这些,我们愿意为此付出时间和精力。你们呢?

作者介绍

Jiři Holuša 是一个专注于开源的软件工程师,他热爱他的工作。之前在 Red Hat 工作,目前是 Hazelcast 质量工程团队的负责人。Hazelcast 是一家开发内存计算平台的公司。Holuša 喜欢深挖一个问题,从来都不放弃,直到问题得到解决。除此之外,他喜欢运动,作为一个真正的捷克人,他喜欢在喝啤酒时与别人进行愉快的交谈。如果你想看到更多由 Holuša 提供的有关性能测试的演讲,可以看他在 2019 年欧洲 TestCon 大会上做的 Performance Testing Done Right 演讲。你可以在 Twitter 通过 @jholusa 联系他。

原文链接:

Lessons Learned in Performance Testing


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK