购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第4章
性能测试与评估

性能测试是一种非功能性软件测试方法,用于检查软件在特定工作负载下的稳定性、吞吐量、可扩展性和响应时间等特性。尽管它是确保软件质量的重要过程,但往往被忽视,被视为事故后才会做的事情。在大多数情况下,性能测试只在软件出现性能问题后才被考虑。

性能测试是软件开发过程中不可或缺的一部分,因此需要尽早介入,并在不同阶段使用不同的性能测试方法,选择合适的工具,识别瓶颈并验证对应问题是否已得到解决。通过性能测试,不仅可以在软件上线之前评估其性能,还可以检查软件的可扩展性和可靠性,并向利益相关者提供软件性能和稳定性等度量信息。这些性能度量信息,可以为服务的稳定性奠定坚实基础。

4.1 软件性能的度量

本节将介绍性能测试的目的、意义,以及常见的性能度量指标。

4.1.1 性能测试的目的及意义

性能测试的目的是验证软件是否达到客户提出的性能指标要求,发现软件中存在的性能瓶颈并进行针对性优化。从理论上来说,功能测试和性能测试没有先后顺序,它们都是软件测试的一部分。

在开发过程中,应随时关注可能导致性能问题的局部函数或第三方依赖。例如,关注加解密算法、压缩算法、序列化与反序列化、网络I/O等消耗资源的操作,要对这些局部函数进行性能测试,并查看官方的性能指标参数,以便提前发现和消除性能问题。

在测试过程中,通常先进行功能测试。当软件在功能上不存在严重问题且能够正常运行后,再进行性能测试。对于任何一款软件,首先要保证其基本功能正常,否则,即使性能再好,也失去了存在的意义。虽然性能测试需要在功能测试通过之后才能进行,但是对性能测试准备工作(确认性能测试目标、搭建环境、编写用例等)的重视程度应与功能测试一样。

在发布过程中,要确保软件中的参数配置是最优的,以将软件的性能提升到极致,并尽可能降低硬件资源的消耗。

此外,进行性能测试还具有以下意义。

1.市场营销

出于市场营销的目的,需要对外提供产品的性能指标。这要求我们提供的性能指标不能具有误导性,不能给出一个相对模糊或者没有前提条件的结果。虽然前期夸大其词可能会带来较好的营销结果,但当客户选择一款产品后,不仅会关注当前厂家给出的性能指标,还会分析和对比其他竞争对手的产品的性能指标。如果性能指标与实际情况不符,那么在客户那里将失去信誉。

因此,在向客户提供性能指标的同时,也需要提供一些细则和性能测试的背景信息,如操作系统版本、配置调整项、CPU型号和数量、使用的磁盘类型以及其他相关配置。

2.容量规划

现代的互联网不仅涉及软件之间的互动,还涉及人与软件之间的互动。产品使用的主动权不完全掌握在运营者手中。某个话题的出现可能会导致用户量激增,进而导致站点崩溃。为了缩短产品的恢复时间,运营人员需要清楚自己的产品能够承载的用户数量。当出现不可预知的流量时,能够有针对性地采取措施,比如横向扩容一定数量的机器以应对本次流量激增的情况。扩容机器的数量和激增流量之间的关系,需要通过性能测试确定。

3.系统设计

通常,开发人员更关注软件架构的扩展性、数据库表结构设计是否合理、代码是否存在性能问题、内存使用方式是否正确、线程同步方式是否合理、是否存在临界区,以及是否可以考虑更优的算法和数据结构等。通过性能测试,可以得出各种中间件或软件内部逻辑的性能指标,这有利于公司在研发层面进行关键技术选型或做出重要架构决策。

总而言之,后期优化成本要远远高于前期性能设计成本,所以前期加入性能测试是降低优化成本的最好方式。

4.1.2 性能测试的度量指标

本小节所说的“指标”主要用于常规软件性能测试的评估,是统一的性能测试指标。软件的度量指标范围广泛,比如数据库的缓存命中率度量指标、Java的GC度量指标等,如果工作中会涉及这些指标,则可以到对应官方网站查询和借鉴使用,这里不对此展开介绍。另外,若希望对性能指标有整体认知,则可以参阅2.3节的内容。本小节仅对延时、TPS、资源利用率、错误率等性能测试指标进行分析。

1.延时

完成一次操作所需的时间包括等待、逻辑处理和返回结果的时间。这个时间通常用于量化性能,因此也被称为响应时间。对于一些对延迟敏感的场景,延时成为性能问题分析的重点。例如,一次HTTP请求的执行过程如图4-1所示。

图4-1 一次HTTP请求的执行过程

描述延时时,必须加上限定词,以表示测量的是谁到谁的延时,例如是业务逻辑延时还是网页渲染延时。

不同行业对延时的衡量标准各不相同。对于一些实时竞技类游戏,延时要求是50ms;对于实时交互类视频,延时要求是150ms。如果超过这个时间,用户可能会放弃使用。而对于软件下载和安装,5~10min的延时在可接受范围内。

延时通常有两种表示方式:平均响应时间和百分位数。

· 平均响应时间指的是所有请求平均花费的时间。例如,如果有100个请求,其中的98个耗时为1ms,2个耗时为100ms,那么平均响应时间为(98×1+2×100)/100=2.98ms。

· 百分位数(Percentile)是一个统计学名词。以响应时间为例,99%的百分位响应时间指的是99%的请求响应时间都处在这个值以下,通常用P99表示。以上述响应时间为例,整体平均响应时间为2.98ms,但99%的百分位响应时间却是100ms。

相对于平均响应时间,百分位响应时间通常更能反映服务的处理效率。例如,某高速公路的人工出口一天出车1440辆,每分钟1辆车,看起来非常顺畅。实际情况是在早上9:00—9:30过了600辆,下午5:00—5:30过了600辆,假设只有人工窗口,1min可以办结1辆车的放行(只有1个人工窗口),这两个时间段1辆车需要等待20min。由此可见,4%的时间发生了拥堵,P99不能保证每分钟1辆车。

2.TPS

TPS是每秒执行的事务数。它描述了在持续1s内执行了多少事务。一个事务是指一个客户端向服务器发送请求,然后服务器给出响应的过程。客户端在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。换句话说,TPS就是事务执行的速率。

例如,一个超市收银员每分钟最多能够完成6次收银操作,那么他的事务执行速率峰值就是6/60=0.1TPS。另外,可以看到,TPS受每次操作完成时间的影响,在一定程度上来说,完成时间越短,TPS越高。有些软件更关注并发数量(同时在线用户数量),并发数=TPS×平均响应时间。

提到TPS,就不得不提QPS,它代表每秒的查询次数,是一台服务器每秒能够完成的查询次数,是衡量特定查询服务器在规定时间内所能处理的流量的标准。QPS与TPS类似,不同的是,一次完整的访问形成一次事务,但可能会产生多次对服务器的请求,这些请求都会计入QPS中。

通常,在性能测试工具中,会输出持续测试下的TPS。每个服务和中间件都会产生自己的QPS。通常可以使用计数器进行计算,即统计固定采集周期内的请求数,然后用这个数除以相应周期即可得出QPS。

