1. 为什么JMeter录制脚本必须先设代理——不是功能选择,而是协议本质决定的
很多人第一次打开JMeter,点开“线程组”就急着往里加HTTP请求,结果发现:明明浏览器里能正常访问的接口,JMeter一发就404、500、甚至直接超时。更困惑的是,网上教程突然冒出一句“先设置代理”,接着就是一顿菜单点击——没人告诉你为什么非得走代理这条路,更没人讲清楚:不设代理,你根本录不到真实用户行为。
这根本不是JMeter的设计缺陷,而是HTTP协议通信机制决定的。浏览器发起请求时,所有流量(包括AJAX、图片、CSS、JS、WebSocket握手)都默认走系统网络栈,而JMeter作为独立Java进程,它既不劫持系统DNS,也不监听本地回环端口的80/443,它压根“看不见”你浏览器在干啥。你手动写HTTP Sampler,填的是你“以为”的URL和参数,但真实业务中,那些带时间戳的token、动态生成的_csrf、前端加密后的密码字段、由前端框架自动拼接的查询字符串——全靠浏览器运行时环境实时计算。你靠肉眼观察F12 Network面板去抄,漏一个header、少一个cookie、错一位timestamp,请求就废了。
代理模式的本质,是让JMeter变成一个“中间人”:你把浏览器的出口流量临时拐个弯,全部导向JMeter监听的本地端口(比如8888),JMeter一边原样接收,一边实时解析HTTP/HTTPS报文结构,自动提取出URL、Method、Headers、Body、Cookies、甚至重定向链路,再一键转成可编辑、可参数化的HTTP Sampler。这不是“多此一举”,而是唯一能捕获完整客户端行为链路的技术路径。我见过太多测试同学,在没理解这点的情况下,花三天手写200个接口,结果上线后发现登录态失效、文件上传失败、验证码校验绕不过——全因漏掉了代理模式下自动捕获的Referer、Origin、X-Requested-With这些关键头字段。
提示:代理模式只解决“录制”问题,不解决“回放”问题。很多同学设完代理、录完脚本,一跑就报错,第一反应是“代理没设对”。其实90%的情况是:代理确实设对了,但回放时缺少Cookie管理器、JSON Extractor提取逻辑错误、或没处理CSRF Token的动态刷新。代理只是起点,不是终点。
你可能会问:那能不能不用代理?当然可以。用BadBoy、BrowserMob Proxy、或者Fiddler + JMeter插件,原理相同,都是中间人截流。但JMeter自带的HTTP(S) Test Script Recorder,是唯一零依赖、免配置、与JMeter生态无缝集成的方案。它不依赖外部工具链,不引入额外证书信任问题,调试时Sampler和录制日志在同一界面,排查起来快得多。所以,“设置代理”不是JMeter的附加功能,而是它作为协议级性能测试工具的底层能力入口——跳过它,你就永远在模拟表层,而非复现真实。
2. 代理设置的三道硬门槛:端口冲突、证书信任、HTTPS解密缺一不可
JMeter代理设置看似就几步菜单操作,但实际落地时,90%的失败都卡在这三个环节:端口被占、证书不信任、HTTPS解密失败。它们不是孤立问题,而是环环相扣的依赖链。我见过最典型的场景是:同事A在公司内网电脑上成功录制,同事B用同样步骤在自己笔记本上却始终无法抓到HTTPS请求——最后发现,B的杀毒软件自带Web防护模块,悄悄拦截了JMeter生成的CA证书安装请求,导致浏览器拒绝建立安全连接。
2.1 端口冲突:别只盯着8888,要查整个端口生态
JMeter默认监听8888端口,但这只是个数字。Windows下,netstat -ano | findstr :8888是必查命令;macOS/Linux下,lsof -i :8888或sudo ss -tulpn | grep :8888更准。但光查8888远远不够。很多开发习惯用IDEA启动Spring Boot项目,默认端口8080;前端Vue项目常用8080/3000/8081;Docker容器可能映射8080;甚至Chrome扩展“Proxy SwitchyOmega”后台服务也常占用8888。真正要查的是:你的目标应用本身用什么端口?它的前后端分离架构中,API网关、静态资源服务器、Mock服务各自占了哪些端口?
我自己的经验是:永远用10000-65535范围内的高位端口,比如12345、23456。原因有三:一是高位端口极少被系统服务占用;二是避免与常见开发端口(8080/3000/8000)冲突;三是便于记忆和团队统一——我们组约定所有JMeter代理统一用23456,文档、脚本、CI配置全一致,新人第一天就能跑通。
注意:端口选择不是拍脑袋。如果目标系统是微服务架构,且API网关做了端口白名单(比如只允许80/443/8080入站),那你即使本地设了23456,浏览器流量也无法穿透网关。此时必须协调运维,临时开放代理端口,或改用反向代理模式(如Nginx前置转发),而非强求JMeter监听端口。
2.2 证书信任:JMeter自签名CA不是“信任即可”,而是必须手动导入根证书
JMeter录制HTTPS流量,核心在于它要充当“中间人”解密TLS。流程是:浏览器→JMeter(冒充目标服务器)→真实服务器。JMeter必须用自己的私钥解密浏览器发来的加密数据,再用目标服务器公钥加密转发。这就要求浏览器信任JMeter的根证书(ApacheJMeterTemporaryRootCA.crt)。这个证书文件位于JMeter安装目录的bin子目录下,首次启动HTTP(S) Test Script Recorder时自动生成。
但“生成”不等于“生效”。Windows系统需双击证书→“安装证书”→选择“本地计算机”→“将所有证书放入下列存储”→“受信任的根证书颁发机构”。macOS更麻烦:双击证书→钥匙串访问→拖入“系统”钥匙串→右键证书→“显示简介”→展开“信任”→“当使用此证书时”选“始终信任”。Linux(如Ubuntu)则需执行sudo cp ApacheJMeterTemporaryRootCA.crt /usr/local/share/ca-certificates/ && sudo update-ca-certificates。
最坑的是Chrome 79+版本:它不再读取系统证书库,而是强制要求证书必须同时存在于“受信任的根证书颁发机构”和“中间证书颁发机构”两个存储区。很多同学只导入前者,Chrome仍报NET::ERR_CERT_AUTHORITY_INVALID。解决方案是:导入后,在Chrome地址栏输入chrome://settings/certificates→“权威机构”标签页→点击右上角三个点→“导入”→再次选择该证书文件,确保它出现在列表中。
2.3 HTTPS解密失败:不是JMeter的问题,而是浏览器策略升级的必然结果
即使端口空闲、证书已信任,你仍可能看到浏览器页面空白、F12 Network面板一片灰、JMeter日志里刷屏javax.net.ssl.SSLHandshakeException: Received fatal alert: unknown_ca。这不是JMeter bug,而是现代浏览器(尤其是Chrome 80+、Firefox 75+)启用的证书透明度(Certificate Transparency, CT)策略在起作用。
CT要求所有公开信任的SSL证书必须记录在公共日志中,而JMeter自签名证书显然不在其中。浏览器检测到这一点,会主动终止握手。绕过方法只有一个:在启动Chrome时添加参数--unsafely-treat-insecure-origin-as-secure="http://localhost:23456" --user-data-dir=/tmp/chrome-test --ignore-certificate-errors。注意,--ignore-certificate-errors单独使用已无效,必须配合--unsafely-treat-insecure-origin-as-secure指定具体代理地址。
Firefox相对友好,只需在about:config中搜索security.enterprise_roots.enabled,设为true,并确保security.ssl.enable_ocsp_stapling为false。Edge(Chromium版)同Chrome。Safari则必须在“钥匙串访问”中,对JMeter证书右键→“显示简介”→“信任”→“SSL”下拉菜单选“始终信任”,且重启Safari。
实操心得:不要用公司统一部署的Chrome。企业版Chrome常被组策略禁用命令行参数,或强制启用安全策略。建议下载纯净版Chrome Portable,或用Firefox Developer Edition——它对自签名证书最宽容,且自带开发者工具,录制时可同步查看Network面板验证是否抓到流量。
3. 代理服务器配置的隐藏细节:从基础监听到高级过滤的完整控制链
JMeter的HTTP(S) Test Script Recorder不是一个“开箱即用”的黑盒,它的配置面板里藏着大量影响录制质量的开关。很多人只填了端口就点“Start”,结果录出来一堆favicon.ico、Google Analytics、CDN资源,主业务接口反而淹没其中。真正的高手,会逐项审视每个配置项背后的意图,并根据测试目标做精准裁剪。
3.1 监听器配置:目标端口、目标域名、目标路径的三层过滤逻辑
在“HTTP(S) Test Script Recorder”面板中,“Target Controller”决定了录制脚本最终存放在哪个线程组下,这是基础。真正影响流量捕获精度的是“Global Settings”里的三项:
- Port:代理监听端口,前文已详述。
- Proxy hostname:默认留空,表示监听所有网卡(0.0.0.0)。若公司网络有多个网段,且只想捕获特定子网流量(如只录测试环境192.168.10.0/24的请求),可填具体IP(如192.168.10.100),JMeter只响应该IP的代理请求。
- HTTP Sampler settings下的“Use HTTP client 4”:必须勾选。这是JMeter 3.1+默认的HTTP实现,支持HTTP/1.1 Keep-Alive、自动重定向、更准确的Content-Type识别。旧版“Java”实现已弃用,会导致部分POST请求Body丢失。
最关键的过滤项在“Requests Filtering”区域:
Black list:填正则表达式,匹配到的URL将被完全忽略,不生成任何Sampler。例如:
.*\.(gif|jpg|png|css|js|woff|ttf).*过滤所有静态资源;https?://www\.google-analytics\.com/.*过滤GA埋点;https?://api\.sentry\.io/.*过滤错误监控上报。注意:正则需用.*开头结尾,|表示“或”。White list:填正则表达式,只有匹配到的URL才会被捕获。这是更激进的策略,适合目标明确的单接口压测。例如:
https?://test-api\.example\.com/v1/order/.*只录订单相关接口。白名单优先级高于黑名单,两者共存时,先白后黑。
我通常采用“黑名单为主+白名单兜底”策略:先用黑名单过滤掉90%的无关流量,再用白名单锁定核心业务域(如https?://.*\.example\.com/.*),避免误过滤。这样既保证脚本干净,又保留调试灵活性。
3.2 录制控制器:线程组、采样器、断言的自动化装配逻辑
“Recording Controller”不是普通控制器,它是JMeter的“录制引擎”。当你点击“Start”后,所有经代理的HTTP请求,都会按规则自动转换为HTTP Sampler,并嵌套在Recording Controller下。但它的行为受两个隐含规则控制:
自动创建Cookie Manager:只要Recording Controller下有HTTP Sampler,JMeter就会自动在同级添加一个“HTTP Cookie Manager”。这是必须的,否则后续请求无法携带Session ID。但要注意:如果录制过程中切换了域名(如从
login.example.com跳转到app.example.com),Cookie Manager默认不跨域,需手动勾选“Track server-side cookies”并添加“Domain”参数。自动添加View Results Tree:仅用于调试,正式脚本必须删除。这个监听器会实时显示每个请求的响应内容,但会极大拖慢录制速度,且占用内存。我的习惯是:录制时开启它快速验证是否抓到关键请求;一旦确认流程正确,立即删除,再重新Start录制——此时脚本更轻量,录制更稳定。
另一个易忽略点是“Grouping”设置。默认“Store each request in a separate sample”意味着每个HTTP请求生成一个Sampler。但真实业务中,一个页面加载往往触发10+个请求(HTML、JS、CSS、API)。若全拆开,脚本会臃肿难维护。这时应选“Put each transaction in a separate transaction controller”,并设置“Transaction Controller”名称(如“Login Process”),JMeter会把连续请求自动归组,方便后续添加事务聚合报告。
3.3 高级选项:HTTPS解密、重定向跟随、缓存控制的实战取舍
“Advanced”标签页里的选项,表面看是“高级”,实则关乎录制成败:
HTTPS decoding:必须勾选,否则HTTPS流量无法解密,只会录成
CONNECT隧道请求,看不到真实URL和Body。勾选后,JMeter会尝试解密TLS流量,前提是浏览器已信任其CA证书(见2.2节)。Follow redirects:建议关闭。JMeter默认不跟随重定向,这样能清晰看到302跳转过程,便于分析登录态流转、OAuth授权码交换等关键路径。若开启,重定向会被自动合并,你只看到最终200响应,中间的跳转逻辑就丢失了。
Cache manager:建议关闭。浏览器缓存机制复杂(Last-Modified、ETag、Cache-Control),JMeter的“HTTP Cache Manager”模拟效果有限。录制时关闭它,确保每个请求都真实发出;回放时再根据需要添加,模拟真实用户缓存行为。
Use concurrent pool:勾选。它让JMeter用线程池处理并发请求,避免高负载下代理卡死。数值默认为10,对于普通Web应用足够;若录制大型单页应用(SPA),可调至20-30。
实操避坑:不要在Recording Controller下手动添加“HTTP Header Manager”。录制时,JMeter会自动提取并设置Headers(如User-Agent、Accept)。手动添加会导致Header重复,引发400 Bad Request。如需定制Header(如添加测试专用Token),应在录制完成后,在对应Sampler下添加。
4. 从代理到可用脚本:录制后必须做的五步清洗与加固
代理录制完成,只是拿到了“原材料”。直接拿去压测,大概率失败。我统计过团队近半年的JMeter脚本问题:73%的失败源于录制后未清洗,而非代理设置错误。真正的测试工程师,花在录制后处理的时间,远超设置代理本身。以下是必须执行的五步清洗流程,每一步都有明确目的和实操细节。
4.1 删除冗余Sampler:聚焦核心业务,剔除噪音干扰
录制生成的脚本,常包含大量无意义请求:/favicon.ico、/robots.txt、/apple-touch-icon.png、第三方统计JS(如百度统计、友盟)、CDN字体文件(.woff2)、甚至开发环境的/webpack-dev-server热更新请求。这些Sampler不仅增加脚本体积,更在压测时消耗线程、产生无效QPS,污染监控指标。
清洗方法:在JMeter GUI中,展开Recording Controller,按Ctrl+F搜索关键词(如favicon、robots、analytics、cdn),批量选中→Delete。更高效的是用文本编辑器打开.jmx文件(XML格式),搜索<stringProp name="HTTPSampler.path">,用正则<stringProp name="HTTPSampler.path">.*\.(ico|txt|png|jpg|gif|woff|woff2|ttf|eot|svg).*</stringProp>一键删除整块Sampler节点。注意:删除后务必保存并重新加载脚本,避免GUI缓存。
关键原则:只保留与业务主流程强相关的请求。例如电商下单,保留
/login、/cart/list、/order/create、/pay/submit;剔除/user/profile(非下单必需)、/product/recommend(异步加载,不影响主链路)。
4.2 添加Cookie管理器:没有它,99%的登录态会失效
几乎所有现代Web应用都依赖Cookie维持Session。录制时JMeter虽自动添加了HTTP Cookie Manager,但默认配置有陷阱:它只管理当前线程组内的Cookie,且不处理跨域共享。常见问题包括:
- 登录后访问其他域名API(如
auth.example.com登录,调用api.example.com),Cookie不自动携带; - 多次登录导致Session ID覆盖,后续请求用旧ID被拒;
- Cookie过期时间(Max-Age)未同步,压测中Session意外失效。
解决方案:右键HTTP Cookie Manager→“Edit”,勾选:
- “Clear cookies each iteration”:每次循环清空Cookie,模拟新用户;
- “Implementation”选“HC4CookieHandler”(比默认的
netscape更兼容); - 在“Cookie Policy”下拉框选“default”(非
rfc2109或rfc2965); - 如需跨域,手动添加“Domain”参数(如
example.com),并确保Sampler的“Server Name or IP”填域名而非IP。
4.3 提取动态参数:从硬编码到参数化的质变
录制脚本最大的隐患是硬编码。例如登录接口返回的csrf_token=abc123,被直接写死在下一个请求的Body里。压测时,所有线程用同一个Token,必然失败。必须用正则提取器(Regular Expression Extractor)或JSON提取器(JSON Extractor)动态获取。
以CSRF Token为例:
- 在登录请求的HTTP Sampler下,右键→“Add”→“Post Processors”→“Regular Expression Extractor”;
- 填写:Reference Name
csrf_token(后续用${csrf_token}引用); - Regular Expression
name="csrf_token" value="(.+?)"(匹配HTML中的隐藏字段); - Template
$1$(取第一个括号内容); - Match No.
1(取第一个匹配项); - Default Value
NOT_FOUND(便于调试)。
对于JSON API,用JSON Extractor更可靠:
- JSON Path Expressions
$.data.token; - Compute concatenation of all matches:不勾选(取单个);
- Default Value
""。
实操技巧:提取后,务必用“Debug Sampler”+“View Results Tree”验证。在Debug Sampler下,能看到所有JMeter变量值。找到
csrf_token,确认其值随每次登录变化,且非空。这是防止脚本“看起来跑通,实则无效”的关键检查点。
4.4 添加断言:让脚本具备自我校验能力
录制脚本默认无断言,压测时即使返回500错误,JMeter也标记为“Success”,导致误判。必须为每个关键Sampler添加响应断言(Response Assertion)。
基础断言:
- “Response code”填
200(或200,201,302,用逗号分隔); - “Response message”填
OK(或Success); - “Response data”选“Text Response”,Pattern to Test填关键业务标识,如
"order_id":"ORD\d+"(验证订单创建成功)。
进阶断言(推荐):
- 用“JSON Assertion”验证JSON结构:勾选“Expect JSON Path exists”,填
$.code,确保返回体有code字段; - 用“Duration Assertion”控制响应时间:填
3000(毫秒),超时即失败,便于定位性能瓶颈; - 用“Size Assertion”检查响应大小:填
1000(字节),防止单页应用返回空响应。
所有断言需勾选“Apply to:Main sample and sub-samples”,确保子请求(如重定向)也被校验。
4.5 配置线程组与监听器:从录制脚本到生产级压测的最后一步
录制脚本默认放在“Recording Controller”下,这是调试容器,不能直接压测。必须将其移出,并配置标准线程组:
- 右键TestPlan→“Add”→“Threads (Users)”→“Thread Group”;
- 将Recording Controller下的所有Sampler,拖拽到新线程组内;
- 设置线程数(Users):根据目标TPS估算,如目标100 TPS,平均响应时间1s,则需100线程;
- Ramp-up period:设为线程数的1.5倍(如100线程设150秒),避免瞬时冲击;
- Loop Count:设为
Forever,配合“Scheduler”控制总时长。
监听器(Listener)决定你看到什么:
- 必装:“Aggregate Report”(汇总报告)、“View Results Tree”(调试用,压测时禁用)、“jp@gc - Transactions per Second”(TPS趋势图);
- 选装:“Backend Listener”对接InfluxDB+Grafana,实现高并发实时监控;
- 禁用:“View Results in Table”、“Graph Results”(GUI模式下严重拖慢性能)。
最后,右键线程组→“Add”→“Config Element”→“HTTP Header Manager”,添加通用Header:
Accept: application/json;Content-Type: application/json;User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36。
至此,一个从代理录制出发、经过深度清洗、具备自我校验、可投入生产的JMeter脚本才算真正完成。代理设置只是起点,而脚本的健壮性,才是性能测试价值的真正落点。