
做接口压测的时候很多人一上来就把登录接口和业务接口放在同一个线程组里结果跑完看报告傻眼了明明说好测“订单查询”结果所有瓶颈都集中在登录接口上事务响应时间高得离谱业务接口的真实性能完全被淹没。这个“只登录一次”的需求本质上是性能测试里的会话预处理问题把登录态准备好让后续所有并发请求复用它专心地去压目标业务接口。这篇文章就讲清楚怎么用 JMeter 做到“登录一次、全线程复用登录态”核心方案是 setUp 线程组 全局属性传参顺便把 cookie 场景、token 场景、常见坑一次聊透。适合刚接触 JMeter 的测试新人也适合已经会跑脚本但一直被登录问题困扰的初中级性能测试工程师。1. 整体设计与思路拆解1.1 为什么“每个线程都登录一次”是错的先想清楚一个概念JMeter 的默认执行模型是“线程组里的每个线程都会从头到尾执行一遍所有采样器”。如果你把登录 HTTP 请求和业务请求放在同一个线程组里10 个线程并发压测登录接口就会被压 10 次循环 10 轮登录接口就被压 100 次。这不仅仅是多跑了几个请求那么简单问题在于登录接口通常不是纯接口它背后有密码加密、验证码校验、数据库查询、Session 写入、可能还有外部认证服务调用这些逻辑本身就是重操作。登录接口一旦响应慢会直接影响整个线程的节奏。后面的业务请求都要排队等登录完成最后你看到的平均响应时间其实是登录接口和业务接口的混合值根本没法用来评估业务接口的性能。更麻烦的是如果登录接口对同一账号有并发限制、验证码失效、或者服务端做了登录频率控制压测中途大量登录请求会直接失败进而导致后续带不上 token整个压测数据全废。真实用户的行为模型也不是这样。用户是登录一次然后在会话有效期内连续操作。所以压测脚本应该模拟“一次认证持续访问”的状态而不是反复认证。1.2 几种“登录一次”方案的横向对比我在实际项目里用过不少方案各有适用场景简单做个对比方案做法优点缺点适用场景手动固定 token浏览器登录后从 F12 里复制 token硬编码到 HTTP 信息头管理器最快不用写提取器token 会过期不适合长时间压测换一次账号就要改一次脚本快速冒烟验证、调试脚本仅一次控制器登录请求放在“仅一次控制器”里放在普通线程组开头实现简单线程组内部只执行一次多线程下每个线程仍会各执行一次没有真正“全局共享”的概念线程数少、登录开销低、不追求严格复用时setUp 线程组 全局属性登录放在 setUp 线程组提取登录态后写入 JMeter 属性普通线程组统一读取严格保证所有线程执行前只跑一次登录态全局共享扩展性好需要理解属性与变量的区别配置稍复杂绝大多数业务压测场景重点推荐上表里第三行就是本文的主角。setUp 线程组是 JMeter 专门用来做“测试前准备”的组件它的执行顺序在普通线程组之前而且可以通过配置让所有普通线程等待它执行完成再开始。把登录放在这里天然就解决了“只登录一次”的顺序问题。1.3 整体链路设计我推荐的完整链路是这样的setUp 线程组线程数设为 1循环次数设为 1内部只有一个登录请求。登录请求发出后用 JSON 提取器或正则提取器把返回里的 token、cookie 等信息提取出来。通过 JSR223 处理器把提取到的值写入 JMeter 属性Properties属性是全局的所有线程组都可以读取。普通压测线程组里在 HTTP 信息头管理器或者 HTTP Cookie 管理器中读取这个属性拼到每次请求里。业务请求正常配置监听器只保留必要的压测结束后直接看聚合报告。这套链路最关键的两个点一个是 setUp 线程组的“前置执行”特性另一个是“属性Properties”的全局共享作用域。这两点理解了后面配置都不难。2. 从零到一完整落地步骤2.1 环境准备先把 JMeter 跑起来JMeter 是纯 Java 应用先确认本机有 JDK。我一般用 JDK 8 或 JDK 11JMeter 5.x 都可以正常跑。从官网下载二进制包直接解压即可Windows 下运行 bin/jmeter.batmacOS 或 Linux 下运行 bin/jmeter.sh。下载和安装本身没什么坑唯一要提醒的是如果压测计划要跑比较大的并发建议修改 bin 目录下的 setenv.shWindows 是 setenv.bat把 JVM 堆内存调大一点例如export HEAP-Xms1g -Xmx4g -XX:MaxMetaspaceSize512m这个只是给 JMeter 本身用的内存不是被测服务的配置。GUI 模式下 JMeter 会吃掉不少本地资源所以真正的压测一定要用命令行非 GUI 模式跑GUI 只用来写脚本和调试。如果你的被测接口是 HTTPSJMeter 第一次访问可能会报 SSL 证书相关错误这时候要么让开发导出证书导入到 JMeter 的 cacerts 里要么在 JMeter 的 bin 目录下用系统属性临时跳过校验。导入证书的方式最稳妥压测结果也更接近真实。2.2 搭建测试计划骨架打开 JMeter 的 GUI先创建一个测试计划改个有意义的名字比如“订单查询接口压测”。然后按顺序添加以下组件测试计划右键 - 添加 - 线程用户 - setUp 线程组测试计划右键 - 添加 - 线程用户 - 线程组测试计划右键 - 添加 - 配置元件 - HTTP 请求默认值测试计划右键 - 添加 - 配置元件 - 用户定义的变量测试计划右键 - 添加 - 监听器 - 查看结果树仅调试用正式跑时关掉测试计划右键 - 添加 - 监听器 - 聚合报告压测结束后看结果用HTTP 请求默认值里填上被测服务的协议、服务器名称或 IP、端口号。这样后续所有 HTTP 请求不用重复填主机名只填路径和参数就行。如果被测服务有多个环境可以把主机名放到“用户定义的变量”里换环境时只需要改一个变量。2.3 在 setUp 线程组里写登录请求setUp 线程组的线程数设置为 1循环次数设置为 1。这个设置很重要我们只登录一次线程数多了反而会执行多次登录失去了“只登录一次”的意义。Ramp-Up Period 保持默认的 0 秒即可。在这个线程组下添加一个 HTTP 请求名称登录接口HTTP 方法一般是 POST具体看接口文档路径/api/login参数或消息体数据填账号密码。如果接口要求 JSON 格式点击“消息体数据”页签填一段 JSON比如 {username:test01,password:123456}并记得在 HTTP 头管理器里加 Content-Type: application/json登录成功后登录接口一般会返回一个 token结构通常类似下面这种 JSON{ code: 0, message: success, data: { accessToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..., expiresIn: 7200 } }为了把 token 取出来我给这个登录请求添加一个 JSON 提取器右键登录请求 - 添加 - 后置处理器 - JSON 提取器变量名称login_tokenJSON 路径表达式$.data.accessToken匹配数字1默认值NOT_FOUND这里有一个新手常犯的错误JSON 路径写错比如把 $.data.accessToken 写成 $.data.token提取结果直接变成 NOT_FOUND后面所有的请求都会带上一个错误值。所以写完提取器之后一定要先用查看结果树里的响应数据核对字段名。提取到变量之后token 还只是存在当前线程的局部变量里其他线程组读不到。所以我再加一个 JSR223 后置处理器用 Groovy 把它写入全局属性props.put(loginToken, vars.get(login_token));这一段代码的意思是把当前线程的变量 login_token 的值存到 JMeter 全局属性 loginToken 里。Groovy 是 JMeter 内置脚本语言不需要额外装插件。为了验证这一步是否成功我可以临时在 setUp 线程组里加一个 Debug Sampler运行后查看输出里是否包含 loginToken 属性。确认没问题之后再删掉这个 Debug Sampler避免干扰正式压测。2.4 普通线程组读取登录态专心压业务接口普通线程组就是真正模拟用户并发的线程组。线程数、Ramp-Up、循环次数根据你的压测目标来定比如 50 线程Ramp-Up 5 秒循环 10 次。这里不需要再加登录请求直接在普通线程组下添加业务接口请求即可。为了让每个业务请求都自动带上 token我给普通线程组添加一个 HTTP 信息头管理器在里面配置认证头名称Authorization值Bearer ${__P(loginToken,)}__P 是 JMeter 的属性读取函数语法是 ${__P(属性名, 默认值)}。如果属性不存在就取后面的默认值这里我留空。这样每个线程发出请求时都会自动在请求头里带上之前登录获得的 token。如果你的登录态不是 token 而是 cookie处理思路略有不同。这种情况下根本不需要存属性只需要在测试计划级别添加一个 HTTP Cookie 管理器并且勾选“跨线程组共享 Cookie”选项。setUp 线程组登录成功后服务端返回的 Set-Cookie 会被 Cookie 管理器保存普通线程组发请求时自动携带这些 cookie不用写任何脚本。然后把业务请求填进去。比如压测一个订单列表接口方法GET路径/api/orders参数按接口文档填业务参数再给请求添加一个响应断言判断响应码是否 200或者业务字段是否符合预期确保压测过程中每个请求都是有效请求而不是一片报错还在傻跑。2.5 用命令行跑压测别在 GUI 里硬扛脚本在 GUI 调试通过后把测试计划保存为 .jmx 文件。正式压测时我强烈建议直接关掉 GUI用命令行执行jmeter -n -t login_once_test.jmx -l result.jtl -e -o report参数说明-n非 GUI 模式-t指定脚本路径-l保存原始结果数据的 jtl 文件路径-e -o生成 HTML 格式的可视化报告-o 指定报告输出目录如果希望压测过程中能看到实时进度可以用 -j 参数指定日志文件然后 tail 日志观察jmeter -n -t login_once_test.jmx -l result.jtl -j jmeter.log压测结束后直接打开 report 目录下的 index.html里面包含吞吐量、响应时间分布、错误率等关键指标比自己在 GUI 里看聚合报告方便得多而且不消耗 GUI 渲染资源。3. 关键细节与原理解析3.1 变量、属性、跨线程组传参的区别很多 JMeter 新手搞不清楚“变量”和“属性”的区别这里我打个比方变量像每个线程自己的背包线程 A 往背包里放的东西线程 B 拿不到属性像贴在墙上的公共公告栏所有人都能看所有人都能改而且可以从一个线程组传递到另一个线程组。具体到代码里// 存变量 - 属性 props.put(loginToken, vars.get(login_token)); // 取属性 - 变量压测线程组信息头里自动解析 // ${__P(loginToken,)}还有几个东西也容易混vars 是当前线程的变量上下文props 是全局属性对象prev 是前一个采样器的结果对象。如果你在 JSR223 里看到 props.put、vars.get、prev.getResponseDataAsString()心里要清楚它们各管哪一段数据。属性还有一个特点可以通过 -J 命令行参数在外部注入。比如我想让测试计划和环境解耦可以把账号密码也做成属性跑压测时这样指定jmeter -n -t login_once_test.jmx -JtestUsertest01 -JtestPass123456 -l result.jtl -e -o report脚本内部用 ${__P(testUser)} 读取这样每次压测不用打开脚本改数据。3.2 setUp 线程组的执行时序到底靠不靠谱JMeter 官方的执行顺序是setUp 线程组 - 普通线程组 - tearDown 线程组。setUp 线程组的所有线程执行完后普通线程组才会开始。所以只要登录写在 setUp 线程组普通线程组开始压测时 token 一定已经准备好了。不过有一个细节要注意setUp 线程组和普通线程组的关系不是“严格串行”而是“setUp 的所有线程结束后普通线程组才开始”。如果你在 setUp 里配置了多个线程或者循环它会先全部执行完所以刚才我让 setUp 线程数1、循环1。另一个容易踩的坑是调试时顺序乱了。如果你在 GUI 里手动点击某个普通线程组的“启动”按钮JMeter 不会先跑 setUp而是直接跑你点的那部分导致 token 属性不存在。任何一次修改脚本之后都要老老实实从测试计划层面点启动不要跳着执行。3.3 token 过期时间与压测时长的关系token 一般都有有效期可能是 30 分钟、2 小时甚至更长。如果你的压测场景是长时间稳定性测试比如跑 4 小时、8 小时一个 token 很可能撑不到最后。这时候策略要调整短期压力测试10 分钟、30 分钟内一次登录足够。长期稳定性测试建议写一个定时器或者使用“循环控制器 定时检查”在 token 快要过期时重新调用登录接口并更新全局属性。注意这里的“重新登录”是低频的比如每 20 分钟执行一次不会干扰业务接口的并发模型。简单实现方式是设置一个“续期线程组”独立于业务线程组运行循环等待到 token 有效期的 80% 时执行一次登录并更新属性。实际项目中很多时候我们跑短期压测根本不会遇到过期问题但如果你负责的是稳定性场景这条一定要提前设计。3.4 cookie 跨线程组共享的正确姿势如果登录接口返回的是 cookie 而不是 token你在 setUp 线程组登录后cookie 并不会自动给普通线程组用原因和变量一样JMeter 默认每个线程组有独立的 Cookie 上下文。解决办法有两个在测试计划层面添加 HTTP Cookie 管理器勾选“跨线程组共享”选项。不勾选而是用 JSR223 提取 Set-Cookie 值存入属性再在普通线程组里用 HTTP 头管理器把 Cookie 头带上。第一种方法最省事但需要注意“共享 Cookie”意味着所有线程发出去的请求都带同一个用户身份的 cookie。如果被测系统对同一用户有并发限制这就不合适了。第二种方法更灵活你可以结合 CSV 参数化实现不同线程用不同账号。具体怎么做见 3.6 节。3.5 响应断言和超时配置别偷懒压测脚本要稳定断言和超时一定要配。超时配置在 HTTP 请求的“高级”页签里有“连接超时”和“响应超时”两个值。我一般建议连接超时 3000ms响应超时 5000ms具体根据业务接口的耗时特征调整。如果不配超时接口一旦挂起线程会一直等浪费掉宝贵的加压能力。响应断言建议断言两层第一层是 HTTP 状态码 200第二层是业务响应里的 code 字段。比如响应里 code0 代表成功。只断言 200 有个问题服务端可能返回 HTTP 200 但业务报错比如 token 过期返回 401 就还好有些系统即便是登录态失效也会返回 HTTP 200 加一个 error code这时候如果只断言 200错误请求会被当成成功请求统计压测报告完全失真。给 HTTP 请求添加响应断言的路径是右键请求 - 添加 - 断言 - 响应断言。在“测试模式”里选择“响应文本”然后添加一个模式比如 “code:0”并勾选“匹配完全”或者只做“包含”匹配按你接口的实际情况来。3.6 多账号并发时登录态策略要换思路前面讲的“全局唯一 token”方案适合那些用一个测试账号就能覆盖大多数接口的场景。但很多业务接口会校验用户维度数据比如“A 用户只能查 A 用户自己的订单”。如果你 50 个并发线程都带着同一个 token 去查订单系统返回的数据全是同一个人的而且很容易触发接口的幂等限制或者数据隔离校验导致一部分请求失败。这时候只登录一次就不够了需要“每线程登录一次线程内复用”。这个场景的配置方式跟前面不同普通线程组线程数设为 N循环次数 M。在普通线程组内部加一个“仅一次控制器”把登录请求放进去。登录请求后加提取器把 token 存为线程局部变量。业务请求统一通过 ${login_token} 使用这个变量。“仅一次控制器”保证的是每个线程在一轮循环中只执行一次登录之后该线程的所有循环都复用这个 token。从整体看是登录了 N 次而不是 N×M 次符合“登录一次压多次”的模型又能保证每个线程有独立身份。如果要每个线程用不同账号登录配合 CSV 数据文件即可CSV 里放一列用户名、一列密码变量引用设置为“线程间共享”JMeter 会给每个线程分配不同的账号登录后各自的 token 互不干扰。判断用“全局单 token”还是“每线程独立 token”核心看被测接口是否存在用户数据隔离。拿不准的时候先问开发同一账号并发请求会不会有锁、有没有限流。4. 常见问题与排查技巧实录4.1 登录执行了 N 遍token 还是空的这种情况八成是把登录请求放在了普通线程组里而不是 setUp 线程组。普通线程组里的请求会随着每个线程、每一轮循环被反复执行。如果你在普通线程组里看到了登录请求右键把它剪切粘贴到 setUp 线程组下面。还有可能是 setUp 线程组的线程数被改大了检查线程数是否仍为 1。4.2 响应正常但后续请求全是 401/403请求带上了登录态但服务端不认账排查顺序先看查看结果树里登录请求的响应确认 token 是否真的提取到了。在普通线程组的 HTTP 信息头管理器里加一个调试采样器确认请求头里 Authorization 的值。确认服务的鉴权头格式是 Bearer 还是 token有些接口要求 “Authorization: token xxx” 而不是 “Bearer xxx”。如果是 cookie 登录态确认测试计划里有 HTTP Cookie 管理器且开启了跨线程组共享。确认 token 没过期。如果脚本调试时间很久了重新跑一次登录再压。4.3 JSON 提取器报 “No match” 或者一直取默认值先打开查看结果树切到“响应数据”标签确认实际返回的 JSON 结构。你以为的路径是 $.data.accessToken实际可能是 $.data.access_token或者多包了一层 $.data.token.accessToken。别猜直接复制响应文本用 JMeter 的 JSON 断言插件或者 Postman 里的 JSONPath 验证工具试一下路径对不对。我就是经常在字段命名上栽跟头下划线、驼峰混着来眼都看花了最后还是老老实实复制响应查了一遍。4.4 HTTPS 请求报证书错误错误信息一般是 “SSL certificate problem: unable to get local issuer certificate”。最直接的解决方式是把被测服务的 HTTPS 证书导入 JMeter 使用的 JDK 证书库keytool -import -alias myserver -keystore cacerts -file server.crt另一个简单粗暴但真实压测中经常用的方式是在 jmeter.properties 里或命令行加参数跳过校验jmeter -Djavax.net.ssl.trustStore... -Djavax.net.ssl.trustStorePasswordchangeit ...这个方案不推荐在生产环境严格测试时用因为 SSL 握手本身就是性能观察点之一。但如果你只是连一个内网测试环境导入证书太麻烦可以先跳过校验把脚本调通。4.5 压测过程中登录失败导致后面全废登录失败的原因可能是服务端对频繁登录做了限流也可能是测试账号密码被重置还有可能是网络抖动。最稳妥的做法是在 setUp 线程组里加一个“如果登录失败就停止整个测试”的逻辑。我常用响应断言 断言结果处理来实现给登录请求添加响应断言检查业务 code0。在响应断言上右键添加“断言结果处理”勾选“如果断言失败停止测试”或者“停止当前线程组”。这样登录一旦失败压测任务立即停止不会出现后面几千个请求全部 401 还傻跑的情况。还有一个小习惯压测前先手动把登录接口用浏览器或 Postman 调一次确认账号没有任何锁定再开始跑。4.6 上传文件接口怎么压如果你的业务接口里面有文件上传HTTP 请求类型选择 POST文件上传切换在“文件上传”页签文件名称本地文件路径参数名称接口要求的文件字段名比如 fileMIME 类型按文件类型填 image/png、application/octet-stream 等这个其实和登录关系不大但既然做接口压测难免会遇到上传场景一并提一下。上传文件时要注意压测机本地 IO 和带宽会影响结果文件不要太大多台压力机时最好都放一份相同的本地文件路径。4.7 常见问题速查表现象可能原因排查与解决setUp 里执行了多次登录setUp 线程数大于 1 或循环次数大于 1线程数设为 1循环设为 1token 提取到但请求头没带上信息头管理器放在错误的线程组或者没读取属性确认普通线程组下信息头值写成 ${__P(loginToken,)}401/403token 过期、鉴权头格式错误、cookie 未共享检查有效期、请求头格式、Cookie 管理器共享选项业务接口报错但 HTTP 200断言没做业务层校验补充响应断言检查 code 字段压测数据波动很大测试数据没有隔离单账号并发冲突用 CSV 准备多账号、多业务数据只听得到登录的慢看不到业务的慢登录请求混在业务线程组里登录挪到 setUp 线程组聚合报告里出现大量 socket 超时带宽、连接数、请求超时时间设置不当适当调大超时检查压力机资源对我个人来说这套“setUp 线程组 全局属性”的写法已经成为我做接口压测的固定模板。每次拿到新的压测需求我先问自己三个问题被测接口是单用户维度还是多用户维度登录态用的是 cookie 还是 token这次是短期压测还是长时间稳定性压测三个问题一确定登录方案基本就定了。单用户维度用全局属性方案多用户维度用每线程独立登录方案。最后分享一个小技巧正式压测之前我习惯先把线程数压到 1、循环压到 1开着查看结果树跑一遍确认登录请求返回正常、token 提取正确、业务请求的响应也正确。连续三步全绿再把线程数调上去跑正式的。每改一次脚本配置都重复一遍这个简单验证流程。看似浪费时间实际上能省掉后面排查一堆 401、No match 的功夫。压测脚本这事儿慢就是快顺手把登录态问题解决好后续的结果分析才真正有价值。