高手的存在,就是让服务10亿人的时候,你感觉只是为你一个人服务......

性能测试瓶颈分析方法指导

目录
  1. 1. 一、 性能瓶颈分析流程
  2. 2. 二、 测试脚本优化
    1. 2.1. 1、 减少日志打印
    2. 2.2. 2、 减少字符类型转换
    3. 2.3. 3、 入参放到before方法中
  3. 3. 三、 硬件资源使用率高
    1. 3.1. 1、us cpu高
    2. 3.2. 2、sy cpu高
    3. 3.3. 3、io高
    4. 3.4. 4、net高
    5. 3.5. 5、物理内存高
    6. 3.6. 6、jvm内存高
  4. 4. 四、 应用日志分析
    1. 4.1. 1、 系统日志
    2. 4.2. 2、 业务日志
    3. 4.3. 3、 rpc调用日志
  5. 5. 五、 GC频繁
  6. 6. 六、 内存溢出
    1. 6.1. 2、永久代 / 方法区溢出
    2. 6.2. 3、栈内存溢出
    3. 6.3. 4、系统内存溢出
  7. 7. 七、 线程分析
    1. 7.1. 1、线程阻塞
    2. 7.2. 2、线程池不够
  8. 8. 八、 数据库问题
    1. 8.1. 1、 数据库连接池不释放
    2. 8.2. 2、 数据库死锁
    3. 8.3. 3、 SQL使用不合理
  9. 9. 九、 redis问题
    1. 9.1. 1、 redis命中率低
    2. 9.2. 2、 redis连接数不够
  10. 10. 十、 方法执行耗时分析
    1. 10.1. 1、 通过日志分析
    2. 10.2. 2、 通过arthas分析

一、 性能瓶颈分析流程

1、 查看性能测试脚本,检查脚本设计是否合理。
2、 查看压力机的CPU/IO/NET/MEM硬件资源使用情况,是否达到资源瓶颈。
3、 查看服务器的CPU/IO/NET/MEM硬件资源使用情况,是否达到资源瓶颈。
4、 查看项目日志,是否有报错、异常现象。
5、 查看数据库死锁、连接池情况。
6、 查看app的JVM堆栈和GC等情况。


二、 测试脚本优化

1、 减少日志打印

脚本中减少grinder.logger.info();grinder.logger.error();日志打印。

2、 减少字符类型转换

入参和出参的报文拼接中,减少String、Map类型的转换。对于出参为String类型的不需要转换为Map再做断言判断。
Alt text

3、 入参放到before方法中

入参的拼接尽量放到before方法中,减少对@Test耗时计算的干扰。
Alt text


三、 硬件资源使用率高

1、us cpu高

使用nmon命令(按l键)查看cpu使用情况,us cpu过高,超过50%以上。
Alt text

排查手段:
(1)使用top命令是哪个进程消耗CPU高。
(2)再找到CPU消耗高的线程:top -H -p 进程号
(3)把线程号转换成16进制:printf “%x\n” 线程号。
(4)再用jstack命令分析这个线程是在干什么:jstack 进程号 | grep 16进制的线程号。
(5)或者直接执行show-busy-java-threads脚本,会按照线程消耗cpu的大小排序。
Alt text

2、sy cpu高

使用nmon命令(按l键)查看cpu使用情况,sy cpu过高,超过50%以上。
Alt text

排查手段:
(1)首先查看磁盘繁忙程度、磁盘的队列(iostat、nmon)。
(2)如果磁盘没有问题,则使用strace&&ltrace命令查看系统内核调用情况。
Alt text

3、io高

使用nmon命令(按d键)查看io使用情况。
Alt text

如果io高使用iostat -x命令对磁盘操作活动进行监视。
Alt text

排查手段:
(1) 如果 %iowait 的值过高,表示磁盘存在 I/O 瓶颈。
(2) 如果 %util 接近 100%,说明产生的 I/O 请求太多,I/O 系统已经满负荷,该磁盘可能存在瓶颈。
(3) 如果 svctm 比较接近 await,说明 I/O 几乎没有等待时间;
(4) 如果 await 远大于 svctm,说明 I/O 队列太长,I/O 响应太慢,则需要进行必要优化。
(5) 如果 avgqu-sz 比较大,也表示有大量 IO 在等待。
通过iotop命令(按C键,按照cpu消耗排名)来监控io压力大的进程。
Alt text

4、net高

使用nmon命令(按n键)查看network的使用情况。
Alt text

排查手段:
(1) 单台服务器的网卡一般为1Gbps,转换为Byte为128MBps。检查网卡入口(Recv)和出口(Trans)的带宽使用,如果入口带宽使用高则检查入口报文大小;如果出口带宽使用高则检查出口报文大小。

5、物理内存高

使用free -m命令查看物理内存的使用情况。
Alt text

