1. 搭建前准备与整体思路1.1 为什么非要在 Linux 服务器上跑 JMeter刚开始接触性能测试那会儿,我习惯在自己电脑上直接压测。Windows 环境下跑 JMeter,小并发还行,但一旦线程数拉上去,本机资源先被吃满,测出来的数据根本不能反映服务器真实处理能力。后来我把压测环境搬到 Linux 服务器上,才发现这才是一切性能测试的正确起点。在 Linux 服务器上搭建 JMeter 性能测试与监控环境,到底解决了什么问题?三点最核心:消除客户端资源瓶颈:压测机本身需要消耗大量 CPU、内存和网络带宽。用独立的 Linux 服务器做压测机,被测服务不被压测工具抢占资源,数据才干净。稳定性和自动化优势:Linux 服务器的进程管理、定时任务、远程执行能力,天然适合做长时间稳定性测试,不用人盯着点鼠标。监控体系一体化:被压的服务器和压测机能在同一套监控体系里看数据,排查瓶颈时不用东拼西凑工具。这套环境的适用人群也很明确:后端开发做接口性能验证、测试团队搞压测基准、运维做容量评估——只要你不是那种只打算在自己笔记本上点两下看看响应时间的业余玩法,都需要这套环境。1.2 环境选型:我的推荐配置清单先说我踩过坑之后固定下来的组合方案,直接抄作业就行:角色推荐方案说明操作系统Ubuntu 20.04 LTS / 22.04 LTSCentOS 也可,但 Ubuntu 的包管理和内核版本对新硬件支持更好JDKOpenJDK 11(或 17)JMeter 5.x 要求 Java 8+,但实测 11 以上 GC 表现更稳定JMeter5.5 或 5.6.x5.x 系列对 HTTP/2、HAR 导入支持完善压力机配置4C8G 起步,带宽按需压测机 CPU 核数决定你本地能生成的最大并发监控方案Prometheus + node_exporter + Grafana统一看板,适合长期记录辅助工具nmon / dstat / iostat快速排查时用,轻量无侵入为什么推荐 Ubuntu?不是说 CentOS 不行,而是 Ubuntu 的 apt 源里 OpenJDK 版本更新更及时,编译安装时缺依赖也更容易补齐。如果团队已有 CentOS 的标准化镜像,用 CentOS 也一样跑,核心逻辑没区别。1.3 JDK 安装与配置:一步错步步错的环节JMeter 本质上是 Java 应用,JDK 配不好,后面全是坑。这里直接说我的标准操作流程:# Ubuntu 下安装 OpenJDK 11 apt-get update apt-get install -y openjdk-11-jdk # 验证安装 java -version # 配置 JAVA_HOME(写入 /etc/profile) export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 export PATH=$JAVA_HOME/bin:$PATH # 让配置生效 source /etc/profile一个非常关键但容易被忽略的点:务必确认 JMeter 的启动脚本找得到 JAVA_HOME。我遇到过一种情况,命令行 java -version 正常,但 ./jmeter.sh 直接报错找不到 Java。原因就是 JAVA_HOME 写进了 ~/.bashrc 而不是 /etc/profile,非交互 Shell 没加载到。这类细节等你们生产环境压测脚本定时执行时会要命。2. JMeter 安装与目录结构解析2.1 下载、解压与版本选择JMeter 安装本身不复杂,Apache 官网下载 tar.gz 包解压即用。但版本选择上有个经验:别追求最新版,选上一个稳定版本就行。新版本刚发布时插件生态往往还没跟上,比如 PerfMon 插件对 5.6.x 某些版本的兼容性问题就折腾过我。# 创建专用目录 mkdir -p /opt/jmeter cd /opt/jmeter # 下载 5.5 版本(用 apache 镜像源,国内访问更快) wget https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.5.tgz # 解压 tar -zxvf apache-jmeter-5.5.tgz # 建立软链接,方便后续升级切换 ln -s apache-jmeter-5.5 jmeter解压完后目录结构里有几个重点:bin/:启动脚本、配置文件。jmeter.sh 是 Linux 下的启动命令,jmeter.properties 是核心配置。lib/:存放依赖 Jar 包。扩展插件和 JDBC 驱动都丢这里。ext/:通过插件管理器安装的插件实际也放在 lib/ext 下面。logs/:运行日志目录,调试脚本时记得来这里翻日志。backups/:自动备份目录,这个容易忽略。JMeter 默认开启保存前备份,测试计划被改坏时能救回来。2.2 JVM 参数调优:压测机性能的关键阀门JMeter 压不出高并发,十有八九是 JVM 内存不够导致的。默认启动脚本里只分配了 256M 到 512M 的堆内存,这种配置下线程数加到几百就开始疯狂 GC,测试结果完全失真。修改 jmeter.sh 里的 HEAP 参数:# 打开 jmeter.sh,找到以下内容并修改 # 原配置 # HEAP="-Xms1g -Xmx1g" # 修改为(根据压测机内存调整) HEAP="-Xms4g -Xmx4g -XX:MaxMetaspaceSize=1024m"给几个基于实测的参考数值:压测机物理内存建议 JVM 堆内存参考最大线程数8G4G1000-200016G8G2000-400032G12G~16G4000+注意:不是内存越大堆就越大。JMeter 的高并发压测依赖的是本地线程调度能力和端口资源,堆内存太大反而增加 GC 停顿时间,反而拖垮生成压力的能力。4G~8G 堆一般够用了。另外还要调一个参数,bin/jmeter-server 的 RMIC 端口,分布式压测时要用:# jmeter-server 脚本中,找到或添加 RMI_HOST_DEF=-Djava.rmi.server.hostname=192.168.1.100这个 IP 填压测机的内网 IP,不然 Agent 节点注册不了。2.3 目录规划与权限配置我强烈建议不要在 root 用户下直接跑 JMeter。生产环境压测的审计要求越来越严格,root 跑出来的日志不好追溯,而且万一误操作了完蛋。我的做法是单独建一个轻量用户:# 创建用于压测的系统用户 useradd -M -s /bin/bash jmeter passwd jmeter # 授权目录 chown -R jmeter:jmeter /opt/jmeter chown -R jmeter:jmeter /var/tmp/jmeter*脚本、测试计划、测试结果都规划好统一目录:mkdir -p /opt/jmeter/tests mkdir -p /opt/jmeter/results mkdir -p /opt/jmeter/scripts这样 Jenkins 集成做 CI 压测时,路径规则清晰,脚本里的相对路径也不会乱。3. 性能监控环境搭建:没有监控的压测等于瞎跑3.1 监控是压测里最重要的隐形环节新手做压测最容易犯的错误是只看最终聚合报告里的响应时间,而忽略整个压测过程中系统资源的变化。响应时间慢了,到底是服务端 CPU 耗尽?内存不足导致频繁 GC?还是带宽瓶颈?没有监控数据支撑,你只能靠猜。监控体系要覆盖三个层面:被压服务的资源监控:CPU、内存、磁盘 IO、网络流量。JVM 层面的监控:GC 频率、堆内存使用、线程数这些。压测机自身的资源监控:确保瓶颈不在压测端。3.2 轻量级监控组合:nmon + dstat + sar前期快速排查,我喜欢用三个命令行工具:# nmon 交互式监控,实时看 CPU/内存/磁盘 apt-get install -y nmon nmon # dstat 聚合查看各类系统资源 apt-get install -y dstat dstat -c -m -d -n -t 2 # sar 收集历史数据,方便事后回看 apt-get install -y sysstat sar -u 1 10nmon 的一个好习惯:压测期间录制数据,压测结束后用 nmon2csv 类工具导出成 CSV,再用 Excel 或者 Python 绘图做报告。# 录制 600 秒(10分钟)的 nmon 数据 nmon -s 5 -c 120 -f -m /tmp/ # 参数说明:-s 采样间隔秒, -c 采样次数, -f 输出为文件这个文件默认生成在指定目录,文件名带时间戳,压测结束后用 Excel 就能打开图表化分析。3.3 可视化监控方案:Prometheus + node_exporter + Grafana如果你们压测是周期性进行的,或者需要团队共享监控看板,那还是上一套标准组合。这套方案我反复搭过多次,给一个经过验证的流程:第一步:安装 node_exporter# 下载最新稳定版(以 1.6.1 为例) wget https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux-amd64.tar.gz tar -zxvf node_exporter-1.6.1.linux-amd64.tar.gz mv node_exporter-1.6.1.linux-amd64/node_exporter /usr/local/bin/ # 后台启动,监听端口 9100 nohup /usr/local/bin/node_exporter --web.listen-address=":9100" \ --collector.systemd \ --collector.processes \ > /var/log/node_exporter.log 2>&1 &这一步如果改了监听端口,后面 Prometheus 配置里也要同步改。第二步:安装 Prometheuscurl -LO https://github.com/prometheus/prometheus/releases/download/v2.47.0/prometheus-2.47.0.linux-amd64.tar.gz tar -zxvf prometheus-2.47.0.linux-amd64.tar.gz mv prometheus-2.47.0.linux-amd64 /opt/prometheus编辑 /opt/prometheus/prometheus.yml,添加被压服务器的任务节点:scrape_configs: - job_name: 'linux-node' static_configs: - targets: - '被压服务器IP:9100' - '压测机IP:9100'第三步:安装 Grafana,导入现成仪表盘apt-get install -y apt-transport-https software-properties-common wget -q -O - https://packages.grafana.com/gpg/gpg.key | apt-key add - echo "deb https://packages.grafana.com/oss/deb stable main" | tee /etc/apt/sources.list.d/grafana.list apt-get update apt-get install -y grafana systemctl start grafana-server浏览器打开 Grafana 后,数据源添加 Prometheus 的地址(默认 localhost:9090),导入模板 ID 例如 16098 这种 Linux 主机的现成看板,几分钟就能看到监控图表。为什么坚持用这套而不买商业监控?因为压测监控本质上是数据要能回放、能对比。Prometheus 存储的多轮压测数据可以任意拉时间范围对比,这是截图式监控工具做不到的。后面做回归对比时,这功能救过我不少次。3.4 JMeter 自身运行状态监控很多人忽略一个问题:压测机自己死了没有?高并发下压测机本身可能成为瓶颈被压垮。此时堆内存爆掉、线程池阻塞、端口耗尽都可能出现,压测数据会严重失真。最简单的判断方式:实时盯 jmeter.log 的尾部:tail -f /opt/jmeter/jmeter.log出现大量Error creating connection或者No buffer space available之类的报错,就是压测机瓶颈的信号。另外配合刚开始配置的 JVM 参数,用 jvisualvm 或 jstat 看 GC 频率:# 找到 JMeter 的进程号 ps -ef | grep jmeter # 每秒查看 GC 情况 jstat -gcutil <PID> 1000如果 Full GC 频率高于每秒一次,压测数据就不可信了,应该停下来加大内存或者压低并发。4. 压测脚本编写与分布式加压方案4.1 脚本编写:线程组、监听器与断言要点测试计划的结构设计直接影响压测结果的可信度。线程组的三个关键参数:线程数:总并发用户数,不是越大越好,要跟被测系统的预期容量匹配。Ramp-Up 时间:线程启动时间。设得太小,瞬间压力峰值会把服务打崩,误判容量上限;设得太大,数据没有参考意义。循环次数:设成永远,再用调度器控制压测时长,这是做稳定性压测的标准姿势。监听器我只保留这两个,其他监听器多余:聚合报告(Summary Report):压测结束后的平均响应时间、TP90、TP99、吞吐量数据。简单数据写入器(Simple Data Writer):把每次请求的延迟原始数据落盘,方便事后做分布分析。聚合报告里有小组数据最容易误读:Error% 和 Throughput 的关系。如果错误率升高但吞吐量和响应时间反而下降,那不是服务处理能力变强了,而是请求在客户端就被拒掉了。这个逻辑我喜欢叫“假性成功”——只看结果数字是被骗的。断言我用得最克制,一般只在冒烟压测(轻量验证)时用响应断言检查状态码,正式压测不挂过多断言,断言本身也会消耗 JMeter 性能。4.2 分布式压测集群:一台压力机不够怎么办单机 JMeter 理论上能跑几千并发,但实际受限于 CPU 核数和网络吞吐。需要更大压力时,就得组建分布式压测集群。分布式压测的架构是:Controller(控制机)负责分发脚本和收集结果,若干 Agent(加压机)负责实际发送请求。第一步:Agent 机配置所有参与加压的机器都执行之前 JMeter 安装的步骤。然后启动 Agent 模式:# 在每台压力机上执行 /opt/jmeter/bin/jmeter-server &启动时如果报 RMI 相关的绑定失败,检查两件事:bin/jmeter-server 脚本里的 RMI_HOST_DEF 是否写的是本机 IP;以及服务器防火墙有没有开放 1099 和随机端口。第二步:控制机分发脚本Controller 会把自己的 jmx 脚本和依赖数据文件同步给 Agent。但要注意:不要在测试计划里写死绝对路径。CSV 参数文件请使用相对路径,并把文件放在 JMeter 的 bin 目录或者测试计划的同级目录下。分布式下 Controller 负责把文件打包发给各 Agent,相对路径大家都能找到,绝对路径必然报错。有两种路径模式最常见的坑:GUI 编辑时填的相对路径,分布式跑的时候可能被解析成不同的根目录;CSV 文件用了 /opt/data/test.csv 这种绝对路径,Agent 机器上根本没有这文件。第三步:执行测试在 Controller 的 JMeter bin 目录执行:./jmeter -n -t /opt/jmeter/tests/order_api.jmx \ -R 192.168.1.101,192.168.1.102 \ -l /opt/jmeter/results/result.jtl \ -e -o /opt/jmeter/results/html_report参数说明:-R:指定要使用的远程 Agent 列表-l:测试结果文件-e -o:测试结束后自动生成 HTML 报告Distributed 压测里,结果默认会汇总到 Controller 上输出,但网络传输结果数据本身也占带宽,所以如果 Agent 数量很多,建议在每一个 Agent 上也指定独立的结果落盘文件,压测结束后再汇总合并。4.3 命令行执行模式:生产环境压测的正确打开方式压测环境搭好之后,90% 的业务都要用命令行模式跑 GUI。GUI 模式只在脚本调试阶段使用,原因是 GUI 本身会消耗 JMeter 进程资源,样本量越大偏差越明显。命令行执行的标准写法:# 进入 JMeter bin 目录 ./jmeter -n \ -t /opt/jmeter/tests/payment_flow.jmx \ -l /opt/jmeter/results/payment_$(date +%Y%m%d_%H%M%S).jtl \ -j /opt/jmeter/logs/payment.log \ -e -o /opt/jmeter/results/payment_report \ -JTHREADS=500 \ -JDURATION=600-J 参数传值非常实用。在 jmx 脚本里,线程数、循环时间这些写变量名:<stringProp name="ThreadGroup.num_threads">${__P(THREADS,100)}</stringProp>调试时用 100 并发快速验证脚本,正式压测时通过 -JTHREADS=500 覆盖。同样的脚本、不同的压力等级,一份 jmx 文件全部搞定,不用维护成百个脚本副本。压测结果命名最好带上时间戳,不然跑多轮之后结果全乱了,你不知道哪份是哪个场景的数据。这就是我目录规划里提到时间戳的原因。4.4 脚本调试的十条心得这部分是我从多次踩坑里总结出来的经验,建议收藏:先 1 线程跑通脚本。不要一上来就 500 并发压上去,验证脚本不报错才是第一步。用非 GUI 模式调试。GUI 模式通过可视化调试没问题,但最后必须跑一次非 GUI 模式确认一切正常才开始正式压测。GUI 和非 GUI 模式的行为有时不完全一致。Debug Sampler 是你的好朋友。在脚本里临时加一个 Debug Sampler,配合 BeanShell 或 JSR223 打印上下文变量,排查参数化问题时比什么都好使。参数化时注意数据唯一性。比如用 CSV 文件做登录账号参数化时,要确认 CSV 的数据没有重复,否则并发冲突时会出现奇怪的报错,干扰问题定位。正则表达式提取器的坑。多个线程之间变量不隔离,边界值没写对很容易提取到上一个请求的残留数据。建议配合 Debug Sampler 检查。时间戳参数要精确控制格式。接口压测常用到时间戳参数,用__time(/1000,)获取的是秒级时间戳,但要确认接口要求的是秒还是毫秒,单位错了直接大面积报错。Cookie 管理器只在需要会话保持时启用。有些接口不关心 Cookie,但默认配置会保存服务端发来的 Set-Cookie,下一次请求自动带上,反而引发会话冲突。JSR223 用 Groovy 脚本前确保线程数别开太高。Groovy 脚本在 JMeter 中每次循环都会执行,脚本写得烂会大量占用压测机 CPU,拉低数据可信度。宁可把逻辑做简单些,也别在压测脚本里炫技。HAR 文件导入。如果是开发已经调试通的请求,直接用浏览器开发者工具导出 HAR 文件,JMeter 支持直接导入并转成 HTTP 请求,比手工抄写请求参数快十倍。压测前的脚本版本管理和 diff。压测脚本放进 Git 管理,每次修改后记得打 Tag,避免团队里有人从微信传来传去改着改着脚本版本就乱了。5. 常见问题与排查技巧实录5.1 高频问题速查表下面这份速查表全是我在真实环境中遇到并复盘过的:问题现象排查方向实际解决方案JMeter 启动失败,报 JAVA_HOME 未设置JAVA_HOME 没写对或没 export确认java -version可用,把 JAVA_HOME 写进 /etc/profile 并 source压测结果吞吐量上不去JVM 堆太小,GC 频繁改 jmeter.sh 的 HEAP 参数,调到 4G 并观察 GC 频率分布式压测 Agent 注册失败RMI 端口不通或 hostname 设置错误检查 jmeter-server 的 RMI_HOST_DEF,确认防火墙开放 1099 和随机端口压测一加到高并发就大量连接错误压测机端口耗尽调高系统可用端口范围和服务端 TIME_WAIT 参数聚合报告中响应时间随测试时间增长服务端存在内存泄漏或资源逐渐耗尽配合监控看资源趋势,确认瓶颈在服务端还是压测端分布式压测结果不一致各 Agent 的时间不同步用 NTP 同步各压测机时间,不然时间戳数据对不上JMeter 脚本在 GUI 正常但命令行为报错GUI 和 CLI 路径解析规则不同改用相对路径,脚本文件放到 bin 或测试计划同目录结果文件越来越大导致磁盘爆了监听器记录了过多无用信息用 Simple Data Writer 只写需要的字段,定期清理旧报告服务端 CPU 打满但响应时间还在上升线程池配置过小或服务端调度瓶颈查看应用线程池队列长度,查数据库慢查询记录测试结果波动很大、不稳定没有做多次取平均值正式结果至少跑 3 轮,取中间值或加权平均,避免偶然性5.2 压测机端口耗尽问题这个坑特别典型,值得单独拿出来讲。压测机本地端口耗尽是指定的并发数没跑满,但本机没有可用端口给新连接了。现象是压测开始时正常,几分钟后报大量Cannot assign requested address错误。原因是高并发短连接场景下,TIME_WAIT 状态的端口没来得及回收,新连接申请不到本地端口。Linux 默认的临时端口范围是 32768-60999(约 2.8 万个),对 2000 以上并发就有点紧。调整方法:# 查看当前端口范围 sysctl net.ipv4.ip_local_port_range # 调大端口范围 sysctl -w net.ipv4.ip_local_port_range="1024 65535" # 开启 TIME_WAIT 端口复用(注意适用场景) sysctl -w net.ipv4.tcp_tw_reuse=1 sysctl -w net.ipv4.tcp_timestamps=1 # 写入 /etc/sysctl.conf 持久化注意:tcp_tw_reuse 开启时要确认压测机和被测服务之间没有 LVS/负载均衡设备且没有 NAT 环境,否则可能造成连接异常。如果是在云环境服务器上,建议先小范围测试验证再全量开启。5.3 时间同步:分布式压测最容易忽略的细节分布式压测时,不同 Agent 压出来的响应时间如果相差明显,大概率是系统时间不同步。Linux 服务器用久了会漂移,加上各 Agent 硬件时钟不一致,结果就乱了。同步方法:# Ubuntu 安装 NTP apt-get install -y ntp systemctl enable ntp systemctl start ntp # 或用 chrony 替代 apt-get install -y chrony systemctl enable chrony systemctl start chrony # 检查当前时间偏差 ntpq -p这个操作在压测任务启动前做一次,能省掉压测后花大把时间排查数据不一致的精力。5.4 监控发现服务端瓶颈后的排查顺序我用监控环境做性能问题排查时,始终保持一套固定的排查顺序:先看 CPU 使用率。如果是 CPU 满载,看负载均值(top 里的 load average,1 分钟/5 分钟/15 分钟),判断是瞬时段还是持续饱和。再用 top 看是不是压测机的线程打满,然后进到服务进程里用 jstack 抓线程快照,找到耗 CPU 的线程堆栈。再看内存和 GC。用 jstat -gcutil 看老年代和 Full GC 情况,Old 占比持续走高说明有内存泄漏或大对象堆积。导出 Heap Dump 后用分析工具打开,找大对象引用链。最后看磁盘 IO 和网络。iostat 看 IO 利用率是否接近 100%,如果是数据库或消息队列就意味着响应时间瓶颈在存储层。网络方面用 sar -n DEV 看收发包量和丢包率。这套顺序练熟了,碰到任何性能问题都能在十几分钟内找到大致方向。6. 最后的实操提醒环境搭建本身其实不算难,真正拉开差距的是对细节的把控。我做完这套环境之后,最大的体会是:压测环境一定要和生产环境隔离。别图省事直接把压测机放在生产网段里,压测过程产生的流量和日志可能对正常业务造成影响。另外一点,压测结果一定要留下过程数据,不只是最终报告。聚合报告等于只给了你几个平均值,原始 JTL 文件里藏着每个请求的真实延迟样本。我后来用 Python 分析 JTL 数据,把每次请求的时间戳和服务端 GC 时间点对齐,才发现好几次响应时间飙升都是因为 Full GC 恰好发生在那几秒。这种结论只看聚合报告根本得不出来。最后一批建议是工作习惯上的:每次压测前打印系统时间和 jmeter 版本信息,作为结果分析的环境快照。压测脚本和配置全部纳入 Git,修改记录完整可回溯,出问题能回滚到某个稳定版本重新跑。监控数据保存期限至少三个月,方便做长期容量趋势分析。压测不只是为了“这次能扛多少并发”,更重要的是“随着系统演进,容量在下沉还是上升”。这批数据和历史的业务高峰数据做对比,比单纯看某一次压测平均值有价值得多。这套环境跑通之后,从脚本编写、分布式加压、资源监控到结果回归,整个链路都清晰了。后续你们可以按同样思路扩展接入 CI 流水线做持续性能验证,团队里任何人提交代码后自动触发一轮轻量压测,那种量变带来的质变,比手动跑压测强出一个量级。
1. 搭建前准备与整体思路1.1 为什么非要在 Linux 服务器上跑 JMeter刚开始接触性能测试那会儿,我习惯在自己电脑上直接压测。Windows 环境下跑 JMeter,小并发还行,但一旦线程数拉上去,本机资源先被吃满,测出来的数据根本不能反映服务器真实处理能力。后来我把压测环境搬到 Linux 服务器上,才发现这才是一切性能测试的正确起点。在 Linux 服务器上搭建 JMeter 性能测试与监控环境,到底解决了什么问题?三点最核心:消除客户端资源瓶颈:压测机本身需要消耗大量 CPU、内存和网络带宽。用独立的 Linux 服务器做压测机,被测服务不被压测工具抢占资源,数据才干净。稳定性和自动化优势:Linux 服务器的进程管理、定时任务、远程执行能力,天然适合做长时间稳定性测试,不用人盯着点鼠标。监控体系一体化:被压的服务器和压测机能在同一套监控体系里看数据,排查瓶颈时不用东拼西凑工具。这套环境的适用人群也很明确:后端开发做接口性能验证、测试团队搞压测基准、运维做容量评估——只要你不是那种只打算在自己笔记本上点两下看看响应时间的业余玩法,都需要这套环境。1.2 环境选型:我的推荐配置清单先说我踩过坑之后固定下来的组合方案,直接抄作业就行:角色推荐方案说明操作系统Ubuntu 20.04 LTS / 22.04 LTSCentOS 也可,但 Ubuntu 的包管理和内核版本对新硬件支持更好JDKOpenJDK 11(或 17)JMeter 5.x 要求 Java 8+,但实测 11 以上 GC 表现更稳定JMeter5.5 或 5.6.x5.x 系列对 HTTP/2、HAR 导入支持完善压力机配置4C8G 起步,带宽按需压测机 CPU 核数决定你本地能生成的最大并发监控方案Prometheus + node_exporter + Grafana统一看板,适合长期记录辅助工具nmon / dstat / iostat快速排查时用,轻量无侵入为什么推荐 Ubuntu?不是说 CentOS 不行,而是 Ubuntu 的 apt 源里 OpenJDK 版本更新更及时,编译安装时缺依赖也更容易补齐。如果团队已有 CentOS 的标准化镜像,用 CentOS 也一样跑,核心逻辑没区别。1.3 JDK 安装与配置:一步错步步错的环节JMeter 本质上是 Java 应用,JDK 配不好,后面全是坑。这里直接说我的标准操作流程:# Ubuntu 下安装 OpenJDK 11 apt-get update apt-get install -y openjdk-11-jdk # 验证安装 java -version # 配置 JAVA_HOME(写入 /etc/profile) export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 export PATH=$JAVA_HOME/bin:$PATH # 让配置生效 source /etc/profile一个非常关键但容易被忽略的点:务必确认 JMeter 的启动脚本找得到 JAVA_HOME。我遇到过一种情况,命令行 java -version 正常,但 ./jmeter.sh 直接报错找不到 Java。原因就是 JAVA_HOME 写进了 ~/.bashrc 而不是 /etc/profile,非交互 Shell 没加载到。这类细节等你们生产环境压测脚本定时执行时会要命。2. JMeter 安装与目录结构解析2.1 下载、解压与版本选择JMeter 安装本身不复杂,Apache 官网下载 tar.gz 包解压即用。但版本选择上有个经验:别追求最新版,选上一个稳定版本就行。新版本刚发布时插件生态往往还没跟上,比如 PerfMon 插件对 5.6.x 某些版本的兼容性问题就折腾过我。# 创建专用目录 mkdir -p /opt/jmeter cd /opt/jmeter # 下载 5.5 版本(用 apache 镜像源,国内访问更快) wget https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.5.tgz # 解压 tar -zxvf apache-jmeter-5.5.tgz # 建立软链接,方便后续升级切换 ln -s apache-jmeter-5.5 jmeter解压完后目录结构里有几个重点:bin/:启动脚本、配置文件。jmeter.sh 是 Linux 下的启动命令,jmeter.properties 是核心配置。lib/:存放依赖 Jar 包。扩展插件和 JDBC 驱动都丢这里。ext/:通过插件管理器安装的插件实际也放在 lib/ext 下面。logs/:运行日志目录,调试脚本时记得来这里翻日志。backups/:自动备份目录,这个容易忽略。JMeter 默认开启保存前备份,测试计划被改坏时能救回来。2.2 JVM 参数调优:压测机性能的关键阀门JMeter 压不出高并发,十有八九是 JVM 内存不够导致的。默认启动脚本里只分配了 256M 到 512M 的堆内存,这种配置下线程数加到几百就开始疯狂 GC,测试结果完全失真。修改 jmeter.sh 里的 HEAP 参数:# 打开 jmeter.sh,找到以下内容并修改 # 原配置 # HEAP="-Xms1g -Xmx1g" # 修改为(根据压测机内存调整) HEAP="-Xms4g -Xmx4g -XX:MaxMetaspaceSize=1024m"给几个基于实测的参考数值:压测机物理内存建议 JVM 堆内存参考最大线程数8G4G1000-200016G8G2000-400032G12G~16G4000+注意:不是内存越大堆就越大。JMeter 的高并发压测依赖的是本地线程调度能力和端口资源,堆内存太大反而增加 GC 停顿时间,反而拖垮生成压力的能力。4G~8G 堆一般够用了。另外还要调一个参数,bin/jmeter-server 的 RMIC 端口,分布式压测时要用:# jmeter-server 脚本中,找到或添加 RMI_HOST_DEF=-Djava.rmi.server.hostname=192.168.1.100这个 IP 填压测机的内网 IP,不然 Agent 节点注册不了。2.3 目录规划与权限配置我强烈建议不要在 root 用户下直接跑 JMeter。生产环境压测的审计要求越来越严格,root 跑出来的日志不好追溯,而且万一误操作了完蛋。我的做法是单独建一个轻量用户:# 创建用于压测的系统用户 useradd -M -s /bin/bash jmeter passwd jmeter # 授权目录 chown -R jmeter:jmeter /opt/jmeter chown -R jmeter:jmeter /var/tmp/jmeter*脚本、测试计划、测试结果都规划好统一目录:mkdir -p /opt/jmeter/tests mkdir -p /opt/jmeter/results mkdir -p /opt/jmeter/scripts这样 Jenkins 集成做 CI 压测时,路径规则清晰,脚本里的相对路径也不会乱。3. 性能监控环境搭建:没有监控的压测等于瞎跑3.1 监控是压测里最重要的隐形环节新手做压测最容易犯的错误是只看最终聚合报告里的响应时间,而忽略整个压测过程中系统资源的变化。响应时间慢了,到底是服务端 CPU 耗尽?内存不足导致频繁 GC?还是带宽瓶颈?没有监控数据支撑,你只能靠猜。监控体系要覆盖三个层面:被压服务的资源监控:CPU、内存、磁盘 IO、网络流量。JVM 层面的监控:GC 频率、堆内存使用、线程数这些。压测机自身的资源监控:确保瓶颈不在压测端。3.2 轻量级监控组合:nmon + dstat + sar前期快速排查,我喜欢用三个命令行工具:# nmon 交互式监控,实时看 CPU/内存/磁盘 apt-get install -y nmon nmon # dstat 聚合查看各类系统资源 apt-get install -y dstat dstat -c -m -d -n -t 2 # sar 收集历史数据,方便事后回看 apt-get install -y sysstat sar -u 1 10nmon 的一个好习惯:压测期间录制数据,压测结束后用 nmon2csv 类工具导出成 CSV,再用 Excel 或者 Python 绘图做报告。# 录制 600 秒(10分钟)的 nmon 数据 nmon -s 5 -c 120 -f -m /tmp/ # 参数说明:-s 采样间隔秒, -c 采样次数, -f 输出为文件这个文件默认生成在指定目录,文件名带时间戳,压测结束后用 Excel 就能打开图表化分析。3.3 可视化监控方案:Prometheus + node_exporter + Grafana如果你们压测是周期性进行的,或者需要团队共享监控看板,那还是上一套标准组合。这套方案我反复搭过多次,给一个经过验证的流程:第一步:安装 node_exporter# 下载最新稳定版(以 1.6.1 为例) wget https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux-amd64.tar.gz tar -zxvf node_exporter-1.6.1.linux-amd64.tar.gz mv node_exporter-1.6.1.linux-amd64/node_exporter /usr/local/bin/ # 后台启动,监听端口 9100 nohup /usr/local/bin/node_exporter --web.listen-address=":9100" \ --collector.systemd \ --collector.processes \ > /var/log/node_exporter.log 2>&1 &这一步如果改了监听端口,后面 Prometheus 配置里也要同步改。第二步:安装 Prometheuscurl -LO https://github.com/prometheus/prometheus/releases/download/v2.47.0/prometheus-2.47.0.linux-amd64.tar.gz tar -zxvf prometheus-2.47.0.linux-amd64.tar.gz mv prometheus-2.47.0.linux-amd64 /opt/prometheus编辑 /opt/prometheus/prometheus.yml,添加被压服务器的任务节点:scrape_configs: - job_name: 'linux-node' static_configs: - targets: - '被压服务器IP:9100' - '压测机IP:9100'第三步:安装 Grafana,导入现成仪表盘apt-get install -y apt-transport-https software-properties-common wget -q -O - https://packages.grafana.com/gpg/gpg.key | apt-key add - echo "deb https://packages.grafana.com/oss/deb stable main" | tee /etc/apt/sources.list.d/grafana.list apt-get update apt-get install -y grafana systemctl start grafana-server浏览器打开 Grafana 后,数据源添加 Prometheus 的地址(默认 localhost:9090),导入模板 ID 例如 16098 这种 Linux 主机的现成看板,几分钟就能看到监控图表。为什么坚持用这套而不买商业监控?因为压测监控本质上是数据要能回放、能对比。Prometheus 存储的多轮压测数据可以任意拉时间范围对比,这是截图式监控工具做不到的。后面做回归对比时,这功能救过我不少次。3.4 JMeter 自身运行状态监控很多人忽略一个问题:压测机自己死了没有?高并发下压测机本身可能成为瓶颈被压垮。此时堆内存爆掉、线程池阻塞、端口耗尽都可能出现,压测数据会严重失真。最简单的判断方式:实时盯 jmeter.log 的尾部:tail -f /opt/jmeter/jmeter.log出现大量Error creating connection或者No buffer space available之类的报错,就是压测机瓶颈的信号。另外配合刚开始配置的 JVM 参数,用 jvisualvm 或 jstat 看 GC 频率:# 找到 JMeter 的进程号 ps -ef | grep jmeter # 每秒查看 GC 情况 jstat -gcutil <PID> 1000如果 Full GC 频率高于每秒一次,压测数据就不可信了,应该停下来加大内存或者压低并发。4. 压测脚本编写与分布式加压方案4.1 脚本编写:线程组、监听器与断言要点测试计划的结构设计直接影响压测结果的可信度。线程组的三个关键参数:线程数:总并发用户数,不是越大越好,要跟被测系统的预期容量匹配。Ramp-Up 时间:线程启动时间。设得太小,瞬间压力峰值会把服务打崩,误判容量上限;设得太大,数据没有参考意义。循环次数:设成永远,再用调度器控制压测时长,这是做稳定性压测的标准姿势。监听器我只保留这两个,其他监听器多余:聚合报告(Summary Report):压测结束后的平均响应时间、TP90、TP99、吞吐量数据。简单数据写入器(Simple Data Writer):把每次请求的延迟原始数据落盘,方便事后做分布分析。聚合报告里有小组数据最容易误读:Error% 和 Throughput 的关系。如果错误率升高但吞吐量和响应时间反而下降,那不是服务处理能力变强了,而是请求在客户端就被拒掉了。这个逻辑我喜欢叫“假性成功”——只看结果数字是被骗的。断言我用得最克制,一般只在冒烟压测(轻量验证)时用响应断言检查状态码,正式压测不挂过多断言,断言本身也会消耗 JMeter 性能。4.2 分布式压测集群:一台压力机不够怎么办单机 JMeter 理论上能跑几千并发,但实际受限于 CPU 核数和网络吞吐。需要更大压力时,就得组建分布式压测集群。分布式压测的架构是:Controller(控制机)负责分发脚本和收集结果,若干 Agent(加压机)负责实际发送请求。第一步:Agent 机配置所有参与加压的机器都执行之前 JMeter 安装的步骤。然后启动 Agent 模式:# 在每台压力机上执行 /opt/jmeter/bin/jmeter-server &启动时如果报 RMI 相关的绑定失败,检查两件事:bin/jmeter-server 脚本里的 RMI_HOST_DEF 是否写的是本机 IP;以及服务器防火墙有没有开放 1099 和随机端口。第二步:控制机分发脚本Controller 会把自己的 jmx 脚本和依赖数据文件同步给 Agent。但要注意:不要在测试计划里写死绝对路径。CSV 参数文件请使用相对路径,并把文件放在 JMeter 的 bin 目录或者测试计划的同级目录下。分布式下 Controller 负责把文件打包发给各 Agent,相对路径大家都能找到,绝对路径必然报错。有两种路径模式最常见的坑:GUI 编辑时填的相对路径,分布式跑的时候可能被解析成不同的根目录;CSV 文件用了 /opt/data/test.csv 这种绝对路径,Agent 机器上根本没有这文件。第三步:执行测试在 Controller 的 JMeter bin 目录执行:./jmeter -n -t /opt/jmeter/tests/order_api.jmx \ -R 192.168.1.101,192.168.1.102 \ -l /opt/jmeter/results/result.jtl \ -e -o /opt/jmeter/results/html_report参数说明:-R:指定要使用的远程 Agent 列表-l:测试结果文件-e -o:测试结束后自动生成 HTML 报告Distributed 压测里,结果默认会汇总到 Controller 上输出,但网络传输结果数据本身也占带宽,所以如果 Agent 数量很多,建议在每一个 Agent 上也指定独立的结果落盘文件,压测结束后再汇总合并。4.3 命令行执行模式:生产环境压测的正确打开方式压测环境搭好之后,90% 的业务都要用命令行模式跑 GUI。GUI 模式只在脚本调试阶段使用,原因是 GUI 本身会消耗 JMeter 进程资源,样本量越大偏差越明显。命令行执行的标准写法:# 进入 JMeter bin 目录 ./jmeter -n \ -t /opt/jmeter/tests/payment_flow.jmx \ -l /opt/jmeter/results/payment_$(date +%Y%m%d_%H%M%S).jtl \ -j /opt/jmeter/logs/payment.log \ -e -o /opt/jmeter/results/payment_report \ -JTHREADS=500 \ -JDURATION=600-J 参数传值非常实用。在 jmx 脚本里,线程数、循环时间这些写变量名:<stringProp name="ThreadGroup.num_threads">${__P(THREADS,100)}</stringProp>调试时用 100 并发快速验证脚本,正式压测时通过 -JTHREADS=500 覆盖。同样的脚本、不同的压力等级,一份 jmx 文件全部搞定,不用维护成百个脚本副本。压测结果命名最好带上时间戳,不然跑多轮之后结果全乱了,你不知道哪份是哪个场景的数据。这就是我目录规划里提到时间戳的原因。4.4 脚本调试的十条心得这部分是我从多次踩坑里总结出来的经验,建议收藏:先 1 线程跑通脚本。不要一上来就 500 并发压上去,验证脚本不报错才是第一步。用非 GUI 模式调试。GUI 模式通过可视化调试没问题,但最后必须跑一次非 GUI 模式确认一切正常才开始正式压测。GUI 和非 GUI 模式的行为有时不完全一致。Debug Sampler 是你的好朋友。在脚本里临时加一个 Debug Sampler,配合 BeanShell 或 JSR223 打印上下文变量,排查参数化问题时比什么都好使。参数化时注意数据唯一性。比如用 CSV 文件做登录账号参数化时,要确认 CSV 的数据没有重复,否则并发冲突时会出现奇怪的报错,干扰问题定位。正则表达式提取器的坑。多个线程之间变量不隔离,边界值没写对很容易提取到上一个请求的残留数据。建议配合 Debug Sampler 检查。时间戳参数要精确控制格式。接口压测常用到时间戳参数,用__time(/1000,)获取的是秒级时间戳,但要确认接口要求的是秒还是毫秒,单位错了直接大面积报错。Cookie 管理器只在需要会话保持时启用。有些接口不关心 Cookie,但默认配置会保存服务端发来的 Set-Cookie,下一次请求自动带上,反而引发会话冲突。JSR223 用 Groovy 脚本前确保线程数别开太高。Groovy 脚本在 JMeter 中每次循环都会执行,脚本写得烂会大量占用压测机 CPU,拉低数据可信度。宁可把逻辑做简单些,也别在压测脚本里炫技。HAR 文件导入。如果是开发已经调试通的请求,直接用浏览器开发者工具导出 HAR 文件,JMeter 支持直接导入并转成 HTTP 请求,比手工抄写请求参数快十倍。压测前的脚本版本管理和 diff。压测脚本放进 Git 管理,每次修改后记得打 Tag,避免团队里有人从微信传来传去改着改着脚本版本就乱了。5. 常见问题与排查技巧实录5.1 高频问题速查表下面这份速查表全是我在真实环境中遇到并复盘过的:问题现象排查方向实际解决方案JMeter 启动失败,报 JAVA_HOME 未设置JAVA_HOME 没写对或没 export确认java -version可用,把 JAVA_HOME 写进 /etc/profile 并 source压测结果吞吐量上不去JVM 堆太小,GC 频繁改 jmeter.sh 的 HEAP 参数,调到 4G 并观察 GC 频率分布式压测 Agent 注册失败RMI 端口不通或 hostname 设置错误检查 jmeter-server 的 RMI_HOST_DEF,确认防火墙开放 1099 和随机端口压测一加到高并发就大量连接错误压测机端口耗尽调高系统可用端口范围和服务端 TIME_WAIT 参数聚合报告中响应时间随测试时间增长服务端存在内存泄漏或资源逐渐耗尽配合监控看资源趋势,确认瓶颈在服务端还是压测端分布式压测结果不一致各 Agent 的时间不同步用 NTP 同步各压测机时间,不然时间戳数据对不上JMeter 脚本在 GUI 正常但命令行为报错GUI 和 CLI 路径解析规则不同改用相对路径,脚本文件放到 bin 或测试计划同目录结果文件越来越大导致磁盘爆了监听器记录了过多无用信息用 Simple Data Writer 只写需要的字段,定期清理旧报告服务端 CPU 打满但响应时间还在上升线程池配置过小或服务端调度瓶颈查看应用线程池队列长度,查数据库慢查询记录测试结果波动很大、不稳定没有做多次取平均值正式结果至少跑 3 轮,取中间值或加权平均,避免偶然性5.2 压测机端口耗尽问题这个坑特别典型,值得单独拿出来讲。压测机本地端口耗尽是指定的并发数没跑满,但本机没有可用端口给新连接了。现象是压测开始时正常,几分钟后报大量Cannot assign requested address错误。原因是高并发短连接场景下,TIME_WAIT 状态的端口没来得及回收,新连接申请不到本地端口。Linux 默认的临时端口范围是 32768-60999(约 2.8 万个),对 2000 以上并发就有点紧。调整方法:# 查看当前端口范围 sysctl net.ipv4.ip_local_port_range # 调大端口范围 sysctl -w net.ipv4.ip_local_port_range="1024 65535" # 开启 TIME_WAIT 端口复用(注意适用场景) sysctl -w net.ipv4.tcp_tw_reuse=1 sysctl -w net.ipv4.tcp_timestamps=1 # 写入 /etc/sysctl.conf 持久化注意:tcp_tw_reuse 开启时要确认压测机和被测服务之间没有 LVS/负载均衡设备且没有 NAT 环境,否则可能造成连接异常。如果是在云环境服务器上,建议先小范围测试验证再全量开启。5.3 时间同步:分布式压测最容易忽略的细节分布式压测时,不同 Agent 压出来的响应时间如果相差明显,大概率是系统时间不同步。Linux 服务器用久了会漂移,加上各 Agent 硬件时钟不一致,结果就乱了。同步方法:# Ubuntu 安装 NTP apt-get install -y ntp systemctl enable ntp systemctl start ntp # 或用 chrony 替代 apt-get install -y chrony systemctl enable chrony systemctl start chrony # 检查当前时间偏差 ntpq -p这个操作在压测任务启动前做一次,能省掉压测后花大把时间排查数据不一致的精力。5.4 监控发现服务端瓶颈后的排查顺序我用监控环境做性能问题排查时,始终保持一套固定的排查顺序:先看 CPU 使用率。如果是 CPU 满载,看负载均值(top 里的 load average,1 分钟/5 分钟/15 分钟),判断是瞬时段还是持续饱和。再用 top 看是不是压测机的线程打满,然后进到服务进程里用 jstack 抓线程快照,找到耗 CPU 的线程堆栈。再看内存和 GC。用 jstat -gcutil 看老年代和 Full GC 情况,Old 占比持续走高说明有内存泄漏或大对象堆积。导出 Heap Dump 后用分析工具打开,找大对象引用链。最后看磁盘 IO 和网络。iostat 看 IO 利用率是否接近 100%,如果是数据库或消息队列就意味着响应时间瓶颈在存储层。网络方面用 sar -n DEV 看收发包量和丢包率。这套顺序练熟了,碰到任何性能问题都能在十几分钟内找到大致方向。6. 最后的实操提醒环境搭建本身其实不算难,真正拉开差距的是对细节的把控。我做完这套环境之后,最大的体会是:压测环境一定要和生产环境隔离。别图省事直接把压测机放在生产网段里,压测过程产生的流量和日志可能对正常业务造成影响。另外一点,压测结果一定要留下过程数据,不只是最终报告。聚合报告等于只给了你几个平均值,原始 JTL 文件里藏着每个请求的真实延迟样本。我后来用 Python 分析 JTL 数据,把每次请求的时间戳和服务端 GC 时间点对齐,才发现好几次响应时间飙升都是因为 Full GC 恰好发生在那几秒。这种结论只看聚合报告根本得不出来。最后一批建议是工作习惯上的:每次压测前打印系统时间和 jmeter 版本信息,作为结果分析的环境快照。压测脚本和配置全部纳入 Git,修改记录完整可回溯,出问题能回滚到某个稳定版本重新跑。监控数据保存期限至少三个月,方便做长期容量趋势分析。压测不只是为了“这次能扛多少并发”,更重要的是“随着系统演进,容量在下沉还是上升”。这批数据和历史的业务高峰数据做对比,比单纯看某一次压测平均值有价值得多。这套环境跑通之后,从脚本编写、分布式加压、资源监控到结果回归,整个链路都清晰了。后续你们可以按同样思路扩展接入 CI 流水线做持续性能验证,团队里任何人提交代码后自动触发一轮轻量压测,那种量变带来的质变,比手动跑压测强出一个量级。