ARTICLE DETAIL

资讯详情

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

JMeter性能测试入门:从脚本编写到压测报告的完整实战指南

JMeter性能测试入门:从脚本编写到压测报告的完整实战指南 第一次接手压测任务是好几年前的事了。那时候我对“性能测试”的理解还停留在表面工具能开多少线程就往服务器上怼多少并发。结果开压没多久数据库连接池被打满接口错误率直接飙到30%线上环境差点被玩坏了。也是从那一次开始我才明白性能测试不是“跑个脚本拿个报告”那么简单它是一套需要理论打底、工具落地的完整工作流。这篇文章我会用一条真实可落地的主线来讲前半部分先把“性能测试到底在测什么”掏开揉碎后半部分从JMeter安装配置开始用一个带登录、参数化、断言、报告的完整案例带你从零跑通。内容适合刚开始接触性能测试的测试工程师、想补全基础概念的开发同学也适合准备性能测试岗位面试时用来做一轮梳理。1. 先搞懂性能测试到底在测什么1.1 性能测试解决的三个问题很多人一提到性能测试就联想到“并发压垮服务器”其实这只是其中一个场景。我更愿意把它拆成三个层面的问题。第一层系统能不能扛住预期用户量。这里说的“用户量”不是注册用户总数也不是在线人数而是某一时刻真正在同时操作、会同时产生请求的活跃用户。比如一个活动页同时有10万人在线但真正在点击按钮、提交订单的也许只有几千人压测要模拟的就是这几千人产生的后端压力。第二层压力大了以后系统还能不能保持稳定。很多系统在低负载下表现得很好响应时间在100毫秒以内可一旦负载上到某个阈值响应时间会骤然恶化甚至出现服务不可用。性能测试就要找出这个“拐点”在它发生之前提前通过扩容、限流、调优来应对。第三层瓶颈究竟出在哪。有时候用户反馈“页面好慢”但你不知道慢在数据库、慢在外部接口还是慢在应用服务器的线程池被打满。性能测试的价值之一就是通过分层观测把瓶颈定位出来让调优有的放矢。在项目实践中这三层问题往往是同时发生、互相纠缠的。比如你测一个登录接口发现TPS上不去第一反应是“并发额度不够”但深入看可能是数据库连接池太小也可能是Redis连接超时导致的线程阻塞。所以搞性能测试脑子里要始终带着一条链路用户请求从入口进来经过网关、应用、中间件最终落到数据库或第三方服务。每一环都可能成为短板。1.2 核心指标响应时间、TPS与错误率在正式看任何压测报告之前至少要把五个基础指标记牢响应时间、TPS/QPS、并发用户数、错误率、资源利用率。我把它们整理成了一个常用速查表。指标含义性能测试里为什么重要响应时间从发起请求到收到完整响应的时间直接影响用户体感通常还看P95、P99而不是平均值TPS每秒完成的事务数接口压测里通常指每秒完成的请求数反映系统吞吐能力是容量规划的核心依据QPS每秒查询数偏读取场景的名词概念上接近TPS很多团队混用但统计口径要对齐并发用户数同一时间正在执行操作的虚拟用户数是JMeter线程组设置的基础参数错误率失败请求数占总请求数的比例超过阈值说明系统在负载下出了问题资源利用率应用服务器CPU、内存、磁盘、网络的占用率用来判断瓶颈在服务器侧还是应用代码侧这里我想特意提醒别只盯着平均响应时间。平均数很容易被少数慢请求带偏比如99个请求都是30毫秒1个请求是10秒平均值仍然很低但实际情况对真实用户来说已经不可接受了。建议把关注重心放到“百分位响应时长”P95、P99上。P95的意思是“95%的请求响应时间都小于这个值”它比平均值更能反映绝大多数用户的体验。1.3 常见测试分类及适用场景性能测试不是一个孤立的动作而是分场景、分目的的一组测试。开发中经常用到的有负载测试、压力测试、稳定性测试、容量测试和并发测试。负载测试的核心目标是看系统在预期负载下表现如何比如预估业务高峰期有500个并发用户那就以500为基准跑一轮看响应时间和错误率是否达标。压力测试则是不断加压直到系统开始崩溃或响应严重劣化目的是找“极限点”。稳定性测试通常用较低的负载跑几小时甚至几天目标不是打崩系统而是观察内存泄漏、连接泄漏这类长时间运行才会暴露的隐患。容量测试更多用于“未来规划”比如针对明年预估的流量测出当前集群最多扛多大据此决定要不要扩容。举个例子如果是普通的内部管理系统并发用户数不高压测更多关注功能在持续使用下的稳定性如果是电商、抢票、秒杀类业务压测就要重点关注瞬时峰值下的吞吐能力和削峰策略。所以做性能测试前一定要先问清楚业务预期和场景不要拿着一套模板到处套。2. JMeter环境准备与基础操作2.1 下载安装与环境变量配置我常用的JMeter版本是5.6.x它依赖Java环境。安装前先把JDK装好建议用JDK 11或JDK 17下载安装完成之后在命令行输入java -version确认能看到版本信息。JMeter本身不需要安装下载压缩包之后解压就能用。我自己的习惯是把解压后的目录放到一个路径简单且不含中文的位置例如D:\tools\apache-jmeter-5.6.3。路径里有中文或空格可能在执行命令行压测时遇到编码或路径解析问题这种小坑看着不起眼真踩到会浪费不少时间。解压完成后配置环境变量。Windows系统上在“此电脑”右键属性进入“高级系统设置-环境变量”新建一个变量名JMETER_HOME变量值填JMeter的解压目录然后在Path里追加%JMETER_HOME%\bin。Mac或Linux用户可以直接在~/.bashrc或~/.zshrc里加上export JMETER_HOME/opt/apache-jmeter-5.6.3 export PATH$JMETER_HOME/bin:$PATH配置完重开一个终端窗口输入jmeter -v能正常输出版本号就说明环境OK了。如果你打开bin目录找不到jmeter.sh或jmeter.bat十有八九是压缩包没解压完整重新解压一遍就行。2.2 启动JMeter与中文界面设置环境配置完成后Windows双击bin目录下的jmeter.batMac或Linux在终端执行jmeter就可以看到JMeter的主界面。启动时弹出的黑色命令行窗口不要随意关闭那是JMeter的运行控制台关了等同于杀掉整个工具。JMeter默认界面是英文的刚入门的同学看着可能有点慌。设置中文很简单菜单栏找到Options点开Choose Language在下拉列表里选择Chinese (Simplified)界面就会切换为简体中文。但有一个细节值得注意JMeter的界面语言只是表面翻译组件名称和配置项的含义并不会变。换句话说你仍然需要理解“线程组”“取样器”“监听器”这些对象解决什么问题否则单纯靠中文界面还是不知道怎么组织脚本。后面我在描述每个组件时会尽量把它在请求链路中的角色说清楚。2.3 认识JMeter脚本里的核心组件JMeter脚本的组织方式很像一个树状项目结构所有元素都挂在“测试计划”下面。对初学者而言先掌握四类组件就可以开始干活了。线程组是并发模型的起点里面设置的“线程数”就是虚拟用户数“Ramp-Up时间”表示所有线程在多长时间内启动完毕“循环次数”控制每个线程跑多少轮。HTTP请求是真正发起请求的取样器类似一个“动作”你告诉它协议、IP、端口和路径它就替你向服务器发请求。监听器负责收集展示结果最常用的是“查看结果树”和“聚合报告”。配置文件则用来定义请求的一些公共参数比如HTTP信息头管理器里统一设置Content-Type: application/json。树状结构里配置类组件通常放在同一层级会自动作用于下层节点就好比父目录里的配置会被子目录继承。比如把HTTP信息头管理器放在线程组底下线程组里的所有HTTP请求都会自动带这套请求头如果只放在某个HTTP请求下面那就只有这个请求会用它。理解这个问题后面组织多接口脚本时会顺手很多。2.4 建立第一个最小化压测计划先不急着模拟复杂业务用一个最简单的GET请求把链路跑通。操作流程如下。在测试计划上右键添加一个线程组线程数先填1Ramp-Up时间填1秒循环次数填1。接着在线程组下添加一个HTTP请求协议填http服务器名称或IP填被测环境的地址端口填服务端口路径填一个健康检查或列表接口。添加完成后在线程组下加一个“查看结果树”监听器点击工具栏的绿色启动按钮。等请求跑完在查看结果树里能看到请求和响应数据就说明最小链路已经通了。这一步我会特别强调“先小后大”。很多人上手就填500个线程跑挂了也不知道是自己脚本问题还是系统问题。最小化压测计划的意义就在于先确认请求通不通、参数对不对、响应能不能解析确认一切正常后再逐步增加线程数去压真实负载。3. 从零压测一个登录接口3.1 典型的登录业务脚本结构接口测试和性能测试的脚本结构差别不大区别在于性能测试更关心在并发条件下的表现。这里拿最常见的登录获取用户信息场景做例子带token的接口结构是压测中最常遇到的。测试计划的组织方式可以参考下面这棵树线程组HTTP信息头管理器HTTP请求-登录JSON提取器提取tokenHTTP请求-获取用户信息断言查看结果树聚合报告先配置登录接口。HTTP请求的主要字段如下协议选择http服务器名称填被测主机端口填应用端口请求方法选POST路径填登录接口路径。在请求Body里传JSON参数比如用户名、密码。需要注意JMeter的Body Data区域不支持直接填写变量以外的动态计算值所以通常会把用户数据先定义成变量再用${变量名}的方式引用。登录接口往往需要一个HTTP信息头管理器来指定内容类型。如果系统要求Content-Type: application/json就添加一个信息头名称叫Content-Type值为application/json。这一步漏掉的话很多后端框架会直接返回415或400让初学的人误以为自己脚本写得不对。3.2 用户参数化CSV数据与变量实际压测时不能每次都用同一个账号去登录一方面服务器可能会做单账号登录限制另一方面真实场景本身就不该是千篇一律的账号行为。参数化的意思就是让不同线程使用不同数据。最简单的做法是把用户名和密码放到一个CSV文件里放在JMeter脚本同目录下比如users.csv内容分两列username,password zhangsan,123456 lisi,123456 wangwu,123456在登录请求所在线程组下添加一个“CSV数据文件设置”。配置项里“文件名”填CSV的绝对路径或相对JMeter脚本的位置“变量名称”填username,password“分隔符”填逗号勾选“遇到文件结束符时停止线程”这样一个线程会按顺序读取一行数据整个文件读完就正好覆盖了多组用户。这里有个经验供参考CSV文件编码最好保存成UTF-8。我之前遇到过一段密码里有中文的场景文件用系统默认编码保存压测中部分请求因为编码问题登录失败排查了很久才发现是编码的锅。另外在团队合作时不要把账号密码直接放到一个不相关的目录最好和JMeter脚本放在一起并且在脚本里使用相对路径免得换台机器执行时出现找不到文件的尴尬。3.3 JSON提取器把登录返回的令牌传给后续请求现在很多系统采用前置登录接口返回token后续业务接口在请求头里带上token的鉴权方式。如果登录成功后拿不到token后面的“获取用户信息”接口就会因为没有凭证而失败。JMeter里处理这类问题的标准做法是JSON提取器。它其实是一种后置处理器会在请求响应返回后从JSON结构中提取内容并存入变量。把JSON提取器添加到“登录请求”下面配置几个关键字段变量名称填access_tokenJSONPath表达式填$.data.token匹配编号填1默认值留空或填一个错误提示。JSONPath表达式需要根据登录接口实际返回的报文结构来写。如果登录响应长这样{ code: 0, data: { token: abc123, expire: 7200 } }那提取token的表达式就是$.data.token。如果服务器返回的是Snake风格字段如access_token表达式就相应改成$.data.access_token。不知道该写什么时先用浏览器控制台或Postman调一下接口把真实响应结构拿到手再写表达式而不是靠猜。提取成功之后后续的“获取用户信息”请求中在HTTP信息头管理器里添加一行名称填Authorization值填Bearer ${access_token}这样变量就会被替换成前面登录接口返回的token。需要特别注意一点JSON提取器必须放在登录请求下面而不能放在线程组顶层否则它拿到的是上一个请求的响应容易出现空值。3.4 断言、查看结果与单用户跑1分钟验证写压测脚本时我习惯先把“验证”环节做扎实再开始上并发。这里用响应断言做初步校验在HTTP请求下添加响应断言选择“响应文本”包含输入登录成功后才有的标志字段例如code:0或登录成功。加了断言之后监听器里就会把不包含该内容的响应标记为失败比人眼逐条看响应要可靠得多。场景配置初期不要一上来就填几百个线程。更稳的方式是先做“单用户1分钟”验证线程组里线程数填1循环次数勾选“永远”并在“调度器”区域勾选“持续时间”为60秒。这样脚本会以单个用户的节奏持续跑1分钟期间可以通过查看结果树确认每次请求都成功也能初步判断单用户的响应时间是否正常。单用户验证通过后再放大到10个线程、50个线程逐步逼近预期并发量。整个过程我都建议开着聚合报告观察它统计了样本数、平均响应时间、中位数、P90/P95/P99、吞吐率和错误率。先小规模跑通业务流再上高并发找瓶颈这是把“能跑的脚本”变成“能用的压测结果”的关键一步。4. 进阶实战场景设计、监控与报告4.1 让压测更接近真实思考时间与步调控制真实用户不会像机器人一样毫秒不差地连续点击他们从打开页面到点击按钮之间会有阅读时间、填写时间。JMeter把这种间隔称为“思考时间”对应的实现方式是在取样器之间添加“固定定时器”Constant Timer。不加思考时间的直接后果是流量模型过于激进。比如你模拟100个用户并发操作一个商城接口如果没有思考时间每个人都在满负荷发请求最终测出来的TPS可能远超真实峰值反而会让系统在过载状态下表现失真。通常的做法是从业务数据里估算用户平均思考时间比如表单填写场景可以设置3000毫秒简单点击场景设置1000毫秒。定时器作用于其所在层级添加到哪个请求下面就意味着执行完该请求之后等待多久再执行下一个请求。如果需要更精准地控制想要达到的每秒请求数可以考虑JMeter插件中的“吞吐量整形定时器”jpgc - Throughput Shaping Timer。它把加压过程可视化成一个时间-吞吐量折线例如在测试前60秒让TPS从0爬到1000并保持5分钟这样压测曲线会比固定线程数更贴近真实业务流量。4.2 HTTP/HTTPS脚本录制与证书处理如果被测项目没有完整接口文档或者是一个页面操作链路过长用JMeter录制功能生成初始脚本能省不少时间。JMeter的录制原理是通过代理接收浏览器的请求然后自动生成对应的HTTP取样器。在测试计划下添加“HTTP(S)测试脚本记录器”设置端口为8080目标控制器指向一个线程组。然后用浏览器或系统设置把代理指向本机8080端口在浏览器里正常操作被测页面录制器就会把所有HTTP请求收集到线程组里。录制过程中会有大量JS、CSS等静态资源请求通常先把这些不需要的请求过滤或删除只保留需要关注的业务接口。涉及HTTPS页面时录制会提示证书相关的问题因为JMeter代理会临时充当中间人签发自己的证书。一般在浏览器访问http://localhost:8080下载JMeter生成的根证书并导入到系统信任列表之后再访问HTTPS页面就不会报证书错误。不过现在很多接口的链路稳定性依赖动态签名或加密参数录制出来的脚本可能需要大量手工适配所以我个人更推荐的做法是录制只作为了解接口结构和调用顺序的辅助手段最终的性能脚本还是手工用HTTP请求组装比较可控。4.3 用命令行模式压测与生成HTML报告JMeter的图形界面适合编写脚本和单步调试但不适合正式压测。GUI模式本身会消耗不少内存监听器在压测过程中频繁刷新界面也会影响采样性能。正规的做法是在命令行模式下执行压测。命令行执行的基础命令如下jmeter -n -t login_test.jmx -l result.jtl -e -o report-n表示非GUI模式-t指定测试计划文件-l输出原始结果文件-e表示测试结束后生成HTML报告-o指定报告输出目录。需要特别注意的是报告输出目录必须不存在或者为空否则JMeter会报错。如果结果文件result.jtl已经存在建议换一个新文件名或先删除旧文件。生成的HTML报告会包含概览、TPS曲线、响应时间曲线、错误率统计等图表非常适合作压测总结和团队评审。相比在聚合报告里截一张表格HTML报告的信息完整度和可读性都更高这也是我认为“JMeter能出测试报告吗”这个问题的最好回答不仅能出还能出得很专业。4.4 实时监控链路JMeter InfluxDB GrafanaJMeter自带的HTML报告是压测结束后的“事后复盘”但如果压测跑了一个小时运行到第20分钟系统开始抖动报告只能告诉你那段区间有问题无法让你在过程中实时发现并干预。实时监控通常用三件套来完成JMeter负责施压和把结果推送到时序数据库InfluxDB负责存储Grafana负责可视化展示。在JMeter测试计划里添加“后端监听器”Backend Listener实现类选择InfluxdbBackendListenerClient。配置里填入InfluxDB的写入地址比如http://localhost:8086/write?dbjmeter应用启动压测之后JMeter客户端会周期性地把吞吐率、响应时间、错误数等指标写入InfluxDB。Grafana里装上Dashboard模板选好InfluxDB数据源就能看到一条接近实时的曲线压测过程系统有没有劣化观察曲线就能感知非常及时和直观。这套方案在分布式压测场景下价值更明显。多个压力机同时跑每台机器的结果汇总到同一个InfluxDB从Grafana看的是整体状态。如果没有这套实时监控想确认压力是否均匀、是否有一台压测机拖后腿会麻烦得多。4.5 关注服务器资源与浏览器端采样很多性能瓶颈不在应用层而在更底层的基础设施。JMeter插件里有一个很有用的组件叫“PerfMon Metrics Collector”jpgc - PerfMon Metrics Collector配置好需要监控的目标服务器地址后可以在压测的同时采集CPU、内存、磁盘、网络等指标。服务器上需要运行对应的Agent程序监听默认端口4444。把JMeter的TPS曲线和服务器CPU曲线放到同一时间轴上看能很快判断出应用是到了性能上限还是外部依赖造成的延迟。比如TPS上不去但CPU只有30%那瓶颈大概率不在应用处理能力而在数据库连接数或下游慢接口。单看请求结果容易得出“服务器扛不住”的简单结论配上资源监控才能往下拆一层。如果系统里有大量用户页面交互操作且服务端接口表现正常但用户仍然觉得卡可以考虑用JMeter的WebDriver采样器jpgc - WebDriver Sampler来做浏览器端真实渲染级的压力测试。它通过Selenium WebDriver调用浏览器操作页面能拿到页面真正渲染的时间。需要注意的是浏览器级压测非常消耗资源并发不宜开太高一般作为端到端验证使用不能用来代替接口级压测作为容量评估依据。5. 常见问题与排查技巧实录5.1 高频“坑”上传文件、编码、结果丢失结合我日常答疑时遇到的情况把几个高频问题单独列出来包括上传文件、响应中文乱码、CSV路径和命令行报告生成失败这些典型场景。问题现象典型原因解决方法上传文件接口返回参数错误缺少文件参数名或MIME类型在HTTP请求的“Files Upload”页签配置文件名、参数名、MIME类型响应里的中文全部乱码JMeter默认按ISO-8859-1解析响应在HTTP请求或jmeter.properties里设置sampleresult.default.encodingUTF-8CSV参数一直取不到值文件路径写错或编码不是UTF-8优先用脚本相对路径确保文件保存为UTF-8格式命令行生成报告报目录已存在报告目录必须为空每次执行前清理或换新的输出目录GUI模式跑高并发内存溢出线程数和内存不匹配增大JMeter堆内存或改用命令行模式远程执行处理上传文件请求时很多人习惯直接放在“参数”里传文件内容这是不正确的。应该到HTTP请求的“文件上传”Files Upload页签选择本地文件路径填写参数名称比如用的API字段是file参数名就填fileMIME类型按文件类型写常见图片是image/png或image/jpeg。如果不填参数名很多后端会解析不到文件字段直接报“缺少必要参数”。响应乱码的问题是压测脚本调试里最浪费时间的坑之一。排查思路是先在“查看结果树”里看响应头如果返回的Content-Type没指定编码JMeter会按默认编码解析导致中文显示成乱码同时也会影响响应断言。解决办法是在JMeter的bin/jmeter.properties中把sampleresult.default.encodingUTF-8改好并保证脚本里的HTTP请求也正确使用了UTF-8编码。5.2 性能测试岗位面试题盘点性能测试面试题高频出现这里把一些常问的方向整理出来也都是日常工作中真正需要想清楚的问题。第一个问题通常是响应时间、TPS、并发用户数之间是什么关系这个问题的陷阱在于很多人会把“并发用户数”和TPS等同。并发用户数是站在用户视角的描述TPS是站在系统处理能力的描述。100个用户同时操作不代表系统每秒要处理100个事务取决于事务时长和思考时间。回答时最好把公式关系用“响应时间越小、并发越大则TPS上限越高”这个关系串起来。第二个常见问题是如何设计一个压测场景建议回答时从“业务模型-流量模型-环境准备-指标监控-结果分析”五个环节展开。先用日志统计数据明确核心链路和比例再转换成JMeter线程数和循环次数压测过程中通过后端监听器和Grafana实时观测结束后对比各项指标是否达到目标。第三类问题高度偏向实战排查如果TPS上不去从哪些角度排查一般可以分成四步压力机自身是否成为瓶颈CPU、内存、负载网络链路有没有丢包或延迟应用服务器CPU是否打满线程池是否有大量阻塞数据库连接池、慢查询、锁等待是否异常下游依赖服务是否超时。真正做个几次压测复盘这类经验会积累得很快。5.3 分布式压测与多压力机如果单台JMeter的压力机无法产生足够高的TPS需要启动多台压力机时就要用到分布式压测模式。JMeter的控制机Master负责调度执行机Agent负责实际产生压力。执行机上进入bin目录运行jmeter-server控制机修改jmeter.properties里remote_hosts配置把执行机IP和端口写进去然后通过“远程启动”或命令行-R参数下发脚本。采用分布式压测前一定要先确认执行机本身资源充足如果每个执行机都把CPU跑满那么TPS数据会受压力机影响结果并不可信。另外执行机之间的时间要同步否则后续查看时间轴曲线会对不上一般用NTP对时就能解决。从我个人的经验来说不到万不得已不建议一上来就上分布式。先把单台压力机的最大能力测出来再评估是否需要扩容。很多系统在单机千级并发以下跑得动没必要把问题复杂化。只有在目标TPS确实超出了单台JMeter的承载能力时分布式才是个值得做的方向。5.4 脚本跑起来之后我的一点个人经验压测跑完拿到报告不等于工作结束。我平时会在报告之外再确认几件事一是有没有请求失败如果有不能简单地归类为“系统挂了”要去翻应用日志看具体报错二是看服务器CPU、磁盘、网络等资源曲线是否符合预期确认负载确实压到了被测系统三是把测试结果与上一次基线做对比比如这轮改造后P95是否下降TPS是否提升要有可量化的结论。还有一个小技巧是给每一次正式压测都保留原始结果文件和JMeter脚本版本命名里写清日期、场景、并发数。这样后续排查问题时能快速定位当时的测试环境也方便性能回归做纵向对比。我在实际项目中靠这些记录不止一次帮团队省下了大量重复压测的时间。希望这篇内容能让你少走一些我走过的弯路如果你正准备做第一次压测不妨按这个流程先从单用户跑通开始再一步一步把复杂场景加进来。
返回列表