ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JMeter压力测试实战:从方案设计到瓶颈定位的完整指南

JMeter压力测试实战:从方案设计到瓶颈定位的完整指南 最近团队刚完成一个老项目的迁移上云迁移前我特意预留了一天时间用 JMeter 把整套接口从回归到高并发完整压了一遍给运维要了个资源预留的承诺也给领导发了份带预测曲线的汇报数据。这些事靠的是 JMeter 里一套完整的压测流程从写计划、配参数、跑脚本到出报告每一步都有讲究。这篇就专门讲讲我实际用 JMeter 做压力测试的完整步骤和踩过坑之后总结出的重点。这些步骤是我在很多项目里反复验证过的看单接口性能、验证数据库连接池上限、全链路压测排查瓶颈基本都能覆盖。如果你想给自己的系统做一次靠谱的压测可以参考这套流程走一遍。我尽量说人话不放空话。1. 压测前的方案设计先想清楚再动手很多人一上来就打开 JMeter拖一个线程组就开始填数字填完之后跑出来一堆数据也不知道该信哪个。这是把压测做成了撞运气。真正靠谱的做法是先在脑子里过一遍压测方案至少把三个问题想明白测什么、怎么测、达标线在哪。1.1 压测不只是测一下性能压力测试的核心目的不是把机器打到 CPU 100% 就完事而是要回答一个业务问题这个系统在指定压力下能不能稳定提供服务响应时间是否存在断崖式下跌系统有没有隐藏的瓶颈。我之前见过有人压一个登录接口线程数填了 2000结果压出来的平均响应时间到了 8 秒他拿这个报告去找开发说性能有问题后来发现是他自己本机网络带宽已经跑满了这个锅不应该让系统背。所以设计压测方案时要有一个完整的思考框架具体包括这几个要素业务场景明确压的核心链路是什么。比如登录、下单、查询列表这些场景对资源消耗完全不一样不能拿一个查询接口的测试结果去说明整个系统的性能。压力模型设计并发用户数、QPS 目标、持续时长、峰值形态。比如电商秒杀场景需要瞬间大流量OA 系统更关注长时间中等负载下的稳定性两者的压力模型差别非常大。指标口径统一大家必须对性能达标有一致的定义不能你觉得 5 秒算快开发说 2 秒才算快。一般我们看平均响应时间、95 分位响应时间、TPS、错误率这几个指标互相配合才能看清问题。1.2 压测脚本结构和数据准备在 JMeter 里一个压测脚本的结构可以设计得很简单但实际工作里我强烈建议按测试计划 → 线程组 → 配置元件 → 取样器 → 断言 → 监听器这个套路来组织。层级清晰了后面修改和维护才不痛苦。压测数据的准备是很容易被忽略的地方。不能用生产数据直接压也最好不要每次都用同一份静态数据尤其是带有唯一性约束的接口。最稳妥的方案是用 CSV 文件做参数化从真实数据里抽取一部分脱敏后作为压测数据池。数据量建议至少是并发数的 3 到 5 倍不然压到后面数据重复接口开始报错你还以为是性能问题其实是数据问题。2. 环境搭建和基础配置别在第一步就翻车Jmeter 本身是绿色软件解压就能用比很多商业工具都要简单。但也正因为太简单了很多人反倒在最开始就踩了暗坑——JDK 版本没配对、插件装不上、脚本目录有中文导致报错这类问题浪费的时间往往比测试本身还长。2.1 安装版本的选择与 JDK 匹配先说版本选择JMeter 4.0 之前需要 JDK 8JMeter 5.0 到 5.5 版本推荐 JDK 8 或 JDK 11JMeter 5.6 以上推荐 JDK 11 甚至 JDK 17。我是直接装了 JDK 11 最新稳定版的 JMeter这个组合用起来兼容性最好社区遇到的坑也最少。下载安装包的时候直接去官网下不要从乱七八糟的下载站拿。有些第三方打包的版本会捆绑插件或者修改启动脚本出问题你根本不知道去哪排查。下载的时候注意选apache-jmeter-xxx.zip文件Windows 上解压后放到一个路径不含中文、不含空格的目录比如D:\tools\apache-jmeter-5.6.3。这一步很多人不在意但实际遇到 JVM 找不到类或者脚本路径解析错误就明白我说的了。装完之后进入bin目录Windows 双击jmeter.bat启动。如果你是 Linux 服务器做压测就用sh jmeter.sh启动。启动后如果弹了个黑框框那是正常的别关掉关掉 JMeter 也就退出了。2.2 语言、界面和常用配置微调JMeter 默认启动界面是英文的看起来费劲的话在Options → Choose Language里可以改成简体中文。这个设置在每次启动后需要手动重新选择如果你嫌麻烦可以直接修改jmeter.properties文件里这一行languagezh_CN改完保存重启就永久生效了。顺手把字体也调大一点jmeter.properties里搜索jsyntaxtextarea.font.size默认是 14改成 16 或者 18脚本多的时候看着不费眼。还有人问界面布局怎么调整JMeter 的左右结构是固定的我实际用下来觉得默认布局挺好不用特意改。2.3 常用插件管理与安装JMeter 本身是个纯 Java 程序但它有两个派系的插件体系新手容易搞混JMeter Plugins Manager即plugins-manager.jar一个插件管理器可以装很多增强型组件比如jpgc - Standard Set里的jpgc - Stepping Thread Group、jpgc - Throughput Shaping Timer以及Custom Thread Groups。这些插件在做阶梯加压、动态调速时特别有用。第三方协议插件比如压 MQTT 需要mqtt-jmeter插件压 WebSocket 需要websocket-sampler插件。这些插件需要下载 jar 包放到lib/ext目录或者通过 Plugins Manager 的Available Plugins页签搜索安装。我之前压过一个物联网平台的 MQTT 接入能力就是靠 JMeter 的 MQTT 插件完成的。如果不装这个插件你只能用原生的 TCP 取样器自己拼报文痛苦得多。安装好插件后一定要重启 JMeter否则新组件不会出现在组件列表里。3. 编写压测脚本的关键细节脚本是压测的核心而脚本里最核心的永远是线程组和取样器的配置。我在面试别人或者指导团队新人的时候第一件事就是看他们线程组里的参数是怎么填的基本能判断出这个人对 JMeter 的理解水平。3.1 线程组参数的设计逻辑线程组面板上有几个参数线程数、Ramp-Up 时间、循环次数、调度器。很多人把线程数直接填成预估的并发用户数这是对 JMeter 并发模型最常见的误解。JMeter 的每个线程代表一个独立的虚拟用户线程数确实是并发上限但真正决定瞬时压力峰值的是线程数除以 Ramp-Up 时间的斜率。举个例子如果线程数填 100Ramp-Up 填 100 秒那么平均每秒只新增 1 个用户这意味着前 50 秒实际的并发压力远没有达到 100。反过来如果你填线程数 100Ramp-Up 填 1 秒那几乎是瞬间打进去 100 个并发这才是秒杀场景。一般业务逻辑里我会按这个策略来配普通接口压测线程数 目标并发Ramp-Up 线程数 / 10也就是让并发在 10 秒内逐步建立避免瞬时冲击导致误判。阶梯加压用jpgc - Stepping Thread Group插件设置Start Threads Count和Add Threads Count让压力稳步上升每个梯度维持一段时间这样可以观察到系统承载能力随压力变化的过程。稳定性测试线程数固定循环次数勾选永远然后通过调度器设置持续时间比如运行 30 分钟或者 8 小时看长时间运行下有没有内存泄漏或连接池耗尽。另外不要忽略循环次数和持续时间的区别。循环次数是每个线程执行脚本的遍数适合做冒烟验证持续时间是整体压测的运行时长适合做长期稳定性验证。做压测的时候一般用持续时间模式这样压力更均匀可控。3.2 HTTP 请求怎么配置才算对线程组下面添加 HTTP 请求取样器几个容易出问题的地方协议和端口别填错如果是 HTTPS 页面协议那里一定要改成https端口填443不然会报malformed URL之类的错误。请求体格式新版 JMeter 在Body Data里面写 JSON 很顺手但记得在 HTTP 请求的HTTP Header Manager里加上Content-Type: application/json; charsetutf-8。少了这个头很多接口会直接返回 415 或者解析失败。超时时间建议在Timeout (ms)里填连接超时 3000、响应超时 60000 之类。不填超时的话如果接口一直不返回线程会一直挂住越来越卡最后整个压测变成一堆堆积的线程在等待。提示对 RESTful 风格接口如果路径上有动态参数比如/api/user/{id}不要直接在路径里拼死值用变量替换这样后续参数化时才方便。3.3 参数化CSV 数据集和 JDBC 参数取值参数化是压测中把数据池用起来的核心手段。JMeter 里最常用的参数化元件是CSV Data Set Config它可以直接读 CSV 文件按行给变量赋值。在热词里我看到有人问同一个 CSV 参数化文件中每个线程分块取值的问题这个就是 CSV Data Set Config 的Sharing mode设置All threads所有线程共享同一个文件索引每次迭代取下一行这是默认模式。如果数据行数不够多多个线程会循环复用数据。Current thread group每个线程组独立读取适合多线程组脚本。Current thread每个线程独立从文件头开始读适合每个线程需要固定分配数据块的方式比如这个线程一直在用第 1 到第 10 行数据另一个线程用第 11 到第 20 行。压测时如果对数据隔离性有要求建议选Current thread并保证每个线程分配的数据足够用。如果你是希望所有线程在同一个文件里按顺序取数、不重复那就选All threads然后数据量做大一点同时在文件末尾多准备几行缓冲。除了 CSVJMeter 还可以通过JDBC Connection Configuration和JDBC Request直接连数据库做参数化。比如压一个查询接口参数来自订单表里的订单号你可以先在setUp Thread Group里执行一条SELECT order_id FROM orders WHERE status 1 LIMIT 5000把结果存到一个变量里供后续线程组使用。这个场景更进阶一点我可以给大家一个思路先用 JDBC 查出 5000 个订单号写入本地 CSV然后再用 CSV Data Set Config 去读这个文件。这样做的好处是查询数据只执行一次后续高并发时不用反复查数据库避免把压测目标之外的数据库连接池也打满混淆了瓶颈判断。3.4 观看接口依赖正则提取器和 JSON 提取器现在基本都是前后端分离接口之间经常需要 token、订单号之类的依赖。Jmeter 里常用正则表达式提取器或者JSON Extractor来做关联。比如有一个接口 A 返回一个access_token要在接口 B 的请求头里带这个 token可以在 A 请求下面加一个正则提取器配置如下引用名称access_token正则表达式access_token:(.?)模板$1$匹配数字1然后在后续请求上加一个HTTP Header Manager里面的Authorization值写${access_token}就能实现动态取值。热词里还有个__RequestVerificationToken的问题这个是 ASP.NET MVC 的防伪标记。处理思路就是在请求第一步先 GET 一次页面通过正则提取器或者 XPath 提取器拿到__RequestVerificationToken隐藏域的值再在 POST 请求的 Body Data 里拼上这个值。这个方案是 JMeter 做 MVC 项目压测的标配解法其实没那么邪门核心就是先取再传。3.5 断言别只写响应包含 200关于断言我看到jmeter beanshell断言这个热词说明有人对断言已经有进阶需求了。我最常用的断言方式响应断言检查响应文本里是否包含关键业务标识比如status:SUCCESS或者code:0。不要只检查 HTTP 状态码是 200因为很多接口在处理异常时也是返回 200但业务失败。Beanshell 断言可以在请求后写一段简单的 Java 脚本做更精细的判断。比如判断响应时间和响应体里的某个值之间的关系可以这样写String response prev.getResponseDataAsString(); if (response.contains(FAIL)) { Failure true; FailureMessage 业务返回了 FAIL 状态; } long responseTime prev.getTime(); if (responseTime 5000) { Failure true; FailureMessage 响应时间超过 5 秒; }断言的处理逻辑断言失败后默认这条样本会被标记为失败但线程会继续跑。这个行为在大多数场景是合理的因为你要看整体错误率。但如果某个断言失败会导致后续接口依赖的数据不可用那就应该加一个IfController做逻辑分支处理。3.6 监听器组合与结果解读监听器是 JMeter 出结果的地方。实际做压测的时候不需要把每个监听器都挂上监听器本身也是有内存开销的挂多了反而影响压测机性能。我建议按用途选择监听器用途使用场景查看结果树查看请求/响应详情脚本调试阶段压测时不建议开启聚合报告平均响应时间、吞吐量、错误率小型压测快速得出结论汇总报告类似聚合报告列更全压测后整理数据后端监听器实时发送指标到监控平台长时间压测、实时趋势观测jpgc - Response Times Over Time响应时间变化趋势图观察响应时间随压测时间的变化需要特别注意的一点正式的压测跑数据不要开查看结果树。结果树会把每个请求的完整请求内容和响应体都保存在内存里压个几千并发很容易就把堆内存占满出现 GC 停顿压测结果就完全不可信了。聚合报告里最值得关注的指标不是平均响应时间而是 90% 分位或者 95%、99% 分位。因为平均值很容易被少数慢请求拉高也容易被一些极快请求掩盖问题。比如日志里 100 个请求中有一个 10 秒超时平均响应时间就可能从 300ms 变成 580ms但 90% 分位仍然能反映大多数用户的真实体感。4. HTTPS 脚本录制与调试技巧现在的业务系统基本十个有九个是 HTTPS。直接手动在 JMeter 里配置 HTTP 请求遇到各种踩坑就需要用录制的方式先快速把脚本搭出来。4.1 录制原理JMeter 的HTTP(S) Test Script Recorder本质上是一个本地代理服务器。启动录制时JMeter 会在本机的一个端口默认 8888监听 HTTP 请求然后在浏览器或手机上设置代理指向这个端口所有流量经过 JMeter 时就会被记录下来转成脚本里的 HTTP 请求取样器。4.2 录制 HTTPS 脚本的完整步骤打开 JMeter在测试计划下添加HTTP(S) Test Script Recorder。添加一个线程组和一个录制控制器。注意请求只录到录制控制器下面方便管理。在录制器里配置端口默认 8888 就可以。如果你需要录制的目标是 HTTPS 接口还需要安装 JMeter 的 CA 证书。证书在bin目录下的ApacheJMeterTemporaryRootCA.crt文件双击安装到系统的受信任的根证书颁发机构里。全局设置在浏览器或者系统网络设置里把 HTTP 和 HTTPS 代理都指向127.0.0.1:8888。点击录制器面板的启动按钮这一步可能会弹一个确认证书的框确认之后开始执行浏览器操作。操作完业务流程后停止录制回到 JMeter 就可以看到录制控制器下生成了一堆请求。提示录制 HTTPS 时如果系统代理设置没问题但就是录不到包优先检查 JMeter 证书是否安装到了当前用户的受信任根目录而不是本地计算机。这是一个我踩过多次的坑。录完之后还不能直接用。录制下来的脚本往往包含大量静态资源请求比如 CSS、JS、图片这些在压测时大多没有意义要把它们删掉只保留业务接口请求。还有 Cookie、Session 相关的请求头也要整理在压测时完整的会话追踪关系必须理清楚。5. 执行压测GUI 还是命令行差别很大Jmeter 的 GUI 模式日常调试用很好但真正压测的时候我强烈建议切换到命令行模式。原因很直白GUI 本身要渲染大量 UI 元素会消耗 CPU 和内存在压测机上抢资源导致结果数据失真。更严重的并发一高 GUI 线程直接卡死数据也救不回来。5.1 命令行压测的标准姿势以我常用的压测命令为例完整的执行命令是这样jmeter -n -t /path/to/test_plan.jmx -l /path/to/result.jtl -e -o /path/to/html_report -j /path/to/jmeter.log参数说明-n非 GUI 模式运行不启动图形界面-t指定要运行的测试计划文件jmx 文件-l指定输出结果文件路径jtl 格式-e压测结束后自动生成 HTML 报告-oHTML 报告的存放目录目录必须不存在或者为空否则会报错-j日志文件路径排查问题很好用命令跑起来之后它会一直运行直到测试计划结束。如果你设置了永远循环和调度器时长那就等到时长结束自然停止。需要手动停的话按CtrlC是强制中断在某些场景会丢失部分数据最好让任务自然跑完。5.2 远程分布式压测的取舍有人问 JMeter 能不能做分布式压测官方确实自带了一个jmeter-server机制可以在多台机器上部署 slave 节点由 master 统一调度。但我不推荐在没充分理由的情况下就用分布式。原因有两条第一分布式压测的瓶颈容易落在 master 的网卡和磁盘 IO 上结果数据汇总本身就可能失真。第二配多台 slave 要同步 JMeter 版本、插件、CSV 数据文件还要处理防火墙端口运维成本不低。如果你的压测目标系统在云上是多节点集群建议先用单机 JMeter 配合高配置压测机比如 16 核 32G 的机器把 JVM 调大一点通常能模拟几千并发。确实不够了再考虑分布式也不迟。5.3 高并发期间对压测机的监控压测机本身的状态要盯一眼。跑压测的时候我习惯另开一个终端用top看压测机的 CPU 和内存如果压测机的 CPU 已经 100% 了说明压力瓶颈可能已经在压测机一侧这时候压出来的响应时间数据就不能完全反映目标系统的真实情况。6. 结合迁移场景压测如何验证云上承载能力开头提到的迁移项目完整场景是这样的整套环境从单节点 k8s 迁到阿里云 ECS迁移要求是准不停服、不丢数据迁移完成之后由压测人员用配套 Jmeter 脚本做高并发测试验证云上环境的承载能力。这里面的关键步骤和普通压测不太一样我需要单独说一下。6.1 迁移后压测的步骤规划迁移后的压测不是直接上高并发而是先做一个阶梯式验证。具体拆成三个阶段冒烟验证5 到 10 个线程跑完核心链路确认功能没有因为迁移出错。这一步主要是验证网络连通、域名解析、NAT 网关、数据库白名单这些基础项是否正常。容量摸底线程组从 50 开始每 5 分钟增加 50连续增加到 500观察 TPS、响应时间、错误率的变化趋势。这个阶段主要是找到性能拐点。持续稳定性验证用接近容量摸底得到的最大 TPS 的 80% 作为负载跑 30 分钟到 1 小时。这个阶段主要看系统长时间高负载下是否稳定有没有内存泄漏、连接池耗尽、Full GC 频繁的问题。迁移项目上我们当时还额外加了数据一致性校验压测前后各跑一次对账 SQL确保压测过程中没有产生数据错乱。这个不是 JMeter 做的事但作为压测方案的一环必须提前准备。6.2 报告解读和性能瓶颈定位压完之后把聚合报告和 HTML 报告导出按业务链路进行对比分析。我觉得压测报告最容易犯的错误是只给最终结果表格不给结论和定位。所以我一般在报告里加上这么一段结论先行系统在 X 并发下TPS 达到 Y平均响应时间 Zms错误率 N%是否满足预期目标。拐点分析在并发增加到哪个区间时TPS 出现平台期甚至下降。如果有拐点下降说明系统资源或者某些组件已经到达瓶颈需要进一步定位。瓶颈线索如果响应时间在某个分段后急剧拉升优先查数据库连接池、慢日志、中间件队列、网络带宽。压测时配合在服务器上执行top、vmstat、iostat、netstat这些命令记录同一时间点的资源使用情况。当时我们压到 300 并发时 TPS 出现拐点检查后发现是数据库连接池默认配置只有 20连接等待时间涨到了 2 秒调整连接池参数之后 TPS 直接翻了一倍。这算是迁移上云后最常见的调优点之一。7. 常见问题与排查技巧实录这一节专门整理我在实际压测中遇到的高频问题按照现象、原因、解法三方面给出速查表方便大家直接定位。问题现象常见原因排查与解决压测刚开始就大量Connection refused目标服务端口未监听或者防火墙拦截先 curl 验证连通性再排查安全组和本地防火墙压测中报java.io.IOException: Error writing to server目标服务或中间件连接池耗尽服务端主动断开连接查服务端连接数配置、线程池配置适当调大连接超时时间响应时间慢慢爬升TPS 下降目标服资源不足GC 频繁用top、jstat看服务器负载和 GC 情况重点查堆内存配置同一接口偶发超时错误率不高慢查询、锁等待、网络抖动结合监控工具查慢 SQL 和锁等待情况结果文件中数据量巨大文件占满磁盘没关闭结果树或者采集粒度太细压测时关闭结果树监听器限制 JTL 文件只记录需要的信息并发高时 JMeter 本身卡死压测机 JVM 堆内存不够修改jmeter启动脚本里的HEAP-Xms4g -Xmx4g并关闭监听器压测中出现__RequestVerificationToken防伪标记验证失败MVC 项目需要先获取 token 再提交用正则提取器先取页面 token再用变量传递这里额外说一个 JMeter 压测过程中最常见的不是问题的问题——端口耗尽。压测机默认可用端口数量有限大量并发连接时端口号的四元组组合不够用就会报Cannot assign requested address或者类似连接失败的错误。解决方案是修改压测机系统参数在 Linux 上可以调大本地端口范围sysctl -w net.ipv4.ip_local_port_range1024 65535 sysctl -w net.ipv4.tcp_tw_reuse1这两条命令的作用是扩大可用端口范围并复用 TIME_WAIT 状态的连接。压测机调这个参数不会影响被测系统却经常能解决一大半的压测机自己先挂了的问题。8. 压测脚本维护和团队协作的几点实践压测脚本不是一次性产物上线新功能、容量扩容、活动大促之前都会翻出来重新跑一遍。这就需要脚本本身具备可维护性。我的实践是命名规范测试计划名称、线程组名称、取样器名称写清楚中文注释不要出现Thread Group 1、HTTP Request 2这种毫无信息量的命名。团队里每个人看到名字就能明白这个模块在压什么。变量集中管理用用户自定义变量或者配置元件统一管理协议、域名、端口、公共参数。这样切换测试环境、预发环境、生产环境只需要改一个地方。脚本版本管理jmx 文件本质是 XML可以放进 Git 管理。每次压测前记录一下 JMeter 版本、插件版本避免换个人跑就出现兼容性问题。压测这个事工具只是执行者真正值钱的是你对业务场景的理解、对测试数据的分析能力以及出了问题能快速定位瓶颈的经验。JMeter 作为一款开源压测工具生态成熟、插件丰富、上手门槛低这也是我这么多年一直用它做项目压测的首选原因。按这套流程走下来你会发现压测这件事其实是可以做得非常规范和可控的。
返回列表