排查手段:
(1) 检查-/+ buffers/cache行,used代表实际使用的内存,free代表空闲的内存。
(2) 如果used内存大于总内存的80%,使用top命名(m键)按照内存使用排序,确认进程占用内存情况。
(3) 在压测过程中,通过vmstat命名观察内存的页切换情况,如果si,so比较高,说明内存不够。
Alt text

6、jvm内存高

使用JConsole、JVisualVM等工具连接服务器,查看JVM内存情况。
Alt text

JVisualVM连接服务器方法:

1
2
3
4
5
6
7
8
9
1.进入监控服务器的jdk的bin目录下,新建文件:jstatd.all.policy
2.添加内容如下:注意替换jdk地址:
grant codebase "file:/opt/wildfly/java64/jdk1.7.0_25/lib/tools.jar" {
permission java.security.AllPermission;
};
执行
./jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=192.168.200.136
3.本地安装jdk,进入jdk bin目录,执行jvisualvm.exe
4.添加远程监控服务器的ip,开启监控。

排查手段:
(1) 压测一段时间,观察JVM的堆内存多次GC后一直大于70%。
(2) 在被测服务器上使用jmap -dump:live,format=b,file=test.dump pid生成test.dump文件,然后使用MAT进行分析内存对象占用情况。


四、 应用日志分析

1、 系统日志

日志路径:/opt/wildfly/standalone/log/server.log
记录系统启停日志信息和jboss系统自身日志,如果有大量业务日志或异常打印,则需求优化。

2、 业务日志

日志路径:/opt/logs/xxx项目
记录业务日志,如果有大量业务异常打印,则需求优化。一般情况无debug日志打印,如果发现打印大量debug日志需要调整日志级别。

3、 rpc调用日志

日志路径:/opt/logs/rpc/xxx项目
记录rpc请求日志,rpc的报错、异常信息和调用耗时都记录在内。如果系统有未mock的rpc请求会体现在rpc_span_request.log和rpc_statistic.log日志中。


五、 GC频繁

现象:
压测执行一段时间后,系统处理能力下降。使用jstat –gcutil(gc整体统计)命令,发现YGC列、YGCT列、FGC列、FGCT列增长迅速。
Alt text

1
2
3
4
5
6
7
8
9
10
S0 :年轻代中第一个survivor(幸存区)已使用的占当前容量百分比 
S1 :年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E :年轻代中Eden已使用的占当前容量百分比
O :old代已使用的占当前容量百分比
P :perm代已使用的占当前容量百分比
YGC :从应用程序启动到采样时年轻代中gc次数
YGCT :从应用程序启动到采样时年轻代中gc所用时间(s)
FGC :从应用程序启动到采样时old代(全gc)gc次数
FGCT :从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)

排查手段:
(1) 使用jstat –gccause(gc原因分析)命令,LGCC列表示GC产生的原因。
Alt text

