gitbook/性能测试实战30讲/docs/200152.md
2022-09-03 22:05:03 +08:00

270 lines
14 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 26丨案例手把手带你理解TPS趋势分析
在性能分析中前端的性能工具我们只需要关注几条曲线就够了TPS、响应时间和错误率。这是我经常强调的。
但是关注TPS到底应该关注什么内容如何判断趋势判断了趋势之后又该如何做出调整调整之后如何定位原因这才是我们关注TPS的一系列动作。
今天我们就通过一个实际的案例来解析什么叫TPS的趋势分析。
## 案例描述
这是一个案例用一个2C4G的Docker容器做服务器。结构简单至极如下所示
![](https://static001.geekbang.org/resource/image/17/1d/177cd65abdaaba1e8056e676cdf96b1d.jpg)
当用个人电脑(上图中压力工具1测试云端服务器时达到200多TPS。但是当用云端同网段压力机上图中压力工具2测试时TPS只有30多并且内网压力机资源比本地压力机要高出很多服务器资源也没有用完。
在这样的问题面前,我通常都会有一堆的问题要问。
* 现象是什么?
* 脚本是什么?
* 数据是什么?
* 架构是什么?
* 用了哪些监控工具?
* 看了哪些计数器?
在分析之前,这些问题都是需要收集的信息,而实际上在分析的过程中,我们会发现各种数据的缺失,特别是远程分析的时候,对方总是不知道应该给出什么数据。
我们针对这个案例实际说明一下。
这个案例的现象是TPS低资源用不上。
下面是一个RPC脚本的主要代码部分。
```
public SampleResult runTest(JavaSamplerContext arg0) {
// 定义results为SampleResult类
SampleResult results = new SampleResult();
// 定义url、主机、端口
String url = arg0.getParameter("url");
String host = arg0.getParameter("host");
int port = Integer.parseInt(arg0.getParameter("port"));
results.sampleStart();
try {
message=detaildata_client.detaildata_test(url);// 访问URL并将结果保存在message中
System.out.println(message); //打印message注意这里
results.setResponseData("返回值:"+ message, "utf-8");
results.setDataType(SampleResult.TEXT);
results.setSuccessful(true);
} catch (Throwable e) {
results.setSuccessful(false);
e.printStackTrace();
} finally {
String temp_results=results.getResponseDataAsString();
results.setResponseData("请求值:"+arg0.getParameter("url")+"\n"+"返回值:"+temp_results, "utf-8");
results.sampleEnd();
}
return results;
```
JMeter脚本关键部分
```
<stringProp name="ThreadGroup.num_threads">100</stringProp>
//我们来看这里ramp_time只有1秒意味着线程是在1秒内启动的这种场景基本上都和真实的生产场景不相符。
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">true</boolProp>
<stringProp name="ThreadGroup.duration">300</stringProp>
...............
<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV Data File Config" enabled="true">
<stringProp name="delimiter">,</stringProp>
<stringProp name="fileEncoding">utf-8</stringProp>
<stringProp name="filename">filename</stringProp>
<boolProp name="ignoreFirstLine">false</boolProp>
<boolProp name="quotedData">false</boolProp>
<boolProp name="recycle">true</boolProp>
<stringProp name="shareMode">shareMode.all</stringProp>
<boolProp name="stopThread">false</boolProp>
<stringProp name="variableNames">url</stringProp>
</CSVDataSet>
```
在这个脚本中逻辑非常简单一个RPC接口1. 发出请求2. 返回响应3. 打印返回信息。
本机跑出来的结果如下:
![](https://static001.geekbang.org/resource/image/68/98/6876aaa2039b95c3c45159e40d867f98.png)
在这个案例中,参数化数据就是根据真实的业务量来计算的,这个可以肯定没有问题。
那么架构呢在最上面的图中已经有了部署的说明。在逻辑实现上也就是一个很简单的服务端内部并没有复杂的逻辑。所用到的监控工具是top、Vmstat。
看了哪些计数器呢CPU、内存、I/O等。
下面我们开始分析。
## 第一阶段
对公网上的测试来说,基本上压力都会在网络上,因为出入口带宽会成为瓶颈,所以先要看一眼自己的带宽用到了多少,再比对一下出口路由上的带宽。
![](https://static001.geekbang.org/resource/image/2a/ff/2ae67c715480b3e8fe1486a943ce05ff.png)
这里1Gbps只用到了0.01%,也就是(1000/8)x0.01%=12.5k这里是将带宽bit换成byte计算
在这样的带宽使用率之下,即使是公网也不见得会有问题,更别说在内网了。可见带宽不是瓶颈点。
既然这样,我们直接在内网里来做分析,看原因是什么。
但是我们要以什么样的场景来跑呢因为带宽现在看到用得并不多但TPS也上不去。**首先应该想到的场景就是把TPS**曲线给做出梯度来。
为什么要这么做最重要的就是要知道到底TPS在多少压力线程下会达到最大值也就是我在各种场合经常强调的一个场景最大TPS场景。关于这种曲线我们不需要性能指标应该就可以做得出来。如下图所示
![](https://static001.geekbang.org/resource/image/a3/2f/a3dea74520a7fe3c192e6cc24c19bd2f.png)
在一个**既定场景、既定数据、既定环境**的压力场景中我们一定要拿到这样趋势的TPS和RT曲线。其中绿色和红色的点都是不需要业务指标来限定的而是通过压力场景中观察TPS趋势线来确定。
我来解读一下这个趋势图:
1. 响应时间一定是从低到高慢慢增加的;
2. TPS一定也是从低到高慢慢增加的并且在前面的梯度中可以和线程数保持正比关联。举例来说如果1个线程TPS是10那2个线程的TPS要在20。依次类推。
而在这个例子中前面有提到100线程1秒加载完这样的比例完全看不出来梯度在哪所以改为100秒加载100个线程再来看看梯度。
测试结果如下:
![](https://static001.geekbang.org/resource/image/ee/c5/eea8cd59a06a800ad4f1757bb41ec4c5.png)
![](https://static001.geekbang.org/resource/image/07/5f/07954ac566e057efd861a15ce18cc85f.png)
![](https://static001.geekbang.org/resource/image/49/71/49ca6af1f764c2688fb62eb756847f71.png)
从这个结果可以看出几点:
1.TPS一点梯度没看出来。为什么说没有看出来呢这里我发一个有明显梯度的TPS曲线出来以备参考这张图不是本实例中的,只用来做分析比对):
![](https://static001.geekbang.org/resource/image/e8/27/e86238087c62ccfad2f48a6038892727.png)
2.响应时间增加的太快了,明显不符合前面我们说的那个判断逻辑。那什么才是我们判断的逻辑呢?这里我发一个有明显梯度的出来以备参考(这张图不是本实例中的,只用来做分析比对):
![](https://static001.geekbang.org/resource/image/4b/8e/4bbf7e0983971a3f51b9acc251f5a18e.png)
1. 粒度太粗对一个duration只有五分钟的场景来说这样的粒度完全看不出过程中产生的毛刺。
2. 至少看到内网的TPS能到180了但是这里没有做过其他改变只是把Ramp-up放缓了一些所以我觉得这个案例的信息是有问题的。
## 第二阶段
针对以上的问题,下面要怎么玩?我们列一下要做的事情。
1. 将Ramp-up再放缓改为300秒。这一步是为了将梯度展示出来。
2. 将粒度改小JMeter默认是60秒这里改为1秒。这一步是为了将毛刺显示出来。强调一点如果不是调优过程而是为了出结果报告的话粒度可以设置大一些。至于应该设置为多大完全取决于目标。
接着我们再执行一遍,看看测试结果:
![](https://static001.geekbang.org/resource/image/22/c7/22522e14612447ffd60a19e7f96522c7.png)
![](https://static001.geekbang.org/resource/image/9a/70/9a841039650c2452551a7b313bb55f70.png)
![](https://static001.geekbang.org/resource/image/9d/25/9d5a375a52dfd8efa064a70c45b9b825.png)
这样看下来,有点意思了哈。明显可以看到如下几个信息了。
1. 响应时间随线程数的增加而增加了。
2. TPS的梯度还是没有出来。
显然还是没有达到我们说的梯度的样子。但是这里我们可以看到一个明显的信息,线程梯度已经算是比较缓的了,为什么响应时间还是增加得那么快?
这里的服务器端压力情况呢?如下所示:
![](https://static001.geekbang.org/resource/image/fc/de/fc6041ea7780c05f35034d744c0972de.png)
从监控图大概看一下服务端CPU、内存、网络几乎都没用到多少有一种压力没有到服务端的感觉。
在这一步要注意,压力在哪里,一定要做出明确的判断。
在这里,当我们感觉服务端没有压力的时候,一定要同时查看下网络连接和吞吐量、队列、防火墙等等信息。查看队列是非常有效的判断阻塞在哪一方的方式。
如果服务端的send-Q积压那就要查一下压力端了。如下所示
```
State Recv-Q Send-Q Local Address:Port Peer Address:Port
......
LISTEN 0 54656 :::10001 :::*
......
```
在网络问题的判断中,我们一定要明确知道到底在哪一段消耗时间。我们来看一下发送数据的过程:
![](https://static001.geekbang.org/resource/image/bc/a0/bcae022c7205236c618db2bb213cb1a0.png)
从上图可以看出,发送数据是先放到`tcp_wmem`缓存中,然后通过`tcp_transmit_skb()`放到TX Queue中然后通过网卡的环形缓冲区发出去。而我们看到的send-Q就是Tx队列了。
查看压力端脚本,发现一个问题。
```
System.out.println(message);
```
一般情况下,我们在调试脚本的时候会打印日志,因为要看到请求和响应都是什么内容。但是压力过程中,基本上我们都会把日志关掉。**一定要记住这一点不管是什么压力工具都要在压力测试中把日志关掉不然TPS会受到很严重的影响**。
了解JMeter工具的都知道-n参数是命令行执行并且不打印详细的返回信息的。但是这里一直在打印日志并且这个日志在JMeter中执行时加了-n参数也是没用的。
这样一来,时间全耗在打印日志中了。知道这里就好办了。我们在这里做两件事:
1. 把打印日志这一行代码注释掉,再执行一遍。
2. 把ramp-up时间再增加到600秒。
为什么我要执着于把ramp-up时间不断增加在前面也有强调就是要知道TPS和响应时间曲线的趋势。
在性能分析的过程中,我发现有很多性能工程师都是看平均值、最大值、最小值等等这些数据,并且也只是描述这样的数据,对曲线的趋势一点也不敏感。这是完全错误的思路,请注意,做性能分析一定要分析曲线的趋势,通过趋势的合理性来判断下一步要做的事情。
什么叫对曲线的趋势敏感?就是要对趋势做出判断,并且要控制曲线的趋势。
有时我们经常会看到TPS特别混乱的曲线像前面发的TPS图一样抖动幅度非常大这种情况就是完全不合理的在遇到这种情况时一定要记得降低压力线程。
你可能会问,降到多少呢?这里会有一个判断的标准,**就是一直降到TPS符合我们前面看到的那个示意图为止**。
再给你一个经验,如果实在不知道降多少,就从一个线程开始递增,直到把梯度趋势展示出来。
## 第三阶段
通过注释掉打印日志的代码,可以得到如下结果:
![](https://static001.geekbang.org/resource/image/cb/24/cb9fb7877d7fbd70d2e104e3d52bef24.png)
![](https://static001.geekbang.org/resource/image/36/5c/36525754e53e007d2ce3fe5d34435c5c.png)
![](https://static001.geekbang.org/resource/image/15/2d/1551ca0a91c0a24856192d5d87ed852d.png)
TPS曲线上可以看到梯度已经明显出来了。在有一个用户的时候一秒就能达到1000多TPS并且在持续上升两个线程时达到2500以上并且也是在持续上升的。
从响应时间上来看也是符合这个趋势的前面都在1ms以下后面慢慢变长。
压力越大曲线的毛刺就会越多所以在TPS达到6000以上后后面的TPS在每增加一个线程都会出现强烈的抖动。
在这种情况下,我们再往下做,有两条路要走,当然这取决于我们的目标是什么。
1. 接着加压,看系统什么时候崩溃。做这件事情的目标是找到系统的崩溃点,在以后避免出现。
2. 将线程最大值设置为10增加ramp up的时间来看一下更明确的递增梯度同时分析在线程增加过程中系统资源分配对TPS的影响以确定线上应该做相对应的配置。
## 总结
在这个案例中我们将TPS从150多调到6000以上就因为一句日志代码。
我分析过非常多的性能案例,到最后发现,很多情况下都是由各种简单的因素导致的,这一反差也会经常让人为这一路分析的艰辛不值得。
但我要说的是,性能分析就是这样,当你不知道问题在哪里的时候,有一个思路可以引导着你走向最终的原因,那才是最重要的。
我希望通过本文可以让你领悟到,**趋势**这个词对曲线分析的重要性。在本文中,我们通过对曲线的不合理性做出判断,你需要记住以下三点:
1. 性能分析中TPS和响应时间的曲线是要有明显的合逻辑的趋势的。如果不是则要降线程增加Ramp-up来让TPS趋于平稳。
2. 我们要对曲线的趋势敏感响应时间的增加不可以过于陡峭TPS的增幅在一开始要和线程数对应。
3. 当TPS和响应时间曲线抖动过于强烈要想办法让曲线平稳下来进而分析根本原因才能给出线上的建议配置。
## 思考题
今天我结合案例具体说明了下如何分析TPS的趋势如果你吸收了文章的内容不妨思考一下这两个问题
1. Ramp-up配置有什么样的作用
2. 为什么说压力工具中TPS和响应时间曲线抖动过大会不易于分析
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起学习交流一下。