从单接口到完整业务链:用一个案例学会 JMeter 业务压测设计
很多人第一次用 JMeter,会先学会发送一个 HTTP 请求,然后把线程数从 1 改成 100。工具确实运行起来了,但这还不能回答一个更实际的问题:100 个用户完成一整段业务时,系统是否仍然可用?
这篇文章用一条脱敏示例链路贯穿全过程:
登录 → 浏览内容列表 → 打开一条详情 → 点赞 → 确认点赞成功 |
读完并跟做后,你应该能够:
- 判断一个需求应该做单接口压测,还是业务链压测;
- 把用户操作拆成 JMeter 测试计划树;
- 用 CSV、JSON 提取器和变量完成登录态与数据关联;
- 根据“用户人数 × 一轮耗时”估算业务吞吐,而不是盲填线程数;
- 先用 1 个用户证明脚本正确,再进入正式负载执行;
- 分开判断接口成功、业务成功和整条链路完成。
文中的接口、字段和数字都是教学示例,不对应任何真实产品。正式压测前必须确认目标环境、执行时间和容量边界。
一、先理解:单接口压测与业务链压测差在哪里
单接口压测回答局部问题
例如只压详情查询接口:
GET /api/content/123 |
它适合回答:
- 这个查询接口能承受多少请求;
- 缓存、索引或代码修改后是否变慢;
- 错误率、响应时间和吞吐量的拐点在哪里。
输入比较固定:接口地址、参数、压力和技术指标。
业务链压测回答用户能否完成一件事
“打开详情并点赞”并不是两个孤立接口。用户通常要先登录,从列表响应中获得内容 ID,打开详情,再携带身份和内容 ID 点赞。
任何一步失败,后面的请求都可能失去业务意义。例如列表没有返回内容 ID,点赞请求却仍发送 ${contentId},HTTP 层也许有响应,但这不是一次有效的用户行为。
| 维度 | 单接口压测 | 业务链压测 |
|---|---|---|
| 测试对象 | 一个技术入口 | 一类完整用户行为 |
| 参数来源 | 通常直接构造 | 经常依赖前序响应 |
| 身份状态 | 可能不需要 | 通常要登录和隔离账号 |
| 节奏 | 请求速率或并发 | 还要考虑阅读、切换和分支 |
| 成功条件 | 接口返回正确 | 每一步正确且到达业务终点 |
| 结果分析 | 按接口汇总 | 同时看接口、事务和用户链路 |
判断方法很简单:如果测试目标中出现“用户完成登录、浏览、下单、发布、互动”等动词链,它通常就不再是单接口问题。
二、把模糊需求改写成可验证目标
假设原始需求只有一句话:
测一下点赞业务能不能支持 100 个并发用户。
这句话还不能直接转换成线程组,因为至少缺少:
- 100 个用户是同时到达,还是逐步进入;
- 每个用户只点赞一次,还是持续浏览并点赞;
- 用户两次操作之间是否等待;
- 测试持续多久;
- 什么结果算通过;
- 在哪个环境运行,是否允许产生写入数据。
把它补成一张场景设计卡:
| 项目 | 本文示例 |
|---|---|
| 测试目标 | 验证 100 个活跃用户持续浏览并点赞时,关键链路是否稳定 |
| 业务链 | 登录 → 列表 → 详情 → 点赞 |
| 用户模型 | 100 个用户在 60 秒内逐步进入 |
| 运行节奏 | 每轮业务结束后等待约 5 秒再开始下一轮 |
| 持续时间 | 稳定运行 10 分钟 |
| 数据 | 每个线程独立账号,内容 ID 从列表动态取得 |
| 业务终点 | 点赞接口返回成功,且响应中的最终状态为已点赞 |
| 观察指标 | 各接口、整条链路、错误类型及服务端资源 |
| 停止条件 | 错误持续超阈值、业务终点大量不可达或服务端进入危险区间 |
这里的数字只是示例。真正项目中,应从线上峰值、容量目标、历史监控或业务预测中取得依据。
三、先画用户链路,再列接口和数据依赖
不要一开始就在 JMeter 里添加组件。先用表格说明每一步“需要什么、产生什么、怎样算成功”。
| 步骤 | 示例请求 | 输入 | 输出给后续步骤 | 成功依据 |
|---|---|---|---|---|
| 登录 | POST /api/login |
CSV 中的账号、密码 | accessToken |
业务码成功且 Token 非空 |
| 内容列表 | GET /api/contents |
accessToken |
contentId |
列表非空且取得可操作 ID |
| 内容详情 | GET /api/contents/${contentId} |
Token、内容 ID | 详情状态 | 业务码成功,返回 ID 与请求一致 |
| 点赞 | POST /api/contents/${contentId}/like |
Token、内容 ID | 点赞结果 | 业务码成功且 liked=true |
这张表解决了业务链的核心问题:每个变量从哪里来,失败后能不能继续,哪个响应证明整条链真正完成。
四、把链路映射成 JMeter 测试计划树
本文示例使用下面的结构:
Test Plan |
逻辑控制器用于改变子元素的执行顺序、次数和选择方式;断言的作用域取决于它在测试树中的位置。Apache JMeter 官方文档建议把只针对某个请求的断言放在该 Sampler 下,避免误作用到其他请求。Apache JMeter:Elements of a Test Plan
为什么登录放在 Once Only Controller 中
线程组循环时,普通请求会每轮重复执行。登录通常代表一个虚拟用户建立一次身份,因此放到 Once Only Controller 中,让它在该线程首次迭代时执行。
注意:这只能解决“同一线程只登录一次”。如果登录线程、后台线程和业务线程分属不同线程组,变量不能自动跨线程共享,需要另行设计用户状态存储和同步。
为什么业务链外面套 Transaction Controller
列表、详情和点赞各自需要接口指标,同时我们还想知道“一轮完整业务”耗时。Transaction Controller 可以生成事务汇总样本,但这个汇总样本不是一个真实 HTTP 接口,分析时不要和接口吞吐混在一起。
五、按顺序搭建 JMeter 组件
第 1 步:配置线程组,但先只用 1 个用户
新建 Thread Group,调试阶段填写:
Number of Threads: 1 |
先证明一个用户能完整跑通。直接从 100 个线程开始,会把脚本错误、数据冲突和系统性能问题混在一起。
正式运行时,再把值改成 Property:
Number of Threads: ${__P(threads,1)} |
正式持续运行时,线程组仍只进入 1 次,在内部增加持续循环的 Loop Controller,并启用线程组的调度器、用 duration 控制总时长。这样每个线程只领取一次账号和登录一次,重复的是业务控制器,而不是整个线程组。
这些值通过 Property 暴露后,可以在不同环境使用 -Jthreads=... 传参,不必反复修改 JMX。
第 2 步:用 CSV 给每个线程分配账号
准备教学示例 users.csv:
user_001,example_password_001 |
CSV Data Set Config 可配置:
Filename: ${__P(dataDir,.)}/users.csv |
请求体中引用:
{ |
如果计划运行 100 个并发用户,却只有 10 个账号,就必须先决定是否允许账号复用。对于会修改用户状态的业务,盲目复用账号可能产生会话覆盖、幂等冲突或限流,结果不能直接解释为系统容量问题。
JMeter 官方最佳实践也给出了用 CSV Data Set 为不同线程提供独立登录数据的方式。Apache JMeter:Best Practices
第 3 步:设置公共地址和 Header
在 HTTP Request Defaults 中填写:
Protocol: https |
登录成功后,JSON 提取器得到 accessToken。随后在 Header Manager 中引用:
Authorization: Bearer ${accessToken} |
不要把真实 Token 写在 JMX 中。Token 是运行时数据,应该由登录响应生成。
第 4 步:提取动态内容 ID
假设列表响应为:
{ |
在列表请求下添加 JSON Extractor:
Names of created variables: contentId |
后面的详情路径写成:
/api/contents/${contentId} |
为什么要设置 NOT_FOUND?因为提取失败时,如果没有清晰的默认值,后续请求可能携带空字符串、未解析变量,甚至沿用上一轮旧值。明确的哨兵值更容易被条件控制器拦截和在结果中定位。
第 5 步:失败时不要继续发送无效请求
在详情和点赞外层增加 If Controller,示意条件为:
${__groovy(vars.get('contentId') && vars.get('contentId') != 'NOT_FOUND')} |
如果 ID 无效,应根据场景规则执行一种明确动作:
- 当前列表为空属于允许情况:跳过本轮并记录原因;
- 数据不足使测试失去意义:停止当前线程;
- 列表接口本应返回数据:将本轮标记失败。
不要让脚本继续请求 /api/contents/NOT_FOUND,否则报告中的错误主要来自脚本,而不是被测系统。
第 6 步:添加三层成功判断
业务链不能只检查 HTTP 200。
- 通信成功:连接正常、状态码符合预期、响应能解析;
- 业务成功:例如
code=0,详情 ID 与请求 ID 一致; - 链路完成:点赞响应中
liked=true,到达本轮终点。
如果系统采用最终一致性,写入成功后,列表或详情不一定立即反映新状态。此时应把“写请求成功”和“稍后读取确认”作为两个判断,并设置有界等待或轮询,不能无限重试,也不能用即时读取失败直接否定写入。
第 7 步:加入真实但可控的思考时间
真实用户在列表和详情页会停留。没有任何等待的 100 个线程,可能变成不断循环的请求机器。
如果要让每轮结束后等待 5 秒,可以在线程内部的 Loop Controller 末尾添加 Flow Control Action,选择暂停并填写:
Duration: ${thinkTimeMs} |
如果要模拟“打开详情前阅读列表”,也可以给详情请求添加 Constant Timer。JMeter 的 Timer 会在其作用域内的 Sampler 执行前等待,放在线程组下会影响多个请求,因此必须先明确等待发生在哪个用户动作上。正式场景可以根据真实行为分布改用随机定时器,但要记录取值依据。
六、线程数不是吞吐量:做一次简单估算
假设稳定阶段有 100 个并发用户,每一轮业务平均包含:
列表请求 1 秒 |
一轮约 8 秒。用闭合用户模型做粗略估算:
业务迭代吞吐 ≈ 并发用户数 ÷ 平均一轮耗时 |
每轮有 3 个业务请求,因此请求吞吐粗略约为:
12.5 × 3 ≈ 37.5 个请求/秒 |
这只是设计阶段的近似值,不是保证值。系统响应变慢后,一轮耗时会增长,闭合模型中的吞吐也会变化;接口并行、重试和后台请求也会改变结果。
这个计算至少能帮助我们识别一个错误:100 个线程不等于 100 RPS。
七、按照四个阶段验证,不要直接正式开压
阶段 1:单用户、单轮调试
临时添加 View Results Tree,只运行 1 个线程、1 轮,逐项检查:
- CSV 是否读取到当前账号;
- 登录 Token 是否非空;
- 列表是否提取到内容 ID;
- 详情和点赞是否使用同一个 ID;
- 断言是否能故意识别错误响应;
- 业务事务是否到达终点。
View Results Tree 适合脚本调试,不适合正式负载执行。JMeter 官方建议正式负载测试使用 CLI 模式,并尽量减少 Listener 以降低负载机资源开销。Apache JMeter:Getting Started
阶段 2:少量用户、多轮验证
例如使用 2~5 个测试账号运行几轮,重点发现:
- 不同线程是否串用 Token;
- 数据能否重复消费;
- 上一轮变量是否污染下一轮;
- 账号耗尽时是否正确停止;
- 写操作是否产生冲突。
阶段 3:阶梯升压
不要从 1 个用户直接跳到目标峰值。可以按经过评审的台阶逐步提高,在每个稳定阶段观察:
- P90/P95/P99 响应时间;
- 错误率及错误类型;
- 业务终点成功率;
- 应用、数据库、缓存和下游资源;
- JMeter 负载机自身 CPU、内存和网络。
阶段 4:正式场景
脚本和少量数据验证通过后,才进入约定的持续时间与目标负载。正式执行使用 CLI,例如:
jmeter -n \ |
示例域名不能直接用于真实执行。请把目标、负载和数据文件替换为已经授权的测试环境配置。
八、结果应该怎样读
假设一次示例执行得到:
| 观察项 | 示例现象 | 还不能直接得出的结论 |
|---|---|---|
| 列表接口 P95 上升 | 从 300 ms 增至 1.2 s | 不能只凭此判断数据库是瓶颈 |
| 点赞错误率 3% | 主要错误为业务码失败 | 不能把所有失败都算作服务器 5xx |
| 业务事务成功率 94% | 部分用户未到达点赞终点 | 不能只看三个接口的平均错误率 |
| 负载机 CPU 95% | JMeter 本身接近上限 | 不能继续把吞吐下降归因于目标系统 |
分析顺序可以是:
- 负载是否按设计产生,JMeter 执行端是否健康;
- 失败是脚本、数据、网络、业务拒绝还是服务端异常;
- 哪个接口首先恶化,是否影响整条业务;
- 服务端监控和日志是否提供同一时间段的证据;
- 本次结果能回答原始目标中的哪一部分。
只有客户端 JTL,没有服务端资源和日志证据时,可以描述现象,但不要武断宣布瓶颈根因。
九、五个最常见的错误设计
错误 1:把多个接口排在一起就叫业务链
修正:为每一步写清输入、输出、失败行为和业务终点。
错误 2:所有线程共用一个写账号
修正:账号与线程建立明确映射;无法隔离时,先确认共享账号是否会改变业务含义。
错误 3:提取失败后仍继续请求
修正:设置明确默认值,用条件控制器阻止空 ID、旧 ID 和未解析变量进入后续请求。
错误 4:只断言 HTTP 200
修正:同时验证业务码、关键字段和最终业务状态。
错误 5:GUI 中带着大量 Listener 正式开压
修正:GUI 用于构建与调试,正式压测使用 CLI,并只保存分析所需字段。JMeter 官方最佳实践也明确建议负载执行时使用 CLI、减少 Listener 和不必要结果数据。Apache JMeter:Best Practices
十、一份可以直接填写的业务压测模板
1. 要回答的问题: |
十一、执行前检查清单
- 我能用一句话说明本次测试要回答什么
- 每个请求都能对应到真实用户动作
- 每个动态变量都有来源、默认值和失败处理
- 登录不会在每轮被意外重复
- 账号数、线程数和数据复用规则一致
- 单用户单轮已经到达业务终点
- 多用户运行时不会串用身份或旧变量
- 线程数、思考时间和预期吞吐做过估算
- 同时定义了接口成功、业务成功和链路完成
- 正式执行使用 CLI,并监控负载机自身资源
- 已约定停止条件、数据清理和结果解释边界
写在最后
业务压测不是“在 JMX 里放更多请求”,而是把一类真实用户行为变成可重复、可计算、可验证的测试模型。
从今天的示例可以记住一条最重要的路径:
先定义业务终点,再拆接口和数据依赖;先让一个用户正确完成,再让很多用户按真实节奏重复;最后用 JTL、日志和服务端监控共同解释结果。
做到这一步,JMeter 才不只是请求发生器,而是在执行一条可信的业务压测场景。







