• 新版网站前后台即将上线,2019年将致力于提高文章质量,加大原创力度,打造一个更加舒适的阅读体验!
  • 极客文库小编@勤劳的小蚂蚁,为您推荐每日资讯,欢迎关注!
  • 新版网站前后台即将上线,2019年将致力于提高文章质量,加大原创力度,打造一个更加舒适的阅读体验!
  • 如果有任何体验不佳的地方,欢迎向客服反馈!

如何做性能测试

性能优化的常见概念

吞吐量(TPS, QPS):简单来说就是每秒钟完成的事务数或者查询数。通常吞吐量大表明系统单位时间能处理的请求数越多,所以通常希望 TPS 越高越好
响应时间:即从请求发出去到收到系统返回的时间。响应时间一般不取平均值,而是要去掉不稳定的值之后再取均值,比如常用的 90%响应时间,指的就是去掉了 10%不稳定的响应时间之后,剩下 90%的稳定的响应时间的均值。从聚类的观点看,其实就是去掉离群点。
错误率:即错误请求数与总请求数之比。随着压力增加,有可能出现处理请求处理不过来的情况,这时错误数会不断增加。

三者有极大的关联,任何孤立的数据都不能说明问题。典型的关系是,吞吐量增加时,响应延迟有可能增加,错误率也有可能增加。因此,单拿出一个 10w 的 TPS 并不能说明问题。

性能调优的思路

一般情况,调优需要有个前提条件,即无论是用线上的真实流水还是线下的压力测试让问题扩大化,明显化。

根据这些比较明显的现象去初判问题,收集证据去验证初判结果成立,然后分析现象产生的原因,并尝试解决问题。

1.性能摸底测试

对于新上的系统或者是有过较大代码改动的系统来说,做一次摸底测试还是很有必要的。一般来说,期望摸底的测试是一次对单机的压力测试。压力测试可以帮你大概搞清楚系统的极限 TPS 是多少,在压力上来时有没有暴露一些错误或者问题,系统大致的资源占用情况是什么,系统可能的性能瓶颈在哪。

如下是一次摸底测试的配置和结果。这是用 12000 并发用户对 10 台机器压测的结果,可以看出,TPS 到 7w 多,平均响应时间为 82ms,错误率在 2.5%。

从图中还可以得到哪些信息?首先,TPS 在后期迅速下落,实际上已经支撑不了如此大的并发量,即进入崩溃区,这里有几个可能,一是系统根本承受不了如此大的并发量,二是系统中间有问题导致 TPS 下跌。其次,随着时间增长,错误率显著增加,说明系统已经处理不了如此多的请求。结合前面两点以及相对平稳的平均响应时间,大致可以推断系统没法承受如此大的并发。另外,由于是 10 台机器,单台的 TPS 大概在 7000 多,今后的调优可以以此为依据。
对于应用的特点,也要在这时候分析出来,即应用可能占用的资源。比如是 CPU 密集型应用还是 IO 密集型应用(还可以细分为是磁盘密集还是网络 )




2.定义性能优化的目标

经常听到人说,做个性能优化,吞吐量越高越好;或者做个性能测试,目标 TPS 是 50000。可实际拿到这个信息,能够做性能测试吗?这个目标足够清晰吗?

事实上,在我看来,未定义清晰的目标去做性能测试都是耍流氓。
性能优化的目标一般是吞吐量达到多少,90%响应时间小于多少,错误率小于多少。同时还需要关注其他的性能指标,cpu 使用情况,内存使用情况,磁盘使用情况,带宽使用情况等。对于摸底测试已经发现问题的,可以针对该问题专门优化,比如负载较高,cpu 消耗过大,则目标可能是 TPS,响应时间以及错误率不变的情况下降低 CPU 负载。或者内存增长过快,gc 较为频繁,则目标可能是找出可能的内存泄露,或者进行相关的 jvm 内存调优。总之,目标可以比较灵活调整,但一定要明确。

3.分析

分析的过程较为灵活,基本上是一千个系统有一千种表现。这里很难一一说明。仅谈谈一些常见的方法,工具以及思路。

针对 CPU:

针对 cpu 的监控,其实 linux 已经提供了两个比较好用的工具,一个是 top,一个是 vmstat。关于这两个命令就不细说了,参考这里 top(http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/top.html),vmstat(http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/vmstat.html)
关于 cpu 主要关注 4 个值:us(user), sy(system), wa(wait), id(idle)。理论上他们加起来应该等于 100%。而前三个每一个值过高都有可能表示存在某些问题。

us 过高:

a. 代码问题。比如一个耗时的循环不加 sleep,或者在一些 cpu 密集计算(如 xml 解析,加解密,加解压,数据计算)时没处理好
b. gc 频繁。一个比较容易遗漏的问题就是 gc 频繁时 us 容易过高,因为垃圾回收属于大量计算的过程。gc 频繁带来的 cpu 过高常伴有内存的大量波动,通过内存来判断并解决该问题更好。
小技巧:如何定位 us 过高的线程并查看它的状态。
a. top 命令找到消耗 us 过高的进程 pid
b. top -Hp pid 找到对应的线程 tid
c. printf %x tid 转为 16 进制 tid16
d. jstack pid | grep -C 20 tid16 即可查到该线程堆栈

sy 过高:

a. 上下文切换次数过多。通常是系统内线程数量较多,并且线程经常在切换,由于系统抢占相对切换时间和次数比较合理,所以 sy 过高通常都是主动让出 cpu 的情况,比如 sleep 或者 lock wait, io wait。

