学习链接:《性能学习实战30讲》
授课人:高楼
课程从三个部分进行阐述:
- 第一个模块是性能测试基础篇。在这个模块里澄清一些性能测试的基础概念,讲解一些关键部分。但并不是对概念的简单描述,而是根据实际项目,讲真正具有指导价值的性能测试概念是什么,并解析这些概念在实际操作中的指导性作用。
- 在第二个模块中,通过性能测试工具的实际操作实例,对应性能测试的前后逻辑关系。在这一部分中,重点讲解为什么要使用某些工具的某些功能,以便确保工具的使用及结果是为性能测试需求指标和性能分析报告而服务的,而不是浮于表面的“炫技”。
- 在第三个模块中,通过操作系统、应用服务器、数据库、缓存服务器、Java、C++ 等监控工具的使用和分析方法,阐述它们产生的数据在性能分析过程中该如何判断,为测试报告及性能分析提供有效的历史数据。
目标:让性能变得有价值
在性能测试分析优化之前,如果 TPS 是 100,你做完了之后 TPS 是 10000,这就是价值。
在性能测试分析优化之前,如果响应时间是 0.1ms,你做完了之后是 0.01ms,这就是有价值。
在性能测试分析优化之前,如果 CPU 使用率是 100%,你做完了之后是 50%,这就是有价值。
往往指标都是一句话:把系统压死为止。这也算指标。
模型:参考生产环境的数据,进行统计出来一个各个业务的并发量,根据并发量确定业务的权重,做出来一个业务权重模型。
灵魂问题:性能流量直接从生产上导的话,是不是就可以不用性能测试人员了?性能测试人员就要被淘汰了?
方案内容:
使用项目管理工具(比如用 Project 或 OmniPlan 之类的工具。)制定测试计划,不仅可以细分条目,还能跟踪各个工作的动态进度,可以设置前后依赖关系,填入资源和成本以便计算项目偏差。
测试前需要确认好以下内容
场景来源于英文的 scenario,对性能场景中的“场景”比较正宗的描述是:在既定的环境(包括动态扩展等策略)、既定的数据(包括场景执行中的数据变化)、既定的执行策略、既定的监控之下,执行性能脚本,同时观察系统各层级的性能状态参数变化,并实时判断分析场景是否符合预期。
场景的常见分类
要不要对项目进行调优,先需要理解性能项目分类,也就是对团队人员的定位。
上图中蓝线表示 TPS,黄色表示响应时间。
在 TPS 增加的过程中,响应时间一开始会处在较低的状态,也就是在 A 点之前。接着响应时间开始有些增加,直到业务可以承受的时间点 B,这时 TPS 仍然有增长的空间。再接着增加压力,达到 C 点时,达到最大 TPS。我们再接着增加压力,响应时间接着增加,但 TPS 会有下降(请注意,这里并不是必然的,有些系统在队列上处理得很好,会保持稳定的 TPS,然后多出来的请求都被友好拒绝)。
通常情况下,我们会根据场景的目的来定义 TPS 的粒度。
如果是接口层性能测试,T 可以直接定义为接口级;
如果业务级性能测试,T 可以直接定义为每个业务步骤和完整的业务流。
如果我们要单独测试接口 1、2、3,那 T 就是接口级的;如果我们要从用户的角度来下一个订单,那 1、2、3 应该在一个 T 中,这就是业务级的了。
所以,性能中 TPS 中 T 的定义取决于场景的目标和 T 的作用。一般我们都会这样来定事务。
你要创建什么级别的事务,完全取决于测试的目的是什么。
因此,TPS就是每秒事务数。它可以反应出一个系统的处理能力。
它描述的是数据库中的 Query Per Second,从上面的示意图中来看,其实描述的是服务后面接的数据库中 SQL 的每秒执行条数。如果描述的是前端的每秒查询数,那就不包括插入、更新、删除操作了。显然这样的指标用来描述系统整体的性能是不够全面的。所以不建议用 QPS 来描述系统整体的性能,以免产生误解。
每秒请求数。
如果一个用户点击了一次,发出来 3 个 HTTP Request,调用了 2 次订单服务,调用了 2 次库存服务,调用了 1 次积分服务,那么这个 Request 该如何计算?如果你是算 GDP 的专家,我觉得可能会是:3+2+2+1=8(次)。而在具体的项目中,我们会单独描述每个服务,以便做性能统计。如果要描述整体,最多算是有 3 个 RPS。如果从 HTTP 协议的角度去理解,那么 HTTP Request 算是一个比较准确的描述了,但它本身的定义并没有包含业务。如果赋予它业务的含义,那么用它来描述性能也是可以的。
每秒点击数。Hit 一般在性能测试中,都用来描述 HTTP Request。但是,也有一些人用它描述真正的客户在界面上的点击次数。
每秒 / 每分钟调用次数。这个描述在接口级是经常用到的,比如说上面的订单服务。显然一次客户界面上的点击调用两次。这个比较容易理解。但是,在操作系统级,我们也经常会听到系统调用用 call 来形容,比如说用 strace 时,你就会看见 Calls 这样的列名。
RT = T2-T1。计算方式非常直接简单。但是,我们要知道,这个时间包括了后面一连串的链路。
如何排查rt较高的问题?
在所有服务的进出口上都做记录,然后计算结果就行了。在做网关、总线这样的系统时,基本上都会考虑这个功能。
而现在,随着技术的发展,链路监控工具和一些 Metrics 的使用,让这个需求变得简单了不少。比如说这样的展示:
上面这张示意图中,其实压力工具是 4 个并发线程,由于每个线程都可以在一秒内完成 4 个事务,所以总的 TPS 是 16。这非常容易理解吧。而在大部分非技术人的脑子里,这样的场景就是并发数是 4,而不是 16。
那么用户数怎么来定义呢?涉及到用户就会比较麻烦一点。因为用户有了业务含义,所以有些人认为一个系统如果有 1 万个用户在线,那就应该测试 1 万的并发线程,这种逻辑实在是不技术。通常,我们会对在线的用户做并发度的分析,在很多业务中,并发度都会低于 5%,甚至低于 1%。
拿 5% 来计算,就是 10000 用户 x5%=500(用户级 TPS),注意哦,这里是 TPS,而不是并发线程数。如果这时响应时间是 100ms,那显然并发线程数是 500TPS/(1000ms/100ms)=50(并发线程)。
通过这样简单的计算逻辑,我们就可以看出来用户数、线程数和 TPS 之间的关系了。
响应时间肯定不会一直都是 100ms,因此这个关系是一个动态的趋势问题
作者否定:有些文章中写性能测试要按 28 原则来计算并发用户数。大概的意思就是,如果一天有 1000 万的用户在使用,系统如果开 10 个小时的话,在计算并发用户数的时候,就用 2 小时来计算,即 1000 万用户在 2 小时内完成业务。
业务模型应该如何得到呢?这里有两种方式是比较合理的:
响应时间如何设计比较合理呢?这里有两种思路推荐
常用工具进行对比
这是全球范围内 5 年 JMeter 和 LoadRunner 热度(红色线是 LoadRunner,蓝色线是 JMeter):
中国范围近 5 年 JMeter 和 LoadRunner 热度:
JMeter、LoadRunner 的基本功能学会,做两件事,做脚本和发压力
压力策略,直白一点说,就是根据业务场景,调整工具中的参数
四问:
- 工具能做什么
- 工具不能做什么?
- 我们用工具的目标是什么?
- 当工具达不到目标时,我们怎么办?
我们假设上图中的这些小人是严格按照这个逻辑到达系统的,那显然,系统的绝对并发用户数是 4。如果描述 1 秒内的并发用户数,那就是 16(1s钟每一个用户迭代了4次)。
在实际的系统中,用户通常是这样分配的:
并发数是 16TPS,就是 1 秒内整个系统处理了 16 个事务。这样描述就够了,不需要纠结。
如上图所示,总共有 32 个用户进入了系统,但是绿色的用户并没有任何动作,那么显然,在线用户数是 32 个,并发用户数是 16 个,这时的并发度就是 50%。并发(1s为单位)
实际系统中
为了能 hold 住更多的用户,我们通常都会把一些数据放到 Redis 这样的缓存服务器中。所以在线用户数怎么算呢,如果仅从上面这种简单的图来看的话,其实就是缓存服务器能有多大,能 hold 住多少用户需要的数据。
所以我们要是想知道在线的最大的用户数是多少,对于一个设计逻辑清晰的系统来说,不用测试就可以知道,直接拿缓存的内存来算就可以了。
因此。
在线用户数:假设一个用户进入系统之后,需要用 10k 内存来维护一个用户的信息,那么 10G 的内存就能 hold 住 1,048,576 个用户的数据,这就是最大在线用户数了。在实际的项目中,我们还会将超时放在一起来考虑。
什么是并发度:在线用户数 / 并发用户数
我们经常使用性能测试工具,设置线程的等待时间(Sleep时间)来保持用户和系统之间的session不断开,但实际上的并发量特别低
上图是几个指标之间的计算关系
我们在整个性能测试中,最需要关注的指标是:服务端的处理能力。
举例:
JMeter(1 个线程) - Nginx - Tomcat - MySQL
Jmeter的处理结果:
summary + 5922 in 00:00:30 = 197.4/s Avg: 4 Min: 0 Max: 26 Err: 0 (0.00%) Active: 1 Started: 1 Finished: 0
summary = 35463 in 00:03:05 = 192.0/s Avg: 5 Min: 0 Max: 147 Err: 0 (0.00%)
summary + 5922 in 00:00:30 = 197.5/s Avg: 4 Min: 0 Max: 24 Err: 0 (0.00%) Active: 1 Started: 1 Finished: 0
summary = 41385 in 00:03:35 = 192.8/s Avg: 5 Min: 0 Max: 147 Err: 0 (0.00%)
summary + 5808 in 00:00:30 = 193.6/s Avg: 5 Min: 0 Max: 25 Err: 0 (0.00%) Active: 1 Started: 1 Finished: 0
summary = 47193 in 00:04:05 = 192.9/s Avg: 5 Min: 0 Max: 147 Err: 0 (0.00%)
我们可以看到,JMeter 的平均响应时间基本都在 5ms,因为只有一个压力机线程,所以它的 TPS 应该接近 1000ms / 5ms=200TPS。
问:为什么有的数据行会少一点?
因为这里算的是平均数,并且这个数据是 30s 刷新一次,用 30 秒的时间内完成的事务数除以 30s 得到的,但是如果事务还没有完成,就不会计算在内了;同时,如果在这段时间内有一两个时间长的事务,也会拉低 TPS。
那么对于服务端呢,我们来看看服务端线程的工作情况。
可以看到在服务端,我开了 5 个线程,但是服务端并没有一直干活,只有一个在干活的,其他的都处于空闲状态。
这是一种很合理的状态。但是需要注意的是,这种合理的状态并不一定是对的性能状态。
错误的压力测试操作:
压力工具中要使用 4000 个线程,结果研发为了测试4000线程,给服务端的 Tomcat 上也配置了 4000 个线程,结果 Tomcat 一启动,稍微有点访问,CS (Context Switch)就特别高,结果导致请求没处理多少,自己倒浪费了不少 CPU。
需要服务端设置回正常的
性能分析思路大纲:
这样的趋势图中,我们是看不到明确的拐点的。
但是我们能做的清晰的判断就是:有瓶颈!
所以对 TPS 曲线来说,它可以明确告诉我们的就是:
这时你可能会问,为什么不看响应时间就武断地下此结论呢?其实响应时间是用来判断业务有多快的,而 TPS 才是用来判断容量有多大的。
多明显的问题,随着线程的增多,响应时间也在增加,是吧。再来看它们对应的 TPS 图:
到第 40 个线程时,TPS 基本上达到上限,为 2500 左右。响应时间随着线程数的增加而增加了,系统的瓶颈显而易见地出现了。
但是,如果只让你看 TPS 曲线,你是不是也会有同样的判断?那就是:有瓶颈!并且和压力有关?所以说,其实 TPS 就可以告诉我们系统有没有瓶颈了,而响应时间是用来判断业务有多快的。
我们来看两个场景的执行对比。
TPS 都是达到 400,但两个场景中线程递增的策略不同,产生的响应时间完全不同。虽然都没有报错,但是第一种场景是完全不符合真实的业务场景的。
场景 2 使用了递增的策略,在每个阶梯递增的过程中,出现了抖动,这就明显是系统设置的不合理导致的。设置不合理,有两种可能性:
那么,对于场景中线程(有些工具中叫虚拟用户)递增的策略,我们要做到以下几点:
上面两点针对的是常规的性能场景。对于秒杀类的场景,我们前期一定是做好了系统预热的工作的,在预热之后,线程突增产生的压力,也是在可处理范围的。这时,我们可以设计线程突增的场景来看系统瞬间的处理能力。如果不能模拟出秒杀的陡增,就是不合理的场景。
有了瓶颈的判断能力,也有了线程递增的意识,那么下面在场景执行中,我们就要有判断性能衰减的能力了吧。
我们先看一个压力过程中产生的结果图。
在递增的压力过程中,随着用户数的增加。我们可以做几次计算。
通过这三次计算,我们是不是可以看到,每线程每秒发出的请求数在变少,但是整体 TPS 是在增加的。
我们有很多做性能测试的人,基本上,只看 TPS 和响应时间的时候,在上面这个示例中,肯定会一直往上加用户。虽然响应时间在增加,但是增加得也不多嘛。
但实际上,通过我们的计算可以知道,性能是在不断地衰减的。我们来看一张统计图:
通过红线的大致比对可以知道,当每线程每秒的请求数降到 55 左右的时候,TPS 就达到上限了,大概在 5000 左右,再接着往上增加线程已经没有用了,响应时间开始往上增加了。
这就是性能衰减的过程(题外话,在上图中,其实还有一个问题,就是在红线前面,性能在上升的过程中有几次抖动,这个抖动到后面变大了,也变频繁了,如果这是必然出现的抖动,那也是配置问题,希望你注意到这一点)。
为什么需要细致描述性能衰减过程?
只要每线程每秒的 TPS 开始变少,就意味着性能瓶颈已经出现了。但是瓶颈出现之后,并不是说服务器的处理能力(这里我们用 TPS 来描述)会下降,应该说 TPS 仍然会上升,在性能不断衰减的过程中,TPS 就会达到上限。
也就是说:性能瓶颈其实在最大 TPS 之前早就已经出现了。
那么我们是不是应该在性能衰减到最大 TPS 时就停止场景呢?
这个不一定的哦。因为停不停场景,取决于我们的场景目标,如果我们只是为了得到最大 TPS,那确实可以停止场景了。但是,如果我们要扩大化性能瓶颈,也就是说为了让瓶颈更为明显,就完全不需要停止场景,只要不报错,就接着往上压,一直压到我们要说的下一个话题——响应时间变长,需要拆分。
如果我们要分析压力工具中的响应时间,拆分的逻辑就是上面这个示意图。
但是在真实的场景中,基本上不是这样的。如果是内网,那基本上都是连在一个交换机上,所以通常是这样的:
在这样的拓扑中,我们仍然可以拆出来 t1 到 t8 的时间。只是实际动手的时候,思路一定要清晰,时间拆分是从哪里到哪里,要画出来,不能混乱。
我们有很多手段可以进行时间的拆分,当然要看我们的应用支持哪一种。
如果我们是这样的架构,拆分时间应该是比较清楚的。
首先我们需要查看 Nginx 上的时间。日志里就可以通过配置requesttimeupstream_response_time
得到日志如下信息:
14.131.17.129 - - [09/Dec/2019:08:08:09 +0000] "GET / HTTP/1.1" 200 25317 0.028 0.028
最后两列中,前面是请求时间的 28ms,后面是后端响应时间的 28ms。
同时,我们再到 Tomcat 上去看时间。
172.18.0.1 - - [09/Dec/2019:08:08:09 +0000] "GET / HTTP/1.1" 200 25317 28 27 http-nio-8080-exec-1
请求时间消耗了 28ms,响应时间消耗了 27ms。
再来看一下前端的时间消耗。
从这里可以看到,从发出请求到接收到第一个字节,即 TTFB 是 55.01ms,内容下载用了 11.75ms。从这就可以看得出 Nginx 基本上没消耗时间,因为它和 Tomcat 上的请求响应时间非常接近。
那么网络上的消耗时间怎么样呢?我看到有很多人用 TTFB 来描述网络的时间。先来说明一下,TTFB 中显然包括了后端一系列处理和网络传输的时间。如下图所示。
下面的紫色点是指要接收的内容。上面的红色线就是 TTFB。
Tomcat 上基本上是消耗了处理的所有时间,当然这中间也包括了 MySQL 花费的时间。而前端看到的其他时间就消耗在了网络中。
主要说明了响应时间怎么一步步拆。当然,如果你是下面这种情况的话,再一个个拆就比较辛苦了,需要换另一种方式。
你肯定想知道每个系统消耗了多长时间,那么我们就需要链路监控工具来拆分时间了。比如像这样来拆分:
从 User 开始,每个服务之间的调用时间,都需要看看时间消耗的监控。这就是时间拆分的一种方式。
其实不管我们用什么样的工具来监控,最终我们想得到的无非是每个环节消耗了多长时间。用日志也好,用链路监控工具也好,甚至抓包都可以。
当我们拆分到了某个环节之后,就有了下一步的动作:构建分析决策树。
分析决策树,对性能测试分析人员实在是太重要了,是性能分析中不可或缺的一环。它是对架构的梳理,是对系统的梳理,是对问题的梳理,是对查找证据链过程的梳理,是对分析思路的梳理。它起的是纵观全局,高屋建瓴的指导作用。
通过上面的几个步骤,我们就会知道时间消耗在了哪个节点上。那么之后呢?又当如何?
总要找到根本的原因才可以吧,我画了如下的分析决策图:
从压力工具中,只需要知道 TPS、响应时间和错误率三条曲线,就可以明确判断瓶颈是否存在。再通过分段分层策略,结合监控平台、日志平台,或者其他的实时分析平台,知道架构中的哪个环节有问题,然后再根据更细化的架构图一一拆解下去。
以数据库分析和操作系统分析举一下例子。
首先我们看一下数据库分析决策树。
比如针对 RDBMS 中的 MySQL,我们就可以画一个如下的决策树:
MySQL 中的索引统计信息有配置值,有状态值。我们要根据具体的结果来判断是否需要增加 key_buffer_size
值的大小。比如这种就无所谓了。
Buffer used 3.00k of 8.00M %Used: 0.0004
从上面的数据可以看到,key buffer size
就用到了 4%,显然不用增加。
再比如,我们看到这样的数据:
__Tables_______________________
Open 2000 of 2000 %Cache: 100.00
Opened 15.99M 4.1/s
这就明显有问题了。配置值为 2000 的 Open Table Cache
,已经被占满了。显然这里需要分析。但是,看到状态值达到配置值并不意味着我们需要赶紧加大配置值,而是要分析是否合理,再做相应的处理。比如说上面这个,Table
确实打开得多,但是如果我们再对应看下这一条。
Slow 2 s 6.21M 1.6/s
你是不是觉得应该先去处理慢 SQL 的问题了?
关于数据库的我们就不举更多的例子了。在这里只是为了告诉你,在分析决策树的创建过程中,有非常多的相互依赖关系。
再来看一下操作系统分析决策树,我在这里需要强调一下,操作系统的分析决策树,不可以绕过。
基于此决策树,每个人都可以做到对操作系统中性能问题的证据链查找。
“CPU 使用率在 TPS 上升的过程中,从 10% 增加到 95%,超过了预期值。” “内存使用率达到 99%,所以是瓶颈点。” “I/O 使用率达到 100%。” 等等。
像这样的描述,在作者的性能团队中,一定会被骂回去重写。我要这些描述有什么用?我要的是为什么达到了这样的值,原因在哪?怎么解决?
中断能占 40%,sy CPU 也能占 40%。这系统还用干业务的事吗?全干自己的事去了,可见操作系统有问题!你是不是要做这个判断了?
而实际情况是,这个主机上只有一个网卡队列,而请求量又比较大。
所以要解决的是网卡队列的问题,至于怎么解决,那手段就多了。可以换个服务器,可以多加几个队列,可以多接几个节点…
以上只是给出几个性能分析过程中常见的决策树示例。在后续的分析过程实例中,我们将秉承着这种分析思路,一步步地走到瓶颈的面前。
其实简单来说,就一句话:当你觉得系统中哪个环节不行的时候, 又没能力分析它,你可以直接做该环节的增加。
从 TPS 曲线中,我们可以明显看到系统是有瓶颈的,但是并不知道在哪里。鉴于系统架构如此简单,我们索性直接在某环节上加上一台服务器,变成这样:
然后得到如下数据:
哟,没好使!怎么办?再接着加其他节点,我加了更多的 JMeter 机器。
再来看下结果:
真巧,TPS 增加了!
这就是我说的场景比对。
当我们不知道系统中哪个环节存在性能瓶颈时,对架构并不复杂的系统来说,可以使用这样的手段,来做替换法,以快速定位问题。