3.资源利用率

利用率又称使用率,用于描述设备的使用情况,通常表示为0~100%,有基于时间和基于容量两个描述维度。

对于CPU和磁盘I/O,通常基于时间的维度来定义利用率,即在规定的时间间隔内,设备工作时间占时间间隔的百分比。即使CPU或者磁盘I/O达到100%,也只是说明在指定时间段内的利用率过高,大多数情况下仍然可以接受更多任务并正常工作。相应公式为:

U = B / T

式中, U 是利用率, B 是观测周期内系统的繁忙时间, T 是观测周期。

对于内存和磁盘,通常使用容量来衡量资源占用比例,此时,若利用率达到100%则意味着磁盘或内存无法再接受更多的工作负载。

通常可以使用Nmon等监控工具来统计CPU、内存、文件系统、磁盘I/O、网络I/O等资源的利用率。

4.错误率

错误率指系统在有负载的情况下出现失败的概率。

错误率=(失败任务数/总任务数)×100%

稳定性较好的系统的错误率应该由超时引起,即表现为超时率。不同系统对错误率的要求不同,但一般不超于6‰,即成功率不低于99.4%。

4.1.3 常见基础设施性能指标

本小节将介绍计算机中常见基础设施的性能指标,帮助读者对这些指标形成直观的了解,以便在出现性能问题时能够凭直觉推断出问题所在。

· 网络延时与传输距离有关,通常每传输100km需要1ms(光纤传输)。

· 在代码开发过程中,经常会出现多个线程共享资源的情况,这时需要加锁,时间成本大约为10ns。

· 当多个线程共享同一个CPU时,需要进行上下文切换,每次切换的时间成本约为1μs。

· 传统磁盘的随机I/O读写延时大约为8ms,万转磁盘的顺序I/O延时一般为0~6ms,利用RAID缓存,最高可以达到100μs。

不同的网络距离延时、CPU操作延时及存储种类延时的参考值如表4-1所示。

表4-1 延时项与延时参考值

(续)

4.2 性能测试常用工具

性能测试离不开工具,每种测试工具都有自己的使用场景和特点。本节重点介绍4种性能测试场景及对应的工具。

4.2.1 性能测试场景

基本上所有的场景需求都有对应的性能测试工具可以满足,因此在进行性能测试之前,需要识别出要测试的性能场景。这里主要对不同的场景进行分类和介绍。

如图4-2所示,从上往下看,在整个逻辑架构中需要考虑的性能测试场景有如下几种。

· 前端性能测试。 前端涉及的用户定制化功能众多,设备类型多样,性能指标主要包括崩溃率、网络请求超时、滚动卡顿、启动时间、流量、耗电量、内存占用量、CPU占用率等。前端性能测试通常比后端性能测试更具挑战性。在开始前端性能测试前,需要制订详细的性能测试计划,这通常包括使用场景和测试环境两部分。我们应该列出软件中最重要的场景,例如,对于电子商务公司来说,关键场景可能包括登录、注册、浏览商品、添加商品到购物车、支付订单等。在完成关键场景的测试后,如果资源允许,还可以进一步测试其他场景。在性能测试中,如果使用模拟器,那么应确保测试环境尽可能接近生产环境。但是,由于软件的性能通常是多个硬件相互作用的结果,因此最好使用真实的硬件进行测试。然而,这种方法可能由于成本过高而收益有限。一种更好的选择是进行全面性能测试,即覆盖市场上的主流运行环境。对于用户较少的运行环境,可以使用模拟器进行测试。例如,之前在开发基于浏览器的办公软件时,由于浏览器的兼容性问题,推广和使用过程中遇到了很大的困难。为了尽快解决问题并控制成本,我们引导低于IE11版本的用户下载Chrome浏览器。

· 服务器性能测试。 并不是所有的服务/接口都需要进行性能测试,这一点需要特别强调。服务器通常包括与外部用户交互的软件、认证授权服务、配置管理服务等相关管理平台。例如,开发人员对管理员使用的管理平台进行性能优化,但这个管理平台只部署了3个副本,每个副本都拥有4核8GB主存,最高并发不超过300TPS,平均时延在10s内即可满足需求。这种软件的优化收益有限,主要是因为资源占用过低,且用户数量太少。一般来说,一个服务只有少数接口需要保证高并发和低延时。我们需要列出这些接口,设置测试基准指标(如错误率、响应时间、资源占用率等),确保接口输入和输出的准确性及场景覆盖度,并在满足基准指标的前提下进行服务器性能测试。因为服务器运行环境相对固定,所以场景分类并不困难。真正困难的是后续的性能问题分析和解决。

图4-2 应用的一般逻辑架构图

· 中间件和数据库性能测试。 只有自研的中间件或进行功能定制的中间件才需要进行性能测试,所涉指标可以从官方或开源社区等多个渠道获取。对于常见的Tomcat、Weblogic等中间件,我们需要更多地关注GC(垃圾回收)频率、线程池数量等指标。常用的MySQL数据库的主要指标包括SQL耗时、吞吐量、缓存命中率、连接数等。我们只需要在运行过程中关注这些常用指标,不断进行调优和扩缩容即可。即使是非常依赖中间件和数据库的业务软件,也只需从软件层面进行性能测试,而不用单独对成熟的中间件和数据库进行性能测试。如果出现一些无法解释的性能问题,则可以使用中间件和数据库的配套工具进行性能测试。

· 操作系统和基础设施(CPU、网络、文件系统、存储)性能测试。 性能测试的目的是优化性能。只有在进行了定制化时,才需要有针对性地对操作系统或基础设施进行性能测试,通常情况下只需要从软件层面进行性能测试。当出现问题时,可以通过操作系统配套的工具来检查操作系统层面的指标。通常,通过修改内核参数,就可以解决与操作系统或基础设施相关的问题。

通常来说,为了提升用户体验和节省资源成本,首要考虑的是端上以及存在高并发场景的业务应用的性能测试,其次考虑中间件和数据库的性能测试,最后才会考虑操作系统和基础设施的性能测试。因为在大多数情况下,操作系统和基础设施都不会成为性能瓶颈。

对于一个完善的软件系统架构,通常会有一些后台管理平台,这些平台辅助核心业务正常运行,如资产管理、提单审批等业务。一般来说,针对这些业务的性能测试并不需要特别关注。

根据上述测试场景,我们可以找到性能测试的重点,从而选择需要的工具。一次性能测试通常只会针对一个软件。

4.2.2 性能测试工具简介

本小节介绍前端、服务端、数据库、操作系统的性能测试工具。

1.前端性能测试工具

前端性能测试工具主要有两种类别:开发和测试阶段使用的性能指标测试工具,通过这些工具可以直接看到性能测试结果,如Xcode自带的Instrument、Android Studio自带的Memory Monitor,以及上线阶段的SDK等,这些工具可以将性能数据发送到监控平台,实现线上问题的定位和追踪。SDK通常是自行开发或集成第三方性能检测代码。