wa 过高:

a. 等待 io 的 cpu 占比较多。注意与上面情况的区别,io wait 引起的 sy 过高指的是 io 不停的 wait 然后唤醒,因为数量较大,导致上下文切换较多,强调的是动态的过程;而 io wait 引起的 wa 过高指的是 io wait 的线程占比较多,cpu 切换到这个线程是 io wait,到那个线程也是 io wait,于是总 cpu 就是 wait 占比较高。

id 过高:

a. 很多人认为 id 高是好的,其实在性能测试中 id 高说明资源未完全利用,或者压测不到位,并不是好事。

针对内存:

关于 java 应用的内存,通常只需要关注 jvm 内存,但有些特殊情况也需要关注物理内存。关于 jvm 内存,常见的工具有 jstat(http://blog.csdn.net/fenglibing/article/details/6411951), jmap(http://www.cnblogs.com/ggjucheng/archive/2013/04/16/3024986.html), pidstat(https://linux.cn/article-4257-1.html), vmstat, top

jvm 内存:

异常 gc :

a. 通常 gc 发生意味着总归是有一块区域空间不足而触发 gc。而许多导致异常 gc 的情况通常是持有了不必要的引用而没有即时的释放,比如像 cache 这样的地方就容易处理不好导致内存泄露引发异常 gc。
b. 有可能是程序的行为是正常的,但是由于没有配置对合适的 gc 参数导致异常 gc,这种情况通常需要调优 gc 参数或者堆代大小参数。
c. Full gc 发生的情况:

  • 永久代满
  • 年老代满
  • minor gc 晋升到旧生代的平均大小大于旧生代剩余大小
  • CMS gc 中 promotion fail 或 concurrent mode fail

OOM:

a. OOM 经常伴随着异常 gc,之所以单独拿出来讲,是因为它的危害更大一些,异常 gc 顶多是收集速度过快或者回收不了内存,但是起码有个缓冲时间,但是出了 OOM 问题就大了。至于各种类型的 OOM 如何区分,如何发生,请参考这里(http://www.jianshu.com/p/2fdee831ed03),算是总结得比较全面的。对于常见的 OOM,基本上可以一下子指出问题所在。
b. heap 区,对象创建过多或持有太多无效引用(泄露)或者堆内存分配不足。使用 jmap 找到内存中对象的分布,使用 ps 找到相应进程及初始内存配置。
c. stack 区, 不正确的递归调用。
d. perm 区,初始加载包过多,分配内存不足。
e. 堆外内存区,分配 ByteBuffer 未释放导致。

针对 IO:

IO 分为网络 IO 和文件 IO,针对网络 IO 比较有用的工具有 sar(https://linuxstory.org/generate-cpu-memory-io-report-sar-command/),netstat(https://linux.cn/article-2434-1.html),netstat 是一个非常牛逼的命令,可以助于排查很多问题, 针对文件 io 的工具有 pidstat,iostat(http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/iostat.html)

文件 IO:

a. 从技术上来说,对于大文件 IO 可以采取的措施是异步批处理,采用异步方式用于削峰并累计 buffer,采用批处理能够让磁盘寻道连续从而更加快速。

网络 IO:网络 IO 的问题较为复杂,仅举几个常见的

a. 大量 TIME_WAIT。根据 TCP 协议,主动发起关闭连接的那一方,关闭了自己这端的连接后再收到被动发起关闭的那一方的关闭请求后,会将状态变为 TIME_WAIT,并等待 2MSL, 目的是等待自己的回执发送到对方。如果在服务器上发现大量 TIME_WAIT,说明服务器主动断开了连接,什么情况下服务器会主动断开连接,很可能是客户端忘了断开连接,所以一个典型的案例就是 jdbc 连接忘记关闭,则数据库服务器可能会出现大量的 TIME_WAIT 状态。
b. 大量 CLOSE_WAIT。CLOSE_WAIT 状态,在收到主动关闭连接的一方发出关闭连接之后,被动关闭的一方进入 CLOSE_WAIT 状态,如果这时候被 hang 住了没进行后续关闭,则会出现大量 CLOSE_WAIT。啥情况会被 hang 住呢,举几个例子,比如刚刚的忘记关闭数据库连接,在应用服务器这端,大量的浏览器请求进来,由于没有连接池连接被 hang 住,这时候浏览器等待一定时间超时发送关闭连接请求,而应用服务器这边由于 servlet 线程被 hang 住了,自然没有办法走第二个关闭回去。因此在应用服务器出现大量 CLOSE_WAIT。另一个例子是 httpClient 的坑,在调用 response.getEntity(); 前都不会做 inputStream.close(),如果在调用 response.getEntity()前就返回了,就狗带了。(这个例子可以参考 http://blog.csdn.net/shootyou/article/details/6615051)

4.优化并重新测试验证



丨极客文库, 版权所有丨如未注明 , 均为原创丨
本网站采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行授权
转载请注明原文链接:如何做性能测试
喜欢 (0)
[247507792@qq.com]
分享 (0)
勤劳的小蚂蚁
关于作者:
温馨提示:本文来源于网络,转载文章皆标明了出处,如果您发现侵权文章,请及时向站长反馈删除。

欢迎 注册账号 登录 发表评论!

  • 精品技术教程
  • 编程资源分享
  • 问答交流社区
  • 极客文库知识库

客服QQ


QQ:2248886839


工作时间:09:00-23:00