当前位置: 首页 > news >正文

Jmeter压测实战:Shell脚本实现Linux服务器性能实时监控与自动化集成

1. 项目概述

做压力测试,最怕的就是“盲测”。你这边Jmeter脚本跑得飞起,TPS(每秒事务数)和响应时间曲线画得挺漂亮,但服务器那边可能已经“水深火热”了——CPU飙到100%、内存耗尽开始疯狂交换、磁盘IO堵成停车场,甚至网络连接数爆表,而你却浑然不知。等到测试结束,分析报告时才发现数据异常,再回头去查,当时的现场早已消失,问题复现和根因定位变得异常困难。这种“后知后觉”的测试,价值大打折扣。

所以,一个合格的性能测试工程师,必须掌握在压测过程中实时监控目标服务器性能的能力。这就像赛车手在极限驾驶时,不仅要看前方的赛道,更要时刻紧盯仪表盘上的转速、水温、油压。本次分享的核心,就是解决这个痛点:在Jmeter执行分布式压测或单机压测时,如何对作为压测目标或Jmeter自身的Linux服务器进行全方位、自动化的性能监控,并提供一个可靠的Shell脚本自启动方案,确保监控与压测同步启停,数据无缝关联。

这个方案的价值在于,它将性能测试从单纯的“客户端负载模拟”升级为“端到端的系统观测”。你不仅能知道系统在压力下的“外在表现”(响应时间、吞吐量),更能清晰地看到其“内在状态”(资源利用率),从而快速定位瓶颈是在应用代码、数据库、中间件,还是基础设施资源。下面,我们就从设计思路开始,拆解这套监控体系的构建。

2. 监控体系设计与核心思路拆解

2.1 为什么需要独立的监控脚本?

很多朋友可能会问:Jmeter本身不是有PerfMon Metrics Collector监听器吗?装上ServerAgent也能监控服务器啊。没错,这是一个方案,但在生产级压测或复杂场景下,它有明显的局限性。

首先,PerfMon通过JMX或SSH拉取数据,在高频采集(比如每秒一次)且监控多项指标时,其自身会产生不小的网络开销和采集负载,这可能会干扰测试结果,尤其是在压测机资源紧张时。其次,它的定制化能力较弱,如果你想监控一些特定的进程资源、某个NIC(网络接口卡)的详细流量、或是磁盘的await(平均等待时间)这类更深入的IO指标,PerfMon就显得力不从心。再者,它的数据存储和展示依赖于Jmeter的UI或生成的结果文件,对于长时间的稳定性测试,数据管理和后期分析不够灵活。

因此,我们采用在目标服务器上部署独立Shell监控脚本的方案。其核心优势在于:

  1. 资源开销极低:Shell脚本调用系统原生命令(如vmstat,sar,pidstat),数据采集发生在操作系统层面,效率极高,对系统性能的影响微乎其微。
  2. 数据全面且深入:可以灵活组合任何Linux性能工具,获取从全局到进程级、从硬件到内核的任意指标。
  3. 灵活性与独立性:监控数据可以实时输出到控制台,也可以写入文件、发送到时间序列数据库(如InfluxDB)或监控平台(如Prometheus)。脚本的生命周期可以独立控制,与Jmeter测试强关联。
  4. 问题现场快照:除了时序数据,脚本还可以在关键指标(如CPU持续>90%)超过阈值时,自动触发更详细的现场抓取(如jstack抓取Java线程栈、tcpdump抓取网络包),为问题诊断保留第一手证据。

2.2 监控指标选型:我们到底需要看什么?

监控不是数据堆砌,要有明确的目的。针对压力测试,我们主要关注四大核心资源:CPU、内存、磁盘I/O和网络。每一类都需要选择关键的、能真实反映瓶颈的指标。