· Instrument是Apple官方提供的一个强大的性能调试工具集,内置在Xcode中。其中,Activity Monitor(活动监视器)可以监控进程级别的CPU、内存、磁盘、网络使用情况,以获取应用在手机运行时的总内存占用大小。Core Animation(图形性能)模块显示程序显卡性能、CPU使用情况以及页面刷新帧率。在网络方面,我们可以使用链接工具分析程序如何使用TCP/IP和UDP/IP链接,使用Energy Log模块进行耗电量监控。尽管Instrument主要用于在调试过程中发现问题并及时优化产品,但是它只能供有应用源码的程序员使用,无法测量用户在真实使用场景下的性能。

· Android Studio内置了4种性能监测工具:Memory Monitor(内存监视器)、Network Monitor(网络监视器)、CPU Monitor(CPU监视器)、GPU Monitor(GPU监视器)。这些工具可以监测App的状态。Memory Monitor主要用于监测App的内存分配情况,以判断是否存在内存泄漏。Network Monitor显示App网络请求的状态。CPU Monitor可以检测代码中的方法对CPU资源的占用情况。GPU Monitor可以展示UI渲染工作所花费的时间。

· Bugly是腾讯发布的一款免费的崩溃(Crash)收集工具,为移动开发者提供专业的崩溃监控和崩溃分析等质量追踪服务。移动开发者(Android/iOS)可以通过监控快速发现用户在使用产品过程中出现的崩溃、Android ANR(应用无响应)和iOS卡顿,并根据上报信息快速定位和解决问题。只需登录Bugly网站,用户就可以清晰地看到被监测产品有多少崩溃,影响了多少用户,并根据Bugly提供的Crash日志修复问题。

· WebPageTest是一个在线性能评测网站,它是一个非常详细且专业的Web页面性能分析工具,支持IE和Chrome,使用真实的浏览器(IE和Chrome)和消费者连接速度,从全球多个地点进行免费的网站速度测试。WebPageTest主要提供了Advanced Testing(高级测试)、Simple Testing(简单测试)、Visual Comparison(视觉对比)和Traceroute(路由跟踪)这4个功能。

· SoloPi是支付宝在移动端实现的一套无线化、非侵入式、免Root的Android专项测试方案。只需直接操作手机,就可以实现自动化的功能、性能、兼容性和稳定性测试。SoloPi支持CPU、内存、FPS(画面每秒传输帧数)、流量等常规指标的实时获取,同时支持记录性能数据,并将数据存储到本地,然后以报表形式展示终端支持性能加压。

表4-2所示为对上述工具的对比。

表4-2 上述前端性能测试工具对比

(续)

2.服务端性能测试工具

这里对一些常用的服务器性能测试工具进行比较,包括wrk、JMeter、LoadRunner、Locust。

· wrk是一款专为HTTP设计的基准测试工具。在单机多核CPU的条件下,它能利用系统自带的高性能I/O机制,如epoll和kqueue等,通过多线程和事件模式对目标机器产生大量负载。这是一款轻量级压力测试工具,只支持单机压测。

· JMeter是一款优秀且精致的Java开源测试工具。JMeter的安装简单,使用方便,非常流行。它能完成大多数场景下的性能测试,所有的性能测试人员都应掌握它。由于JMeter主要针对网络协议进行测试,因此测试人员在熟悉网络协议的前提下,能很快掌握其中的术语和概念。

· LoadRunner是惠普公司的一款测试工具,功能全面,资料丰富,易用,但不是开源的。它的各组成模块都非常强大,比如分析模块中的AutoCorrelation向导。这个向导会自动整理所有的监控和诊断数据,并找出导致性能降低的主要原因。这样就把性能测试结果转化为可处理的结果,从而大大减少开发团队分析问题的时间。

· Locust是一款基于Python的开源测试工具,支持HTTP、HTTPS等基于Python脚本编写的协议。它的一个显著优点是可扩展性好。

表4-3所示是上述4款工具的对比。

表4-3 上述服务端性能测试工具对比

3.数据库性能测试工具

关于数据库测试工具,这里主要介绍如下3个。

· SysBench是一款基于LuaJIT的可编写脚本的多线程基准测试工具。它主要用于测试各种数据库(如MySQL、Oracle和PostgreSQL)性能,也可以测试CPU、内存、文件系统等的性能。SysBench的优点包括具有数据分析和展示功能,多线程并发性好,开销小等。此外,SysBench可以轻松定制脚本以创建新的测试。

· MySQLslap是MySQL自带的压力测试工具,能够轻松模拟大量客户端同时操作数据库的情况。

· redis-benchmark是Redis自带的工具,用于模拟 N 个客户端同时发出 M 个请求的场景,类似于Apache ab程序。

4.操作系统性能测试工具

操作系统的每个部分都有相应的测试工具。一般来说,操作系统本身不会成为性能瓶颈,但许多性能问题会体现在CPU、内存或I/O上。这里只介绍Linux操作系统上的性能测试工具。

· Lmbench是一款简单、可移植的多平台软件。它可以对同级别的系统进行比较测试,以反映不同系统的优劣,评估系统的综合性能。这是一款多平台开源基准测试工具,可以测试文档读写、内存操作、进程创建/销毁、网络等的性能,测试方法简单。

· FIO是一款用于测试IOPS(每秒读写次数)的优秀工具,可对磁盘进行压力测试和验证。磁盘I/O是检查磁盘性能的重要指标,可以按照负载情况分为顺序读写和随机读写两大类。FIO可以生成许多线程或进程,并执行用户指定的特定类型的I/O操作。FIO的典型用途是编写和模拟I/O负载匹配的作业文件。换句话说,FIO是一个多线程I/O生成工具,可以生成多种I/O模式,用于测试磁盘设备性能。在测试过程中,要注意忽略操作系统的缓存,使用直接I/O,否则可能测试的是文件系统。

· Hdparm是一个用于设置和查看硬盘驱动器参数的命令行程序。Hdparm可用于测试磁盘性能,设置磁盘参数。

· Iperf是一款网络性能测试工具,可以测试TCP和UDP的带宽质量。Iperf可以测量最大TCP带宽,具有多种参数和UDP特性。Iperf可以报告带宽、延时抖动和数据包丢失等情况。利用Iperf的这种特性,可以测试一些网络设备(如路由器、防火墙、交换机等)的性能。

4.2.3 性能测试工具选择

要准确选择测试工具,就必须明确测试对象和场景。比如,想测试磁盘性能,但错误地选择了FIO,就会因为工具选择不当而得出错误的结论。确定了对象和场景之后,需要考虑这次性能测试需要完成的操作,如是否需要大规模集群测试、工具是否需要具有场景录制等功能。下面将重点介绍测试工具的使用过程和选择注意事项。

大规模性能测试的一般过程:通过录制和回放定制的脚本(或手动编写性能测试脚本)来模拟多用户同时访问被测试系统,产生负载压力,同时监控并记录各种性能指标,最后生成性能分析结果和报告,从而完成性能测试的基本任务。

一个完备的性能测试平台通常会包含图4-3所示的模块。

图4-3 完备的性能测试平台

· 负载生成模块: 负责生成足够的流量负载,负载的具体数量根据测试类型和需求确定。如果进行的是压力测试,那么生成的流量必须大到测试系统无法处理的程度。

