共计 7371 个字符,预计需要花费 19 分钟才能阅读完成。
前言:
明天测试部门的小梦找到我,冤屈巴巴的说我写的接口有问题,因为她对这个接口进行压力测试时,发现零碎的吞吐量始终上不去,并且 应用服务器(部署接口我的项目的服务器) 的 CPU、内存等资源的使用率也始终很低,导致始终无奈测试出这个接口的压力峰值。
听小梦说完后,本人心想接口都测试了好几遍了,接口代码 相对不可能有问题的,再说了,有问题也不能抵赖呀,看来得往别的中央上扯扯呀;而后我说道,接口应该是没问题的,可能是我的项目环境部署时有些参数没进行调优吧,例如:连接数大小设置、JVM 参数设置、数据库参数优化等;
而后我接着说道,我的项目是谁给你部署配置的呀,小梦说是小王部署的,而后明天小王也没来下班;我说道,小王没来下班呀,没事,让我来,我这 bug 绝缘体质,任何问题遇到我都会退避三舍的。
下面的情景,我置信大家都可能会遇到过的;接下来咱们就通过这次排查压测问题来聊聊一个 单体零碎 的性能优化应该思考哪些点,以及对这些点该怎么进行调优。
本文主线如下图:
我的项目部署环境:
在进行这次压测问题排查前,先通过下图理解下 接口我的项目的状况、我的项目部署的环境、JMeter 测试脚本 的配置状况;
上图中的接口我的项目、Redis、MySql 都是单机装置部署的。
留神: 再进行下文之前,咱们还须要记住一个前提:本我的项目中的接口代码是不存在问题的,并且数据库查问 SQL 都是最优的,都是走索引查问的,本次压力测试问题不是因为代码导致的,而是因为各种参数未调优造成的;代码调优和 SQL 调优在编码阶段就曾经实现了。
简略性能调优的点:
在上文中的我的项目部署状况和测试脚本的配置状况下进行压力测试时,就呈现了小梦说的零碎吞吐量上不去,以及应用服务器(部署接口我的项目的服务器)的 CPU 等资源使用率都很低的状况;
查看 JMeter 测试时零碎的吞吐量如下图:
查看应用服务器(部署接口我的项目的服务器)的 CPU 使用率很低:
依照测试脚本的 50 个并发以及接口中含有计算密集型操作(加解密和验签)的状况,CPU 是不应该这么低的;哎!什么鬼嘛?
别急,上面咱们就开展具体的排查过程,并在排查过程中逐渐理解各个调优的点;
排查过程就是依照文章结尾中的 本文主线 图片中 简略性能调优的点 开展的。
排查 Tomcat 连接器参数配置:
Tomcat 连接器参数配置只须要思考以下几个方面:
-
Connector 应用哪种 protocol 协定
- HTTP/1.1:默认值,应用的协定与 Tomcat 版本无关
- 倡议应用 NIO2:org.apache.coyote.http11.Http11Nio2Protocol
- 除了 NIO2 协定外,还有 BIO、NIO、APR 协定,能够自行去查阅材料;
- acceptCount:期待队列的长度;当期待队列中连贯的个数达到 acceptCount 时,阐明队列已满,再进来的申请一律被回绝;默认值是 100。
-
maxConnections:Tomcat 在任意时刻接管和解决的最大连接数;
- 当连接数达到最大值 maxConnections 后,Tomcat 会持续接管连贯,直到 accept 期待队列填满。
- 如果最大连接数设置为 -1,则示意禁用 maxconnections 性能,示意不限度 tomcat 容器的连接数;
-
最大连接数默认值与连接器应用的 protocol 协定无关:
- NIO 的默认值是 10000;
- APR/native 的默认值是 8192;
- BIO 的默认值为 maxThreads(如果配置了 Executor,则默认值是 Executor 的 maxThreads);
-
maxThreads:每一次 HTTP 申请达到 Tomcat,都会创立一个线程来解决该申请,那么最大线程数决定了 Tomcat 能够同时解决多少个申请。
- maxThreads 默认 200;
- 倡议:maxThreads 应该设置大些,以便可能充分利用 CPU;当然,也不是越大越好,如果 maxThreads 过大,那么 CPU 会破费大量的工夫用于线程的上下文切换,整体效率可能会升高;这须要依据本人我的项目的状况和服务器硬件配置状况配置适合的值即可;
上面就是本次压测时配置的 Tomcat 连接器参数:
<Connector
port="8080"
protocol="org.apache.coyote.http11.Http11Nio2Protocol"
maxThreads="1000"
maxConnections="2000"
acceptCount="1000"
connectionTimeout="20000"
redirectPort="8443"
enableLookups="false"
maxPostSize="10485760"
compression="on"
disableUploadTimeout="true"
compressionMinSize="2048"
acceptorThreadCount="2"
compressableMimeType="text/html,text/xml,text/css,text/javascript"
URIEncoding="utf-8" />
下面连接器中配置参数:的
- 连接器应用的 protocol 协定是 NIO2
- 最大线程数 maxThreads=”1000″
- 最大连接数 maxConnections=”2000″
- 期待队列大小 acceptCount=”1000″
依据下面连接器配置参数,和压测脚本配置的并发申请才 50 个,确定压测问题应该不是因为连接器参数造成的。
其实在查看 Tomcat 配置文件 server.xml 中的连接器参数前,能够应用上面这个命令先间接统计出 JMeter 与 Tomcat 建设的 Http 连接数:留神 8080 指的是 Tomcat 端口号,应用前改成你的 Tomcat 配置的端口
netstat -pan | grep 8080 | wc -l
如果统计出的连接数与测试脚本中配置的并发数的话差不多统一的话,阐明以后连接器是能够满足连贯申请解决的,初步能够判断不是因为连接器参数造成的本次压测问题;而后再能够去查看配置文件中的参数进行确认下。
排查 JVM 参数配置:
JVM 参数配置,个别并发不高的零碎只须要配置下 Heap 堆内存的大小即可,其余应用默认配置即可。
留神:JVM 参数配置比较复杂,这块的参数调优须要依据具体我的项目状况、服务器硬件配置等进行合理配置。
查看以后压测的接口我的项目 JVM 配置参数:
-
间接应用命令查看:
ps -ef | grep tomcat
后果如下:
-
也能够间接去 catalina.sh 配置文件中查看:
JAVA_OPTS="-server -Xms6000m -Xmx6000m"
-
也能够间接通过 jmap 命令查看 JVM 的堆的配置信息:
留神:上面命令中的 129761 是 tomcat 的 pid 过程号;
jmap -heap 129761
后果如下:
看到这,应该会有人问,为什么你的初始堆大小和最大堆大小(-Xms6000m -Xmx6000m)设置为一样呢?
因为如果虚拟机启动时设置的初始堆内存比拟小,这个时候又须要初始化很多对象数据,那么虚拟机就必须一直地扩充堆内存,这样也会产生一些耗费的。
排查下是否因为 JVM 参数配置的不合理导致:
-
应用下面提到的 jmap 命令查看下以后堆内存的应用状况
jmap -heap 129761
通过查看下面的堆内存的应用状况,发现还有很多空间应用呢,应该不是因为 JVM 参数不合理导致的压测问题;
然而咱别着急,咱再查看下 JVM 中垃圾回收 GC 的状况;
-
应用命令 jstat 查看垃圾回收 GC 的状况:
留神:129761 为 Tocamt 的 pid;1000 示意间隔时间毫秒,10 示意输入 10 次 GC 状况
jstat -gc 129761 1000 10
看了下面的 GC 状况,发现垃圾回收也没什么异样,FGC 总共就 3 次,通过查看的堆应用状况和 GC 垃圾回收的状况能够确认压测的问题不是因为 JVM 的参数导致的。
看来这次压测的问题挺辣手呀,到当初还没有确认出问题起因呢!难道我这 bug 绝缘体质因为 明天吃的比拟多,导致生效了吗?皱眉 ing . . . . .
看来还得接着排查,咱间接导出以后接口服务利用过程的 线程堆栈信息 看看吧。
-
应用命令 jstack 导出线程栈信息:
留神:129761 为 Tomcat 的 pid,/usr/local/test.log 为生成的线程堆栈文件 test.log 的寄存地址
jstack 129761 >> /usr/local/test.log
-
导出线程堆栈信息后,应用命令搜寻出状态为 WAITING 的线程;
留神:找 State 为 WAITING 状态的线程;
命令如下:test.log 文件为刚刚导出的 线程堆栈信息文件
cat test.log | grep -n20 "WAITING"
在命令执行后的搜寻后果中发现了下图内容:
留神:
除了线程状态为 WAITING 的之外,还特地要留神 WAITING 状态线程的办法调用栈中存在我的项目业务代码的;
- 下面图片中就展现了线程状态为 WAITING 期待的调用办法栈中有业务代码;
- 业务代码是去 Redis 中查问数据,上图中第二个圈中的内容就是业务代码,并且这行代码上方是去 Redis 连接池中获取连贯,由此确定是因为 Redis 连贯获取不到,导致线程处于 WAITING 期待状态;
- 因为接口服务零碎中很多线程获取不到 Redis 连贯导致阻塞,从而无奈充分利用 CPU;所以在压测时,服务器 CPU 的使用率也压不下来,CPU 的使用率很低;
至此,终于排查出了一处问题中央,不容易呀,感觉排查问题比写代码艰难多了!
下面通过线程的堆栈信息排查出 Redis 连贯不够用,应该是 Redis 连接池中的最大连接数配置的比拟小,那接下来就确定下连接池的配置数据。
排查连接池参数配置:
上文中,在线程的堆栈信息中排查出存在很多线程因为获取不到 Redis 连贯导致处于期待阻塞状态;
接下来,咱们就来具体统计下当初处于压测中的应用服务器(部署接口我的项目的服务器)与 Redis 建设的连接数,顺带一起再排查下我的项目中应用的 MySql 的连接池配置是否也存在问题?
-
应用命令查看以后压测中的应用服务器与 Redis 建设的连接数
留神:6379 为 redis 的端口号
netstat -pan | grep 6379 | wc -l
执行命令查问出与 Redis 建设的连接数为 10,这在测试脚本 50 个并发申请下是肯定不够用的呀,应该是配置的 Redis 连接池的最大连接数比拟小;
而后通过看接口代码中的配置文件发现,的确 Redis 连接池中的最大连接数配置是 10 个;最初批改了最大连接数为 300 个;留神:这个最大连接数须要依据我的项目状况和测试计划来设置,不必设置的太大。
-
接着,应用命令顺带统计下以后处于压测中的应用服务器(部署接口我的项目的服务器)与 MySql 建设的连接数
留神:3306 为 MySql 的端口号
netstat -pan | grep 3306 | wc -l
执行命令发现曾经建设的 MySql 连接数是 50,而后通过查看配置文件的连接池中最大的连接数为 100,发现还没有达到最大连接数,阐明 JDBC 连贯够用,本次压测问题不是因为 JDBC 连贯不够用导致的;并且在上文中查看线程的堆栈信息时,也没有发现调用 JDBC 操作的线程处于 WAITING 期待状态。
排查完了数据库连接池了,咱们就再排查下 MySql 数据库是否进行了参数调优;
如果 MySql 数据库没有进行调优的话,那在压测时就会呈现数据库的读写性能比拟差,也就是我的项目中在进行 JDBC 增删改查操作时会比较慢,进而导致存在 JDBC 操作的线程执行慢,无奈充分利用 CPU 的并发计算能力,所以也会导致应用服务器的 CPU 使用率比拟低,最终压测时整个接口利用零碎的吞吐量也很低。
排查 MySql 数据库调优配置:
接下来咱排查下 MySql 数据库是否进行了参数调优;
排查前先聊一个因为之前 MySql 数据库未进行优化,而导致的压测问题。
MySql 数据库未调优,引发的压测问题 简述:
之前压测时的场景还原:
在压测前所有该调优的中央都调了,就独自遗记对 MySql 数据库进行调优了,最终导致对利用压测时,发现应用服务器(部署接口我的项目的服务器)的 CPU 的使用率始终浮动在 60% 左右,始终压不下来,即便加大压测时的并发申请数,发现还是不行;
最终通过查看 MySql 服务器的磁盘 IO 的状况和 MySql 数据库过程的 CPU、内存等使用率发现 MySql 没有被优化,所以导致 MySql 数据库的读写能力被限度,进而导致利用并发解决能力降落,从而导致应用服务器的 CPU 使用率始终升不下来。
-
应用命令统计下压测时 MySql 数据库所部署在的服务器的磁盘 IO 性能:
留神:1 指的是 间隔时间 1 秒,5 指的是统计 5 次 IO 的状况;
iostat -xdk 1 5
后果如下:
只须要留神图中圈起来的中央;await 指的是 磁盘读写时的等待时间,这个值越小越好;%util 指的是磁盘整体的负载状况,值越大阐明磁盘负载快达到极限了。
-
应用命令查看 MySql 过程的 CPU、内存的使用率:
留神:14208 是 MySql 的 pid;
top -p 14208
后果如图:
留神:
过后就是通过这两条命令统计出的数据,发现数据库服务器磁盘 IO 的负载始终处于 90%,快达到极限了;然而 MySql 过程的 CPU、内存的使用率却很低;
通过这两条数据能够初步确定 MySql 没有进行优化;如果 MySql 进行了调优,那么 MySql 的过程在压测时的 CPU、内存等使用率不可能这么低,并且磁盘 IO 的负载也不会达到了极限;
得出初步论断后,而后去查看 MySql 数据库的配置文件 my.cnf,发现的确没有进行一些参数的优化,只是简略配置了最大连接数为 500;留神,MySql 数据库默认的最大连接数是 100 个;
最初在 my.cnf 配置文件中简略配置了下 innodb 存储引擎缓冲池大小 等;这里就不具体形容调优的参数了,因为调优参数都是须要依据我的项目状况、服务器硬件配置等综合思考配置的;大家能够自行网上查阅 MySql 数据库调优的相干材料;
配置好后,重启了 MySql 数据库,而后从新进行压测,发现应用服务器(部署接口我的项目的服务器)的 CPU 使用率下来了,MySql 数据库过程的 CPU、内存等使用率也升上去了,并且磁盘 IO 的负载也降下来了,最初整个利用零碎的吞吐量也下来了,也反对更大的并发数了。
说完了下面之前遇到的一个压测小场景,咱们接着依照下面的排查步骤排查这次压测的问题,发现 MySql 数据库曾经进行了调优,磁盘 IO 的读写速度也是 OK 的,所以说本次压测问题与 MySql 数据库参数配置无关。
排查完了 MySql 数据库后,咱接着排查下 Linux 服务器的参数是否进行过调优吧!
排查 Linux 服务器参数配置:
服务器参数调优这块目前理解的不多,只晓得配置下服务器的 文件句柄数 fd;倡议将服务器的文件句柄数设置的大些;为什么设置大些呢?
因为支流操作系统的设计是将 TCP/UDP 连贯采纳与文件一样的形式去治理,即一个连贯对应于一个文件描述符(文件句柄);
支流的 Linux 服务器默认所反对最大 文件句柄数 数量为 1024,当并发连接数很大时很容易因为 文件句柄数 有余而呈现 “open too many files” 谬误,导致新的连贯无奈建设。
倡议将 Linux 服务器所反对的最大句柄数调高数倍(具体与服务器的内存数量相干)。
-
应用命令查看以后服务器的文件句柄数:
ulimit -a
后果如图:
上图中的文件句柄数是曾经批改过的;设置文件句柄数能够设置长期值,也能够设置永恒值,倡议设置成永恒值,因为长期值遇到一些状况会生效的。
配置文件句柄数能够自行去网上查问,材料很多的,本文就不做过多形容了。
聊完服务器参数调优配置后,咱最初再聊聊 JMeter 测试工具在压测时有没有该留神的点!
JMeter 分布式测试:
这里次要聊聊 JMeter 分布式测试的内容;什么时候须要进行分布式测试,当你的单机 JMeter 服务器因为硬件配置限度无奈构建出足够的并发压力时,这是就须要进行分布式部署测试了;
怎么判断 单机的 JMeter 服务器 曾经无奈构建出足够的并发压力了呢?
- 通过查看 JMeter 测试服务器的 CPU 的使用率状况,当 JMeter 测试服务器的 CPU 使用率达到 90% 多时,此时就能够初步确认 CPU 无奈在提供足够的计算能力来构建并发压力了;
- 所以此时能够将单机的 JMeter 改为分布式 JMeter 进行测试,只有构建出足够的压力,能力将应用服务器(部署接口我的项目的服务器)的 CPU、内存等资源的使用率压下来,从而测试出接口的压力峰值。
所以,当单机 JMeter 压测时发现应用服务器(部署接口我的项目的服务器)CPU、内存等使用率压不下来的话,也能够去排查下是否是因为 JMeter 服务器硬件配置有余造成的;
本次压力问题,发现 JMeter 测试服务器在压测时 CPU 的使用率达到了 80% 多,阐明此台测试服务器因为硬件限度也是无奈构建更大的并发压力了,所以倡议将单机 JMeter 测试改为 分布式测试;
这里 JMeter 分布式测试网上教程很多,本文就不做过多形容了。
到这里,自己认为的所有该调优的点都排查完;今啊老百姓真呀真呀真快乐!
本次排查总结:
排查完下面的几个点后,最终发现了导致本次压测问题的起因:
- Redis 连接池的最大连接数配置的偏小
- JMeter 单机测试无奈结构出足够的并发压力
正是因为下面的两个问题导致了本次压测呈现了上面的问题:
- 压测时利用零碎的吞吐量偏低
- 并且应用服务器(部署接口我的项目的服务器)的 CPU、内存等使用率偏低;
将下面的两处问题批改过后,从新进行压测,发现一切正常了!
在此,再阐明下本次压测我的项目就是一个简略的单体我的项目,我的项目中没有波及到分库分表、各种中间件以及分布式等,所以排查起来还绝对简略些,否则问题排查会更加艰难的。
因为自己程度无限,如果有未提及到的调优的点,请评论留言呀!
扩大
在排查问题时,如果有一些 排查工具 的话,将极大的不便咱们排查问题,从而使咱们疾速的定位到问题起因!
上面就介绍两个在排查问题十分好用的辅助工具:
- Arthas:阿里开源的线上 Java 利用诊断工具
- jvisualvm:JDK 中自带的 JVM 运行状态监控工具
❤ 点赞 + 评论 + 转发 哟
如果本文对您有帮忙的话,请挥动下您爱发财的小手点下赞呀,您的反对就是我一直创作的能源,谢谢!
您能够 VX 搜寻【木子雷】公众号,大量 Java 学习干货文章,您能够来瞧一瞧哟!