CPU监控

  • 整体利用率(%usr%sys%idlevmstatmpstat命令提供。%usr高通常表示应用计算密集;%sys高可能意味着系统调用频繁或内核态有瓶颈。
  • 负载平均值(Load Average)uptime命令输出。1分钟、5分钟、15分钟的平均负载。如果负载持续高于CPU核心数,说明系统过载。
  • 进程级CPU消耗pidstat命令。定位到具体是哪个进程(如Java应用、数据库)消耗了大量CPU。

内存监控

  • 使用率与交换(Swap)free -m命令。重点观察available内存(真正可用的内存)而非freesi(swap in)和so(swap out)在vmstat中显示,一旦出现非零值,说明物理内存不足,性能会急剧下降。
  • 页错误(Page Fault)vmstat中的cs(上下文切换)和in(中断)虽不直接对应内存,但频繁的上下文切换常与内存不足相关。

磁盘I/O监控

  • 利用率(%utiliostat命令。显示设备带宽使用率。持续接近100%表示磁盘是瓶颈。
  • 响应时间(awaitiostat命令。I/O请求的平均等待时间(毫秒)。值过高表明磁盘队列过长或设备慢。
  • 读写吞吐量(rkB/swkB/s:每秒读写数据量,结合利用率看是否达到硬件极限。

网络监控

  • 带宽与包量sar -n DEV命令。监控特定网卡(如eth0)的rxkB/s(接收)、txkB/s(发送)和rxpck/stxpck/s(收发包数)。
  • 连接状态ss -snetstat -s。查看TCP连接数(特别是TIME_WAIT)、重传率等,对于Web服务压测至关重要。

2.3 整体架构与数据流设计

我们的方案架构非常简单高效:

  1. 监控脚本(server_monitor.sh:部署在待监控的Linux服务器上。它是一个Shell脚本,内部通过cron(但非系统cron)或循环sleep,定时(如每秒)执行一系列性能命令。
  2. 数据采集与存储:脚本将格式化后的性能数据(时间戳、指标名、值)追加写入一个带时间戳的CSV文件中。例如:server_metrics_20231027_143022.csv。CSV格式通用,便于用Excel、Python(pandas)或Jmeter后续导入进行关联分析。
  3. 自启动与关联:通过另一个控制脚本(start_test_with_monitor.sh,实现一键启动。该脚本首先在目标服务器上启动监控脚本(通过SSH),记录监控日志文件的路径和PID;然后,在压测机上启动Jmeter测试;最后,在测试结束时,通过SSH清理目标服务器上的监控进程。确保监控窗口与压测窗口完全对齐。
  4. 数据关联分析:压测结束后,你会得到两个核心文件:Jmeter生成的.jtl结果文件(包含时间戳、响应时间等)和服务器监控的.csv文件。通过时间戳对齐,你可以在分析工具(如Grafana或自定义Python脚本)中绘制叠加图表,直观地看到“当服务器CPU达到80%时,应用响应时间从50ms陡增至500ms”这样的因果关系。

3. 核心Shell监控脚本详解与编写

3.1 脚本骨架与环境检查

一个健壮的监控脚本,首先要处理好自身运行环境、参数和信号。我们创建一个名为server_perf_monitor.sh的脚本。

#!/bin/bash # 服务器性能监控脚本 # 用法:./server_perf_monitor.sh [监控间隔(秒)] [监控时长(秒)] set -euo pipefail # 启用严格模式,遇到错误或未定义变量则退出 # 默认参数 INTERVAL=${1:-1} # 采集间隔,默认1秒 DURATION=${2:-0} # 监控总时长,0表示无限直到手动停止 OUTPUT_DIR="./perf_logs" # 监控数据输出目录 LOG_FILE="${OUTPUT_DIR}/monitor_$(date +%Y%m%d_%H%M%S).log" # 脚本运行日志 METRIC_FILE="${OUTPUT_DIR}/metrics_$(date +%Y%m%d_%H%M%S).csv" # 指标数据文件 # 创建输出目录 mkdir -p "$OUTPUT_DIR" # 记录启动信息 echo "$(date '+%Y-%m-%d %H:%M:%S') - 监控脚本启动,间隔${INTERVAL}秒,输出至:${METRIC_FILE}" | tee -a "$LOG_FILE" # 检查必要命令是否存在 for cmd in vmstat iostat sar pidstat mpstat; do if ! command -v $cmd &> /dev/null; then echo "错误:命令 $cmd 未找到,请安装 sysstat 包。" | tee -a "$LOG_FILE" exit 1 fi done # 写入CSV文件头 echo "timestamp,cpu_user,cpu_system,cpu_idle,load_1m,mem_available_mb,swap_si,swap_so,io_util,io_await,io_rkb_s,io_wkb_s,net_rx_kb_s,net_tx_kb_s,tcp_established" > "$METRIC_FILE"

关键点解析

  • set -euo pipefail:这是编写可靠Shell脚本的黄金法则。-e让脚本在任何一个命令失败时立即退出;-u遇到未定义的变量时报错;-o pipefail确保管道命令中任意一个环节失败,整个管道都视为失败。这能避免脚本在部分命令出错后继续运行,产生脏数据。
  • 参数化:通过命令行参数接收INTERVALDURATION,使得脚本可以灵活用于短时压测或长时间稳定性测试。
  • 输出文件命名:使用date +%Y%m%d_%H%M%S生成带精确启动时间戳的文件名,避免了文件覆盖,也便于与Jmeter测试结果的时间对齐。
  • 依赖检查:在开始前检查vmstatiostat等关键命令是否存在。这些命令通常来自sysstat包,如果缺失,脚本会明确提示并退出。

3.2 核心数据采集函数实现

接下来,我们编写核心的数据采集函数collect_metrics。这个函数会在每次循环中被调用,收集所有预设的指标。

# 核心数据采集函数 collect_metrics() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S.%3N') # 精确到毫秒的时间戳 # 1. CPU与系统负载 - 使用 mpstat 和 uptime local cpu_stats=$(mpstat 1 1 | awk 'NR==4 {print $4,$6,$13}') # %usr, %sys, %idle local load_1m=$(uptime | awk -F'load average:' '{print $2}' | awk -F, '{print $1}' | xargs) # 2. 内存与Swap - 使用 free 和 vmstat local mem_info=$(free -m | awk 'NR==2 {print $7}') # Available memory in MB local swap_io=$(vmstat 1 2 | tail -1 | awk '{print $7,$8}') # si, so # 3. 磁盘I/O - 使用 iostat (假设监控 sda 设备,请根据实际情况调整 DEVICE) local DEVICE="sda" local io_stats=$(iostat -dxk $DEVICE 1 2 | grep ^$DEVICE | tail -1 | awk '{print $14,$10,$6,$7}') # $14: %util, $10: await, $6: rkB/s, $7: wkB/s # 4. 网络 - 使用 sar (假设网卡 eth0,请根据实际情况调整 NIC) local NIC="eth0" local net_stats=$(sar -n DEV 1 1 | grep -w $NIC | tail -1 | awk '{print $5,$6}') # $5: rxkB/s, $6: txkB/s (注意:某些版本sar单位可能是kB/s,需确认) # 5. TCP连接数 - 使用 ss local tcp_conn=$(ss -s | awk '/^TCP:/ {print $4}') # ESTAB 连接数 # 将所有指标输出为一行CSV echo "${timestamp},${cpu_stats},${load_1m},${mem_info},${swap_io},${io_stats},${net_stats},${tcp_conn}" >> "$METRIC_FILE" }

注意事项与实操心得

  1. 命令采样技巧mpstat 1 1iostat 1 2sar 1 1这种格式,第一个数字是采样间隔(秒),第二个数字是采样次数。我们取第二次(tail -1)的结果,是为了避免获取到命令启动时的瞬时值或统计区间不完整的值,确保数据是过去一个间隔内的平均值,更稳定。
  2. 设备名适配:脚本中的DEVICE="sda"NIC="eth0"示例,在生产环境中必须修改。你可以通过lsblk命令查看磁盘设备名(可能是vda,nvme0n1等),通过ip addrifconfig查看活跃的网络接口名(可能是ens192,enp0s3等)。一个更健壮的做法是写一个函数自动探测主要磁盘和活跃网卡。
  3. 网络单位确认:不同版本的sar命令,其网络流量单位可能是kB/skBps,也可能是rxpck/s。你需要在你自己的服务器上运行一次sar -n DEV 1 1,查看输出列的标题来确认。必要时,可以在awk中做单位换算(如$5*8如果单位是kB/s想转为kbps)。
  4. 时间戳精度:使用date '+%Y-%m-%d %H:%M:%S.%3N'获取毫秒级时间戳,这对于后期与Jmeter的毫秒级日志进行精确对齐非常有帮助。

3.3 主循环与信号处理

脚本需要一个主循环来定时执行采集,并优雅地处理用户中断(如Ctrl+C)。

# 信号处理函数,用于优雅退出 cleanup() { echo -e "\n$(date '+%Y-%m-%d %H:%M:%S') - 收到中断信号,停止监控。" | tee -a "$LOG_FILE" echo "$(date '+%Y-%m-%d %H:%M:%S') - 监控数据已保存至:$METRIC_FILE" | tee -a "$LOG_FILE" exit 0 } # 注册信号处理 trap cleanup INT TERM # 主监控循环 echo "$(date '+%Y-%m-%d %H:%M:%S') - 开始采集性能数据..." | tee -a "$LOG_FILE" START_TIME=$(date +%s) ITERATION=0 while true; do collect_metrics ((ITERATION++)) CURRENT_TIME=$(date +%s) ELAPSED=$((CURRENT_TIME - START_TIME)) # 如果设置了监控时长,则检查是否超时 if [ "$DURATION" -gt 0 ] && [ "$ELAPSED" -ge "$DURATION" ]; then echo "$(date '+%Y-%m-%d %H:%M:%S') - 达到预设监控时长 ${DURATION} 秒,停止监控。" | tee -a "$LOG_FILE" break fi # 等待指定的间隔时间,但扣除数据采集本身耗时 SLEEP_TIME=$(echo "$INTERVAL - ($(date +%s.%N) - $CURRENT_TIME)" | bc) if (( $(echo "$SLEEP_TIME > 0" | bc -l) )); then sleep $SLEEP_TIME fi done cleanup # 正常结束时也执行清理

关键点解析

  • trap cleanup INT TERM:这行代码至关重要。它捕获SIGINT(Ctrl+C) 和SIGTERM(kill命令) 信号。当用户想停止脚本时,不会直接退出,而是先执行cleanup函数,打印结束信息,确保最后一条数据已写入文件,再优雅退出。避免了文件损坏或数据丢失。
  • 精确间隔控制:简单的sleep $INTERVAL会导致实际循环周期大于INTERVAL,因为数据采集本身需要时间。我们通过计算SLEEP_TIME = INTERVAL - 采集耗时,并使用高精度计算工具bc,来尽可能保证采集间隔的准确性。这对于生成均匀时间序列数据很重要。
  • 时长控制:通过DURATION参数,可以实现定时自动停止监控,非常适合无人值守的自动化测试场景。

4. 自启动控制脚本与Jmeter集成方案

监控脚本写好了,但我们需要一个“指挥官”来协调监控和压测的启停。这个控制脚本是方案自动化的核心。

4.1 控制脚本设计思路

控制脚本run_perf_test.sh需要完成以下任务:

  1. 读取配置文件或参数,获取目标服务器列表、SSH凭证、Jmeter测试计划路径等。
  2. 通过SSH在每台目标服务器上启动监控脚本,并记录其进程ID(PID)和生成的监控文件路径。
  3. 在压测控制机(Master)上启动Jmeter测试(分布式或非分布式)。
  4. 等待Jmeter测试完成(或超时)。
  5. 测试完成后,通过SSH连接到各目标服务器,发送信号停止监控脚本,并可选地将监控数据文件拉取到本地。

4.2 控制脚本关键代码段

这里展示核心的SSH远程启动和停止部分。

#!/bin/bash # 性能测试集成控制脚本 # 配置区域 TARGET_SERVERS=("user@server1" "user@server2") # SSH连接字符串 JMETER_SCRIPT="/path/to/your/testplan.jmx" JMETER_HOME="/opt/apache-jmeter-5.6.3" LOCAL_RESULT_DIR="./test_results_$(date +%Y%m%d_%H%M%S)" MONITOR_SCRIPT_REMOTE_PATH="/tmp/server_perf_monitor.sh" MONITOR_INTERVAL="1" # 监控间隔 MONITOR_DURATION="600" # 监控时长,假设测试约10分钟 # 为本次测试创建本地结果目录 mkdir -p "$LOCAL_RESULT_DIR" # 1. 将监控脚本分发到所有目标服务器 echo "分发监控脚本到目标服务器..." for server in "${TARGET_SERVERS[@]}"; do scp "./server_perf_monitor.sh" "${server}:${MONITOR_SCRIPT_REMOTE_PATH}" ssh "$server" "chmod +x ${MONITOR_SCRIPT_REMOTE_PATH}" done # 2. 在目标服务器上启动监控 declare -A monitor_pids declare -A metric_files echo "在目标服务器上启动性能监控..." for server in "${TARGET_SERVERS[@]}"; do # 在远程服务器后台启动监控脚本,并获取其PID和输出文件路径 # 注意:这里通过命令替换和echo来获取远程脚本输出的文件路径,需要监控脚本支持 # 一种更简单的方式是固定远程输出路径,或者让监控脚本将文件路径写入一个已知位置。 output_info=$(ssh "$server" "cd /tmp && nohup ${MONITOR_SCRIPT_REMOTE_PATH} ${MONITOR_INTERVAL} ${MONITOR_DURATION} > /dev/null 2>&1 & echo \$!") pid=$(echo "$output_info" | head -1) monitor_pids["$server"]=$pid echo " 服务器 $server 监控进程PID: $pid" # 假设我们知道监控文件会生成在 /tmp/perf_logs/ 下,并以时间戳命名。实际中可能需要更复杂的逻辑来定位文件。 metric_files["$server"]="/tmp/perf_logs/metrics_*.csv" # 示例路径,需适配 done # 3. 等待片刻,确保监控已启动 sleep 5 # 4. 启动Jmeter测试 echo "启动Jmeter性能测试..." cd "$JMETER_HOME/bin" || exit ./jmeter -n -t "$JMETER_SCRIPT" -l "${LOCAL_RESULT_DIR}/result.jtl" -e -o "${LOCAL_RESULT_DIR}/html_report" & JMETER_PID=$! echo "Jmeter主进程PID: $JMETER_PID" # 5. 等待Jmeter测试结束 wait $JMETER_PID JMETER_EXIT_CODE=$? echo "Jmeter测试结束,退出码: $JMETER_EXIT_CODE" # 6. 停止所有目标服务器上的监控 echo "停止远程性能监控..." for server in "${TARGET_SERVERS[@]}"; do pid=${monitor_pids["$server"]} if [ -n "$pid" ]; then ssh "$server" "kill -TERM $pid 2>/dev/null && echo '监控进程 $pid 已停止' || echo '停止监控进程 $pid 失败或进程已结束'" fi # 可选:将监控文件拉取到本地 remote_file_pattern=${metric_files["$server"]} for file in $(ssh "$server" "ls $remote_file_pattern 2>/dev/null"); do scp "${server}:${file}" "${LOCAL_RESULT_DIR}/" echo " 已拉取监控文件: $(basename $file)" done done echo "性能测试与监控全部完成。结果位于: $LOCAL_RESULT_DIR"

实操心得与避坑指南

  1. SSH免密登录:这是自动化脚本的前提。务必先在控制机和所有目标服务器之间配置好SSH密钥对,实现免密登录。否则脚本会卡在密码输入环节。
  2. 监控文件路径同步:脚本中metric_files数组的赋值是个难点。因为监控脚本生成的文件名包含动态时间戳,控制脚本无法预先知道。有两种解决方案:
    • 方案A(推荐):修改监控脚本,在启动时将生成的文件完整路径写入一个固定的“状态文件”(如/tmp/perf_monitor_status),控制脚本通过读取这个文件来获取路径。
    • 方案B:在远程启动命令中,让监控脚本将文件输出到一个固定前缀+时间戳的路径,然后控制脚本通过模式匹配(如ls /tmp/perf_logs/metrics_*.csv)来拉取最新文件。但要注意文件排序和选择。
  3. 进程管理可靠性:使用nohup ... &echo $!获取后台进程PID是标准做法。但在网络不稳定或远程环境复杂时,kill命令可能无法送达。更健壮的做法是,监控脚本在启动时将自己的PID写入一个文件,控制脚本通过读取该文件来发送信号,或者使用pkill -f根据脚本名来终止进程。
  4. 错误处理:上述示例脚本为了清晰省略了大量错误处理(如scp/ssh失败、文件不存在等)。生产脚本中,应对每个关键操作(scp, ssh, kill)检查返回值,并记录详细的日志,便于排错。

5. 数据关联分析与可视化实践

监控数据采集下来后,如何与Jmeter结果关联分析,是产生价值的最后一步。

5.1 数据预处理与时间对齐

Jmeter的.jtl文件通常是CSV格式,包含时间戳(timeStamp,毫秒级Unix时间戳)、耗时(elapsed)等。我们的监控CSV文件也包含时间戳。第一步是将它们的时间轴对齐。

你可以使用Python的pandas库轻松完成:

import pandas as pd # 1. 读取Jmeter结果 jmeter_df = pd.read_csv('result.jtl') # 假设时间戳列名为 'timeStamp',单位毫秒 jmeter_df['datetime'] = pd.to_datetime(jmeter_df['timeStamp'], unit='ms') jmeter_df.set_index('datetime', inplace=True) # 按秒重采样,计算每秒的平均响应时间、TPS等 jmeter_resampled = jmeter_df['elapsed'].resample('1S').agg(['mean', 'count']).rename(columns={'mean': 'avg_response_ms', 'count': 'tps'}) # 2. 读取服务器监控数据 monitor_df = pd.read_csv('metrics_20231027_143022.csv', parse_dates=['timestamp']) monitor_df.set_index('timestamp', inplace=True) # 监控数据可能已经是1秒间隔,无需重采样,但确保索引频率一致 monitor_df = monitor_df.asfreq('1S') # 3. 合并两个DataFrame(按时间索引左连接,以Jmeter数据为主) combined_df = pd.merge(jmeter_resampled, monitor_df, left_index=True, right_index=True, how='left')

5.2 关键场景图表分析

使用matplotlibseaborn绘制叠加图表,是发现关联性的最直观方式。

import matplotlib.pyplot as plt fig, axes = plt.subplots(4, 1, figsize=(15, 12), sharex=True) # 子图1: TPS 与 CPU利用率 ax1 = axes[0] ax1.plot(combined_df.index, combined_df['tps'], color='blue', label='TPS') ax1.set_ylabel('TPS', color='blue') ax1.tick_params(axis='y', labelcolor='blue') ax1.legend(loc='upper left') ax1_twin = ax1.twinx() ax1_twin.plot(combined_df.index, combined_df['cpu_user'], color='red', linestyle='--', label='CPU %usr') ax1_twin.set_ylabel('CPU %usr', color='red') ax1_twin.tick_params(axis='y', labelcolor='red') ax1_twin.legend(loc='upper right') ax1.set_title('TPS vs CPU Utilization') # 子图2: 平均响应时间 与 内存可用量 ax2 = axes[1] ax2.plot(combined_df.index, combined_df['avg_response_ms'], color='green', label='Avg Response (ms)') ax2.set_ylabel('Response (ms)', color='green') ax2.tick_params(axis='y', labelcolor='green') ax2.legend(loc='upper left') ax2_twin = ax2.twinx() ax2_twin.plot(combined_df.index, combined_df['mem_available_mb'], color='orange', linestyle='--', label='Mem Available (MB)') ax2_twin.set_ylabel('Mem (MB)', color='orange') ax2_twin.twin_params(axis='y', labelcolor='orange') ax2_twin.legend(loc='upper right') ax2.set_title('Response Time vs Memory Available') # 子图3: TPS 与 磁盘IO等待时间 ax3 = axes[2] ax3.plot(combined_df.index, combined_df['tps'], color='blue', label='TPS') ax3.set_ylabel('TPS', color='blue') ax3.twin_params(axis='y', labelcolor='blue') ax3.legend(loc='upper left') ax3_twin = ax3.twinx() ax3_twin.plot(combined_df.index, combined_df['io_await'], color='purple', linestyle='--', label='Disk Await (ms)') ax3_twin.set_ylabel('Await (ms)', color='purple') ax3_twin.twin_params(axis='y', labelcolor='purple') ax3_twin.legend(loc='upper right') ax3.set_title('TPS vs Disk I/O Await') # 子图4: 网络吞吐量 ax4 = axes[3] ax4.plot(combined_df.index, combined_df['net_rx_kb_s'], label='Network RX (KB/s)', color='cyan') ax4.plot(combined_df.index, combined_df['net_tx_kb_s'], label='Network TX (KB/s)', color='magenta') ax4.set_ylabel('Network (KB/s)') ax4.set_xlabel('Time') ax4.legend() ax4.set_title('Network Throughput') plt.tight_layout() plt.show()

通过这样的图表,你可以清晰地看到:

  • CPU瓶颈:当TPS曲线上升时,%usr曲线同步线性上升,并最终达到高位平台,而TPS不再增长甚至下降。
  • 内存瓶颈mem_available_mb持续下降,当接近零时,swap_si/so开始活动,同时avg_response_ms急剧上升。
  • 磁盘I/O瓶颈io_await指标飙升,同时可能伴随着%util持续高位,而TPS和响应时间恶化。
  • 网络瓶颈net_rx/tx_kb_s达到网卡带宽上限,或tcp_established连接数接近系统端口或应用限制。

5.3 进阶:集成到Grafana进行实时看板

对于长期或复杂的测试,可以将监控数据实时写入InfluxDB或Prometheus,然后用Grafana制作动态看板。这需要修改监控脚本,将采集的数据通过对应客户端的API推送出去,而非写入本地文件。这超出了本文基础篇的范围,但思路是相通的:将采集、传输、存储、展示解耦,构建更强大的监控体系。

6. 常见问题排查与实战技巧

在实际操作中,你肯定会遇到各种问题。这里记录一些典型的坑和解决方法。

6.1 监控脚本自身资源消耗过高

问题:运行监控脚本后,发现服务器上多了一个CPU占用很高的bashawk进程。排查:使用top -p <pid>pidstat -p <pid> 1观察监控脚本进程的资源消耗。如果采集间隔太短(如0.1秒),且命令复杂,可能会消耗1-2%的CPU。解决

  • 适当降低采集频率。对于大多数压测场景,1-5秒的间隔足以捕捉趋势。
  • 优化Shell脚本。避免在循环内频繁启动新进程。可以考虑将一些命令合并,或者使用更高效的工具(如用dstat替代多个命令的组合,但dstat可能默认不安装)。
  • 将消耗大的计算(如复杂的awk处理)移到循环外,或使用Python等更高效的语言重写数据采集核心部分。

6.2 SSH远程执行命令超时或失败

问题:控制脚本在执行ssh命令启动远程监控或拉取文件时卡住或报错。排查

  1. 网络与防火墙:确保控制机与目标服务器之间的22端口是通的。
  2. SSH配置:检查~/.ssh/config/etc/ssh/ssh_config是否有超时设置。在脚本的ssh命令中增加超时参数:ssh -o ConnectTimeout=10 -o BatchMode=yes user@host 'command'BatchMode=yes可以避免密码提示等交互。
  3. 命令执行环境:远程执行的命令可能因为环境变量(如PATH)问题找不到。使用绝对路径,或者在远程命令中先source /etc/profile
  4. 后台进程与终端:使用nohup时,确保将标准输出和错误重定向(如> /dev/null 2>&1),否则可能会因为尝试写入不存在的终端而挂起。

6.3 监控数据与Jmeter时间戳对不齐

问题:合并图表时,两条曲线的时间轴有偏移,关联性看不出来。排查

  1. 时区问题:确保监控脚本和Jmeter运行所在的服务器时区一致。最好都使用UTC时间。在脚本中使用date -u来获取UTC时间。
  2. 时钟同步:确保所有服务器(压测机、被压测服务器)的时钟通过NTP服务同步。使用date命令检查各机器时间差。
  3. 采集延迟:监控脚本的collect_metrics函数执行需要时间,特别是当系统负载很高时,命令执行会变慢。这会导致数据点的时间戳比实际采集时刻晚。我们的脚本通过计算SLEEP_TIME进行补偿,但无法完全消除。对于要求极端精确的场景,可以考虑在命令执行前记录时间戳,或者使用内核更底层的追踪工具(如/proc文件系统),但复杂度会大大增加。对于性能趋势分析,秒级的对齐精度通常足够。

6.4 磁盘I/O监控不到数据或数据为0

问题iostat输出的%utilawait一直是0或很低,但应用明显感觉慢。排查

  1. 监控了错误的设备:通过lsblkdf -h确认你的应用数据实际写在哪个磁盘或分区上。数据库、日志文件可能在不同的挂载点。监控sda可能没用,需要监控sda1,sdb等。
  2. 缓存(Cache)效应:Linux有强大的Page Cache和Buffer Cache。如果数据读写大部分命中缓存,磁盘实际I/O就会很少,iostat显示自然很低。此时应关注内存使用情况。可以使用iostat -dxkm 1查看更详细的指标,或使用sar -B查看页统计。
  3. 使用iotop进行进程级定位:如果整体磁盘指标不高但应用IO等待长,可能是某个进程在频繁进行小文件或随机IO。使用sudo iotop命令可以实时查看每个进程的磁盘读写速率,这是定位“元凶”的利器。

6.5 网络监控指标单位混淆

问题sar -n DEV看到的rxkB/s数值,和网卡带宽(如千兆网卡=125MB/s)对不上,感觉差了很多。解析:这里是最容易混淆的地方。sar命令输出的rxkB/stxkB/s单位是千字节/秒 (kB/s),注意是小写的k,代表1024字节。而网卡带宽通常说的“千兆”(1Gbps),单位是千兆比特/秒

  • 1 Gbps = 1000 Mbps = 1000,000,000 bit/s
  • 转换为字节/秒 (B/s): 1000,000,000 / 8 = 125,000,000 B/s ≈ 122,070 KB/s ≈ 119.2 MB/s 所以,如果你的rxkB/s显示为 90000,那么实际网络速率大约是 90000 KB/s * 8 / 1000 ≈ 720 Mbps,已经达到了千兆网卡的70%以上,这是一个很高的负载。务必在分析时做好单位换算。
http://www.jsqmd.com/news/1117488/

相关文章:

  • AutoHotkey v2转换器:轻松实现脚本现代化升级的终极方案
  • 【信息科学与工程学】计算机科学与自动化——第五十七篇 计算性与不可计算性01
  • IDM激活脚本终极指南:永久解锁下载神器,告别30天试用限制
  • 六月最贵的三起被盗,没有一个是被“黑“进去的
  • 终极Unity游戏资源编辑器:UABEA完整使用指南与模组制作教程
  • YOLOv8一站式实战指南:从零掌握图像分类、目标检测与实例分割
  • ModEngine2终极指南:魂系游戏模组开发的完整解决方案
  • ICM-42605与PIC32MZ的6DOF运动追踪系统设计
  • Potrace完全指南:如何将位图完美转换为矢量图形
  • 2026年多模态AI爆发的三大工程临界点
  • Java毕业设计-基于 SpringBoot 的商场多功能折扣系统的设计与实现 基于 SpringBoot 的商场商品折扣结算管理系统(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • WidescreenFixesPack:让经典游戏在现代显示器上重获新生的技术解决方案
  • C#集成YOLOv8目标检测:30分钟实现工业视觉应用开发
  • B站视频下载终极指南:三步轻松保存任何B站内容到本地
  • DreamScene2:免费开源Windows动态桌面终极解决方案
  • DjangoAdmin敏捷开发框架FastAPI+AntdVue版更新:新增配置、修复问题,多端兼容提升开发效率
  • DeepSeek V4 Pro实测:大模型性能与成本的业务级平衡
  • 2026年中国自动驾驶真实图景:L2普及、L3落地与L4盈利全景实测
  • 基于Playwright的UI自动化测试平台:从架构设计到CI/CD集成
  • OpenCode 接入 Kimi 2.5 的协议桥接实践
  • Automation Prompting:提示即服务的工程化实践
  • STM32与WSEN-ISDS实现三轴运动追踪方案解析
  • STM32与IIM-42652传感器的6DoF运动解算实践
  • 2026年IEEE第九届机器学习和自然语言处理国际会议 (MLNLP 2026)
  • 相机、激光雷达与事件相机动态感知原理对比
  • Android真机与模拟器双场景Burp抓包配置与HTTPS解密实战
  • Raft 日志复制延迟:多数派确认不等于所有副本都健康
  • ASP.NET是如何在IIS下工作的
  • 70B参数Transformer大模型训练优化实战
  • DC-DC降压转换与I2C控制电源系统设计