· 数据收集和展示模块: 负责收集测试数据,包括各种具体的性能数据。数据收集可以在测试过程中实时完成,也可以在测试完成后进行。有了大量的测试数据,就需要进行分析和展示了。

· 资源监控模块: 在测试过程中,需要对被测试系统和负载生成模块进行实时资源监控,以确保这两个模块的正常运行。具体来说,负载生成模块必须避免过载,否则可能导致产生的流量不足,从而使性能测试失去意义。

· 控制中心模块: 测试人员需要使用此模块与整个测试系统进行交互,如开始或停止测试、改变测试的各种参数等。

市场上有许多性能测试工具可供选择。为了帮助大家选到适合自己需求的工具,下面介绍在选择性能测试工具时需要考虑的一些因素。

· 易用性: 性能测试工具必须足够简单,以减少给测试人员带来的困扰。如果团队中有专家熟悉某种工具,那么可以询问要选择工具的基本情况和易用性。

· 可操作性: 要重点评估所选性能测试工具是否需要支持集群部署,因为这会影响是否可以产生足够的测试流量。如果工具无法充分模拟预期的测试流量,那么可以肯定该工具无法满足测试需求。

· 工具效率: 性能测试工具的效率取决于它能够支持的虚拟用户数量,这影响了在单个设备上执行大规模测试的能力。通常选择简单且偏底层的工具,这种工具消耗的资源少,效率高。

· 协议支持: 不同的工具侧重点不同,支持的通信协议(如HTTPS、HTTP、SSH、FTP/STFP等)也不同。大家需要根据自己要使用的通信协议来选择工具。

· 许可证及其费用: 许可证可能是许多性能测试工具面临的挑战。商业工具通常可提供更好的应用协议支持,但可能存在一些限制。在使用工具之前,要查看该工具的许可证并理解相应的说明或条款。如果它是付费工具,那么需要查询价格,并与其他工具进行比较,然后根据自己的预算确定是否选择该工具。

· 集成能力: 性能测试工具在与其他监控、诊断、缺陷管理和需求管理等工具集成时,应该能够良好地运行。高集成能力意味着该工具可以提供更多的诊断和监控指标,可以跟踪更多类型的测试并轻松发现缺陷。例如,StormForge可以与AWS、GCP、IBM等云提供商的产品无缝集成,并且可以接入Prometheus、Datadog、Circonus等监控工具。

· 可扩展性和适应性: 一种性能测试工具很难具备所有测试功能。因此,了解该工具的可扩展性和适应性是非常重要的。如果用户是初次使用,则建议进行概念验证测试,以确定产品或想法是否可行。这样就可以保证所选工具可兼容其他第三方工具,例如,Apache JMeter具有高度的可扩展性,它允许插入采样器、编写脚本(如Groovy)、插入计时器,并可使用数据可视化插件、分析插件等。

· 社区支持: 了解可以从该工具的供应商那里获得的用户支持级别。供应商通常会通过各种沟通渠道、文档等提供高质量支持。如果使用的是开源软件,则可查看社区支持,还可以通过论坛、活跃成员等获得帮助。

性能测试工具只是提供测试指标的工具,它并不能解决性能问题。所以,性能测试的关键在于选到合适的工具,通过工具得到正确的数据,然后结合系统的实际情况来解决性能问题。

4.3 性能测试的方法、误区和流程

本节主要介绍性能测试的方法、误区和流程。

4.3.1 性能测试的方法

针对性能测试方法的标准很多,对每种测试方法的解读也很多。在狭义上,性能测试主要是指通过模拟生产运行的业务压力或用户使用场景来测试软件性能是否满足生产环境的要求。在广义上,性能测试是压力测试、负载测试、强度测试、容量测试、大数据量测试、基准测试、回放测试、稳定性测试等所有与性能相关的测试的总称。

性能测试的类型众多,但在实际工作中不同的性能测试很难严格区分,因此,我们只要理解了各种测试的特点和概念就可以了。这里主要介绍基准测试、负载测试、压力测试。

1.基准测试

基准测试是在特定的软件、硬件和网络环境下模拟一定数量的虚拟用户来运行一种或多种业务。基准测试的结果将作为基线数据。在系统调优或系统评测过程中运行相同的业务并得到相应的结果,然后用这个结果与基准测试结果进行比较,以确定软件是否达到预期效果。基准测试通常通过单接口测试获取数据,并将这个数据作为基准来比较每次调优(包括对硬件配置、网络、操作系统、应用服务器、数据库等运行环境的调整)前后的性能是否有所提升。

2.负载测试

通过分析软件功能模块以及用户的分布和使用频率,可以构造针对系统综合场景的测试模型,并模拟不同用户执行不同的操作。例如,10%的用户执行登录操作,50%的用户执行查询操作,40%的用户执行数据库更新操作。根据需要,我们可以在每个操作之间加入思考时间。这样可以使模拟的场景尽可能接近真实场景,以准确预测系统投入使用后的性能水平。

一般来说,软件发布后会统计用户行为,然后根据不同用户行为所占比例进行负载测试。通过负载测试结果可以评估软件的处理能力以及各个接口之间是否存在影响性能指标的因素。根据处理能力,我们可以确定容量规划、限流和超时等配置。

3.压力测试

压力测试即通过逐渐增加系统负载,观察软件的性能变化,以确定其在何种负载条件下会失效。这种测试的目的是找出系统的瓶颈或者不能接受的性能点,以确定软件能提供的最大服务级别。

压力测试的核心理念是不断地增加压力,没有预设的性能指标。测试人员要观察软件在何时崩溃,以确定其瓶颈或不能接受的性能转折点,并确定最佳并发数。同时,也可以发现由于资源不足或资源争用而产生的问题。

压力测试并非要破坏系统,而是帮助我们更清晰地了解软件在崩溃时的表现。在进行压力测试时,需要重点关注以下几点。

· 软件是否能保存故障发生时的状态?在故障发生前是否会发出警报?

· 软件是突然崩溃并退出,还是停止响应?

· 停止压力测试后,软件是否能恢复到之前的正常运行状态?如果不能,那么重启后能否正常运行?

4.3.2 性能测试的误区

性能测试与功能测试不同,功能测试只需确保串行请求响应正常即可,而性能测试需要在并发请求下保证输入和输出符合预期。如果软件存在线程或锁使用不当的情况,则可能会出现反常表现,因此应从性能测试端、软件逻辑层、数据层等角度验证结果的正确性。

另外,即使软件已稳定,也可能遇到环境问题,如选择错误的性能测试工具或防火墙导致的网络堵塞、跨集群延时高等问题。遇到这类问题,应首先梳理整个核心链路,然后采用“反问法”逐一解决。

下面介绍在测试过程中笔者犯过的一些低级错误,供大家参考并引以为戒。

误区一:测试对象错误。计划测试A,实际测试了B,然后得出了结果C。

笔者曾经参与过一个项目的性能测试,其整体调用示意图如图4-4所示。

图4-4 性能测试案例的整体调用示意图

