压测准备和性能瓶颈定位方法
本文主要分享一些压测优化需要做的准备工作以及问题定位的方法,针对压测体现出来的问题可以采取方法可以参照之后的文章
性能压测准备
明确性能目标和场景
确定性能指标
- 吞吐量(TPS,QPS)
- 响应时间(Response Time)
- 并发用户数
- 错误率
确定业务场景
- 选择关键的业务流程进行压测
- 考虑常见的用户行为和高峰时段的负载
准备测试环境
搭建测试环境
- 确保测试环境与生产环境尽可能一致,包括硬件配置、网络环境、数据库等
- 配置独立的测试环境,避免影响实际业务
数据准备
- 准备充足且合理的数据集,以模拟真实的业务场景
- 保证数据的一致性和完整性
准备压测工具
- 主流服务端压测工具为 JMeter
做好监控
无监控,不调优
- 使用 Prometheus + Grafana 构建系统实时监控体系
- 开启服务的 GC 日志和 OOM日志
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintHeapAtGC -XX:+PrintReferenceGC -XX:+PrintGCApplicationStoppedTime -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -Xloggc:/usr/local/src/logs/manager-service-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=14 -XX:GCLogFileSize=100M
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/src/logs/manager-service/dump
GC参数说明
#必备
-XX:+PrintGCDetails # 打印GC的详细信息,会打印 youngGC FullGC前后堆【新生代,老年代,永久区】的使用情况以及 GC 时用户态 CPU 耗时及系统 CPU 耗时及 GC 实际经历的时间
-XX:+PrintGCDateStamps 打印CG发生的时间戳,从应用启动开始累计的时间戳
-XX:+PrintGCTimeStamps # 打印从应用启动开始到GC的时间
-XX:+PrintTenuringDistribution -XX:+PrintHeapAtGC -XX:+PrintReferenceGC -XX:+PrintGCApplicationStoppedTime
#可选
-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1
#GC日志输出的文件路径
-Xloggc:/path/to/gc-%t.log
#开启日志文件分割
-XX:+UseGCLogFileRotation
#最多分割几个文件,超过之后从头文件开始写
-XX:NumberOfGCLogFiles=14
#每个文件上限大小,超过就触发分割
-XX:GCLogFileSize=100M
Dump 配置说明
HeapDumpOnOutOfMemoryError # 系统OOM时将堆内存DUMP出来 HeapDumpPath # 系统DUMP文件的路径(需要保证文件夹存在)
瓶颈定位和处理
常见瓶颈
- 数据库(磁盘IO、CPU)
- 应用服务器(CPU、内存)
- 带宽(应用服务器和数据库、应用服务器和客户端)
瓶颈定位
数据库
iostat
iostat 命令 查看 IO 和 CPU 占用
iostat -kx 1
命令结果说明
rrqm/s:IO设备每秒读取请求有多少被merge
wrqm/s:IO设备每秒写入请求有多少被merge
merge r/s:IO设备每秒的发起的读请求次数
w/s:IO设备每秒的写请求次数
rkB/s:每秒读K字节数
wkB/s:每秒写K字节数
avgrq-sz:发送到设备的请求的平均大小,单位是扇区
avgqu-sz:平均请求队列的长度,队列长度越短越好,也是我们观察磁盘性能的核心指标
r_await:每个读请求耗费的平均时间,该时间等于IO设备读操作的时间和在队列中等待的时间
w_await:每个写请求耗费的平均时间,该时间等于IO设备读操作的时间和在队列中等待的时间
await:每个IO请求的处理的平均时间(以毫秒为单位)
svctm:表示平均每次设备I/O操作的服务时间(以毫秒为单位)
%util:在统计时间内所有处理IO时间,除以总共统计时间,表示该设备的繁忙程度;例如,如果统计间隔1秒,该设备有0.5秒在处理IO,而0.5秒闲置,则该设备的%util = 0.5/1 = 50%;一般地,如果该参数是100%表示设备已经接近满负荷运行
结果举例
以下使用 8 核 2000IOPS 的服务器进行压测,CPU 利用率已经接近 100%,而 %util 则很低,说明主要瓶颈在数据库的 CPU 利用率上
top
使用 top 命令,同样可以看到 mysql 的CPU占用率 100%
慢查询日志
方式1:修改数据库并重启(my.cnf)
[mysqld]
slow_query_log = 1
slow_query_log_file = /path/to/your/slow_query.log
long_query_time = 2
方式2:配置全局属性
# 开启慢查询
SET GLOBAL slow_query_log = 'ON';
# 设置慢查询日志路径
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';
# 设置慢查询阈值时间(s)
SET GLOBAL long_query_time = 1;
# 记录未使用索引的查询
SET GLOBAL log_queries_not_using_indexes = 1;
# 验证配置
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'slow_query_log_file';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'log_queries_not_using_indexes';
开启后可以在日志路径中看到超过指定耗时的SQL Query_Time:SQL耗时(秒) Rows_sent:查询结果行数 Rows_examined:扫描行数
应用服务器
Arthas
profiler
# 启动 profiler
profiler start
# 查看当前的profiler 数量
profiler getSamples
profiler stop
- 默认会生成 .html 的文件到应用目录的 arthas-output 路径下,我们打开 .html 文件可以看到生成的火焰图
- 火焰图越宽表示占用的资源越多,默认为CPU耗时,所以我们尽可能优先处理比较宽的火焰图
- .html 使用浏览器打开后可以进行搜索,我们可以搜索我们项目特有的包名,这样优先处理内部代码的优化
- 火焰图是按照调用链路展示的,可能部分通用方法在每块火焰图中占比均不高,但是调用地方比较多就会导致整体耗时比较长,针对这些公共方法进行优化往往可以产生事半功倍的效果
- 火焰图阅读的具体说明可以参考这篇文档 Async-Profiler - Java 火焰图性能分析工具 | 未读代码
trace
trace 可以用来打印单个接口的耗时,可以分别在低负载和高负载时调用,确认不同场景下哪些方法耗时较多
例如图中的方法在高负载时可能调用三方接口发生 HTTP 调用过长
thread
thread -n 5 -i 1000 可以打印当前最繁忙的5个线程(1000 ms 的统计维度)
jps
jps -l 可以查看当前的java线程信息, -l 表示展示详情(带完整路径)
jstat
jstat -gc jstat -gc 可以实时查看当前 GC 的情况,将 jps 中的线程 ID 带入即可查看对应服务的 GC 情况
top
top -H -p 也可以查看指定进程中的线程CPU占用 top -H -p 5897
jstack
jstack -l 可以查看 实时的线程栈信息 jstack -l 5897 可以查看该服务所有的线程信息
其中的 nid 为线程的 16 进制 ID,linux 中可以通过 printf '%x\n' 将 10 进制转换为 16 进制
printf '%x\n' 29322 728a
得到转换后的 线程之后即可通过 jstack 查找对应线程当前执行的操作
jstack -l 5897 | grep -A 10 728a
带宽
iftop 查看服务器当前带宽使用情况
可以在性能压测时,在服务武器上执行 iftop 命令 确认当前的带宽使用情况
iftop -i eth0 -P
# -P 选项会在iftop 的输出结果中开启端口显示
已知我们服务器见的带宽速率为 10MB/s,我们需要判断是否达到瓶颈还需要知道带宽的上限
测试两个服务器的带宽上限
安装iperf3
sudo yum install iperf3
服务器 A 以服务端启动
iperf3 -s
服务器 B 以客户端启动
iperf3 -c xxx.xxx.xxx.xxx(服务器 A 地址)
# 使用 4 个并发流进行 20 s 并发测试
iperf3 -c xxx.xxx.xxx.xxx(服务器 A 地址) -t 20 -P 4
汇总结果说明
各个流的带宽
- 流[4]:发送端533 Mbits/sec,接收端529 Mbits/sec
- 流[6]:发送端409 Mbits/sec,接收端405 Mbits/sec
- 流[8]:发送端587 Mbits/sec,接收端582 Mbits/sec
- 流[10]:发送端471 Mbits/sec,接收端467 Mbits/sec
总和
- 发送端(sender):2.33 GBytes,2.00 Gbits/sec,总共有540个丢包
- 接收端(receiver):2.31 GBytes,1.98 Gbits/sec
- 总带宽:总带宽为2 Gbits/sec(发送端)和1.98 Gbits/sec(接收端),两台服务器之间的网络连接具有大约2 Gbps的带宽
- 丢包率:丢包的数量(sender: 540)显示在多个流上有一些数据包丢失,但丢包率相对较低,表明网络连接比较稳定
- 对称性:发送端和接收端的带宽几乎相同,这表示网络连接在两个方向上的带宽性能是一致的
据上所述,服务器的流量大约 10MB/s,而带宽为 2G,远没有达到带宽瓶颈
参考文档
Linux IO 问题分析利器--iostat-腾讯云开发者社区-腾讯云 Arthas Async-Profiler - Java 火焰图性能分析工具 | 未读代码 ChatGPT
转载自:https://juejin.cn/post/7377195179760009255