mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-19 23:53:47 +08:00
del
This commit is contained in:
269
极客时间专栏/geek/性能测试实战30讲/第四模块:性能测试分析实战篇/26丨案例:手把手带你理解TPS趋势分析.md
Normal file
269
极客时间专栏/geek/性能测试实战30讲/第四模块:性能测试分析实战篇/26丨案例:手把手带你理解TPS趋势分析.md
Normal file
@@ -0,0 +1,269 @@
|
||||
<audio id="audio" title="26丨案例:手把手带你理解TPS趋势分析" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/da/28/da8d7d970d3f51d3b0dff9762d0f2c28.mp3"></audio>
|
||||
|
||||
在性能分析中,前端的性能工具,我们只需要关注几条曲线就够了:TPS、响应时间和错误率。这是我经常强调的。
|
||||
|
||||
但是关注TPS到底应该关注什么内容,如何判断趋势,判断了趋势之后,又该如何做出调整,调整之后如何定位原因,这才是我们关注TPS的一系列动作。
|
||||
|
||||
今天,我们就通过一个实际的案例来解析什么叫TPS的趋势分析。
|
||||
|
||||
## 案例描述
|
||||
|
||||
这是一个案例,用一个2C4G的Docker容器做服务器。结构简单至极,如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/17/1d/177cd65abdaaba1e8056e676cdf96b1d.jpg" alt="">
|
||||
|
||||
当用个人电脑(上图中压力工具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. 打印返回信息。
|
||||
|
||||
本机跑出来的结果如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/68/98/6876aaa2039b95c3c45159e40d867f98.png" alt="">
|
||||
|
||||
在这个案例中,参数化数据就是根据真实的业务量来计算的,这个可以肯定没有问题。
|
||||
|
||||
那么架构呢?在最上面的图中已经有了部署的说明。在逻辑实现上,也就是一个很简单的服务端,内部并没有复杂的逻辑。所用到的监控工具是top、Vmstat。
|
||||
|
||||
看了哪些计数器呢?CPU、内存、I/O等。
|
||||
|
||||
下面我们开始分析。
|
||||
|
||||
## 第一阶段
|
||||
|
||||
对公网上的测试来说,基本上压力都会在网络上,因为出入口带宽会成为瓶颈,所以先要看一眼自己的带宽用到了多少,再比对一下出口路由上的带宽。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/2a/ff/2ae67c715480b3e8fe1486a943ce05ff.png" alt="">
|
||||
|
||||
这里1Gbps只用到了0.01%,也就是(1000/8)x0.01%=12.5k(这里是将带宽bit换成byte计算)。
|
||||
|
||||
在这样的带宽使用率之下,即使是公网也不见得会有问题,更别说在内网了。可见带宽不是瓶颈点。
|
||||
|
||||
既然这样,我们直接在内网里来做分析,看原因是什么。
|
||||
|
||||
但是我们要以什么样的场景来跑呢?因为带宽现在看到用得并不多,但TPS也上不去。**首先应该想到的场景就是把TPS**曲线给做出梯度来。
|
||||
|
||||
为什么要这么做?最重要的就是要知道到底TPS在多少压力线程下会达到最大值,也就是我在各种场合经常强调的一个场景,最大TPS场景。关于这种曲线,我们不需要性能指标应该就可以做得出来。如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a3/2f/a3dea74520a7fe3c192e6cc24c19bd2f.png" alt="">
|
||||
|
||||
在一个**既定场景、既定数据、既定环境**的压力场景中,我们一定要拿到这样趋势的TPS和RT曲线。其中绿色和红色的点都是不需要业务指标来限定的,而是通过压力场景中观察TPS趋势线来确定。
|
||||
|
||||
我来解读一下这个趋势图:
|
||||
|
||||
1. 响应时间一定是从低到高慢慢增加的;
|
||||
1. TPS一定也是从低到高慢慢增加的,并且在前面的梯度中,可以和线程数保持正比关联。举例来说,如果1个线程TPS是10,那2个线程的TPS要在20。依次类推。
|
||||
|
||||
而在这个例子中,前面有提到100线程1秒加载完,这样的比例完全看不出来梯度在哪,所以,改为100秒加载100个线程,再来看看梯度。
|
||||
|
||||
测试结果如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ee/c5/eea8cd59a06a800ad4f1757bb41ec4c5.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/07/5f/07954ac566e057efd861a15ce18cc85f.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/49/71/49ca6af1f764c2688fb62eb756847f71.png" alt="">
|
||||
|
||||
从这个结果可以看出几点:
|
||||
|
||||
1.TPS一点梯度没看出来。为什么说没有看出来呢?这里我发一个有明显梯度的TPS曲线出来以备参考(这张图不是本实例中的,只用来做分析比对):
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e8/27/e86238087c62ccfad2f48a6038892727.png" alt="">
|
||||
|
||||
2.响应时间增加的太快了,明显不符合前面我们说的那个判断逻辑。那什么才是我们判断的逻辑呢?这里我发一个有明显梯度的出来以备参考(这张图不是本实例中的,只用来做分析比对):
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4b/8e/4bbf7e0983971a3f51b9acc251f5a18e.png" alt="">
|
||||
|
||||
1. 粒度太粗,对一个duration只有五分钟的场景来说,这样的粒度完全看不出过程中产生的毛刺。
|
||||
1. 至少看到内网的TPS能到180了,但是这里没有做过其他改变,只是把Ramp-up放缓了一些,所以我觉得这个案例的信息是有问题的。
|
||||
|
||||
## 第二阶段
|
||||
|
||||
针对以上的问题,下面要怎么玩?我们列一下要做的事情。
|
||||
|
||||
1. 将Ramp-up再放缓,改为300秒。这一步是为了将梯度展示出来。
|
||||
1. 将粒度改小,JMeter默认是60秒,这里改为1秒。这一步是为了将毛刺显示出来。强调一点,如果不是调优过程,而是为了出结果报告的话,粒度可以设置大一些。至于应该设置为多大,完全取决于目标。
|
||||
|
||||
接着我们再执行一遍,看看测试结果:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/22/c7/22522e14612447ffd60a19e7f96522c7.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9a/70/9a841039650c2452551a7b313bb55f70.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9d/25/9d5a375a52dfd8efa064a70c45b9b825.png" alt="">
|
||||
|
||||
这样看下来,有点意思了哈。明显可以看到如下几个信息了。
|
||||
|
||||
1. 响应时间随线程数的增加而增加了。
|
||||
1. TPS的梯度还是没有出来。
|
||||
|
||||
显然还是没有达到我们说的梯度的样子。但是这里我们可以看到一个明显的信息,线程梯度已经算是比较缓的了,为什么响应时间还是增加得那么快?
|
||||
|
||||
这里的服务器端压力情况呢?如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fc/de/fc6041ea7780c05f35034d744c0972de.png" alt="">
|
||||
|
||||
从监控图大概看一下,服务端CPU、内存、网络几乎都没用到多少,有一种压力没有到服务端的感觉。
|
||||
|
||||
在这一步要注意,压力在哪里,一定要做出明确的判断。
|
||||
|
||||
在这里,当我们感觉服务端没有压力的时候,一定要同时查看下网络连接和吞吐量、队列、防火墙等等信息。查看队列是非常有效的判断阻塞在哪一方的方式。
|
||||
|
||||
如果服务端的send-Q积压,那就要查一下压力端了。如下所示:
|
||||
|
||||
```
|
||||
State Recv-Q Send-Q Local Address:Port Peer Address:Port
|
||||
......
|
||||
LISTEN 0 54656 :::10001 :::*
|
||||
......
|
||||
|
||||
```
|
||||
|
||||
在网络问题的判断中,我们一定要明确知道到底在哪一段消耗时间。我们来看一下发送数据的过程:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bc/a0/bcae022c7205236c618db2bb213cb1a0.png" alt="">
|
||||
|
||||
从上图可以看出,发送数据是先放到`tcp_wmem`缓存中,然后通过`tcp_transmit_skb()`放到TX Queue中,然后通过网卡的环形缓冲区发出去。而我们看到的send-Q就是Tx队列了。
|
||||
|
||||
查看压力端脚本,发现一个问题。
|
||||
|
||||
```
|
||||
System.out.println(message);
|
||||
|
||||
```
|
||||
|
||||
一般情况下,我们在调试脚本的时候会打印日志,因为要看到请求和响应都是什么内容。但是压力过程中,基本上我们都会把日志关掉。**一定要记住这一点,不管是什么压力工具,都要在压力测试中把日志关掉,不然TPS会受到很严重的影响**。
|
||||
|
||||
了解JMeter工具的都知道-n参数是命令行执行,并且不打印详细的返回信息的。但是这里,一直在打印日志,并且这个日志在JMeter中执行时加了-n参数也是没用的。
|
||||
|
||||
这样一来,时间全耗在打印日志中了。知道这里就好办了。我们在这里做两件事:
|
||||
|
||||
1. 把打印日志这一行代码注释掉,再执行一遍。
|
||||
1. 把ramp-up时间再增加到600秒。
|
||||
|
||||
为什么我要执着于把ramp-up时间不断增加?在前面也有强调,就是要知道TPS和响应时间曲线的趋势。
|
||||
|
||||
在性能分析的过程中,我发现有很多性能工程师都是看平均值、最大值、最小值等等这些数据,并且也只是描述这样的数据,对曲线的趋势一点也不敏感。这是完全错误的思路,请注意,做性能分析一定要分析曲线的趋势,通过趋势的合理性来判断下一步要做的事情。
|
||||
|
||||
什么叫对曲线的趋势敏感?就是要对趋势做出判断,并且要控制曲线的趋势。
|
||||
|
||||
有时,我们经常会看到TPS特别混乱的曲线,像前面发的TPS图一样,抖动幅度非常大,这种情况就是完全不合理的,在遇到这种情况时,一定要记得降低压力线程。
|
||||
|
||||
你可能会问,降到多少呢?这里会有一个判断的标准,**就是一直降到TPS符合我们前面看到的那个示意图为止**。
|
||||
|
||||
再给你一个经验,如果实在不知道降多少,就从一个线程开始递增,直到把梯度趋势展示出来。
|
||||
|
||||
## 第三阶段
|
||||
|
||||
通过注释掉打印日志的代码,可以得到如下结果:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/cb/24/cb9fb7877d7fbd70d2e104e3d52bef24.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/36/5c/36525754e53e007d2ce3fe5d34435c5c.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/15/2d/1551ca0a91c0a24856192d5d87ed852d.png" alt="">
|
||||
|
||||
从TPS曲线上可以看到,梯度已经明显出来了。在有一个用户的时候,一秒就能达到1000多TPS,并且在持续上升;两个线程时达到2500以上,并且也是在持续上升的。
|
||||
|
||||
从响应时间上来看,也是符合这个趋势的,前面都在1ms以下,后面慢慢变长。
|
||||
|
||||
压力越大,曲线的毛刺就会越多,所以在TPS达到6000以上后,后面的TPS在每增加一个线程,都会出现强烈的抖动。
|
||||
|
||||
在这种情况下,我们再往下做,有两条路要走,当然这取决于我们的目标是什么。
|
||||
|
||||
1. 接着加压,看系统什么时候崩溃。做这件事情的目标是找到系统的崩溃点,在以后避免出现。
|
||||
1. 将线程最大值设置为10,增加ramp up的时间,来看一下更明确的递增梯度,同时分析在线程增加过程中,系统资源分配对TPS的影响,以确定线上应该做相对应的配置。
|
||||
|
||||
## 总结
|
||||
|
||||
在这个案例中,我们将TPS从150多调到6000以上,就因为一句日志代码。
|
||||
|
||||
我分析过非常多的性能案例,到最后发现,很多情况下都是由各种简单的因素导致的,这一反差也会经常让人为这一路分析的艰辛不值得。
|
||||
|
||||
但我要说的是,性能分析就是这样,当你不知道问题在哪里的时候,有一个思路可以引导着你走向最终的原因,那才是最重要的。
|
||||
|
||||
我希望通过本文可以让你领悟到,**趋势**这个词对曲线分析的重要性。在本文中,我们通过对曲线的不合理性做出判断,你需要记住以下三点:
|
||||
|
||||
1. 性能分析中,TPS和响应时间的曲线是要有明显的合逻辑的趋势的。如果不是,则要降线程,增加Ramp-up来让TPS趋于平稳。
|
||||
1. 我们要对曲线的趋势敏感,响应时间的增加不可以过于陡峭,TPS的增幅在一开始要和线程数对应。
|
||||
1. 当TPS和响应时间曲线抖动过于强烈,要想办法让曲线平稳下来,进而分析根本原因,才能给出线上的建议配置。
|
||||
|
||||
## 思考题
|
||||
|
||||
今天我结合案例具体说明了下如何分析TPS的趋势,如果你吸收了文章的内容,不妨思考一下这两个问题?
|
||||
|
||||
1. Ramp-up配置有什么样的作用?
|
||||
1. 为什么说压力工具中TPS和响应时间曲线抖动过大会不易于分析?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起学习交流一下。
|
||||
190
极客时间专栏/geek/性能测试实战30讲/第四模块:性能测试分析实战篇/27丨案例:带宽消耗以及Swap(上).md
Normal file
190
极客时间专栏/geek/性能测试实战30讲/第四模块:性能测试分析实战篇/27丨案例:带宽消耗以及Swap(上).md
Normal file
@@ -0,0 +1,190 @@
|
||||
<audio id="audio" title="27丨案例:带宽消耗以及Swap(上)" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/0d/72/0d75a3c492715ca01fadc46878d23d72.mp3"></audio>
|
||||
|
||||
今天我们来看一个真实的案例。事情是这样的,之前有人在微信上问我一个问题,这个问题的现象很典型:典型的TPS上不去,响应时间增加,资源用不上。
|
||||
|
||||
大概的情况是这样的:有两台4C8G的服务器,一台服务器上有2个Tomcat,一台服务器上是DB。压测的混合场景有4个功能模块,其中3个访问一个Tomcat,另外一个访问一个Tomcat。
|
||||
|
||||
Tomcat的监控页面如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/53/0b/532bbd1525be0ad0d08da7335645260b.png" alt="">
|
||||
|
||||
应用服务器系统资源监控页面如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/19/e5/1944bc692902fed979815a538d879be5.png" alt="">
|
||||
|
||||
数据库服务器系统资源监控如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/50/f0/50a996ac196dbfd2b9a23858e87dc8f0.png" alt="">
|
||||
|
||||
JMeter结果如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f8/33/f8b228a61b980c338046fbb3c8875033.png" alt="">
|
||||
|
||||
综上现象就是,单业务场景执行起来并不慢,但是一混合起来就很慢,应用服务器和数据库服务器的系统资源使用率并不高。请问慢在哪?
|
||||
|
||||
这是非常典型的询问性能问题的方式,虽然多给了系统资源信息,但是这些信息也不足以说明瓶颈在哪。
|
||||
|
||||
为什么呢?在现在多如牛毛的监控工具中,除非我们在系统中提前做好分析算法或警告,否则不会有监控工具主动告诉你, 监控出的某个数据有问题,而这个只能靠做性能分析的人来判断。
|
||||
|
||||
我在很多场合听一些“专家”说:性能分析判断要遵守木桶原理。但是在做具体技术分析的时候,又不给人说明白木桶的短板在哪里。这就好像,一个赛车手说要是有一个各方面都好的车,我肯定能得第一名,但是,我没有车。
|
||||
|
||||
话说出来轻而易举,但是请问木桶的短板怎么判断呢?因为CPU高,所以CPU就是短板吗?所以就要加CPU吗?这肯定是不对的。
|
||||
|
||||
因为这个例子并不大,所以可以细细地写下去。今天文章的目的就是要告诉你,性能问题分析到底应该是个什么分析思路。
|
||||
|
||||
## 分析的第一阶段
|
||||
|
||||
### 画架构图
|
||||
|
||||
做性能测试时,我们需要先画一个架构图,虽然简单,但是让自己脑子里时时记得架构图,是非常有必要的。因为架构级的分析,就是要胸怀架构,在看到一个问题点时,可以从架构图中立即反应出来问题的相关性。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fe/2c/fe8abb747bdeebfefd833ae8a5f4e12c.jpg" alt="">
|
||||
|
||||
上面这张图是自己脑子里的逻辑图,数据在网络中的流转并不是这样,而是像下图这样。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/72/8c/72fdc9310d5678fc1988f96f3026b28c.jpg" alt="">
|
||||
|
||||
数据流是从压力机到应用服务器,应用服务器再到网络设备,再到数据库服务器;数据库把数据返回给应用服务器,应用服务器再通过网络设备给压力机。
|
||||
|
||||
如果把里面的Redis、ActiveMQ和MySQL的逻辑再细说明白,那么这个小小的应用都可以描述好一会。所以这里,我先大概描述一下,如果后面的分析中涉及到了相应的逻辑,再一点点加进来。
|
||||
|
||||
应用服务器只有一台,上面有两个Tomcat实例;数据库服务器有三个应用。混合场景中有四个业务,其中三个访问Tomcat1,第四个访问Tomcat2。
|
||||
|
||||
### 场景描述
|
||||
|
||||
有了场景大概的画像之后,我们再来看场景。根据测试工程师描述:
|
||||
|
||||
1. 响应时间慢的,都是可视化页面,有不少图片、JS、CSS等静态资源。公网上是用CDN的,现在只测试内网的部分。静态资源已经做了压缩。
|
||||
1. 单业务测试的容量是可以满足要求的,但混合场景响应时间就长。系统资源用得并不多。
|
||||
1. 压力场景是300线程,Ramp-up period是1秒。
|
||||
1. Duration是72000。
|
||||
1. 各参数化都已经做了,参数化数据也合理。
|
||||
1. 测试环境都是内网。
|
||||
1. 服务器是CentOS,压力机是Win10。
|
||||
|
||||
既然这样,我们还是要看看系统的各个资源,再来判断方向。我在很多场合都强调证据链。对架构比较简单的应用来说,我们都不用再做时间的拆分了,直接到各主机上看看资源就好了。
|
||||
|
||||
### 瓶颈分析定位
|
||||
|
||||
根据我们之前画的架构图,我们从应用服务器、数据库服务器和压力数据分别定位一下。
|
||||
|
||||
- 应用服务器
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5f/b0/5f3f4fc313c833ff2ad979d63de7f9b0.png" alt="">
|
||||
|
||||
从前面的应用服务器资源来看,CPU使用率现在还不高,也基本都在us CPU(就是user消耗的CPU)上,比例也比较合理。
|
||||
|
||||
物理内存8G,还有3.5G。即使不多,对Java应用来说,也要先看JVM,只要不是page fault过多,我们可以先不用管物理内存。
|
||||
|
||||
网络资源接收900KB/s左右,发送11M左右。这样的带宽应该说是比较敏感的,因为对100Mbps和1000Mbps来说,我们要心里有一个数,一个是12.5MB(对应100Mbps),一个是125MB(对应1000Mbps),当和这样的带宽值接近的时候,就要考虑下是不是带宽导致的压力上不去。不过这里我们也不用先下定论。只是留个疑问在这里。
|
||||
|
||||
磁盘资源,基本上没有读写,很正常。
|
||||
|
||||
从Process列表中,也没看到什么异常数据。faults也都是min,major fault并没有。但在这个案例中,还部署了另一个监控工具,上面显示如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9e/b9/9e9bc7528dac06fcbef811eed0ae8bb9.png" alt="">
|
||||
|
||||
为什么这个Swapping要标黄呢?那肯定是过大了嘛。是的,你可以觉得swap过多。这个扣,我们也记在心里。在这里标红加粗敲黑板!
|
||||
|
||||
- 数据库服务器
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/23/14/23b3ce5e2d551a54c59ef24c3601ad14.png" alt="">
|
||||
|
||||
照样分析,CPU、内存、网络、磁盘、Process列表,并没看到哪里有异常的数据,连网络都只有500多k的发送。
|
||||
|
||||
这样的数据库资源状态告诉我们,它不在我们的问题分析主线上。接下来是压力数据。
|
||||
|
||||
- 压力数据
|
||||
|
||||
这是JMeter中的聚合报告:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ee/98/eecc1affbdeca2484d2964a93d75fe98.png" alt="">
|
||||
|
||||
从上面这张图也能看出,响应时间确实挺长的,并且,300线程只有37的TPS,带宽总量10M左右。这个带宽倒是和应用服务器上的带宽使用量相当。关于这个带宽的判断请你一定注意,对于性能分析来说,带宽能不能对得上非常重要。比如,客户端接收了多少流量,服务端就应该是发出了多少流量。如果服务端发了很多包,但是客户端没有接收,那就是堵在队列上了。
|
||||
|
||||
既然其它的资源暂时没出现什么瓶颈。其实在这个时间里,如果是复杂的应用的话,我们最应该干的一件事情就是拆分时间。如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e7/41/e77401663a74e38dacf6cd05f3c96f41.jpg" alt="">
|
||||
|
||||
这里我把时间拆为t1-t5,具体分析哪一段为什么消耗了时间。我们可以在Tomcat中加上%D和%F两个参数来记录request和response的时间。
|
||||
|
||||
在没有做这个动作之前,我们先把前面的扣解决一下。首先,带宽是不是受了100Mbps的限制?
|
||||
|
||||
一般来说,判断网络的时候,我们会有几个判断点。
|
||||
|
||||
首先是带宽的流量大小,也就是前面我们看到的11M左右的值。一般来说,100Mbps是指的bit per second,但是在应用层基本上都是byte,所以对100Mbps来说,是12.5MB。
|
||||
|
||||
其次是,全连接和半连接队列是否已经溢出?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c7/07/c7a67037f03d82378eec8b6c70c32207.png" alt="">
|
||||
|
||||
我们通过SYNs to LISTEN sockets dropped来判断半连接队列是否溢出,通过times the listen queue of a socket overflowed来判断全连接队列是否溢出。
|
||||
|
||||
通过实时的查看,这两个值的增加并不多。所以这里不会是问题点。
|
||||
|
||||
最后是发送和接收队列是否堆积?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fe/72/fe954bf48b860464923418ca90800572.png" alt="">
|
||||
|
||||
通过应用服务器上的send-Q(前面数第三列),可以看到服务器和压力机之间的的队列还是很长的,基本上每次查看都存在,这说明队列一直都有堆积。
|
||||
|
||||
我们再到压力机上看看带宽用了多少:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f2/4d/f2a3e24763892f2eab807adc6934ac4d.png" alt="">
|
||||
|
||||
看这里也是用到了93Mbps,那么到这里我们就可以确定是网络问题导致的TPS上不去,响应时间增加,系统资源也用不上了。
|
||||
|
||||
和系统管理员确认宿主机的带宽后,被告知宿主机确实是100Mbps。
|
||||
|
||||
似乎这个分析到这里就可以结束了,直接把带宽加上再接着测试呗。但是,从项目实施的角度上说,这个问题,并不是阻塞性的。
|
||||
|
||||
为了把更多的性能问题提前找出来,现在我们先不下载静态资源,只发接口请求找下其他性能问题。这个带宽的问题,记一个bug就行了。
|
||||
|
||||
### 优化结果
|
||||
|
||||
我们将静态资源全都过滤掉之后,再次执行场景,结果是下面这样的。
|
||||
|
||||
JMeter压力数据:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/77/aa/771ee29f154f43bc84c8e8df66431caa.png" alt="">
|
||||
|
||||
应用服务器带宽:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ed/b6/ed896cecbdfbb6c9cc4beba10f5744b6.png" alt="">
|
||||
|
||||
数据库服务器带宽:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/74/b8/74a2516200a297a062730341eacf60b8.png" alt="">
|
||||
|
||||
应用服务器网络队列:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/75/73/750402d3482cba2c6e77cd9043616373.png" alt="">
|
||||
|
||||
应用服务器资源监控:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/75/7c/75423cd11a411b3847252d479554a97c.png" alt="">
|
||||
|
||||
通过上面的结果可以看出:
|
||||
|
||||
1. TPS可以达到221.5了,并且Received和Sent的字节加一起不到4MB。
|
||||
1. 应用服务器和数据库服务器的带宽都用到了近40Mbps,和JMeter结果也相当。
|
||||
1. 应用服务器上的网络队列也没有堆积。
|
||||
1. 应用服务器的CPU也已经能消耗到66%了,
|
||||
|
||||
当正在想通过过滤掉静态资源绕过带宽不足的现状来测试其他性能问题的时候,这时,Swap双向都标黄了。这时,性能测试工程师更纠结了,它为什么双向都黄了?CPU使用率才66%嘛。
|
||||
|
||||
其实,这两句话之间并没有什么关系,CPU使用率不管是多少,Swap该黄还是会黄。
|
||||
|
||||
这是为什么呢?这里卖个关子,在下一篇文章中,我们接着分析。
|
||||
|
||||
## 总结
|
||||
|
||||
带宽问题是性能分析中常见的问题之一,其难点就在于,带宽不像CPU使用率那么清晰可理解,它和TCP/IP协议的很多细节有关,像三次握手,四次挥手,队列长度,网络抖动、丢包、延时等等,都会影响性能,关键是这些判断点还不在同一个界面中,所以需要做分析的人有非常明确的分析思路才可以做得到。而且现在的监控分析工具中,对网络的判断也是非常薄弱的。
|
||||
|
||||
而Swap问题不能算是常见,只要出现,基本上就会很多人晕乎。解决的关键就是要明白Swap的原理,查到关联参数,然后就可以很快地定位了。
|
||||
|
||||
## 思考题
|
||||
|
||||
结合今天的内容,你能说一下网络的瓶颈如何判断吗?有哪几个队列?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。
|
||||
344
极客时间专栏/geek/性能测试实战30讲/第四模块:性能测试分析实战篇/28丨案例:带宽消耗以及Swap(下).md
Normal file
344
极客时间专栏/geek/性能测试实战30讲/第四模块:性能测试分析实战篇/28丨案例:带宽消耗以及Swap(下).md
Normal file
@@ -0,0 +1,344 @@
|
||||
<audio id="audio" title="28丨案例:带宽消耗以及Swap(下)" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/16/c5/162726ea16ca9c9d5d4d66e9ebccb4c5.mp3"></audio>
|
||||
|
||||
上一篇文章我主要分析了带宽消耗,今天,我们来看一下分析的第二和第三阶段,也就是Swap分析和数据库分析。
|
||||
|
||||
## 分析的第二阶段
|
||||
|
||||
### Swap的原理和对TPS的影响
|
||||
|
||||
前面有一个扣,是说swap多的问题。要理解swap为什么是黄的,得先知道什么是swap。我先画个简易的示意图。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1f/e5/1fd061cbf986f9cea3509bd4699ddbe5.jpg" alt="">
|
||||
|
||||
这里先解释一下,对于一个Linux系统来说,如果配置并开启了swap分区,那么默认的swappiness参数是60。
|
||||
|
||||
swappiness是在内存reclaim的时候生效的,而reclaim方式同时有两个动作:1. 将file相关内存进行回收;2. 将anon内存交换到swap分区。
|
||||
|
||||
所以swapiness值越大,swap分区就用得越多。
|
||||
|
||||
对我们现在分析的这个系统来说,来看一下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e2/7f/e29ec84d980fb9e667e41010b209427f.png" alt="">
|
||||
|
||||
我们看到这里配置了一个内存为8G左右,已经使用了7G多了,swappiness配置为30%。
|
||||
|
||||
通过free看到现在只有145M的物理内存剩余,可用内存也只有254M了。<br>
|
||||
所以上面图中的swap飘黄也是很合理的喽!
|
||||
|
||||
下面我们就针对应用服务器的swap来看是不是可优化。
|
||||
|
||||
所有人都知道,当swap被用的时候,性能肯定会下降,所以在我的测试过程中,一般我都建议把swap直接关掉测试性能,有人说这样有什么问题?
|
||||
|
||||
那就是没有swap,让不常用的对象直接占用物理内存,如果物理内存不够用,就把对象删了,后面再创建,这时会增加的是major fault,那就增加好了,反正是要性能差的。
|
||||
|
||||
说得如此硬气,那在生产中怎么办呢?开还是关?有人觉得关了心里有安稳,有人觉得开着心里会安稳。而一个系统、一个容器、一个节点,如果容量控制的非常好的情况下,我建议关掉。开着它,也只是心里上的安慰,不会有TPS处理能力上的提升。
|
||||
|
||||
### 瓶颈分析定位
|
||||
|
||||
既然知道了上面的大概原理。对一个运行Tomcat应用的服务器来说,那肯定是要先检查一下JVM设置为多大。先执行ps命令,看一下Java进程吧。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8d/ba/8d6525787d1234c738f629389b5da5ba.png" alt="">
|
||||
|
||||
关键参数如下:
|
||||
|
||||
```
|
||||
JAVA_OPTS="$JAVA_OPTS -server -Xms2048M -Xmx8192m -XX:PermSize=256M -XX:MaxNewSize=2790m -XX:MaxPermSize=512m -XX:SurvivorRatio=8"
|
||||
|
||||
```
|
||||
|
||||
JVM是1.8.0_65。
|
||||
|
||||
这个参数配置有很大的问题。物理内存只有8个G,一个JVM heap就配置了8G,这让其他的东西怎么玩得起来?并且JDK是1.8了,配置permsize是又为啥呢?
|
||||
|
||||
虽说有多个地方配置不合理,但是我们也得要知道一下应该配置多少是合理的吧。
|
||||
|
||||
看参数的时候,JMX也配置上了,那就用工具来看吧。
|
||||
|
||||
首先来看一下系统资源。先看一下系统资源在压力下的表现:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ef/66/ef8154abc5d60e04752a5d9ea29d4966.png" alt="">
|
||||
|
||||
1. 队列已经出现,CS 2万多,in 2万多,说多不多。我们可以先放着。
|
||||
1. I/O没什么压力,swap也一直有值,我们要解决的就是它。
|
||||
1. us:sy接近2:1,这个是不良信号,记在心里,后面再说。
|
||||
|
||||
其次再看下JVM的情况:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d8/f1/d84de678e28c64b46b1b69b8b30b77f1.png" alt="">
|
||||
|
||||
CPU使用在应用上的时间达到60%,GC上没耗什么时间,并且从堆的回收能力上来看,比较正常,只是只用到了3G左右,这里有必要给8G吗?
|
||||
|
||||
线程活动的达到347,看起来还是在增加的,这里也可能是个问题点,只是现在我们不用关心,它还没跳出来。
|
||||
|
||||
从这个JVM状态上来看,它完全用不到8G。在这种状态下,还有另一个Tomat,并且另一个Tomcat中也没有配置-Xmx -Xms参数,当没有配置时,默认-Xmx是物理内存的1/4。再加上thread用的,所以swap飘黄也是吻合的。
|
||||
|
||||
### 优化结果
|
||||
|
||||
首先,我们把JVM配置成最简,JVM设置为4G。
|
||||
|
||||
```
|
||||
JAVA_OPTS="$JAVA_OPTS -server -Xms4096M -Xmx4096m"
|
||||
|
||||
```
|
||||
|
||||
perm区在1.8里都没有了,这几个参数也没啥用。在我的习惯中,MaxNewSize也是先看要用到多少,再决定配置不配置。有些应用自己不熟悉,也无法直接给出配置,只有测试之后再配置。
|
||||
|
||||
各部分配置为多大,都没有定数,要通过测试看需要多少。
|
||||
|
||||
而我们现在最重要的是先把性能调整上去,再考虑这些细节内容。这样修改JVM就是为了把物理内存使用率低下来,先不修改swapiness的比例是为了看下结果,如果用不到swap就不再调了,如果还是用了swap,再来调它。
|
||||
|
||||
当我们把JVM修改了之后,再执行起来场景。看到内容如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/40/44/40116bf5ff367647f3f41143fd47fd44.png" alt="">
|
||||
|
||||
CPU使用率相对前面没有什么变化,但是堆4G只用到了1.5G,可见这个堆连4G都用不到。当然我们还是要分析下其他的内容。
|
||||
|
||||
还记得我们要解决的是什么问题吧?swap飘黄了!
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d8/1f/d8ec1c105537e046a98440790462f91f.png" alt="">
|
||||
|
||||
从这张图可以看到Swapping不报警了!CPU占用70%左右。说明现在available的内存是充足的。
|
||||
|
||||
这时我们再看一下系统资源,首先是应用服务器系统资源。
|
||||
|
||||
应用服务器系统资源vmstat如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/10/67/1002a324619b0fe3ba5d6a0d7af53e67.png" alt="">
|
||||
|
||||
应用服务器系统资源top:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/61/41/61b13011e46b803fef61dde5030e4541.png" alt="">
|
||||
|
||||
应用服务器系统资源iftop:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/41/de/4109d34df75fed3d71cdc842d516d9de.png" alt="">
|
||||
|
||||
上图中可以看到,对比之前的资源,swap基本上没有了,CPU使用率多起来了。但是队列依旧长,sy CPU消耗还是有点多了。
|
||||
|
||||
应用服务器的si已经到了13.1%了,这个值要关注下,暂时还不能说是问题,但是接着增加下去,肯定会是问题。
|
||||
|
||||
网络已经超过70Mbps了,峰值上到87Mbps,这是一个好事,它说明现在处理的业务量确定多了。
|
||||
|
||||
接下来是数据库服务器系统资源:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/85/27/851e322ad9561715a30fc1ce79052027.png" alt="">
|
||||
|
||||
你可以看到数据库CPU都用到这么高了?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f3/2b/f36982dff7c811cbe9d5ca8d9cf0862b.png" alt="">
|
||||
|
||||
TPS能到259.2了,较之前的221.5没有提升多少。但是我们解决了swap的问题,还是有了一点点的提升。
|
||||
|
||||
那下一个瓶颈在哪里呢?通过上面的数据库资源来看,数据库早就已经被用到了100%的CPU,队列也嗖嗖地涨到了好几十,高的都超过100了。
|
||||
|
||||
可见我们在处理应用服务器的时候,数据库这边已经早就吃不消了。那下面,我们就先把应用服务器的优化部分放一下,再去分析下一个短板:数据库。
|
||||
|
||||
### 后续性能工作建议
|
||||
|
||||
但是这里并不是说应用服务器的优化工作就完成了,还有一些部分需要做的。
|
||||
|
||||
1. 优化JVM配置参数,至于应该配置成什么值,还需要再测试,可能会有人说,这个测试人员怎么知道呢?请你相信,如果这个值性能测试人员都测试不出来的话,一般的架构师也不可能知道该设置为多少。
|
||||
1. 通过监控分析确定swapiness的值。
|
||||
1. 网络带宽又快到占满了,如果TPS再提高,网络肯定又支撑不了。
|
||||
|
||||
这些扣也都放在这里。因为我们主要是找到系统的短板,并一一解决,才能使整体的TPS增加,虽说现在应用服务器上还有优化的空间,但是现在它不是最短的板。
|
||||
|
||||
我们在不忘记应用服务器这些问题的同时,再将目光转向数据库。
|
||||
|
||||
## 分析的第三阶段
|
||||
|
||||
### 瓶颈分析定位
|
||||
|
||||
先来看看数据库的系统资源。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/dc/c4/dc2569aa95f7eab54930c479ee8b04c4.png" alt="">
|
||||
|
||||
我在很多场合都在强调一个词:证据链。所以基本上分析也会是从OS层面开始。
|
||||
|
||||
但是证据链这个词说起来容易理解,实际上要想真的有链起来的能力,必须具有基础知识,像分析数据库就更明显。因为当我们不了解系统架构时,想说明一个事情就非常困难。
|
||||
|
||||
像上面的这个top,显然us CPU使用率非常地高,idle几乎没有了,只有一个si占了5.7%,这个si并不算高,我们在上一阶段看到的应用服务器的si都已经达到了13%了。
|
||||
|
||||
我们说si的高或者低,倒不是关键,关键的是它有没有成为我们的瓶颈点。在这个系统中,us cpu才是我们要关注的重点,因为它实在是太高了。
|
||||
|
||||
对于一个数据库来说,要干的事情就是执行SQL。当分析多了数据库之后,基本上也形成了套路。不管怎么样,还是先看一下基本的监控信息,以下截取一些Spotlight on MySQL的有用的图,如果你没有这个工具,用其他的监控工具也是一样的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bd/09/bd2d900a6d737649dcccf53c81584409.png" alt="">
|
||||
|
||||
从上面的图可以看到,CPU使用率99%,Query Cache 是OFF的。记下这个位置!
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c2/1d/c2c538d0c263c6e87d77b3b78402151d.png" alt="">
|
||||
|
||||
从上图看到,负载队列非常长,但Disk I/O没多少,说明队列和I/O无关,只是CPU的队列,非常好!
|
||||
|
||||
Network也不算大,进出每秒5000多个包,我们再来看一下网络用到多少了?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3e/c7/3eb362a32b7bcaabce509dbd790286c7.png" alt="">
|
||||
|
||||
峰值也才70Mbps左右,即使是100Mbps带宽,现在仍然认为有余量(注意!我这里说有余量是因为我同时也检查了网络队列,并没有阻塞,并不是只看了这个值就武断地做了判断)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/cf/63/cf347ab9406ffcb3a281f1ef4a646263.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/23/ec/23e8f83c88e76c1c44badabaea44deec.png" alt="">
|
||||
|
||||
通过上面的图可以看到,每秒执行2500-3000的SQL,Sorts per second达到800-1000,Sort rows per second达到8000-10000。
|
||||
|
||||
session用得倒是也不多,但Miss Rates在压力过程中Query Cache都是在100%,并且从最上面的summary中可以看到Query Cache也是OFF的。
|
||||
|
||||
为什么没有在看到Query Cache是OFF的时候就敲黑板呢,这是因为在一些应用中,如果不是查询多的话,这个值OFF也不能说有问题,但是在这个应用中几乎所有的语句都是select,那这个Query Cache再不打开就说不过去了呀。这里先记录下这个问题,待会我们的优化动作就是打开Query Cache。
|
||||
|
||||
不管怎么说,对一个数据库来说,主要是执行SQL嘛,而对MySQL来说,不看slow log,还能看什么呢。
|
||||
|
||||
通过整理slow log,看到如下内容:
|
||||
|
||||
```
|
||||
# Overall: 280 total, 1 unique, 0.59 QPS, 9.53x concurrency ______________
|
||||
# Time range: 2019-09-26T13:44:08 to 2019-09-26T13:52:06
|
||||
# Attribute total min max avg 95% stddev median
|
||||
# ============ ======= ======= ======= ======= ======= ======= =======
|
||||
# Exec time 4555s 12s 19s 16s 18s 2s 16s
|
||||
# Lock time 52ms 130us 662us 185us 273us 53us 167us
|
||||
# Rows sent 0 0 0 0 0 0 0
|
||||
# Rows examine 30.81M 112.69k 112.69k 112.69k 112.69k 0 112.69k
|
||||
# Query size 186.48k 682 682 682 682 0 682
|
||||
|
||||
|
||||
# Profile
|
||||
# Rank Query ID Response time Calls R/Call V/M Item
|
||||
# ==== ================== ================ ===== ======= ===== ===========
|
||||
# 1 0xBED932B8C940697E 4555.0867 100.0% 280 16.2682 0.16 SELECT test2
|
||||
|
||||
```
|
||||
|
||||
什么情况?只有1 unique?0.59TPS?我前面的TPS可是有259.2,这结果一看就感觉不对。
|
||||
|
||||
查看一下long_query_time,配置成了10s,怪不得看不到慢SQL。
|
||||
|
||||
改long_query_time为1s,再跑一遍。看到如下结果:
|
||||
|
||||
```
|
||||
# Overall: 620.47k total, 30 unique, 259.39 QPS, 16.76x concurrency ______
|
||||
# Time range: 2019-09-26T13:44:08 to 2019-09-26T14:24:00
|
||||
# Attribute total min max avg 95% stddev median
|
||||
# ============ ======= ======= ======= ======= ======= ======= =======
|
||||
# Exec time 40082s 88us 23s 65ms 323ms 649ms 332us
|
||||
# Lock time 61s 30us 363ms 97us 152us 1ms 69us
|
||||
# Rows sent 1.92M 0 633 3.25 21.45 7.43 0.99
|
||||
# Rows examine 987.99M 0 112.69k 1.63k 10.29k 5.49k 420.77
|
||||
# Query size 240.33M 89 1.52k 406.15 833.10 258.19 246.02
|
||||
|
||||
|
||||
# Profile
|
||||
# Rank Query ID Response time Calls R/Call V/M Item
|
||||
# ==== ================== ================ ====== ======= ===== ==========
|
||||
# 1 0x160FA75270C56FB8 22892.5442 57.1% 65352 0.3503 0.16 SELECT test1
|
||||
# 2 0xBED932B8C940697E 15914.4501 39.7% 986 16.1404 0.44 SELECT test2
|
||||
# 4 0xF0AE7AFA7851C7E8 245.0176 0.6% 175 1.4001 0.11 SELECT test3
|
||||
# 5 0xFB5A64603A53BFCE 97.2016 0.2% 77 1.2624 0.04 SELECT test4
|
||||
# 14 0x1E088E88CDC208BE 8.7701 0.0% 14 0.6264 0.23 SELECT test5
|
||||
|
||||
```
|
||||
|
||||
嗯,这看着顺眼多了。前两个SQL占了所有执行时间的96.8%!第一个SQL平均执行时间350ms,方差16%。而第二个语句更夸张,平均执行时间16s,方差44%。这得收拾!
|
||||
|
||||
但是要不要优化这样的SQL,我们就需要根据SQL的分析和业务的分析来判断了。这里我先把执行计划列出来看看。
|
||||
|
||||
SQL1的执行计划:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/77/69/7746e95a65b8ca2537672ccd5a474c69.png" alt="">
|
||||
|
||||
1. 没有分区。
|
||||
1. 不包含子查询或者union操作。
|
||||
1. 全表扫描。
|
||||
1. 第一个表所查有70行,第二个表所查有631行,此值仅做为参考,并不精准。
|
||||
1. 第一个表返回结果只占了读取行数的1.43%(优化点),第二个表返回结果只占了0.16%(优化点)。
|
||||
1. 在第一个表中,Extra有一个值,using where。
|
||||
1. 在第二个表中,Extra有一个值,Range checked for each record (index map: 0x1) 。
|
||||
|
||||
SQL2的执行计划:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ec/c4/ecbcccf81290dc50cb6192782500cfc4.png" alt="">
|
||||
|
||||
1. 没有分区。
|
||||
1. 不包含子查询或者union操作。
|
||||
1. 非唯一索引查找,也列出了具体的索引。
|
||||
1. 第一个表索引列上有102570行,第二个表索引列上有118行。此值仅做为参考,并不精准。
|
||||
1. 第一个表返回结果只占了读取行数的3.33%(优化点),第二个表返回结果占了100%。
|
||||
1. 在第一个表中,Extra有三个值,using index condition; using where; using filesort。
|
||||
1. 在第二个表中,Extra有一个值,using where。
|
||||
|
||||
这里我要敲黑板了!!!你是不是不记得Extra这些值的含义了?是不是要祭出你的搜索引擎,要开始查了!
|
||||
|
||||
我们这里再来回顾一遍。
|
||||
|
||||
using where:对结果用where子句中的条件过滤。
|
||||
|
||||
Range checked for each record (index map: 0x1):MySQL没有找到可以使用的索引,如果前面的表的列值已知,可能会部分使用索引。
|
||||
|
||||
using index condition:先条件过滤索引,找到所有符合索引条件的数据行,再用where子句中的条件做过滤。
|
||||
|
||||
using filesort:Query中有Order By操作,又无法用索引完成排序,MySQL不得不选择相应的排序算法来实现。是不是对应上了前面的sorts per second?
|
||||
|
||||
知道了这些基础知识之后,下面再来看一下,两句语句很显然都有优化的空间,尽量使用filtered的比例能大一些,至于能不能用到索引,那就看业务的需要了,如果确实是要查很大的索引,表扫还能快点。所以这两个语句,要丢给开发做业务分析了。
|
||||
|
||||
### 优化结果
|
||||
|
||||
对数据库,我们有两个优化的方向还记得吧,第一个是SQL语句,第二个是 Query Cache。
|
||||
|
||||
我们先做第2个,将Query Cache开启,看一下效果如何。
|
||||
|
||||
```
|
||||
mysql> show variables like 'query_cache%';
|
||||
|
||||
```
|
||||
|
||||
查看结果如下:
|
||||
|
||||
```
|
||||
query_cache_type ON
|
||||
query_cache_size 1048576
|
||||
|
||||
```
|
||||
|
||||
再执行起来场景,看系统资源:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a5/b7/a5fb75d8b6af58760d2eb7f9976df4b7.png" alt="">
|
||||
|
||||
效果还不错哦,us CPU降到了50%以下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/81/e9/81652d6852324114300d4b36355f31e9.png" alt="">
|
||||
|
||||
网络峰值时能达到90Mbps了,又快把带宽占完了。
|
||||
|
||||
查看网络队列:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/29/8c/297e4d951b9b1aa3e439137aab87598c.png" alt="">
|
||||
|
||||
再检查下队列,这时看到已经有接收队列了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4a/09/4a813c91a6a79d3ff359666c581f3c09.png" alt="">
|
||||
|
||||
从TPS上来看,现在能到300多一点,同时网络接收发送加在一起8M左右。
|
||||
|
||||
### 后续性能工作建议
|
||||
|
||||
接下来数据库的优化方向就是优化SQL。
|
||||
|
||||
当然还有别的优化建议,我们将在后面再说。
|
||||
|
||||
## 总结
|
||||
|
||||
这个案例从一个概括的描述开始,到各阶段的分析定位,是一个非常完整的过程。从一个项目的角度上来说,现在是不是性能已经达标,要有两方面的判断。
|
||||
|
||||
1. 技术方面来说,显然这系统还有很多优化的空间,我们在文中也留了不少的扣。
|
||||
1. 业务方面来说,系统是否可以上线,就取决于业务指标了。
|
||||
|
||||
但是这个性能是不是已经做得完整了呢?显然还没有。现在只是调了一个节点而已。因为这是在测试环境中做的,硬件环境显得非常简单。线上部署结构也会包括分布式多节点集群等。所以从一个性能项目的角度来说,还远远没有结束。我想如果把这个项目完整地写下来,一本书的容量应该不为过。
|
||||
|
||||
从技术细节上来说,通过几个阶段的具体操作,可以让你有一个性能分析定位的宏观感受,这也是这两篇内容的初衷。性能优化是无止境的,我们要做的是以最少的时间和金钱成本,达到最大的优化效果。
|
||||
|
||||
## 思考题
|
||||
|
||||
这一篇文章延续上一篇的分析思路,你能讲一下Swap的原理和逻辑,以及分析思路吗?另外,慢SQL如何定位出来呢?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。
|
||||
@@ -0,0 +1,348 @@
|
||||
<audio id="audio" title="29丨案例:如何应对因网络参数导致的TPS呈锯齿状?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b5/39/b559ccb208fb81d11b20080f67c4ba39.mp3"></audio>
|
||||
|
||||
在苍茫的性能分析道路上,不管你是一只多老的鸟,在经历了多个性能测试的项目之后,你都会发现对于性能问题而言,你仍然不敢说能全部解决。因为下一个问题可能真的是你完全没有见过的。
|
||||
|
||||
再加上技术的飞速发展,想跟得上技术的进步都是一件痛苦的事情,更别说要完全掌握并且融会贯通了。
|
||||
|
||||
我经常看到有些人在简历中动辄说自己做过上百个性能项目,以彰显自己有充足的经验。事实上,如果一个性能项目需要做两个星期的话,基本上做不到调优的层面,最多是弄个脚本压个报告。在我的经验中,基本上一个完整的架构级的性能项目从准备开始到写出测试报告、调优报告,需要1.5个月以上。你可以想像,这样的项目,就算一年不停地做,做10个都算是非常快的了,而要做上百个这样的项目,至少需要10年的时间。
|
||||
|
||||
并且不是每一个项目都能让你有分析性能瓶颈的机会,因为有很多问题都是重复的。
|
||||
|
||||
所以性能分析是一个需要不断总结出自己的分析逻辑的工作,有了这些分析逻辑,才能在新项目中无往不利。请注意我的描述,我强调的是要有自己分析的逻辑,而不是经历多少个性能问题。因为问题可能会遇到新的,但是分析逻辑却是可以复用的。
|
||||
|
||||
在今天的文章中,我仍然用一个之前项目中出现过的案例给你讲一讲性能分析的思路。
|
||||
|
||||
## 案例问题描述
|
||||
|
||||
这个项目是我调优过两次的项目。我介入这个项目之后,和同事们一起从100TPS调到1000TPS。
|
||||
|
||||
但是调到这个阶段,也只是在测试环境中调的,并没有按生产的架构调优。从测试部署架构上来说,就是Tomcat+Redis+MySQL,负载均衡的Nginx部分还没有加进去。
|
||||
|
||||
本来想着如果只是加个Nginx,也复杂不到哪里去。于是,我就撤了。但是当我离开一周之后,那个项目组同事又给我打电话,说这个项目仍然有问题,加了Nginx之后,TPS达不到1000了。啊,这还得了,要砸我招牌呀。
|
||||
|
||||
于是我又介入这个项目了,直到再次解决这个新的性能问题。
|
||||
|
||||
在今天的内容里,我会将记忆中所有的细节都记录下来,有些是同事调的步骤,有些是我调的步骤。在这个久远的项目中,希望我能写的完整。
|
||||
|
||||
下面来看这个具体的问题分析过程。
|
||||
|
||||
这个系统的简单架构图如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9c/44/9cb4fcc28c3ff00473346d4033330b44.jpg" alt="">
|
||||
|
||||
## 压力工具图
|
||||
|
||||
照例,我们先看压力工具中给出来的重要曲线。
|
||||
|
||||
用户递增图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e2/48/e2778129b5c97937427af1dcd6e43148.png" alt="">
|
||||
|
||||
TPS图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/dc/d7/dc2f7c78248f4f35600b3ca8997dfed7.png" alt="">
|
||||
|
||||
响应时间图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d0/06/d098a98768e4ebf41c10221dd2467406.png" alt="">
|
||||
|
||||
从上面的曲线可以看到,TPS在上升到一个量级的时候就会掉下来,然后再上到同样的量级再掉下来,非常规律。而响应时间呢,在第一次TPS掉下来之后,就变得乱七八糟了。响应时间不仅上升了,而且抖动也很明显。
|
||||
|
||||
这是什么情况?从来没见过呀。
|
||||
|
||||
## 分析过程
|
||||
|
||||
我们需要经过一系列的工作——看操作系统的CPU、I/O、Memory、NET等资源;看数据库、Tomcat、Nginx监控数据等等。
|
||||
|
||||
经过分析,我们觉得其他数据显示正常,网络连接状态是有问题的。如下所示:
|
||||
|
||||
```
|
||||
tcp 0 0 ::ffff:192.168.1.12:59103 ::ffff:192.168.1.11:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:59085 ::ffff:192.168.1.11:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:59331 ::ffff:192.168.1.11:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:46381 ::ffff:192.168.1.104:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:59034 ::ffff:192.168.1.11:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:59383 ::ffff:192.168.1.11:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:59138 ::ffff:192.168.1.11:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:59407 ::ffff:192.168.1.11:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:59288 ::ffff:192.168.1.11:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:58905 ::ffff:192.168.1.11:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:58867 ::ffff:192.168.1.11:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:58891 ::ffff:192.168.1.11:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:59334 ::ffff:192.168.1.11:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:46129 ::ffff:192.168.1.100:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
tcp 0 0 ::ffff:192.168.1.12:59143 ::ffff:192.168.1.11:3306 TIME_WAIT timewait (0.00/0/0)
|
||||
|
||||
```
|
||||
|
||||
从这里我们可以看到,网络中有大量的timewait存在,这是有价值的信息了,但也只是现象。
|
||||
|
||||
## 尝试优化
|
||||
|
||||
### 尝试一:为TIME_WAIT修改TCP参数
|
||||
|
||||
通过检查sysctl.conf,我们看到所有的配置均为默认,于是尝试如下修改。
|
||||
|
||||
其实这个修改,应该说是在分析得不够精准的情况下做的判断。
|
||||
|
||||
因为在服务端出现大量的timewait,说明是服务端主动断开的TCP连接。而我们处理这样的连接,无非就是释放服务端的句柄和内存资源,但是不能释放端口,因为服务端只开了一个listen端口。
|
||||
|
||||
```
|
||||
net.ipv4.tcp_tw_recycle = 1
|
||||
net.ipv4.tcp_tw_reuse = 1
|
||||
net.ipv4.tcp_fin_timeout = 3
|
||||
net.ipv4.tcp_keepalive_time = 3
|
||||
|
||||
```
|
||||
|
||||
回归测试,问题依旧。
|
||||
|
||||
### 尝试二:修改Nginx的proxy_ignore_client_abort
|
||||
|
||||
考虑到当客户端主动断开时,服务器上也会出现大量的timewait,所以我们打开proxy_ignore_client_abort,让Nginx忽略客户端主动中断时出现的错误。
|
||||
|
||||
```
|
||||
proxy_ignore_client_abort on;
|
||||
|
||||
```
|
||||
|
||||
修改后,重启Nginx,问题依旧。
|
||||
|
||||
这个操作纯属根据经验做的猜测。因为是加了Nginx之后才出现的问题。但是这个调整并没有起到作用,可见不是压力端主动断开连接而导致的问题,也就是说,和压力机无关了。
|
||||
|
||||
### 尝试三:换Nginx和Nginx所在的服务器
|
||||
|
||||
因为这个案例是通过Nginx服务器才看到TPS上到300就会下降,所以我们考虑是Nginx的问题。但是查来查去,也没看到Nginx有什么明显的问题,于是我们就下载了Nginx的源码,重新编译一下,什么模块都不加,只做转发。
|
||||
|
||||
通过回归测试发现,并没有解决问题。
|
||||
|
||||
到这里,那就显然和Nginx本身没什么关系了,那么我们就换个服务器吧。于是我们在另一台机器上重新编译了Nginx,但是问题依旧。
|
||||
|
||||
服务器和Nginx都换了,但还是没有解决问题,那么问题会在哪呢?想来想去,还是在操作系统层面,因为Nginx实在是没啥可调的,只做转发还能复杂到哪去。
|
||||
|
||||
但是操作系统层面又有什么东西会影响TPS到如此规律的状态呢?在考虑了应用发送数据的逻辑之后(请参考[《18丨CentOS:操作系统级监控及常用计数器解析(下)》](https://time.geekbang.org/column/article/191522)中的网络部分中的“数据发送过程”和“数据接收过程”),我觉得操作系统本身应该不会存在这样的限制,网络配置参数我也看过,不会导致这样的问题。
|
||||
|
||||
那么在操作系统发送和接收数据的过程中,只有一个大模块我们还完全没有涉及到,那就是防火墙。于是我查了一下系统的防火墙状态。
|
||||
|
||||
激活状态的防火墙是如下这样的:
|
||||
|
||||
```
|
||||
[root@node-1 zee]# systemctl status firewalld.service
|
||||
● firewalld.service - firewalld - dynamic firewall daemon
|
||||
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled)
|
||||
Active: active (running) since Mon 2015-02-17 23:34:55 CST; 2s ago
|
||||
Docs: man:firewalld(1)
|
||||
Main PID: 32052 (firewalld)
|
||||
Tasks: 2
|
||||
Memory: 32.4M
|
||||
CGroup: /system.slice/firewalld.service
|
||||
└─32052 /usr/bin/python -Es /usr/sbin/firewalld --nofork --nopid
|
||||
|
||||
```
|
||||
|
||||
从上面的“active (running)”可以看到防火墙确实是开着的。那怎么办?果断地先停掉再说。
|
||||
|
||||
### 尝试四:停掉防火墙
|
||||
|
||||
和网络连接有关的内容,剩下的就只有防火墙了。于是执行如下命令:
|
||||
|
||||
```
|
||||
Service iptables stop
|
||||
|
||||
```
|
||||
|
||||
之后我们就会看到TPS立即就上去了,这就明显和防火墙相关了。
|
||||
|
||||
dmesg查下系统日志,可以看到如下的大量信息:
|
||||
|
||||
```
|
||||
Nov 4 11:35:48 localhost kernel: __ratelimit: 108 callbacks suppressed
|
||||
Nov 4 11:35:48 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:48 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:48 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:48 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:48 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:48 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:48 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:48 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:48 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:48 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:53 localhost kernel: __ratelimit: 592 callbacks suppressed
|
||||
Nov 4 11:35:53 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:53 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:57 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:57 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:57 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:57 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:57 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:57 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:57 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:57 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:58 localhost kernel: __ratelimit: 281 callbacks suppressed
|
||||
Nov 4 11:35:58 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:58 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:58 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:58 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:58 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:58 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:58 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:58 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:58 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:35:58 localhost kernel: nf_conntrack: table full, dropping packet.
|
||||
Nov 4 11:36:14 localhost kernel: __ratelimit: 7 callbacks suppressed
|
||||
|
||||
```
|
||||
|
||||
这个信息很明显,那就是nf_conntrack的表满了,于是把包给丢了。可是看到这里的时候,我还不知道nf_conntrack是个啥玩意。
|
||||
|
||||
后来通过查资料才知道,nf_conntrack(以前叫ip_conntrack)是Linux中一个跟踪TCP连接条目的模块,它会用一个哈希表记录TCP的连接信息。当这个哈希表满了之后,就会报`nf_conntrack: table full,dropping packet`这样的错误。
|
||||
|
||||
`__ratelimit: N callbacks suppressed`的意思是系统中这样重复的日志太多了,达到了内核参数中的net_ratelimit()的上限。Linux中也是用这个参数来避免DDos的。
|
||||
|
||||
可是,为啥要丢掉这些包呢?满就满了呗,你可以报错呀。丢人家的包,这一招不是很地道呀。
|
||||
|
||||
## 原理分析
|
||||
|
||||
下面来看一下nf_conn的数据结构:
|
||||
|
||||
```
|
||||
struct nf_conn {
|
||||
/ Usage count in here is 1 for hash table/destruct timer, 1 per skb, plus 1 for any connection(s) we are `master' for /
|
||||
struct nf_conntrack ct_general; / 连接跟踪的引用计数 /
|
||||
spinlock_t lock;
|
||||
/* Connection tracking(链接跟踪)用来跟踪、记录每个链接的信息(目前仅支持IP协议的连接跟踪)。每个链接由“tuple”来唯一标识,这里的“tuple”对不同的协议会有不同的含义,例如对TCP,UDP来说就是五元组: (源IP,源端口,目的IP, 目的端口,协议号),对ICMP协议来说是: (源IP, 目的IP, id, type, code), 其中id,type与code都是icmp协议的信息。链接跟踪是防火墙实现状态检测的基础,很多功能都需要借助链接跟踪才能实现,例如NAT、快速转发、等等。*/
|
||||
struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
|
||||
|
||||
unsigned long status; / 可以设置由enum ip_conntrack_status中描述的状态 /
|
||||
|
||||
|
||||
struct nf_conn master; / 如果该连接是某个连接的子连接,则master指向它的主连接 */
|
||||
/ Timer function; drops refcnt when it goes off. /
|
||||
struct timer_list timeout;
|
||||
|
||||
|
||||
union nf_conntrack_proto proto; / 用于保存不同协议的私有数据 /
|
||||
/ Extensions /
|
||||
struct nf_ct_ext ext; / 用于扩展结构 */
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
上面的nf_conn有最大个数限制,它是通过nf_conntrack_max来限制的,默认值是65535。
|
||||
|
||||
通过查找资料,我发现nf_conn的主要作用,如下描述:
|
||||
|
||||
>
|
||||
在nf_conntrack模块中,实现了对连接跟踪。它利用netfilter框架中的nf_register_hook/nf_unregister_hook函数来注册钩子项,调用nf_conntrack_in来建立相应连接,ipv4_conntrack_in挂载在NF_IP_PRE_ROUTEING点上(该函数主要实现了创建连接),从而实现连接跟踪。
|
||||
|
||||
|
||||
从上面的描述我们就可以看到,原来这个模块并不是防火墙必须的模块,并且从实现手法上来说,是通过注册hook函数实现的功能。有黑客经验的人应该经常听到hook函数,很多病毒软件都用这个思路往其他的应用中插入自己的代码模块的。
|
||||
|
||||
## 参数调整
|
||||
|
||||
知道了是什么模块之后,就很容易找到和它相关的参数了,这个信息你可以直接在操作系统的官网信息中找得到。
|
||||
|
||||
那么nf_conntrack的参数是什么呢?
|
||||
|
||||
```
|
||||
net.netfilter.nf_conntrack_max
|
||||
//是允许的最大跟踪连接条目,是在内核内存中netfilter可以同时处理的“任务”。
|
||||
net.netfilter.nf_conntrack_tcp_timeout_established
|
||||
//是TCP连接创建时的超时时间。
|
||||
|
||||
```
|
||||
|
||||
其实还有其他参数,只是这两个和性能相关,所以我在这里单独列了出来。
|
||||
|
||||
根据官方的公式,nf_conntrack_max的最大值是`CONNTRACK_MAX = RAMSIZE(in bytes)/16384/(ARCH/32)`。
|
||||
|
||||
其中,RAMSIZE是物理内存大小,ARCH是操作系统位数,16384是netfilter框架中hash桶参数nf_conntrack_buckets的默认大小。
|
||||
|
||||
以32G内存,64位操作系统为例:
|
||||
|
||||
```
|
||||
CONNTRACK_MAX = 32*10241024*1024/16384/2 = 1048576条
|
||||
|
||||
```
|
||||
|
||||
也就是说,如果你有内存,就可以把这个值调大点。
|
||||
|
||||
我在这里就直接往后面加了个0,改为:
|
||||
|
||||
```
|
||||
net.netfilter.nf_conntrack_max=655350
|
||||
|
||||
|
||||
```
|
||||
|
||||
知道了原理,就不止一个调优方案了,下面我们就来看看。
|
||||
|
||||
## 其他解决方案
|
||||
|
||||
### 关闭防火墙
|
||||
|
||||
```
|
||||
chkconfig iptables off
|
||||
chkconfig ip6tables off
|
||||
service iptables stop
|
||||
service ip6tables stop
|
||||
|
||||
```
|
||||
|
||||
### 禁用在这个端口上跟踪(也称为使用裸表)
|
||||
|
||||
在防火墙规则中这样配置:
|
||||
|
||||
```
|
||||
-A PREROUTING -p tcp -m tcp --dport 80 -j NOTRACK
|
||||
|
||||
```
|
||||
|
||||
你也可以直接对TCP禁用跟踪,至于规则配置,就看你的想像力了(这也是我比较不喜欢弄防火墙的原因,配置也太灵活了,容易乱)。
|
||||
|
||||
### 删除跟踪模块
|
||||
|
||||
你还可以直接删除跟踪模块。首先,查看有哪些模块:
|
||||
|
||||
```
|
||||
lsmod | grep nf_conntrack
|
||||
|
||||
|
||||
```
|
||||
|
||||
再用rmmod删除它们。
|
||||
|
||||
总之,不管你用什么方式,都需要再你的应用可以安全并能正常提供服务的前提下使用。
|
||||
|
||||
知道了解决方案之后,我们就来看下解决之后的TPS是怎样的。
|
||||
|
||||
## 解决问题后的TPS图
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3a/97/3a9c72c2c461232f8a3f367390c49997.png" alt="">
|
||||
|
||||
上图中有两次TPS下降的过程,这是因为我又尝试修改了防火墙的禁用参数配置,重启了两次防火墙。并不是出现了新的问题,所以不要紧张。
|
||||
|
||||
后来在这个项目中又调优了一些其他的参数,下面是最终达到的调优效果。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/80/45/809f69e91a0da566d4ced5ef8c2cf345.png" alt="">
|
||||
|
||||
蓝色是调优前的,红色是调优后的。
|
||||
|
||||
我们可以看到响应时间下降了很多,TPS也上去了,客户非常满意,还请我吃了顿忘不了的牛肉火锅。哈。
|
||||
|
||||
## 总结
|
||||
|
||||
性能问题总是层出不穷,不管你以前多有经验,都可能会遇到不懂的性能问题。
|
||||
|
||||
如果建立了分析问题的思路逻辑,并且又善于学习和查找各种资料,找到根本原因,最后都会给出完整的解决方案。
|
||||
|
||||
这个案例应该说是个比较偏门的性能问题了,解决问题的思路就是我上面说的那样。
|
||||
|
||||
其实你也可以看到,有很多时候,我们的性能问题和代码并没有关系,所以这里也提醒那些一玩性能就想看代码的人,眼光要放开阔一些。还有就是遇到性能问题时,一定要记住,不要慌!
|
||||
|
||||
## 思考题
|
||||
|
||||
这是个刁钻的案例,你能说一下为什么在本例中,最后想到了看防火墙呢?以及,为什么说timewait的TCP链接只是问题的现象呢?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。
|
||||
@@ -0,0 +1,233 @@
|
||||
<audio id="audio" title="30丨案例:为什么参数化数据会导致TPS突然下降?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/98/af/98758ea4bf081965041c99203edac5af.mp3"></audio>
|
||||
|
||||
写这篇文章的时候,我想起来一句似乎无关紧要的话:“我离你如此之近,你却对我视而不见。”
|
||||
|
||||
在性能测试中,参数化数据是少有的每个性能测试工程师都会用得到,却经常出现问题的技术点之一。从我的角度来说,究其原因,大部分是因为对性能参数化数据的理解不足。导致的结果就是用了参数化,但和真实的用户场景不一致,从而使得整个性能测试场景都失去了意义。
|
||||
|
||||
这样的例子不在少数。
|
||||
|
||||
一个项目开始之初,由于没有历史沉淀的数据,所以我们需要造一些数据来做性能测试。造多少呢?并不是按未来生产的容量来造,而是按性能场景中需要的数据量级来造。这种错误的做法是很多项目中真实出现的事情。
|
||||
|
||||
这并不止是性能测试工程师之过,还有很多其他的复杂原因,比如时间不够;经验不足,只能造重复的数据等等。
|
||||
|
||||
那么性能测试参数化数据的获取逻辑到底是什么呢?我们来看一个图吧。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6b/ca/6bc265c84949230ec7f028e2de3fa1ca.jpg" alt="">
|
||||
|
||||
在这个图中,我用不同的颜色表示不同组件中的数据。压力工具中的参数化数据有两种,这一点,我们前面有提到过,参数化数据有两大类型:
|
||||
|
||||
1. 用户输入的数据同时在后台数据库中已存在。
|
||||
1. 用户输入的数据同时在后台数据库中不存在。
|
||||
|
||||
当我们使用数据库中已存在的数据时,就必须考虑到这个数据是否符合真实用户场景中的数据分布。当我们使用数据库中不存在的数据时,就必须考虑输入是否符合真实用户的输入。
|
||||
|
||||
在本篇要说的案例中,我们来看一下参数化数据如果做错了,对性能结果会产生什么样的影响。
|
||||
|
||||
## 案例问题描述
|
||||
|
||||
在一次压力测试的过程中,出现了如下所示的TPS数据(本篇文章中一些截图会有些模糊,因为来自于之前项目中的具体案例,在当时截图时,也并没有考虑清晰度,不过我们只要看趋势就好)。
|
||||
|
||||
在下图中,我们可以看到,在压力测试过程中,出现了TPS陡减到底的情况。这显然是不合理的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fa/76/fa8f22f6af28ecd5db29dccba9f09576.png" alt="">
|
||||
|
||||
这个曲线的趋势把性能瓶颈呈现得非常明显。在出现这个问题之后,当时我们也尝试过把线程数降低,观察TPS的趋势,结果从300到100到50到10,最后到1,发现都会出现这样的TPS陡减到底的情况,只是时间长度不同而已。
|
||||
|
||||
这非常像某个资源因为处理业务量的累积达到了某个临界点而产生的情形。
|
||||
|
||||
但不管怎样,我们还是要按正常分析的思路来分析它。
|
||||
|
||||
## 分析过程
|
||||
|
||||
首先,仍然是画一个架构图。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4a/c4/4a46607bf10433a9ec853d0081e355c4.jpg" alt="">
|
||||
|
||||
在这个图中,我们可以看到,JMeter是连接到第一层服务(这里是有两个Tomcat实例),再到第二层服务(这里是也有两个Tomcat实例),然后再连到DB中。这个DB是一个互联网金融DB(通过MySQL改造来的)。
|
||||
|
||||
了解了架构图之后,现在就开始查看下性能数据吧。
|
||||
|
||||
### 查操作系统
|
||||
|
||||
先看一下操作系统的性能数据:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5c/7f/5c2ccd6185203914ee7ea9b7b726e77f.png" alt="">
|
||||
|
||||
从top中,我们可以看到这个应用服务器没啥压力,在这样的状态中,你可能都不用再去查其他的操作系统信息了,因为目前的压力对这个系统来说确实是小了点。
|
||||
|
||||
### 查应用
|
||||
|
||||
再看下应用的状态,这里用的工具仍然是前文中提到过多次的JvisualVM(请你在用性能监控工具的时候,不要纠结,只要工具好使,用到吐都行,不用跟风)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/41/df/4197eadcca5a7d55872f2d742cebd6df.png" alt="">
|
||||
|
||||
从这个图中可以看到的是,这个应用使用到的CPU确实很低,并且堆也没用多少。其实在这一步,我查了四个Tomcat的状态,只是截了一个图而已。
|
||||
|
||||
在这里还是要啰嗦一下了,对这样的曲线,我们一定要一眼就能看出问题在哪里。出现上图这样的情况是因为以下两个原因:
|
||||
|
||||
1. 应用CPU使用率(橙色CPU线)确实是太低了,才15%左右。这和前面的top也是能对得上的。Java的GC几乎没占CPU(蓝色CPU线),也就是说Tomcat在这里没压力。
|
||||
1. 从堆曲线的趋势上来看,1G的堆才到了400M多一点,并且回收一直都非常正常。怎么判断这个“正常”呢?首先,年轻代、年老代回收很有规律,并且没消耗什么CPU;其次,每次FullGC都能回到150M左右,非常平稳。可见这个内存使用没啥问题。
|
||||
|
||||
当然到了这里,我当时也是查了网络的,只是也没什么压力,所以没做具体的记录(从这点可以看出,如果你在做性能测试的时候,要想记录性能瓶颈的分析过程,一定要记得把数据记全了,不然以后你可能都想不起来当时做了什么事情)。
|
||||
|
||||
### 查DB
|
||||
|
||||
既然上面都没啥问题,DB又是一个MySQL,所以这里,我先手动执行了几个常规的查询语句。在DB中查看如下信息。
|
||||
|
||||
查`processlist`、`innodb_trx`、`innodb_locks`、`innodb_lock_waits`。在没有监控工具时,这几个是我经常在MySQL数据库检查的表,因为数据库如果慢的话,基本上会在这几个表中留些蛛丝马迹。
|
||||
|
||||
processlist是看当前数据库中的session的,并且也会把正在执行的SQL列出来,快速刷新几次,就可以看到是不是有SQL一直卡在那里。
|
||||
|
||||
`innodb_trx`是正在执行的SQL事务表,这个表很重要。
|
||||
|
||||
`innodb_locks`和`innodb_lock_waits`是为了看有没有锁等待。
|
||||
|
||||
拿一条业务SQL执行一下,看看在压力之中会不会慢。这是在没有数据库监控时,快速判断业务的方法。因为这个业务很单一,用的SQL也单一,所以我在这里可以这样做。执行了之后,并没有发现业务SQL慢。
|
||||
|
||||
由此基本判断DB没什么问题。
|
||||
|
||||
注意,判断到了这里,其实已经出现了证据不完整产生的方向偏离!
|
||||
|
||||
### 陷入困局之后的手段
|
||||
|
||||
更悲催的是这个业务系统的日志记录的非常“简洁”,连时间消耗都没有记录下来。想来想去,在这么简单的一个架构中,没什么可查的东西了吧,除非网络中有设备导致了这个问题的出现?
|
||||
|
||||
在没有其它监控工具的情况下,当时我们上了最傻最二最基础又最有效的时间拆分手段:抓包!
|
||||
|
||||
抓包其实是个挺需要技巧的活,不止是说你能把包抓出来,还要能分析出来时间消耗在谁那里。这时我提醒一下,当你学会抓包工具的使用时,不要在每个场景下都想露一手你的抓包能力,通过抓的包分析响应时间的消耗点。
|
||||
|
||||
在我的工作中,只有万般无奈时才会祭出“抓包”这样的手段,并不是因为我对网络不够了解。恰恰是因为了解得足够多的,我才建议不要随便抓包。因为但凡在应用层有工具可以分析响应时间,都会比抓网络层的包来得更加简单直观。
|
||||
|
||||
经过一段段的分析之后,在数据库的一个主机上看到了如下信息:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/80/db/80c93157c5ebb04484ea4ef6ef7426db.png" alt="">
|
||||
|
||||
看到这里的TCP segment of a reassembled PDU没有?它之上是ACK。放大一下,看看这里的时间:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ea/2a/ea7d5ffd1403206a14b1a4bfdec3cf2a.png" alt="">
|
||||
|
||||
看到没有,这里有两秒的时间才发数据,那它是在干吗呢?
|
||||
|
||||
这里就要说明一下`TCP segment of a reassembled PDU`了,PDU就是`Protocol Data Unit`。
|
||||
|
||||
以下高能烧脑,不喜可跳过!
|
||||
|
||||
它是指在TCP层接收到应用层发的非常大的数据之后,需要将数据大刀阔斧地砍成几段之后再发出去。就是这个砍数据的过程消耗了2秒的时间。
|
||||
|
||||
可是为什么TCP层要干这个事呢?上层应用给了你一大块数据包,你直接往外扔不就行了吗?还要自己reassemble(重新装配),费老大劲。
|
||||
|
||||
这其实TCP的一个参数来决定的,它就是MSS(Maximum Segment Size)。在TCP一开始打招呼的时候(就是握手的过程),已经通过MSS这个可选项告诉对方自己能接收的最大报文是多少了,这是不加任何信息的大小,纯的。而在以太网上,这个值是设置为1460字节的,为啥是1460呢?因为加上TCP头的20个字节和IP头的20个字节,刚好不大不小1500字节。
|
||||
|
||||
当你看到1500字节的时候,是不是有一种似曾相识的感觉?它就是现在普遍设置的MTU(Maximum Transmission Unit)的大小呀。
|
||||
|
||||
这时你可能会说了,那我可以把MTU设置大嘛。可是你自己设置不行呀,别人(各主机和网络设备)都得跟着你设置才行,要不然到了MTU不大的地方,还得分包,还是要费时间。
|
||||
|
||||
而接收端呢?接数据时接到这些包的ACK序号都是一样的,但Sequence Number不同,并且后一个Sequence Number是前一个Sequence Number+报文大小的值,那接收端就可以判断这是一个TCP Segment了。
|
||||
|
||||
好了,解释完这些之后,回到前面的问题。数据库自己耗时了两秒来做reassemble PDU。至于吗?不就是过来查个数据吗?考虑了一下业务特征,这就是根据客户ID查一个帐户的一个月或三个月的记录信息,通常是100条左右,最多也就200条,也不至于有这么大。但是不管怎么样,还是数据库的问题!
|
||||
|
||||
这就是我前面说的查DB的时候,由于证据不全导致了分析思路的偏差。因为我手动执行了这个语句的时候并不慢,只要10几毫秒,所以,那时候我觉得数据库不是问题点。
|
||||
|
||||
但是经过了抓包之后,发现问题还是出在DB上。有时候真不能那么自信呀,容易给自己挖坑,要是早把活干得细致一点,也不至于要抓包了。
|
||||
|
||||
### 接着分析DB
|
||||
|
||||
那我们肯定要接着看DB上的信息了,既然数据量大,SQL执行得慢,那就先捞出慢日志看看。
|
||||
|
||||
查看如下负载信息:
|
||||
|
||||
```
|
||||
# Profile
|
||||
|
||||
|
||||
# Rank Query ID Response time Calls R/Call V/M Item
|
||||
# ==== ================== ================= ===== ======= ===== ==========
|
||||
# 1 0xB5DEC0207094BA2F 117365.8906 44.9% 14120 8.3120 8.46 SELECT
|
||||
# 2 0xFF8A1413823E401F 62050.0287 23.7% 12078 5.1374 2.78 SELECT
|
||||
# 3 0xC861142E667B5663 36004.3209 13.8% 21687 1.6602 0.13 SELECT
|
||||
# 4 0xFB7DBC1F41799DDD 32413.9030 12.4% 19615 1.6525 0.09 SELECT
|
||||
# 5 0xC065900AEAC5717F 11056.5444 4.2% 9304 1.1884 0.02 SELECT
|
||||
# 9 0x6422DFBA813FC194 202.4342 0.1% 54 3.7488 1.83 INSERT
|
||||
# 11 0x197C9DCF5DB927C8 137.4273 0.1% 36 3.8174 1.14 INSERT
|
||||
# 13 0x1A9D64E72B53D706 97.9536 0.0% 31 3.1598 2.65 UPDATE
|
||||
# 36 0x3B44178A8B9CE1C3 20.1134 0.0% 16 1.2571 0.04 INSERT
|
||||
# 39 0x370753250D9FB9EF 14.5224 0.0% 11 1.3202 0.04 INSERT
|
||||
# MISC 0xMISC 2152.2442 0.8% 151 14.2533 0.0 <72 ITEMS>
|
||||
|
||||
```
|
||||
|
||||
你可以看到确实有四个SQL消耗了更多的时间,并且时间还不短。这是明显的性能问题,但是我把这SQL拿出来执行过呀,并不慢。
|
||||
|
||||
怎么回事呢?
|
||||
|
||||
我让做数据库运维的人把DB proxy层的所有SQL日志拿出来分析一遍。为什么我要DB proxy层的数据呢?因为这一段会把所有执行的SQL都记录下来,而慢日志记录的是1s以上的(取决于DB中的配置)。首先是把time cost大于200ms的SQL都拉出来,结果发现,真的在TPS下降的那个时间段,出现了SQL执行超长的情况,并且和我执行的,还是同样的业务SQL。
|
||||
|
||||
怎么办?既然到这个层面了,这些执行的SQL只有一点区别,那就是查询条件。慢的SQL的查询条件,我拿回来试了,果然是慢,查出来的数据也是完全不一样的,居然能查出几万条数据来。前面说了,这个语句是根据客户ID查出记录数的,那么就根据客户ID,做一次group by,看下数据量为啥有这么多大差别。
|
||||
|
||||
于是得到了如下的结果:
|
||||
|
||||
```
|
||||
客户ID, 数量
|
||||
'这一列只是客户id,无它', '91307'
|
||||
'这一列只是客户id,无它', '69865'
|
||||
'这一列只是客户id,无它', '55075'
|
||||
'这一列只是客户id,无它', '54990'
|
||||
'这一列只是客户id,无它', '54975'
|
||||
'这一列只是客户id,无它', '54962'
|
||||
'这一列只是客户id,无它', '54899'
|
||||
'这一列只是客户id,无它', '54898'
|
||||
'这一列只是客户id,无它', '54874'
|
||||
'这一列只是客户id,无它', '54862'
|
||||
....................
|
||||
'这一列只是客户id,无它', '161'
|
||||
'这一列只是客户id,无它', '161'
|
||||
'这一列只是客户id,无它', '161'
|
||||
'这一列只是客户id,无它', '161'
|
||||
'这一列只是客户id,无它', '161'
|
||||
'这一列只是客户id,无它', '161'
|
||||
'这一列只是客户id,无它', '160'
|
||||
'这一列只是客户id,无它', '160'
|
||||
|
||||
```
|
||||
|
||||
从这个结果可以看到,不同客户ID的记录条数差别太大了。这是谁干的好事?!我们一开始就强调数据需要造均衡,要符合生产真实用户的数据分布。
|
||||
|
||||
到这里,问题基本上就明确了,查一下参数化的数据,里面有10万条数据,而取到记录数在五六万左右的客户ID的时候,才出现了响应时间长的问题。
|
||||
|
||||
而我之前的执行的SQL,恰好试了多次都是数据量少的。
|
||||
|
||||
下面怎么办呢?先做个最规矩的实验,把5万条往后的数据全都删掉!场景再执行一遍。
|
||||
|
||||
于是就得到了如下的结果:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/cb/3b/cbf4620fd4fb76e9a1862226da33253b.png" alt="">
|
||||
|
||||
问题完美解决。
|
||||
|
||||
可是问题怎么出现的呢?
|
||||
|
||||
经过询问负责产生基础数据的人,最后得知,一开始数据库里的基础数据不够。由于我在项目中要求基础数据量和参数化数据量要达到生产级别。于是把这个工作安排给了一个同事,就是造出每个客户都和生产环境中差不多量级的记录。当时是用压力脚本做客户ID的参数化,然后用执行压力的方式造的数据。
|
||||
|
||||
本来这个事情在做的时候,应该是把每个客户ID都加到差不多的记录的。但是这个人在做的时候,觉得一个个循环加下去实在是太消耗时间了,终于等不急了,于是在干了几个小时之后,觉得每个客户ID上都有了一些数据量之后,自己做了个决定,把客户ID减少到只有几百个,这样很快就干完了!
|
||||
|
||||
哭笑不得的感觉有没有?!
|
||||
|
||||
## 总结
|
||||
|
||||
很多性能问题,在出现的时候,都会觉得无从下手,而当分析到根本原因的时候,就觉得啼笑皆非。
|
||||
|
||||
但很多时候,在真实的场景中,很多性能问题连原因都没有分析出来,连啼笑皆非的机会都没有,就开始寻找规避的手段了,这就像用一个坑去埋另一个坑,于是大坑套小坑、小坑套水洼。
|
||||
|
||||
还有,在做性能分析的时候,有经验固然是好事,但是经验也并不是在所有的场景中都能有效地帮你解决问题,相反,它们有时也会成为累赘,成为判断出现偏差的原因。
|
||||
|
||||
所以我现在都会诚心地告诫一些性能测试从业人员:一定要全局监控、定向监控一层层数据查,不要觉得查了某个点就判断这个组件没问题了。像我这样的老鸟也照样得从全局查起,不然也是掉坑里。而这个“全局-定向”的思路,也照样适用一些新手,可以形成排查手册。
|
||||
|
||||
在我带过的项目中,我经常会讲这样的思路,制作排查手册(因为每个项目用的东西都会有些区别),而这些思路和排查手册,现在就变成了你一篇篇看过的文章。
|
||||
|
||||
所以我希望看专栏的人都能知道真正的分析性能瓶颈的过程是什么样子。不要在意自己现在会什么,要多在意以后会什么。
|
||||
|
||||
## 问题
|
||||
|
||||
讲完了今天的内容,你能说一下为什么通过抓包可以判断出响应时间的拆分吗?以及,数据分布不均衡还会带来哪些性能问题?
|
||||
|
||||
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。
|
||||
@@ -0,0 +1,171 @@
|
||||
<audio id="audio" title="31丨案例:当磁盘参数导致I/O高的时候,应该怎么办?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ef/43/efd5e5524ffbce0fe3c4dc60716ebd43.mp3"></audio>
|
||||
|
||||
在大部分的性能项目中,当系统调优到一定程度的时候,性能的瓶颈往往会体现在两类计数器上:一个是CPU,另一个就是磁盘I/O了。所以我们也经常会在一些性能优化的文章中看到两个分类,分别是CPU密集型和磁盘I/O密集型。
|
||||
|
||||
有人说为什么不说内存呢?内存是那么重要。不是说内存不会成为瓶颈,只不过内存的瓶颈基本上都可以转嫁给CPU和磁盘I/O。当内存不够的时候,大不了就是清理得快一点。内存能表现出来的,就是满不满,而谁去清理呢?那就是CPU了。清理得快就得CPU转得快。
|
||||
|
||||
我们经常会听到有人说什么性能优化到最后就是“空间转时间、时间转空间的优化”。如此带有禅意的一句话,其实意思就是,CPU不够用,就扩大内存;内存不够,就让CPU计算得更快一些。
|
||||
|
||||
举个例子,当我们需要在内存中使用很多变量时,如果内存不够,就会导致CPU不断清理内存中没被引用的变量来释放内存,这就导致了释放内存的动作会消耗更多的CPU。而这时,我们就可以用增加内存的方式,让CPU不那么繁忙。
|
||||
|
||||
但这个“空间、时间转化”的论点并不会在所有的场景下成立。比如说一个应用并不需要多大内存,就是纯计算型的,那你加内存也没啥用。另外这里提到的“空间”也不是硬盘,因为如果CPU不够用,拿再多磁盘补也无济于事。
|
||||
|
||||
所以这句话只能是做为高深谒语让初入性能的小白们仰望,实际上适用的场景非常少。
|
||||
|
||||
而磁盘I/O和内存有很大的区别。只要系统需要保存运行过程中留下的数据,那就必然会用到磁盘I/O。分析磁盘I/O的时候,相对于其他的分析环节还是要复杂一些,因为磁盘I/O栈是比较长的。有了Direct I/O技术之后,磁盘I/O栈短了一段,速度快了,但是同样也没有给性能测试分析人员带来什么福音,因为从分析的角度来说,我们看计数器并没有减少什么。
|
||||
|
||||
在性能分析过程中,操作系统是绕不过去的一个环节。在当前的技术栈中,我们仍然要把操作系统当成一个非常重要的环节。
|
||||
|
||||
现在对于I/O的分析和判断,大部分还停留在磁盘百分比、队列、磁盘延时之类上。可是这样的计数器的值多了,少了,你要干什么呢?怎么解决它呢?是磁盘参数有问题,还是应用有问题?还是磁盘硬件能力就那样了?
|
||||
|
||||
这是性能分析人员应该给出的答案,可是有很多性能测试工程师还真的给不出来这样的答案,甚至有些人连哪个线程导致的I/O高都判断不出来。至少现在的状态说明了,性能测试行业还远远不够成熟,我们还有很多的机会。
|
||||
|
||||
现在我们所说的I/O,基本上把File I/O和Disk I/O都说进去了,所以在分析上,层级更是非常模糊。如下图所示的一个I/O栈:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6f/ee/6fee04695be068f0622fbf67e55f01ee.png" alt="">
|
||||
|
||||
(此图来自于:[https://www.thomas-krenn.com/de/wikiDE/images/b/ba/Linux-storage-stack-diagram_v4.0.png](https://www.thomas-krenn.com/de/wikiDE/images/b/ba/Linux-storage-stack-diagram_v4.0.png))
|
||||
|
||||
在这个图中,我们可以明确看到磁盘I/O栈有多长。从应用层开始,产生系统调用,再通过visual FS Layer → FS Layer → Block Layer这样一层一层地调用下去,最后才写到了硬件存储里。
|
||||
|
||||
当然还有一层,我们需要关注的就是从系统的全局状态到具体的线程,这个我在操作系统那一篇的I/O部分已经描述过。
|
||||
|
||||
下面我们来看一个I/O高的分析案例。
|
||||
|
||||
## 案例现象
|
||||
|
||||
在这个项目的压力测试过程当中,发现了经典的现象:
|
||||
|
||||
1. **TPS上不去**;
|
||||
1. **硬件资源用不上**。
|
||||
|
||||
应该说,性能分析要是分类比较宽泛,基本上是两种。
|
||||
|
||||
**第一种是资源用不上,TPS上不去,响应时间随压力而增加**,如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/11/ce/1199fc4d93022aee612a2aca437278ce.png" alt="">
|
||||
|
||||
**第二种是资源用得上,TPS上不去,响应时间随压力而增加**。如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/72/6d/729cfa4bad0f164ea88e55ae71e8e06d.png" alt="">
|
||||
|
||||
这样的划分方式看似有调侃味道,却是很多人疑惑的起点。为什么这么说呢?
|
||||
|
||||
我们在开始做性能分析的时候,经常会面对这种情况,那就是资源用不上去。也就是路不够宽,硬件资源才用不上的。我们要解决的,就是把路扩宽一些。
|
||||
|
||||
但是,当我们面对一些客户或领导的时候,如果去汇报某资源使用率达到100%这样高的值的时候,有可能得到的一个反馈是,能不能把资源使用率降下来,因为资源使用率高会让人有种不安全感,而TPS反而不是关注的重点。这就让我们很疑惑:TPS上去了,资源必然用得多呀,难道让资源使用率稳定保持在50%,而TPS蹭蹭往上涨吗?这不现实呀。
|
||||
|
||||
我的建议是,不用怕资源使用率高,做为专业性能分析人员,更应该怕的是资源使用率不高!
|
||||
|
||||
在这个例子中,一开始同事跟我描述的时候,说**资源怎么也用不上去,TPS也上不去**。这样的描述很常见。
|
||||
|
||||
还是看看压力结果,如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b1/33/b1486cc6adfe2d0a3ecbe3f2685f1133.png" alt="">
|
||||
|
||||
(场景开始时报了少量的错,不过这不是我们这个案例要分析的部分,请看官忽略。)
|
||||
|
||||
像这种描述,基本上没办法让人分析,因为信息太少了。特别是有些人直接给一个固定线程数的压力结果,用来描述资源上不去的情况,这就更得不到有用的信息了,所以问性能问题的人一定要注意收集足够的信息,不然只能让人从头查一遍。
|
||||
|
||||
## 分析过程
|
||||
|
||||
因为没有什么具体的信息,所以我只能让同事把压力再压起来。
|
||||
|
||||
我登录到服务器上之后,顺手执行了一个top命令:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b1/7d/b1c1e4b1cd41d7481086388994f4997d.png" alt="">
|
||||
|
||||
这里给个小提醒,执行top的时候一定要习惯性地点一下数字1,这样可以把所有的CPU都列出来,因为我要看到每个CPU的各个计数器的使用率。别看这个动作不起眼,就算是这一个小小的操作,也是工作经验的积累。
|
||||
|
||||
果不其然,看到了一个问题,那就是上图中CPU0的wa达到了72%。像这样的问题,如果不看每个CPU的每个计数器,是发现不了的。
|
||||
|
||||
看到这样的东西,我们要有个基本的判断,对于一般的Java应用来说,I/O线程数基本上不会是1个,除非是特别的理由,所以这种情况还是很少的。
|
||||
|
||||
不过既然是I/O的问题导致了wa CPU使用率高,那么我来看一下CPU的热点在哪个层面。这是我在工作中经常执行的一个动作,因为我不想在后面的分析过程中偏离方向,而看CPU热点可以让我记得这个方向。
|
||||
|
||||
如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e1/2e/e1080375920d5fd944d2df9dadaf8e2e.png" alt="">
|
||||
|
||||
有人说,即然wa CPU使用率已经高了,接下去直接看磁盘I/O不就行了,看不看CPU热点还重要吗?我要说,当然重要!看到前面的磁盘I/O栈了吗?那么长,怎么知道是在哪一层呢?
|
||||
|
||||
这是定向细化分析过程中的经验之一。
|
||||
|
||||
在CPU热点中我们看到了,是内核模块中的blk_queue_bio消耗的CPU大。blk_queue_bio是用来做I/O调度合并的,可以对I/O请求进入后向、前向合并操作,互斥方式就是加锁。
|
||||
|
||||
根据它我们就可以知道,这是一个有queue的调度过程,再接着看它下面的几个函数也可以看出,都是kernel级的调用,并没有user级的调用,这几个函数基本上也说明了当前系统忙于journal写。
|
||||
|
||||
接着执行iotop,看是哪个进程在做这个动作。这里可以看到jbd2,和上面CPU热点的函数也是对应得上的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6a/fc/6a7690bbf843e84e1eeb806093a390fc.png" alt="">
|
||||
|
||||
那么jbd2是个啥玩意呢?jbd2英文名是这样的:Journaling Block Device 2。
|
||||
|
||||
它是跟着ext4格式来的,当然它也可以在其他磁盘格式下工作。这个进程的工作原理是这样的:文件系统写入数据,要提交给驱动程序,而jbd2就是在文件系统调用驱动之前工作的。文件系统要先调用jbd2,然后jbd2会根据系统的设置(设置有writeback、ordered、journal),进行数据的备份,然后再让文件系统提交数据。当文件系统将数据写入了块设备之后,jbd2就会把备份的数据删除。如果文件系统写块设备时出了问题(比如说可悲的断电),那jbd2这里还有一个备份,在进行完整性检查时就会把数据补全,所以数据不会丢。
|
||||
|
||||
jbd2就是这样保证数据的完整性的。
|
||||
|
||||
但是!对嘛,我就喜欢说但是。jbd2保证的数据完整性也只是一个原子操作的完整性,即一个原子操作如果操作了2M的数据,它就只能保证这2M的数据不会丢失。如果你一下子操作了1T的数据,jbd2也不能保证完整性。
|
||||
|
||||
jbd2有一个参数barrier,它用来开启磁盘屏障。就是设置一个栅栏,要先把barrier之前的数据全都写到磁盘设备之后,才会写barrier之后的数据,也就是说它是用来保证原子操作中数据的完整性的。当然了,开启barrier的一个后果就是性能下降。
|
||||
|
||||
了解了这些内容之后,我们就要判断一下了。这个应用有没有必要用这个功能来保证原子操作的完整性?我考虑了一下之后,觉得这个应用就是写日志而已,也没有其他什么重要的东西是要写日志的,重要的业务数据都写到数据库去了。
|
||||
|
||||
所以,没必要保证数据写入的完整性,就算丢了一些日志数据又怎么样呢?如果真的就那么寸,应用出现大bug,恰好块设备也出问题,或者断电,那真的只有烧香拜佛了。
|
||||
|
||||
于是我觉得这个功能不用也罢,把原理说明白之后,和架构组、开发组、运维组以及领导层们沟通了一圈,告诉了他们原理,实在是没有打开的必要。
|
||||
|
||||
最后大家一致同意:关掉!
|
||||
|
||||
我们再回过头来看下这个参数:
|
||||
|
||||
```
|
||||
[root@主机A]# cat /proc/mounts
|
||||
/dev/mapper/data_lvm-data_lv /data ext4 rw,relatime,barrier=1,data=ordered 0 0
|
||||
|
||||
```
|
||||
|
||||
上面的这个barrier=1,就是我们要找的目标。想关了它,就是挂载磁盘的时候使用挂载选项-o barrier=0或者nobarrier。我这里是直接设置为0的。
|
||||
|
||||
你也可以直接关掉journal功能:
|
||||
|
||||
```
|
||||
tune2fs -o journal_data_writeback /dev/mapper/data_lvm-data_lv tune2fs -O "^has_journal" /dev/mapper/data_lvm-data_lv e2fsck -f /dev/mapper/data_lvm-data_lv
|
||||
|
||||
```
|
||||
|
||||
## 问题解决之后
|
||||
|
||||
我们再来看下问题解决后的压力工具结果:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/74/3d/74a3e5ceb53b45db668e768d8b7f843d.png" alt="">
|
||||
|
||||
从压力结果来看,同样的压力之下,TPS增加了一倍,响应时间快了一倍。
|
||||
|
||||
top如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ab/7d/ab9a6ef19d55c87f0b380f9a6dcf937d.png" alt="">
|
||||
|
||||
集中的wa CPU也消失不见了,现在使用的也挺均衡的了。
|
||||
|
||||
iotop:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/85/ac/8534cc3d96242eb3f017fbd84735b6ac.png" alt="">
|
||||
|
||||
在I/O top的结果中,也看不到I/O非常高的jbd2了。
|
||||
|
||||
问题得到了完美地解决。
|
||||
|
||||
## 总结
|
||||
|
||||
不管是什么样的性能问题,其实从分析思路上仍然逃不开我一直提到的思路——那就是一个分析的完整链路。当你一层一层往下找问题时,只要抓住了重点,思路不断,找到根本原因就可以解决问题。
|
||||
|
||||
在这个I/O的问题中,难点在于怎么能知道jbd2的原理和参数。应该说,不管是谁,都不能保证自己的知识体系是完整的,那怎么办呢?查资料,各种学习,看源码,看逻辑。实在看不懂,那也没办法,接着修炼基础内功呗。
|
||||
|
||||
所以说性能测试行业中,经常只测不分析,也是因为做性能分析需要的背景知识量有点大,还要不断分析各种新的知识点。不过也就是因为如此,性能测试和性能分析才真的有价值。只测不调只是做了一半工作,价值完全体现不出来。
|
||||
|
||||
## 思考题
|
||||
|
||||
最后问你两个问题吧:为什么TPS上不去时,资源用不上才是更让人着急的问题?以及为什么要在CPU高时查看CPU热点函数呢?
|
||||
|
||||
欢迎你在评论区写下你的思考,我会和你一起交流。也欢迎你把这篇文章分享给你的朋友或者同事,一起交流一下。
|
||||
@@ -0,0 +1,218 @@
|
||||
<audio id="audio" title="32丨当Postgres磁盘读引起I/O高的时候,应该怎么办?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c0/7d/c0bdffcc2bea2e4e21ec47f3ab56297d.mp3"></audio>
|
||||
|
||||
在性能分析的人眼里,性能瓶颈就是性能瓶颈。无论这个性能瓶颈出现在代码层、操作系统层、数据库层还是其他层,最终的目的只有一个结果:解决掉!
|
||||
|
||||
有人可能会觉得这种说法过于霸道。
|
||||
|
||||
事实上,我要强调的性能分析能力,是一套分析逻辑。在这一套分析逻辑中,不管是操作系统、代码还是数据库等,所涉及到的都只是基础知识。如果一个人都掌握这些内容,那确实不现实,但如果是对一个性能团队的要求,我觉得一点也不高。
|
||||
|
||||
在性能测试和性能分析的项目中,没有压力发起,就不会有性能瓶颈,也就谈不上性能分析了。所以每个问题的前提,都是要有压力。
|
||||
|
||||
但不是所有的压力场景都合理,再加上即使压力场景不合理,也能压出性能瓶颈,这就会产生一种错觉:似乎一个错误的压力场景也是有效的。
|
||||
|
||||
我是在介入一个项目时,首先会看场景是否有效。如果无效,我就不会下手去调了,因为即使优化好了,可能也给不出生产环境应该如何配置的结论,那工作就白做了。
|
||||
|
||||
所以要先调场景。
|
||||
|
||||
我经常会把一个性能测试项目里的工作分成两大阶段:
|
||||
|
||||
### 整理阶段
|
||||
|
||||
在这个阶段中,要把之前项目中做错的内容纠正过来。不止有技术里的纠正,还有从上到下沟通上的纠正。
|
||||
|
||||
### 调优阶段
|
||||
|
||||
这才真是干活的阶段。
|
||||
|
||||
在这个案例中,同样,我还是要表达一个分析的思路。
|
||||
|
||||
## 案例问题描述
|
||||
|
||||
这是一个性能从业人员问的问题:为什么这个应用的update用了这么长时间呢?他还给了我一个截图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fe/ac/fe83f42fd8b130b561e2a8f79c7cabac.png" alt="">
|
||||
|
||||
从这个图中可以看到时间在100毫秒左右。根据我的经验,一个SQL执行100ms,对实时业务来说,确实有点长了。
|
||||
|
||||
但是这个时间是长还是短,还不能下结论。要是业务需要必须写成这种耗时的SQL呢?
|
||||
|
||||
接着他又给我发了TPS图。如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ee/24/ee0ec6e2a7038611ccb30d7b5bf66824.png" alt="">
|
||||
|
||||
这个TPS图确实……有点乱!还记得前面我对TPS的描述吧,在一个场景中,TPS是要有阶梯的。
|
||||
|
||||
如果你在递增的TPS场景中发现了问题,然后为了找到这个问题,用同样的TPS级别快速加起来压力,这种方式也是可以的。只是这个结果不做为测试报告,而是应该记录到调优报告当中。
|
||||
|
||||
而现在我们看到的这个TPS趋势,那真是哪哪都挨不上呀。如此混乱的TPS,那必然是性能有问题。
|
||||
|
||||
他还告诉了我两个信息。
|
||||
|
||||
1. 有100万条参数化数据;
|
||||
1. GC正常,dump文件也没有死锁的问题。
|
||||
|
||||
这两个信息应该说只能是信息,并不能起到什么作用。另外,我也不知道他说的“GC正常”是怎么个正常法,只能相信他说的。
|
||||
|
||||
以上就是基本的信息了。
|
||||
|
||||
## 分析过程
|
||||
|
||||
照旧,先画个架构图出来看看。
|
||||
|
||||
每次做性能分析的时候,我几乎都会先干这个事情。只有看了这个图,我心里才踏实。才能明确知道要面对的系统范围有多大;才能在一个地方出问题的时候,去考虑是不是由其他地方引起的;才能跟着问题找到一条条的分析路径……
|
||||
|
||||
下面是一张简单的架构图,从下面这张架构图中可以看到,这不是个复杂的应用,是个非常典型的微服务结构,只是数据库用了PostgreSQL而已。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/21/3d/21e32cd936482c970abb2ef02007563d.jpg" alt="">
|
||||
|
||||
由于这个问题反馈的是从服务集群日志中看到的update慢,所以后面的分析肯定是直接对着数据库去了。
|
||||
|
||||
这里要提醒一句,我们看到什么现象,就跟着现象去分析。这是非常正规的思路吧。但就是有一些人,明明看着数据库有问题,非要瞪着眼睛跟应用服务器较劲。
|
||||
|
||||
前不久就有一个人问了我一个性能问题,说是在压力过程中,发现数据库CPU用完了,应用服务器的CPU还有余量,于是加了两个数据库CPU。但是加完之后,发现数据库CPU使用率没有用上去,反而应用服务器的CPU用完了。我一听,觉得挺合理的呀,为什么他在纠结应用服务器用完了呢?于是我就告诉他,别纠结这个,先看时间耗在哪里。结果发现应用的时间都耗在读取数据库上了,只是数据库硬件好了一些而已。
|
||||
|
||||
因为这是个在数据库上的问题,所以我直接查了数据库的资源。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/dc/78/dcacb613ed1d09dfa56d500c10307e78.png" alt="">
|
||||
|
||||
查看vmstat,从这个结果来看,系统资源确实没用上。不过,请注意,这个bi挺高,能达到30万以上。那这个值说明了什么呢?我们来算一算。
|
||||
|
||||
bi是指每秒读磁盘的块数。所以要先看一下,一块有多大。
|
||||
|
||||
```
|
||||
[root@7dgroup1 ~]# tune2fs -l /dev/vda1 | grep "Block size"
|
||||
Block size: 4096
|
||||
[root@7dgroup1 ~]#
|
||||
|
||||
```
|
||||
|
||||
那计算下来大约就是:
|
||||
|
||||
$(300000*1024)/1024/1024\approx293M$
|
||||
|
||||
将近300M的读取,显然这个值是不低的。
|
||||
|
||||
接下来查看I/O。再执行下iostat看看。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/59/74/59600c6b74472e8d8e3e9c785bb22674.png" alt="">
|
||||
|
||||
从这个结果来看,%util已经达到了95%左右,同时看rkB/s那一列,确实在300M左右。
|
||||
|
||||
接着在master上面的执行iotop。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8b/5a/8b4e86cc2835768bc7477e09a36c3a5a.png" alt="">
|
||||
|
||||
我发现Walsender Postgres进程达到了56.07%的使用率,也就是说它的读在300M左右。但是写的并不多,从图上看只有5.77M/s。
|
||||
|
||||
结合上面几个图,我们后面的优化方向就是:**降低读取,提高写入**。
|
||||
|
||||
到这里,我们就得说道说道了。这个Walsender Postgres进程是干吗的呢?
|
||||
|
||||
我根据理解,画了一个Walsender的逻辑图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a2/3b/a250bc12f25ded65ea6287912891dd3b.jpg" alt="">
|
||||
|
||||
从这个图中就可以看得出来,Walsender和Walreceiver实现了PostgreSQL的Master和Slave之间的流式复制。Walsender取归档目录中的内容(敲黑板了呀!),通过网络发送给Walreceiver,Walreceiver接收之后在slave上还原和master数据库一样的数据。
|
||||
|
||||
而现在读取这么高,那我们就把读取降下来。
|
||||
|
||||
先查看一下几个关键参数:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e3/bf/e3313d522881096d711e77503e0454bf.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/93/b1/93441006fdb611b1b07beec5566129b1.png" alt="">
|
||||
|
||||
这两个参数对PostgreSQL非常重要。checkpoint_completion_target这个值表示这次checkpoint完成的时间占到下一次checkpoint之间的时间的百分比。
|
||||
|
||||
这样说似乎不太好理解。画图说明一下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e9/30/e9fccc9914c54e17e75d1c44baeb9b30.jpg" alt="">
|
||||
|
||||
在这个图中300s就是checkpoint_timeout,即两次checkpoint之间的时间长度。这时若将checkpoint_completion_target设置为0.1,那就是说CheckPoint1完成时间的目标就是在30s以内。
|
||||
|
||||
在这样的配置之下,你就会知道checkpoint_completion_target设置得越短,集中写的内容就越多,I/O峰值就会高;checkpoint_completion_target设置得越长,写入就不会那么集中。也就是说checkpoint_completion_target设置得长,会让写I/O有缓解。
|
||||
|
||||
在我们这个案例中,写并没有多少。所以这个不是什么问题。
|
||||
|
||||
但是读取的I/O那么大,又是流式传输的,那就是会不断地读文件,为了保证有足够的数据可以流式输出,这里我把shared_buffers增加,以便减轻本地I/O的的压力。
|
||||
|
||||
来看一下优化动作:
|
||||
|
||||
```
|
||||
checkpoint_completion_target = 0.1
|
||||
checkpoint_timeout = 30min
|
||||
shared_buffers = 20G
|
||||
min_wal_size = 1GB
|
||||
max_wal_size = 4GB
|
||||
|
||||
```
|
||||
|
||||
其中的max_wal_size和min_wal_size官方含义如下所示。
|
||||
|
||||
max_wal_size (integer):
|
||||
|
||||
>
|
||||
Maximum size to let the WAL grow to between automatic WAL checkpoints. This is a soft limit; WAL size can exceed max_wal_size under special circumstances, like under heavy load, a failing archive_command, or a high wal_keep_segments setting. The default is 1 GB. Increasing this parameter can increase the amount of time needed for crash recovery. This parameter can only be set in the postgresql.conf file or on the server command line.
|
||||
|
||||
|
||||
min_wal_size (integer):
|
||||
|
||||
>
|
||||
As long as WAL disk usage stays below this setting, old WAL files are always recycled for future use at a checkpoint, rather than removed. This can be used to ensure that enough WAL space is reserved to handle spikes in WAL usage, for example when running large batch jobs. The default is 80 MB. This parameter can only be set in the postgresql.conf file or on the server command line.
|
||||
|
||||
|
||||
请注意,上面的shared_buffers是有点过大的,不过我们先验证结果再说。
|
||||
|
||||
## 优化结果
|
||||
|
||||
再看iostat:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/23/45/23ab209aedc2282eee042f0c4b941645.png" alt="">
|
||||
|
||||
看起来持续的读降低了不少。效果是有的,方向没错。再来看看TPS:<br>
|
||||
<img src="https://static001.geekbang.org/resource/image/f1/bc/f12e8ba227c6a66e0dfb9d60794c46bc.png" alt="">
|
||||
|
||||
看这里TPS确实稳定了很多,效果也比较明显。
|
||||
|
||||
这也就达到我们优化的目标了。就像在前面文章中所说的,在优化的过程中,当你碰到TPS非常不规则时,请记住,一定要先把TPS调稳定,不要指望在一个混乱的TPS曲线下做优化,那将使你无的放矢。
|
||||
|
||||
## 问题又来了?
|
||||
|
||||
在解决了上一个问题之后,没过多久,另一个问题又抛到我面前了,这是另一个接口,因为是在同一个项目上,所以对问问题的人来说,疑惑还是数据库有问题。
|
||||
|
||||
来看一下TPS:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/dc/97/dcd93f0218e4311de099404bba562297.png" alt="">
|
||||
|
||||
这个问题很明显,那就是后面的成功事务数怎么能达到8000以上?如果让你蒙的话,你觉得会是什么原因呢?
|
||||
|
||||
在这里,告诉你我对TPS趋势的判断逻辑,那就是**TPS不能出现意外的趋势。**
|
||||
|
||||
什么叫意外的趋势?就是当在运行一个场景之前就已经考虑到了这个TPS趋势应该是个什么样子(做尝试的场景除外),当拿到运行的结果之后,TPS趋势要和预期一致。
|
||||
|
||||
如果没有预期,就不具有分析TPS的能力了,最多也就是压出曲线,但不会懂曲线的含义。
|
||||
|
||||
像上面的这处TPS图,显然就出现意外了,并且是如此大的意外。前面只有1300左右的TPS,后面怎么可能跑到8000以上,还全是对的呢?
|
||||
|
||||
所以我看到这个图之后,就问了一下:是不是没加断言?
|
||||
|
||||
然后他查了一下,果然没有加断言。于是重跑场景。得到如下结果:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bc/a0/bc81f1c069d430d56786dd44e9e28ba0.png" alt=""><br>
|
||||
从这个图上可以看到,加了断言之后,错误的事务都正常暴露出来了。像这种后台处理了异常并返回了信息的时候,前端会收到正常的HTTP Code,所以才会出现这样的问题。
|
||||
|
||||
这也是为什么通常我们都要加断言来判断业务是否正常。
|
||||
|
||||
## 总结
|
||||
|
||||
在性能分析的道路上,我们会遇到各种杂七杂八的问题。很多时候,我们都期待着性能测试中的分析像破案一样,并且最好可以破一个惊天地泣鬼神的大案,以扬名四海。
|
||||
|
||||
然而分析到了根本原因之后,你会发现优化的部分是如此简单。
|
||||
|
||||
其实对于PostgreSQL数据库来说,像buffer、log、replication等内容,都是非常重要的分析点,在做项目之前,我建议先把这样的参数给收拾一遍,不要让参数配置成为性能问题,否则得不偿失。
|
||||
|
||||
## 思考题
|
||||
|
||||
最后问你两个问题吧。为什么加大buffer可以减少磁盘I/O的压力?为什么说TPS趋势要在预期之内?
|
||||
|
||||
欢迎你在评论区写下你的思考,我会和你一起交流。也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。
|
||||
Reference in New Issue
Block a user