在笔者负责的众多接口中,有一个接口通过Nginx访问ServiceA,ServiceA再调用ServiceB来获取信息列表,该列表用于展示App首页的新闻或视频列表,且无须登录就可查看。该接口已在之前的测试用例中被脚本化,但由于开发人员对其进行了一些调整和优化,笔者只需要例行执行测试,验证调整和优化工作没有产生性能影响即可。

测试过这个接口之后发现,性能比之前提高了一倍,TPS上万,P99延时可以控制在100ms以内。由于其完全满足业务目标,笔者并未深入探究。然而,当笔者分享性能测试数据时,负责这部分开发的同事发现了问题。他指出,这个列表查询ServiceB和MySQL的延时在20ms左右,连接池为50,无论如何,性能不应超过2500QPS(1000/20×50QPS)。

经过深入探索,我们发现开发人员配置了一个容易触发的熔断策略。当延时超过某个阈值时,就会自动触发熔断,返回降级数据。由于降级数据缓存在服务内存中,因此查询速度非常快。

由此,笔者总结了如下3个经验教训。

· 在进行性能测试之前要清楚了解业务架构。

· 在测试过程中要观察核心链路指标变化。

· 无论结果是正向的还是不满足目标的,都要对性能数据进行推导和分析,给出合理的解释,而不仅为了完成目标。

从另一个角度来看,测试目标错误可能是由于对软件内部逻辑理解不足,或者一开始就设定了错误的目标。笔者在工作中见过一些测试人员对一些极其复杂的业务场景进行性能测试,发现确实存在性能瓶颈。但是,这个性能问题只有在极端情况下才会触发,也就是说,TPS太低,资源有限,即使进行优化也不会带来大的收益。

因此,在进行性能测试之前,理解内部逻辑非常重要,但更重要的是理解本次性能测试的意义。

误区二:过于依赖专家的意见。遇到专家发言,就停止思考。

在日常工作中,笔者经常见到这样的情况:面对难以解释的性能问题时会寻求专家的建议。对于一些相对简单的问题,如数据库索引的添加或线程数量的配置,专家可以根据经验提供建议,这在一定程度上可以解决问题。但是,对于由复杂业务的多个环节共同引起的性能问题,特别是在不了解业务背景的情况下,专家很难提供有效的解决方案。专家提供的解决方案要么难以实施,要么就是“保持现状,寻找重构的机会”。

当专家发表意见后,性能测试人员会立即停止问题分析和优化方案的设计。他们在专家的背书下,过早地得出不能优化的结论,这使得实际问题得不到解决。

针对这种情况,笔者建议在性能测试过程中从如下两个方面来解决问题。

· 从性能测试开始的那一刻起,专家就需要参与其中,直到性能测试和优化结束。

· 清楚地介绍业务背景和性能测试情况,利用各项性能指标数据来说明性能瓶颈的位置,有针对性地请教问题并排除性能问题。

误区三:错把现象当原因。

在早期的工作中,笔者测试过公司的WAF服务。这项服务不仅包含一些OWASP规则判断,还依赖于Redis中间件。为了方便测试,笔者直接连接到公司云服务上的Redis集群。在测试过程中,性能很难达到100QPS,且时延在100ms左右,这很难满足单核1500QPS、99.9%的时延在50ms以内的目标。笔者当时便大胆地推测是WAF服务本身存在性能问题。

笔者找到相关的开发人员,经排查发现,WAF所在的服务器和Redis服务器之间存在约100ms的网络延时(如果调整到同一个机房,可以达到约200μs)。如果服务和Redis之间使用单个连接,则查询速度无法超过10QPS。

在这次性能测试过程中,笔者意识到当服务性能不符合预期时,应首先逐一排查各环节的时延分布,排除外界因素的影响,然后从服务本身寻找问题,而不应该直接怀疑服务本身存在问题。

虽然这个问题很基础,但在进行性能测试时却常常会犯这样的错误。例如,软件查询数据库的速度很慢,除了网络原因外,还可能是数据库SQL查询慢,或者可能是软件内部逻辑慢。如果是软件处理慢,那么为什么慢,慢在哪个环节?这就需要我们通过strace等工具或者一些链路追踪方案去排查和分析慢的根源。如果直接给出相对笼统和模糊的答案,将问题交给相关开发人员去分析和解决,那么开发人员就需要从头开始,这将大大降低性能测试的效率。

4.3.3 性能测试的流程

为了达到性能测试的目标,我们在进行性能测试之前,需要深入理解以下几点。

· 性能测试对象。 在当前微服务架构盛行的环境下,一个业务可能由几十个甚至上百个服务组成,一个接口的调用可能需要经过几十个服务的数据传递。部分接口可能还存在限流、超时、降级和熔断的情况。如果我们对服务间的处理机制不清楚,那么测试结果可能会不准确,需要重新进行。因此,在开始性能测试时,我们必须明确测试目标,按照“先整体,后细节”的方式厘清软件的全貌。

· 限制因素。 通常情况下,对于测试来说,生产环境的基础设施优于测试环境。如果我们直接在生产环境进行测试,那么可以得到更接近真实情况的测试结果。如果这次测试不能在生产环境中进行,那么我们需要列出生产环境和测试环境的差异,包括但不限于CPU型号、磁盘、内存大小、网卡配置、网络架构、数据库存储容量和现有数据量、依赖服务的版本和交互方式等。

· 结果处理。 如4.3.1节所述,性能测试方法有很多种。有时,我们进行性能测试可能仅是为了验证某个第三方系统,得到一组性能数据后交给领导,不需要关心具体的性能指标。这类似于功能测试中的黑盒测试,仅是验证性测试,更适用于一些成熟的系统。否则,可能会得出一些错误的结果。

一种更主动的性能测试方式是根据用户场景进行性能测试(通常情况下,可以同时进行基准测试、负载测试等),在测试过程中收集软件的性能指标,关注是否达到预期的性能水平。如果没有达到,则需要找出瓶颈在哪里。在必要的情况下,我们需要使用工具进行性能分析,发现问题并修改后再次验证结果是否符合预期。

下面将介绍性能测试流程,我们将重点强调性能测试的思路,而不会讲解性能测试工具或需求管理工具的使用。

1.定义性能测试的范围和目标

确认范围和目标的过程通常需要找到被测服务的负责人、市场营销人员、开发人员、运维人员和功能测试人员。如果是一个较大的系统,那么还需要技术总监的参与。整个过程都应形成文档记录,只有在负责人确认无误后,才能执行后续的性能测试工作。

· 性能测试环境。 如果在生产环境中进行性能测试,就需要找到对正常用户影响最小或无影响的测试方式;如果在测试环境中进行性能测试,那么要建立一套硬件规格、操作系统、数据库配置、依赖服务与线上相似的最小化环境。如果找不到这样的环境,就需要确保不一致的环境不会对测试结果产生影响。这种性能测试环境通常需要在功能测试阶段建立和初步确认。

· 性能测试场景。 如果是已经对外提供的服务,可以通过监控指标看出各个接口的使用比例,按照使用比例设计场景即可。如果是一个未上线的系统,那么基本上只能靠经验评估了。据不完全统计,一个核心软件需要应对突发流量的接口在5个以内,例如我们常见的电商首页模块,主要有搜索商品、查看商品、加入购物车这3个高频使用的接口。因此,在设计性能测试场景时,我们只需针对这3个接口进行设计,包括用例的执行比例、性能测试方法选择、用户数量等,尽可能接近真实场景,以保证性能测试结果的正确性。