(2) 查看GC日志(/opt/wildfly/standalone/log/verbose.gc.0.current),分析gc具体原因;或者将gc日志上传至gc分析平台进行分析(http://www.gceasy.io/)
Alt text
Alt text

解决思路:
(1) 优化对象生成大小与频率。
(2) 调整jvm内存配置(-Xms –Xmx –Xmn等)。
(3) 选择合适的垃圾回收器(目前使用最多效果最好的CMS垃圾回收器)。


六、 内存溢出

##1、堆内存溢出
现象:
(1)压测执行一段时间后,系统处理能力下降。这时用JConsole、JVisualVM等工具连上服务器查看GC情况,每次GC回收都不彻底并且可用堆内存越来越少。
(2)压测持续下去,最终在日志中有报错信息:java.lang.OutOfMemoryError.Java heap space。
排查手段:
(1)使用jmap -histo pid > test.txt命令将堆内存使用情况保存到test.txt文件中,打开文件查看排在前的类中有没有熟悉的或者是公司标注的类名,如果有则高度怀疑内存泄漏是这个类导致的。
(2)如果没有,则使用命令:jmap -dump:live,format=b,file=test.dump pid生成test.dump文件,然后使用MAT进行分析。

2、永久代 / 方法区溢出

现象:压测执行一段时间后,日志中有报错信息:java.lang.OutOfMemoryError: PermGen space。
产生原因:由于类、方法描述、字段描述、常量池、访问修饰符等一些静态变量太多,将持久代占满导致持久代溢出。
解决方法:修改JVM参数,将XX:MaxPermSize参数调大。尽量减少静态变量。

3、栈内存溢出

现象:压测执行一段时间后,日志中有报错信息:java.lang.StackOverflowError。
产生原因:线程请求的栈深度大于虚拟机所允许的最大深度,一般是递归或循环调用造成。
解决方法:
(1)调整代码逻辑,减少递归和循环调用。
(2)修改JVM参数,将Xss参数改大,增加栈内存。

4、系统内存溢出

现象:压测执行一段时间后,日志中有报错信息:java.lang.OutOfMemoryError: unable to create new native thread。
产生原因:操作系统没有足够的内存提供创建线程。线程(-Xss)不在堆内存中管理,占用机器额外的物理内存。
解决方法:
(1)减少线程数量。
(2)增加物理内存。


七、 线程分析

1、线程阻塞

现象:
(1)响应时间长、响应时间超时。
排查手段:
(1)使用jstack命令查看Java进程下所有线程的情况:jstack -l 进程号。
(2)也可以使用JConsole、JVisualVM及Arthas等工具直接查看所有线程的情况。
(3)如果有Blocked状态的线程,说明有线程死锁的状况。如果大量线程都是Waiting状态,则需要去关注数据库和中间件,可能会有排队情况。

2、线程池不够

现象:
(1)响应时间长、响应时间超时。
排查手段:
(1)使用jstack命令查看Java进程下所有线程的情况:jstack -l 进程号。
(2)也可以使用JConsole、JVisualVM及Arthas等工具直接查看所有线程的情况。
(3)如果所有的线程都被占用处理业务逻辑,则说明线程池不够。
解决方法:
(1) http请求在jboss配置文件上调整线程池大小,默认为cpu核数*16。修改/opt/wildfly/bin/standalone.conf

1
2
3
<subsystem xmlns="urn:jboss:domain:io:1.1">
<worker name="default" task-max-threads="200"/>
<buffer-pool name="default"/>

(2) rpc请求在rpc平台上调整线程池大小。


八、 数据库问题

1、 数据库连接池不释放

现象:压测进行一段时间后,报连接超时的错误。
排查手段:
(1)去数据库查看应用程序到数据库的连接有多少个:show full processlist(mysql)。如果应用程序中配置的最大连接数为30,而通过命令查看到的从应用服务器连接过来的连接数也为30,证明数据库连接池占满了。
(2)将应用程序中的最大连接数改大一点(比如100),再重新进行压测,如果还是出现连接池被占满的情况,则证明是数据库连接池不释放造成的.。

2、 数据库死锁

现象:
(1)压测进行一段时间后,报连接超时的错误。
(2)程序在执行的过程中,点击确定或保存按钮,程序没有响应,也没有出现报错。
排查手段:查看数据库日志,看有没有死锁的情况:show engine innodb status。

3、 SQL使用不合理

现象:数据库事物响应时间慢
排查手段:
(1) 如果是DB2数据库,使用db2top命令(db2top –d db实例名)查看sql执行情况,利用explain来优化这条SQL语句。

1
2
3
4
5
6
1. 先查找Database name
db2 list database directory;
2. 执行db2top -d <database name>
按大写D
按z,倒序排序
L,输入sql的序列号,查看sql明细

(2) 如果是mysql数据库,查看慢查询日志(/mysql/data/mysql_slow.log)或使用mytop命令排查sql;再利用explain来优化这条sql语句。


九、 redis问题

1、 redis命中率低

排查手段:
(1) linux命令 ./redis-cli info。
(2) keyspace_hits:XX #命中key的次数。
(3) keyspace_misses:XX #未命中的次数。
(4) redis的命中率keyspace_hits/(keyspace_misses+ keyspace_hits)<95%说明命中率不够。

2、 redis连接数不够

排查手段:
(1) linux命令 ./redis-cli info。
(2) connected_clients#当前redis的连接数(redis最大连接数默认1W)
(3) 检查app服务器的redis配置(一般情况在scm上配置),如果app的redis最大连接数*app数量与redis的连接数接近,说明连接数可能不够。


十、 方法执行耗时分析

1、 通过日志分析

通过开发人员在业务日志中打印方法执行的耗时日志来分析。
排查手段:
(1) 一般情况日志放在app服务器/opt/logs/xxx项目目录下,找到业务执行的线程号,执行grep ‘线程号’ xxx.log,筛选完成的业务流程,计算方法执行的耗时。

2、 通过arthas分析

通过开源工具arthas分析业务方法执行的耗时。
排查手段:
(1) 使用监控平台(http://10.244.147.59:8884/backstage/index)或自己安装arthas,执行trace xxxx类 xxx方法监控该方法中所有的执行耗时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Arthas安装方法:
步骤一:配置JAVA环境变量
编辑 /etc/profile,添加JAVA配置
export JAVA_HOME=/opt/wildfly/openjdk/openjdk-1.8.0_92/
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
添加完记得执行source /etc/profile ,使文件生效
步骤二:安装arthas
unzip arthas-3.1.0-bin.zip
chmod -R 777 * ----把文件全部赋最高权限
切换jbossuser用户
su - jbossuser
进入arthas安装目录,执行bash install-local.sh
步骤三:监控
执行java -jar arthas-boot.jar
选择需要监控的进程
使用trace命令:trace xxxx类 xxx方法
https://alibaba.github.io/arthas/trace.html