
1. 为什么单机压测迟早会撞墙做过性能测试的人大概都有过这样的经历本地用 JMeter 跑个 500 并发CPU 直接飙到 100%风扇呼呼转结果报告里响应时间曲线跟过山车一样你根本分不清是服务端扛不住了还是压测机自己先跪了。我最早做压测的时候就踩过这个坑当时测一个订单接口单机设了 800 线程跑出来的 TPS 只有理论值的三分之一排查了半天才发现瓶颈根本不在被测服务而是压测机自己的 CPU 和内存先到顶了。这就是单机压测最核心的问题压测工具本身也是要消耗资源的。JMeter 每个线程都会占用一定的内存和 CPU当并发数上去之后压测机的资源消耗会急剧增加最终导致压测结果失真。一般来说一台配置还不错的机器比如 8 核 16G用 GUI 模式跑个几百并发就到头了命令行模式non-GUI能撑到一两千但再往上就力不从心了。那怎么办答案就是分布式压测。简单说就是找一台机器当指挥官我们叫它 Master 或者控制机再找若干台机器当打手叫 Slave 或者执行机/负载机指挥官负责下发测试脚本和汇总报告打手们负责实际发压。这样压测能力就可以通过增加打手节点来线性扩展理论上你想压多大就压多大。这篇文章我会把搭建 JMeter 分布式压测机的完整过程拆开讲清楚包括架构原理、环境准备、配置细节、启动流程、常见坑的排查以及一些我实际用下来觉得很有用的经验技巧。不管你是刚接触性能测试的新手还是已经用过 JMeter 但没搭过分布式的老手应该都能从里面找到有用的东西。2. 分布式压测的架构与核心原理2.1 Master-Slave 模式到底是怎么工作的JMeter 的分布式架构其实不复杂核心就是 Master-Slave 模式。Master 节点上运行 JMeter 的控制端Slave 节点上运行 JMeter 的服务端叫 jmeter-server。当你从 Master 发起测试时Master 会把测试计划.jmx 文件分发给所有 Slave每个 Slave 按照自己的配置启动对应数量的线程去发压压测过程中 Slave 会把结果数据回传给 Master最后由 Master 汇总生成报告。这里有个关键点很多人一开始会搞混并发数是分摊到各个 Slave 上的。比如你设了 1000 个线程有 4 台 Slave那每台 Slave 实际只跑 250 个线程。所以你在 Master 上配置线程组的时候填的是总并发数JMeter 会自动帮你分配到各个 Slave。这一点跟很多人直觉不太一样我第一次搭的时候也以为是每台都跑 1000结果压出来的量远超预期。还有一个容易忽略的细节Master 本身也可以参与发压。默认情况下 Master 只负责调度和汇总不跑线程。但如果你机器数量有限也可以让 Master 同时作为一台 Slave 来用不过我不太推荐这么做因为 Master 要处理所有 Slave 回传的结果数据本身负担就不轻再让它发压容易成为瓶颈。2.2 为什么选 JMeter 做分布式压测市面上压测工具不少Locust、Gatling、k6 各有各的优势但 JMeter 在分布式这块有几个很实在的好处。第一是成熟稳定JMeter 的分布式模式已经存在很多年了虽然配置起来有点繁琐但踩过的坑基本都有人踩过遇到问题容易找到解决方案。第二是协议支持广HTTP、HTTPS、TCP、JDBC、JMS 等等都支持一个工具能覆盖大部分压测场景。第三是结果汇总方便Master 会自动收集所有 Slave 的数据生成聚合报告不用你自己去合并。当然 JMeter 分布式也有它的局限。比如 Slave 之间的时钟同步要求比较高如果各节点时间差太大汇总出来的响应时间会不准。还有就是 Master 和 Slave 之间的通信如果网络不稳定结果回传可能会丢数据。这些问题后面我都会讲到怎么处理。2.3 分布式压测的适用场景与不适用场景分布式压测不是万能的得看你的实际需求。适合用的场景需要模拟超高并发几千甚至上万、单机资源不够、需要从多个网络位置发起请求模拟真实用户分布。不太适合的场景并发量本来就不大几百以内单机完全够用、测试脚本里有大量文件上传下载分发和回传开销大、对测试结果精度要求极高分布式汇总会有微小误差。我个人的经验是如果你的目标并发在 2000 以内先试试单机命令行模式调优把 JVM 堆内存调大、用非 GUI 模式跑很多时候就够了。真正需要上分布式一般是并发要求到了 3000 以上或者单机怎么调都撑不住的时候。3. 环境准备与节点规划3.1 机器配置与数量估算搭分布式之前第一件事是算清楚需要几台机器。这个没有绝对公式但有个经验估算法每台 Slave 大概能承载 500-1000 个线程取决于脚本复杂度和机器配置。如果脚本里有大量断言、正则提取、JSON 解析那单机能承载的线程数会明显下降可能只有 300-500。假设你要压 5000 并发脚本中等复杂度那大概需要 5-8 台 Slave。机器配置建议至少 4 核 8G最好 8 核 16G。CPU 核心数比内存更重要因为 JMeter 是 CPU 密集型工具。网络带宽也要考虑如果压测请求的响应体很大带宽可能成为瓶颈。Master 节点配置可以稍低一些4 核 8G 就够但如果 Slave 数量多比如超过 10 台Master 要处理大量结果回传建议也上 8 核 16G。3.2 操作系统与 JDK 版本选择JMeter 是 Java 写的所以每个节点都要装 JDK。JDK 版本建议用 8 或 11这两个版本最稳定兼容性最好。JDK 17 也能跑但有些老插件可能不兼容除非你确认所有用到的插件都支持否则别轻易上新版本。操作系统方面Linux 是首选CentOS 7/8、Ubuntu 20.04/22.04 都可以。Windows 也能做 Slave但作为 Master 或者高并发 Slave 不太推荐因为 Windows 的网络栈和文件句柄管理不如 Linux 高效。如果团队里有人习惯 Windows可以让他用 Windows 做 Master 做脚本调试实际压测还是用 Linux 集群。所有节点的 JDK 版本和 JMeter 版本必须完全一致这是硬性要求。版本不一致会导致序列化错误、类找不到等各种诡异问题。我见过有人 Master 用 JMeter 5.4Slave 用 5.5结果跑起来各种报错排查了半天才发现是版本问题。3.3 网络与时钟同步要求所有节点必须在同一个局域网内或者至少网络延迟要低且稳定。跨公网搭分布式压测基本不可行因为 Master 和 Slave 之间的通信量很大延迟高了结果会严重失真。时钟同步是另一个容易被忽略的点。所有节点的时间必须一致误差最好在 1 秒以内。Linux 上用 NTP 服务同步就行配置好之后用date命令检查各节点时间是否一致。如果时间差太大汇总报告里的响应时间会出现负数或者异常大的值非常影响判断。防火墙方面Slave 节点需要开放 JMeter 的通信端口默认是 1099RMI 端口和 50000实际数据传输端口可以自定义。如果公司网络有安全组或者防火墙策略提前找运维开通别等到跑测试的时候才发现连不上。4. 核心配置与实操步骤4.1 JMeter 安装与目录规划先在每个节点上下载并解压 JMeter。建议统一放在/opt/jmeter或者/usr/local/jmeter这样的路径下方便管理。解压之后设置好环境变量把JMETER_HOME和PATH配好这样在任何目录下都能直接敲jmeter命令。# 解压 JMeter假设下载的是 apache-jmeter-5.5.tgz tar -zxvf apache-jmeter-5.5.tgz -C /opt/ mv /opt/apache-jmeter-5.5 /opt/jmeter # 配置环境变量编辑 /etc/profile export JMETER_HOME/opt/jmeter export PATH$JMETER_HOME/bin:$PATH export CLASSPATH$JMETER_HOME/lib/ext/ApacheJMeter_core.jar:$JMETER_HOME/lib/jorphan.jar # 使配置生效 source /etc/profile目录规划上有个小建议把测试脚本和结果数据放在独立的数据盘上不要跟系统盘混在一起。压测过程中会产生大量临时文件和结果数据如果系统盘空间不够可能导致压测中断甚至系统异常。我一般会在每台 Slave 上挂一个/data目录专门放这些。4.2 Master 节点关键配置Master 节点的配置主要集中在jmeter.properties文件里。找到 JMeter 安装目录下的bin/jmeter.properties修改以下几个关键参数# 指定 Slave 节点列表格式是 IP:端口多个用逗号分隔 remote_hosts192.168.1.101:1099,192.168.1.102:1099,192.168.1.103:1099 # Master 监听端口默认 1099如果冲突可以改 server_port1099 # 关闭 SSL内网环境可以关简化配置 server.rmi.ssl.disabletrue # 结果回传模式standard 是默认的数据量大时可以改成 stripped 或 batch modestandard # 结果收集超时时间默认 60000 毫秒网络慢可以调大 # 这个参数在较新版本里叫 remote_result_timeoutremote_hosts这个参数是核心把所有 Slave 的 IP 和端口列进去。注意端口要跟 Slave 上配置的一致默认都是 1099。如果 Slave 数量多这一行会很长建议按顺序排列方便后面排查。server.rmi.ssl.disabletrue这个参数在内网环境强烈建议打开设为 true因为 JMeter 默认启用 RMI SSL 加密配置证书很麻烦内网环境没必要。如果不关启动 Slave 的时候会报 SSL 相关的错误。4.3 Slave 节点关键配置Slave 节点的配置跟 Master 略有不同重点是让它可以被 Master 远程调用# Slave 监听端口 server_port1099 # 关闭 SSL server.rmi.ssl.disabletrue # 绑定 IP如果机器有多网卡指定内网 IP java.rmi.server.hostname192.168.1.101 # 关闭 GUI 模式Slave 不需要 GUI jmeterengine.nonguitruejava.rmi.server.hostname这个参数特别重要如果机器有多张网卡或者有 Docker 虚拟网卡不指定的话 RMI 可能会绑定到错误的 IP 上导致 Master 连不上。我踩过这个坑当时机器上装了 DockerJMeter 默认绑到了 docker0 的网卡上Master 怎么都连不上排查了好久才发现是这个原因。4.4 启动 Slave 与 Master 的连接验证配置改完之后先启动所有 Slave 节点。在每台 Slave 上执行# 进入 JMeter 的 bin 目录 cd /opt/jmeter/bin # 启动 jmeter-server后台运行并输出日志 nohup ./jmeter-server -Djava.rmi.server.hostname192.168.1.101 jmeter-server.log 21 启动之后检查日志看到Created remote object和Server started之类的字样就说明启动成功了。如果报错重点看是不是端口被占用、SSL 没关、或者 hostname 绑定有问题。所有 Slave 启动完之后在 Master 上验证连接。有两种方式一是用 GUI 模式点击运行菜单里的远程启动看能不能看到所有 Slave 列表二是用命令行# 在 Master 上执行测试远程连接 ./jmeter -n -t test.jmx -R 192.168.1.101,192.168.1.102 -l result.jtl-R参数指定要使用的 Slave 列表如果连接正常会看到每个 Slave 都开始执行测试。如果某个 Slave 连不上会报Connection refused或者Timeout之类的错误这时候就要去检查那台 Slave 的配置和网络。5. 压测执行与结果汇总5.1 命令行模式下的分布式压测实际压测一定要用命令行模式non-GUIGUI 模式只用来调试脚本。命令行分布式压测的基本命令格式./jmeter -n -t /path/to/test.jmx -R 192.168.1.101,192.168.1.102,192.168.1.103 -l /path/to/result.jtl -e -o /path/to/report参数说明-n表示非 GUI 模式-t指定测试脚本-R指定 Slave 列表也可以用-r表示使用配置文件里所有 Slave-l指定结果文件-e -o表示测试结束后自动生成 HTML 报告。这里有个细节要注意-l指定的结果文件路径Master 和 Slave 上都要有写权限。因为 Slave 会先把结果写到本地临时文件再回传给 Master。如果路径不存在或者没权限压测会直接失败。5.2 线程数与 Slave 数量的分配策略前面说过Master 上配置的线程数是总数会自动分摊到各 Slave。但这里有个坑如果线程数不能整除 Slave 数量分配会不均匀。比如 1000 线程分给 3 台 Slave会变成 334、333、333基本均匀。但如果 Slave 配置差异很大比如有的 8 核有的 4 核平均分配就不合理了性能差的机器会先成为瓶颈。这种情况下可以用-R参数配合多个测试计划或者用 JMeter 的remote_hosts分组功能把性能相近的机器分到一组。不过更简单的做法是尽量保证所有 Slave 配置一致这样平均分配就没问题。5.3 结果数据的收集与合并分布式压测的结果汇总有两种模式在jmeter.properties里通过mode参数控制模式说明适用场景standard每个 Sample 结果都回传需要详细分析每个请求stripped只回传成功/失败和响应时间数据量大减少网络传输batch批量回传减少通信次数高并发场景推荐statistical只回传统计信息只关心聚合结果高并发场景我强烈建议用batch或stripped模式因为 standard 模式下每个请求结果都要回传网络开销巨大Master 很容易成为瓶颈。用 batch 模式可以把结果攒一批再发大幅降低通信压力。结果文件.jtl生成之后可以用 JMeter 自带的报告生成器转成 HTML./jmeter -g /path/to/result.jtl -o /path/to/html_report生成的 HTML 报告包含 TPS、响应时间、错误率等关键指标直接打开index.html就能看。6. 常见问题与排查技巧实录6.1 Slave 连不上 Master 的排查思路这是最常见的问题排查顺序建议这样走先确认 Slave 上的 jmeter-server 进程是否在运行ps -ef | grep jmeter再看端口是否监听netstat -tlnp | grep 1099然后从 Master 上 telnet 一下 Slave 的端口telnet 192.168.1.101 1099如果 telnet 不通就是网络或防火墙问题。如果端口通但 JMeter 还是连不上八成是java.rmi.server.hostname没配对。可以在 Slave 上执行hostname -I看看所有 IP确认绑定的 IP 是 Master 能访问到的那个。另外检查server.rmi.ssl.disable是否在 Master 和 Slave 上都设成了 true只要有一边没关就会握手失败。6.2 压测结果异常偏低的可能原因跑完压测发现 TPS 远低于预期先别急着怀疑被测服务按这个顺序排查压测机资源用top看 CPU、free -h看内存如果压测机 CPU 满了就是压测机瓶颈、网络带宽用iftop或nmon看网络流量是否打满、Slave 分配是否均匀看各 Slave 的日志确认线程数分配是否合理、脚本本身的开销断言、提取器、监听器都会消耗资源压测时应该关掉所有不必要的监听器。我遇到过一次 TPS 只有预期十分之一的情况最后发现是脚本里加了一个查看结果树监听器这个监听器会把每个请求的完整响应都存到内存里几千并发下直接把 Slave 的内存吃满了。压测脚本里绝对不能保留查看结果树和聚合报告这类监听器它们只适合调试阶段用。6.3 时钟不同步导致的数据异常如果汇总报告里出现响应时间为负数、或者某个 Slave 的数据时间戳明显偏离基本可以确定是时钟不同步。解决办法是在所有节点上配置 NTP# 安装 NTP yum install -y ntp # CentOS apt install -y ntp # Ubuntu # 启动并设置开机自启 systemctl start ntpd systemctl enable ntpd # 手动同步一次 ntpdate -u ntp.aliyun.com同步完之后用date命令对比各节点时间误差在 1 秒以内就没问题。建议在压测前都跑一次时间同步养成习惯。6.4 常见问题速查表问题现象可能原因解决方法Slave 连接被拒绝端口未开放/进程未启动检查防火墙和 jmeter-server 进程SSL 握手失败SSL 未关闭所有节点设 server.rmi.ssl.disabletrue结果文件为空路径无权限/磁盘满检查路径权限和磁盘空间TPS 异常低压测机瓶颈/监听器开销关监听器检查压测机资源响应时间出现负值时钟不同步配置 NTP 同步所有节点Master 内存溢出结果回传模式不当改用 batch 或 stripped 模式部分 Slave 无数据网络抖动/连接中断检查网络调大超时时间7. 实操心得与性能调优建议7.1 JVM 参数调优JMeter 默认的 JVM 堆内存比较小高并发下容易 OOM。在bin/jmeter文件Linux里找到HEAP相关的配置调整成# 默认是 -Xms1g -Xmx1g改成 HEAP-Xms4g -Xmx4g -XX:MaxMetaspaceSize512m堆内存大小建议设为物理内存的一半左右比如 16G 内存的机器设 8G。不要设太大否则 GC 停顿时间会变长反而影响压测。另外建议加上-XX:UseG1GC启用 G1 垃圾回收器高并发下表现比默认的 Parallel GC 更稳定。7.2 操作系统层面的优化Linux 默认的一些参数在高并发下会成为瓶颈压测前建议调整# 增大文件句柄数限制 ulimit -n 65535 # 调整 TCP 参数编辑 /etc/sysctl.conf net.ipv4.tcp_tw_reuse 1 net.ipv4.ip_local_port_range 1024 65000 net.ipv4.tcp_max_tw_buckets 5000 net.core.somaxconn 65535 # 生效 sysctl -pip_local_port_range这个参数特别重要因为每个 TCP 连接都要占用一个本地端口默认范围只有 3 万多个高并发下很快就不够用了会报 Cannot assign requested address 的错误。调大之后能支撑更多并发连接。7.3 脚本层面的优化技巧脚本写得好不好对压测能力影响巨大。几个实用技巧用 CSV 参数化代替函数生成函数每次调用都要计算开销大、关掉所有断言除非你确实需要验证响应内容、用 JSON 提取器代替正则提取器JSON 提取器性能更好、把 HTTP 请求默认值配置好避免每个请求都重复配置、用事务控制器合并相关请求减少结果数据量。还有一个容易被忽略的点不要在压测脚本里用聚合报告监听器。很多人习惯在脚本里加一个聚合报告看实时数据但这个监听器会持续消耗资源。正确做法是压测时不加任何监听器结果写到 .jtl 文件压测结束后再用-g参数生成报告。7.4 分布式压测的扩展与维护集群搭好之后日常维护也有讲究。建议写一个启动脚本一键启动所有 Slave#!/bin/bash # start_slaves.sh SLAVES(192.168.1.101 192.168.1.102 192.168.1.103) for slave in ${SLAVES[]}; do ssh $slave cd /opt/jmeter/bin nohup ./jmeter-server jmeter-server.log 21 echo Started slave: $slave done这样每次压测前跑一下脚本就行不用一台台登录。停止的时候也类似用pkill -f jmeter-server批量停掉。如果集群规模要扩展新增 Slave 只需要三步装好相同版本的 JMeter、改好配置文件、在 Master 的remote_hosts里加上新 IP。整个过程十分钟以内能搞定。8. 一些踩坑之后的个人体会分布式压测这套东西配置本身不难难的是排查问题。我前后搭过好几套集群最大的体会就是日志是你的救命稻草。Master 和 Slave 的日志都要留着出问题的时候对照着看基本都能定位到原因。特别是 Slave 的jmeter-server.log里面会记录连接建立、脚本接收、结果回传的全过程比瞎猜高效多了。另一个体会是不要追求一次到位。刚开始搭的时候先用两台机器跑通流程确认 Master 能连上 Slave、脚本能正常分发、结果能正常汇总然后再逐步加机器。我见过有人一上来就配十台 Slave结果连不上十台机器一台台排查搞了一整天。先用最小规模验证再扩展这是最省时间的做法。最后说个实际用下来的小技巧压测前先跑一轮小并发做冒烟测试。比如目标 5000 并发先用 50 并发跑一分钟确认脚本没问题、各节点连接正常、结果数据正常再上全量。这一步花不了几分钟但能避免很多跑到一半发现脚本有 bug的尴尬情况。压测是个细致活准备工作做足了跑起来才踏实。