图4-5所示是一个电商首页服务的性能测试场景示例。

图4-5 电商首页服务的性能测试场景示例

如果软件高度依赖第三方系统,那就需要在性能测试之前拿到第三方系统的最佳实践和性能测试结果,并且在测试过程中关注第三方系统性能指标是否会影响当前系统,记录不同场景下第三方系统的性能指标。

如果软件高度依赖第三方系统,那么在进行性能测试之前,需要获取第三方系统的最佳实践和性能测试结果。同时,在测试过程中,要关注第三方系统的性能指标是否会影响当前系统,并记录第三方系统在不同场景下的性能指标。

根据服务场景设计性能测试用例也很重要。例如,如果用户的系统可用于网络转发服务,那么需要测试不同消息大小下的性能指标,并在真实场景中扩展性能测试矩阵。另外,性能测试本身就是一个无底洞,如果无限扩展,那么有限的工作时间根本无法完成。因此,当面对一些无法完成的场景时,一定要记录并假设这个系统能够按照预期工作。如果有必要,我们可以对某个系统进行进一步的测试,而不必从头开始。

在4.1.2节中,我们介绍了性能测试的度量指标,包括对外的延时和TPS,以及对内的资源利用率和错误率。虽然可以参考业界的常见指标,但还需要根据实际情况定义自己的指标。例如延时,业界通常认为超过3s是不可接受的,但如果用户的系统可用于文件下载服务,那么即使30s的延时也可能被大众接受。

2.性能测试环境搭建

性能测试环境应尽可能与生产环境保持一致。出于对成本和复杂性的考虑,数量上可能无法做到一致,但应确保硬件型号和软件版本一致,或者确保不一致的部分对性能测试不产生重大影响。

如果有条件在生产环境中进行测试,那么一般会在非正常服务时间进行,同时也要避免污染线上数据。

1) 硬件环境,包括服务器和网络环境。 生产环境通常采用集群部署以处理海量请求。如果过分要求性能测试环境与生产环境完全一样,则可能导致无法承受的成本。可以通过对集群中的一个节点进行性能测试,得出该节点的处理能力,然后计算每增加一个节点的性能损失。通过这种方式,也可以通过建模得到大型负载均衡下的预计承载能力。

2) 软件环境,具体涉及以下几个方面。

· 版本一致性: 包括操作系统、数据库、中间件、第三方系统、被测系统等的版本。

· 配置一致性: 系统(操作系统、数据库、中间件、被测试服务)参数的配置应保持一致,因为这些系统参数的配置可能对系统性能产生巨大影响。

· 部署模型一致性: 各软件服务的网络拓扑部署应尽可能与生产环境保持一致。例如,如果生产环境中的部分第三方服务部署在广域网,而性能测试环境中的所有服务都部署在局域网,那么可能导致测试数据指标与实际情况不一致。

· 数据一致性: 应根据业务预测生成与生产环境相似的数据量和数据类型分配。例如,数据库表中有10条数据和几千万条数据时,在进行性能测试时,获取的数据指标可能有很大差异。

· 监控告警: 正常情况下,每个子系统都应有可视化的指标仪表盘。应统一收集网络链路的每个环节的监控指标,并进行监控面板的建设。

· 施压机器: 作为性能测试的发起点,必须保证有足够的机器数量来产生压力。性能测试工具可以模拟出多个虚拟用户。具体产生的并发数量很大程度上取决于被测系统架构和内部逻辑处理机制。测试过程中需要时刻关注压力机器的各项指标,否则可能产生无意义的数据。

3.性能测试脚本编写

用户可以直接使用性能测试工具的录制功能,也可以自行编写性能测试脚本和参数化业务逻辑。

进行单条用例测试时,检查服务内部是否有错误日志,并确保数据能正确更新到数据库,这样可以保证脚本输入和输出的准确性。

对于没有编程经验的测试人员来说,这部分可能较为复杂。但是,如果存在编写代码的场景,一定要亲自尝试编写,这是一个提升技能和成长的机会。从长期从事性能测试工作的角度来看,能编写代码的测试人员有更广阔的职业发展空间。

4.性能测试执行

如图4-6所示,通过管理控制端对压力系统进行操作,模拟用户行为发出期望请求。被测试软件接收这些请求,用户可以持续观察到被测试软件的指标反馈。

理想情况下,性能测试应仅作为验证业务性能指标的过程,而不是修复Bug的过程;然而,在实际工作中,测试人员可能需要进行多次部署和重复测试。因此,在执行性能测试前,建议在每个环节中都用性能测试工具进行一次或多次完整预演,一旦预演得到正确的结果,再按照以下流程进行。

(1)基准测试

在基准测试过程中,经常会遇到“设置多大并发用户数合适”的问题。这里有一个简单的公式:

并发压测用户数(VU)=每秒执行事务数(TPS)×响应时间(RT)

因此,在寻找合适的并发用户数时,建议使用性能测试的“梯度模式”逐渐增加并发用户数。这时,压力也会逐渐增大。当TPS的增长率小于响应时间的增长率时,这就是性能的拐点,也就是最合理的并发用户数。当TPS不再增长或者开始下降时,此时的压力就是最大的,所使用的并发用户数就是最大的并发用户数。

如图4-7所示,当压测端并发达到 Ay 时,此时TPS达到最大值 t ,资源消耗为 Ax 。如果继续提高压测并发数,则会出现下降趋势,此时的 t 就是软件性能的最优点。

图4-6 性能测试的执行示意图

图4-7 并发测试的最大用户数

(2)负载测试

在基准测试的基础上开始进行负载测试,所有的性能测试脚本都应该按照实际场景的比例为目标用户数量分配虚拟用户。

(3)压力测试

压力测试主要用于评估服务在当前硬件基础设施支持下的极限承压能力。

(4)稳定性测试

稳定性测试是一种性能测试,也被称为浸泡测试。它通过模拟软件的常见场景,并以天和周为单位进行性能测试,主要用于发现软件的内存泄漏以及长时间运行导致的不稳定因素。

5.性能测试结果报告

首先收集性能测试数据,编写性能测试报告,建立性能基线,确保已完成性能测试数据的收集,并对整个测试过程的数据进行备份。

然后将性能测试结果与目标结果进行对比,查看差异。在这个过程中,可能需要进行复测。

接着向相关人员分享测试过程的关键执行步骤和性能指标。在大家一致认可后,完成性能测试。

最后将这些测试结果作为生产环境中限流和监控告警的基线数据。如果超过这些阈值,就需要触发告警。这正是性能测试的重要意义。

6.资源回收

性能测试完成后,应释放测试过程中占用的硬件资源。特别是许多企业已经完成了100%的上云,借助软件虚拟化和Kubernetes编排调度能力,可在几分钟内生成上百个副本,极大地方便了性能测试。但是,测试完成后,一定要记得进行缩容操作,否则可能产生不必要的资源消耗。

4.4 性能测试的结果分析与评估

当开始进行性能测试的那一刻,用户就会看到部分测试结果,但这些结果可能只是假象或者中间状态。在日常工作中,大部分性能测试人员往往只关注获得测试结果,却忽视了对性能根源的分析。这种测试方式,只完成了任务的一部分。剩下的任务是找出软件的性能瓶颈,找到这些问题的根源。有人可能会说,只关注指标结果就可以了,无须关心资源消耗点。但实际上,这种观点是错误的。性能优化本身就像一个无底洞,我们无法预知何时应该停止优化。如果我们已经清楚地知道软件的资源消耗点,就可以根据成本和收益来决定是否需要优化。即使这次没有进行性能优化,后续也可以有现成的低成本性能优化方案(例如,升级依赖库版本以优化某个算法,从而提升性能)。这样,我们就能“对症下药”,一次性解决性能问题。从另一个角度来看,在性能测试过程中找到资源消耗点,可为后续打造高性能软件开发奠定基础。

性能根源分析是性能测试中的重要环节,一般分为实时分析和事后评估。

实时分析是指在性能测试执行过程中查看服务器节点资源利用率、延时和TPS,找出系统瓶颈,进行系统调优。这个过程可能会比较烦琐,需要多次修改运行参数、代码和进行测试,部署后还可能发现效果不明显,甚至性能指标下降。调试半天,成果有限。这时,我们需要查阅文档,反复琢磨和测试这些参数的含义。性能分析需要有耐心和综合技术能力,这是一项既有趣又有挑战性的工作。

通常,事后评估意味着已经完成整个性能测试过程,需要向相关人员分享性能指标数据、性能消耗点、测试前中后的过程和完整记录。在这个过程中,我们要推导和评估各项指标数据的准确性。

4.4.1 施压机器的指标观测

监控压力测试机器的状态,确保它们不会过载。需要特别关注的是CPU、内存、磁盘I/O和网络I/O。图4-8所示为服务器资源总览示例。图4-9所示为性能的度量指标。在进行性能测试时,应始终关注总览表,如果出现问题,则查看详细页面。在必要时,可以为压力测试节点设置告警阈值,以便在出现问题时及时发出告警。

图4-8 服务器资源总览示例

图4-9 性能的度量指标

在性能测试过程中,建议采用阶梯式并发压力测试,逐步增加压力,直到被测试的软件资源耗尽或延时无法满足要求。此方法可以找出软件能够支持的最大TPS。如图4-10和图4-11所示,随着TPS的上升,延时开始逐渐增加并趋于稳定。

当压力测试服务器没有出现任何性能问题时,应开始关注被压测软件的性能指标和资源消耗情况。

图4-10 压力模型图

图4-11 TPS与错误率

4.4.2 软件的指标分析

性能问题通常并非孤立存在,可能由多个因素造成。当遇到性能问题时,我们应从全局角度分析问题的根源,而不是仓促下定论。例如,当指标中的iowait高达95%或CPU使用率满载时,可能是由于服务器CPU降频导致的处理能力不足,也可能是因为软件设计存在问题。因此,性能指标分析至少需要从系统资源消耗和软件指标两个维度来分析性能瓶颈。

1.系统资源消耗分析

观察关联服务器操作系统层面的指标(CPU、内存、网络I/O、磁盘I/O),这些指标可以根据软件的特性进行调整。例如,如果软件是CPU密集型程序,那么需要重点关注用户态和内核态的CPU占用、系统负载、上下文切换次数、缺页异常等。

在观测过程中,我们需要不断完善监控平台指标的采集。在非必要情况下,无须远程登录到具体的节点上执行性能分析命令,因为通过监控平台可以直接观测到绝大多数的性能问题。

笔者曾遇到一个问题,即在测试过程中,软件部分节点的CPU利用率频繁升高,即使在停止性能测试后,CPU利用率还会频繁升高。一开始,笔者推测可能是监控指标采集问题,因此,笔者按照常规思路登录到相应节点并输入top命令,看到CPU利用率确实很高。但是,当输入pidstat 1时,笔者仍然无法确定是哪个进程占用了这么高的CPU。

后来,笔者找到了很多CPU分析工具,并阅读了其中的文档。其中的一种工具是execsnoop,笔者就是通过这个工具找到了问题的根源。原来,机器节点上的镜像分发工具由于配置错误,导致了快速反复重启,从而引发了CPU利用率频繁升高的问题。通过修改其配置,问题得到了解决。

由于性能测试和分析是一个相对耗时的工程,资源消耗会体现在服务器的CPU、内存、I/O指标上。一般来说,服务器本身不会出现性能瓶颈问题。所以,当操作系统出现不可解释的现象时,我们应该首先分析软件的指标,而不是假设服务器本身存在问题。如果在同一服务器的不同节点上发现了同一软件的指标差异,那么我们需要反过来进行服务器性能指标分析。

2.软件指标分析

观察整个调用链路的延时消耗情况,通常情况下,不同服务的延时SLA(服务等级协议)有所区别。例如,缓存服务Redis的延时SLA可以保证P99.9在10ms以内,而大多数软件的P99.9在200ms以内就能满足需求。对于需要重点关注延时的接口,应在不同层面构建自身的监控指标。

如图4-12所示,服务A通常更关注A接口发起调用到B接口返回结果的总耗时,而B接口更关注从收到请求到处理结束的耗时。为了方便查看,所有的时延指标都应尽可能放到一个面板上,一般使用分位数表示。

图4-12 服务调用的延时指标

通过这种方式,如果出现延时问题,那么我们基本可以直观地判断是由于服务A自身的问题还是由于服务B处理过慢导致的。然而,对于极少数的请求超时情况,因为存在平均的问题,所以很难从监控指标层面发现问题。在这种情况下,需要从链路追踪日志或堆栈信息层面来排查具体的异常。

例如,图4-13所示是面向服务视角的延时指标。服务A调用服务B接口,服务B的处理时间大约为10ms,服务A从发起调用到获取返回结果,总共耗费大约50ms(通过hping3探测,从服务A到服务B的网络传输精确耗时为15ms)。但是,服务A在大约1min的时间里,P99.9的延时增加了20ms,而服务B虽然有一些波动,但P99.9的延时可以控制在13ms以下。通过排查,我们发现这段时间的性能测试数据是平时的3倍,因为服务A每次都需要对这些数据进行解密和签名操作,所以导致服务A的延时有一个明显的上升。因此,我们在代码中添加了数据大小限制逻辑来解决这个问题,因为在实际场景中不会存在过大的数据输入。

图4-13 面向服务视角的延时指标

这个案例主要讨论了由于性能测试数据过大引起的延时问题。然而,延时可能是多个因素的连锁反应,如数据量过大、占用过多的内存、触发了编程语言底层的垃圾收集(GC)机制等。频繁启动的GC线程占用了过多的CPU资源,导致软件没有可用的CPU资源,最终表现为频繁GC导致的延时指标上升。在解决这个性能问题的过程中,如果没有找到GC线程频繁启动的根本原因就开始进行GC调优,同时后续软件创建了更大的对象或存在引用泄漏,那么这次的调优可能失效。

上文主要介绍了服务之间的延时指标查看方法。当确定一个服务内部接口的处理时间过长,但这个内部接口逻辑非常复杂,无法直观看出是什么逻辑导致耗时过高时,就需要对单个接口耗时情况进行排查,单接口的耗时跟踪如图4-14所示。阿里巴巴开源的arthas可以通过字节码增强技术跟踪Java代码从调用开始到结束的耗时情况。

图4-14 单接口的耗时跟踪

操作系统层面提供了strace工具以追踪操作系统调用时间的消耗,但要注意,strace本身会占用一定的系统资源。因此,在使用strace进行性能测试的过程中应谨慎。

在满足延时指标的前提下,可以逐渐增加压力,从而提高CPU资源的使用效率,以获取软件的最大处理能力。性能测试资源占用通常有如下两种结果。

· 达到一定的TPS后,CPU资源基本上会被耗尽。

· TPS在某个值附近固定,无论如何增加压力,CPU资源都无法得到充分利用。

一些性能测试人员非常担心压力测试过程中CPU的利用率达到90%及以上,他们认为这是不正常的。实际上恰恰相反,只要在SLA范围内,CPU作为可伸缩的弹性计算资源是没有问题的。

接下来,我们需要分析CPU具体的使用情况,可以使用perf+f lamegraph(火焰图)、profile、Async-profiler(Java)进行CPU的定量分析。从图4-15所示的火焰图,我们可以清楚地看到readSerialData函数占用了39.83%的CPU。火焰图是由Brendan Gregg发明的,它以直观的方式展示了软件的调用栈信息。从垂直方向看,顶部的框表示在CPU上执行的函数,下面的则是它的调用者。从水平方向看,框的宽度表示函数在CPU上运行的时间。火焰图是交互式的,用户可以通过单击查看其中的各个方法信息。利用这些特性,我们可以通过火焰图来快速定位软件中的性能瓶颈,即哪些方法消耗了较多的CPU资源,它们是否可以被进一步优化等。

图4-15 火焰图示意

对于CPU无法利用的情况,可以使用perf off line、offcputime等命令来观察CPU闲置期间的调用栈信息。通常,这种问题主要由以下两种情况引起。

· 由I/O密集型软件导致。可以通过使用本地缓存、增加I/O操作的线程数量、进行批量化处理、异步调用等方式进行有效优化。

· 代码中存在同步锁。应确保是否真的需要加锁,并针对性地进行锁消除。

在工作中,笔者曾遇到过由网络I/O引起的性能问题。公司内部的软件需要获取第三方软件的历史数据。在运行过程中,经常出现iowait高达90%,而CPU利用率只有大约20%,但由于I/O等待而导致请求延时过高的现象。经过分析发现,要获取的数据不会有太大的变动,因此我们直接将每次同步的数据缓存在内网的Redis集群中。当遇到不存在的数据时,可以进行增量拉取更新。通过将网络I/O改为缓存I/O,iowait从原来的90%几乎降低到0,性能提升了近2倍。

4.4.3 软件的事后评估

事后评估主要利用一些常识来验证性能测试报告的准确性,并充分利用指标数据。

图4-16所示为一个性能测试报告的指标数据。测试报告不仅要涵盖核心指标(如延时、TPS、CPU利用率、错误率),还要包括这些核心指标的截图以及依赖于底层中间件或第三方软件的指标数据。同时,也可以记录从搜索引擎中找到的权威性的性能瓶颈优化方案。

图4-16 一个性能测试报告的指标数据

“查看商品”的TPS是1550,为什么不是2000?如图4-17所示,随着TPS的上升,内存GC正常,软件逐渐完全占用了服务器的所有CPU资源(图4-18)。如果再进行加压,那么其中加入购物车的P99.9延时超过450ms(图4-19)。在观测期间,依赖的操作系统和其他服务正常。这表明已经压测到软件的极限,如果想进一步提高,那么必须针对性地进行性能优化。

图4-17 TPS峰值

图4-18 CPU核心使用率

图4-19 服务端延时

我们需要不断质疑并推导数据指标的合理性。例如,当看到搜索商品P99.9的延时是400ms时,应该问为什么延时是400ms?这时需要通过测试报告来定位这400ms的调用耗时分布图,通过这种方式发现有误导的性能指标。例如,软件A依赖于软件B的某个接口,但测试结果显示软件B的QPS明显低于软件A,通过排查发现,由于负载均衡的配置问题,软件A的部分流量并未路由到软件B,而是发送到生产环境。

在查看性能测试报告数据时,我们至少需要从当前软件指标和资源消耗两个方面来验证数据的正确性。正确的测试报告应该符合现实逻辑,可复现,可用数据图表展示,且性能测试结论能被所有人理解和分析。

在确认性能测试数据无误后,我们需要计算每个请求消耗的CPU资源。通过下面的公式,我们可以推导出软件上线后所需的硬件资源:

每个请求的CPU利用率=总CPU利用率/请求总数

所需CPU数量=总TPS×每个请求的CPU利用率

例如,如果1550 TPS占用8核CPU,那么每个请求的CPU利用率为8×90%/1550≈0.46%。如果要满足50000TPS的需求,那么就需要230核(50000×0.46%)。当然,这是根据性能测试指标评估出来的资源占用,上线初期最好预留20%缓冲资源。我们需要不断观察监控指标,计算生产环境的TPS和资源占用的实际大小,并与性能测试数据进行比较,不断校正其中的偏差,从而使软件的TPS和资源配比达到一个合理的状态。

4.5 本章小结

本章主要介绍了软件的性能测试指标、常用工具,以及性能测试的方法、误区和过程。前期,我们将实现软件指标的可视化,并在性能测试过程中采用“先现象后原因”的分析方式,找到影响性能的根本原因。总的来说,我们首先通过监控指标找出资源利用率过高的软件,然后分析占用资源过多的代码逻辑,当发现延时过高时,我们需要找出耗时过多的软件,并分析其内部耗时逻辑,最后找到无法提高TPS的根本原因。

虽然我们已经找到了影响性能的根本原因,但要优化这些性能问题还需要进一步努力。在性能优化的过程中,我们会面临诸多挑战。“过早优化是万恶之源”这句话是由To n y Hoare在《计算机编程艺术》一书中提出的,他其实并不反对优化,而是在强调优化的必要性。他真正的意思是,我们不应该浪费时间去做那些并不必要的优化。这句话为一些工程师提供了一个偷懒的借口,让他们觉得“性能不足,补充资源”是最好的解决方案。而有些工程师不怕犯错,大胆进行必要的性能优化,笔者认为这种工程师在代码和技术上是有追求的。但优化意味着变更,变更就可能会出错,出错就可能会被当作反例。因此,性能优化应尽可能向前移动,在高并发的业务场景中,我们应尽量从性能的角度进行软件的设计,即使后期出现性能问题,也能进行快速调整和优化,尽可能降低性能优化的成本。 Zl+mCyJYaIEuqQtXdOct4EjKxM6BtQPZ4bBcqRpljJ+pGy8IvArNX92JyQ9Rcief

点击中间区域
呼出菜单
上一章
目录
下一章
×